- Chaplin
Chaplin.View → Source
Chaplin’s View
class is a highly extended and adapted subclass of Backbone.View
. By default, all views should inherit from this class to take advantage of its additions and improved memory management.
Views may subscribe to global pub/sub and model/collection events in a manner which allows proper disposal. They have a standard render
method which renders a template into the view’s root element (this.el
).
The templating function is provided by this.getTemplateFunction
. The input data for the template is provided by this.getTemplateData
. By default, this method just returns an object delegating to the model attributes. Views might override the method to process the raw model data for the view.
In addition to Backbone’s events
hash and the delegateEvents
method, Chaplin has the delegate
method to register user input handlers. The declarative events
hash doesn’t work well for class hierarchies when several initialize
methods register their own handlers. The programatic approach of delegate
solves these problems.
When establishing bindings between view and model, this.model.on()
should not be used directly. Instead, Backbone’s built-in methods for handling bindings, such as this.listenTo(this.model, ...)
should be used, so handlers can be removed automatically on view disposal to prevent memory leakage.
Features and purpose
- Rendering model data using templates in a conventional way
- Robust and memory-safe model binding
- Automatic rendering and appending to the DOM
- Registering regions
- Creating subviews
- Disposal which cleans up all subviews, model bindings and pub/sub events
Methods
initialize(options)
- options (default: empty hash)
autoRender
see autoRenderautoAttach
see autoAttachcontainer
see containercontainerMethod
see containerMethod- all standard Backbone constructor options (
model
,collection
,el
,id
,className
,tagName
andattributes
)
options
may be specific on the view class or passed to the constructor. Passing in options during instantiation overrides the View prototype's defaults.
Views must always call super
from their initialize
methods. Unlike Backbone’s initialize
method, Chaplin’s initialize
is required to create the instance’s subviews and listen for model or collection disposal.
Rendering: getTemplateFunction
, render
, …
Your application should provide a standard way of rendering DOM nodes by creating HTML from templates and template data. Chaplin provides getTemplateFunction
and getTemplateData
for this purpose.
Set autoRender
to true to enable rendering upon View instantiation. If autoAttach
is enabled, this will automatically append the view to a container
. The method of appending can be overridden using the containerMethod
property (to html
, before
, prepend
, etc).
getTemplateFunction()
- function (throws error if not overriden)
Core method that returns the compiled template function. Usually set application-wide in a base view class.
A common implementation will take a passed in template
string and return a compiled template function (e.g. a Handlebars or Underscore template function).
@template = require 'templates/comment_view'
this.template = require('templates/comment_view');
or if using templates in the DOM
@template = $('#comment_view_template').html()
this.template = $('#comment_view_template').html();
if using pre-compiled Handlebars
getTemplateFunction: ->
@template
getTemplateFunction: function() {
return this.template;
}
if using non-pre-compiled Handlebars
getTemplateFunction: ->
Handlebars.compile @template
getTemplateFunction: function() {
return Handlebars.compile(this.template);
}
or if using underscore templates
getTemplateFunction: ->
_.template @template
getTemplateFunction: function() {
return _.template(this.template);
}
Packages like Brunch With Chaplin precompile the template functions to improve application performance.
getTemplateData()
- function that returns Object
Empty method which returns the prepared model data for the template. Should be overriden by inheriting classes (often from model data).
getTemplateData: ->
@model.attributes
...
getTemplateData: ->
title: 'Winnetou', author: 'Karl May'
getTemplateData: function() {
return this.model.attributes;
}
...
getTemplateData: function() {
return {title: 'Winnetou', author: 'Karl May'};
}
Often overriden in a base model class to intelligently pick out attributes.
render
By default calls the templateFunction
with the templateData
and sets the HTML of the $el
. Can be overriden in your base view if needed, though this should be suitable for the majority of cases.
attach
The attach
method is called after the prototype chain has completed for View#render
. It attaches the view to its container
element and fires an 'addedToDOM'
event on the view on success.
General options
optionNames
- array (default list of options)
List of options that will be picked from constructor.
Easy to extend:
optionNames: View::optionNames.concat ['template']
optionNames: View.prototype.optionNames.concat(['template'])
Options for rendering
noWrap
- boolean (default
false
)
Specifies whether the default Backbone behavior of wrapping the template with an element, as specified with tagName
, should be used. When true
the template will not be wrapped and the template will be rendered as-is and must contain 1 top-level element. Works when using a region
, container
, or as a CollectionView
item.
Options for auto-rendering and DOM appending
autoRender
- boolean (default
false
)
Specifies whether the view’s render
method should be called automatically when a view is instantiated.
autoAttach
- boolean (default
true
)
Specifies whether the view’s attach
method should be called automatically after render
was called.
container
- jQuery object, selector string, or element (default
null
)
A container element into which the view’s element will be rendered. This may be a DOM element, a jQuery object or a selector string. In the latter case the container must already exist in the DOM.
Set this property in a derived class to specify the container element. As an alternative you might pass a container
option to the constructor.
When the container
is set and autoAttach
is true, the view is automatically inserted into the container when it’s rendered (using the attach
method).
A container is often an empty element within a parent view.
containerMethod
- String, jQuery object method (default
'append'
)
Method which is used for adding the view to the DOM via the container
element. (Like jQuery’s html
, prepend
, append
, after
, before
etc.)
Event delegation
listen
- Object
Chaplin's declarative event bindings follow Backbone's built-in event catalog, with the added benefit of automatically removable event listeners. You can listen to models/collections/mediator etc.
class SomeView extends View
listen:
# Listen to view events with @on.
'eventName': 'methodName'
# Same as @listenTo @model, 'change:foo', this[methodName].
'change:foo model': 'methodName'
# Same as @listenTo @collection, 'reset', this[methodName].
'reset collection': 'methodName'
# Same as @subscribeEvent 'pubSubEvent', this[methodName].
'pubSubEvent mediator': 'methodName'
# The value can also be a function.
'eventName': -> alert 'Hello!'
var SomeView = View.extend({
listen: {
// Listen to view events with @on.
'eventName': 'methodName',
// Same as this.listenTo(this.model, 'change:foo', this[methodName])
'change:foo model': 'methodName',
// Same as this.listenTo(this.collection, 'reset', this[methodName])
'reset collection': 'methodName',
// Same as this.subscribeEvent('pubSubEvent', this[methodName])
'pubSubEvent mediator': 'methodName',
// The value can also be a function.
'eventName': function() {alert('Hello!')}
}
});
delegate(eventType, [selector], handler)
- String eventType - jQuery DOM event (e.g.
'click'
,'focus'
, etc.), - String selector (optional, if not set will bind to the view’s
$el
), - function handler (automatically bound to
this
) - returns the bound handler function
Backbone’s events
hash doesn't work well with inheritance, so Chaplin provides the delegate
method for this purpose. delegate
is a wrapper for jQuery’s this.$el.on
method, and has the same method signature.
For events affecting the whole view the signature is delegate(eventType, handler)
:
@delegate('click', @clicked)
this.delegate('click', this.clicked);
For events only affecting an element or colletion of elements in the view, pass a selector, too, delegate(eventType, selector, handler)
:
@delegate('click', 'button.confirm', @confirm)
this.delegate('click', 'button.confirm', this.confirm);
undelegate(eventType, [selector], handler)
- String eventType - jQuery DOM event (e.g.
'click'
,'focus'
, etc.), - String selector (optional, if not set will bind to the view’s
$el
), - function handler (automatically bound to
this
) - returns the bound handler function
Allows to remove DOM event handlers that have been added using delegate
. undelegate
is a wrapper for jQuery’s this.$el.off
method, and has the same method signature.
Since delegate
automatically binds the handler function to the view, you need to pass the bound handler to remove it. This is a new function and not the same as the original handler passed to delegate
.
To allow this, delegate
returns the bound handler so you can save it for later removal:
# CoffeeScript
@boundConfirm = @delegate 'click', 'button.confirm', @confirm
# Later:
@undelegate 'click', 'button.confirm', @boundConfirm
// JavaScript
this.boundConfirm = this.delegate('click', 'button.confirm', this.confirm);
// Later:
this.undelegate('click', 'button.confirm', this.boundConfirm);
Regions
Regions provide a means to give canonical names to selectors in the view. Instead of binding a view to #page .container > .sidebar
(via the container) you would bind it to the declared region sidebar
which is registered by the view that contained #page .container > .sidebar
. This decouples views from those that nest them. It allows for layouts to be drastically changed without changing the template.
region
This is the region that the view will be bound to. This property is not meant to be set on the prototype — it is meant to be passed in as part of the options hash.
Both of the following code snippets will bind the view MyView
to the declared region sidebar
.
This one sets the region directly on the prototype:
# myview.coffee
class MyView extends Chaplin.View
region: 'sidebar'
# my_controller.coffee
# [...] inside action method
@view = new MyView()
// myview.js
var MyView = Chaplin.View.extend({
region: 'sidebar'
});
// my_controller.js
// [...] inside action method
this.view = new MyView();
And this one passes in the value of region to the view constructor:
# myview.coffee
class MyView extends Chaplin.View
# my_controller.coffee
# [...] inside action method
@view = new MyView {region: 'sidebar'}
// myview.js
var MyView = Chaplin.View.extend({});
// my_controller.js
// [...] inside action method
this.view = new MyView({region: 'sidebar'});
However the latter case is more flexible, as it leaves it to the controller to decide (through whatever logic) where to place the view.
regions
A region registration hash that works much like the declarative events hash present in Backbone. Region names are specifyed as keys, region selectors as values. If the region selector is empty (''
), the view’s own DOM element will be selected.
The following snippet will register the named regions sidebar
and body
and bind them to their respective selectors directly on the prototype:
# myview.coffee
class MyView extends Chaplin.View
regions:
'sidebar': '#page .container > .sidebar'
'body': '#page .container > .content'
'myview': ''
// myview.js
var MyView = Chaplin.View({
regions: {
'sidebar': '#page .container > .sidebar',
'body': '#page .container > .content',
'myview': ''
}
});
And this one passes in the values of regions to the view constructor:
# myview.coffee
class MyView extends Chaplin.View
# my_controller.coffee
# [...] inside action method
@view = new MyView
regions:
'sidebar': '#page .container > .sidebar'
'body': '#page .container > .content'
'myview': ''
// myview.js
var MyView = Chaplin.View({});
// my_controller.js
// [...] inside action method
this.view = new MyView({
regions: {
'sidebar': '#page .container > .sidebar',
'body': '#page .container > .content',
'myview': ''
}
});
When the view is initialized, the regions hashes of all base classes are gathered and registered as well. When two views in an inheritance tree both register a region of the same name, the selector of the most-derived view is used.
registerRegion(selector, name)
- String selector,
- String name
Functionally registers a region exactly the same as if it were in the regions hash. It is meant to be called in the initialize
method as in the following code snippet (which is identical to the previous one using the regions
hash).
class MyView extends Chaplin.View
initialize: ->
super
@registerRegion 'sidebar', '#page .container > .sidebar'
@registerRegion 'body', '#page .container > .content'
@registerRegion 'myview', ''
var MyView = Chaplin.View.extend({
initialize: function() {
Chaplin.View.prototype.initialize.apply(this, arguments);
this.registerRegion('sidebar', '#page .container > .sidebar');
this.registerRegion('body', '#page .container > .content');
this.registerRegion('myview', '');
}
});
unregisterRegion(name)
- String name
Removes the named region as if it was not registered. Does nothing if there is no region named name
.
unregisterAllRegions()
Removes all regions registered by this view, automatically called on View#dispose
.
Subviews
Subviews are usually used for limited scenarios when you want to split a view up into logical sections that are continuously re-rendered or form wizards, etc., though this is only advisable, as long as they all dealing with the same model.
subview(name, [view])
- String name,
- View view (when setting the subview)
Register a subview with the given name
. Calling the method with just the name
argument will return the subview associated with that name
.
This just registers a subview so it can be disposed when its parent view is disposed. Subviews are not automatically rendered and attached to the current view. You can use the autoRender
and container
options to render and attach the view.
If you register a subview with the same name twice, the previous subview will be disposed. This ensures that there is only one subview under the given name.
removeSubview(nameOrView)
Remove the specified subview and dispose it. Can be called with either the name
associated with the subview, or a reference to the subview instance.
Usage
class YourView extends View
render: ->
super
infoboxView = new InfoBox autoRender: true, container: @el
@subview 'infobox', infoboxView
var YourView = View.extend({
render: function() {
View.prototype.render.apply(this, arguments);
var infoboxView = new InfoBox({autoRender: true, container: this.el});
this.subview('infobox', infoboxView);
}
});
Publish/Subscribe
View
includes the EventBroker mixin to provide publish/subscribe capabilities using the mediator
Methods of Chaplin.EventBroker
publishEvent(event, arguments...)
Publish the global event
with arguments
.
subscribeEvent(event, handler)
Unsubcribe the handler
for the given event
(if it exists) before subscribing it. It is like Chaplin.mediator.subscribe
except it cannot subscribe twice.
unsubscribeEvent(event, handler)
Unsubcribe the handler
to the event
. It is like Chaplin.mediator.unsubscribe
.
unsubscribeAllEvents()
Unsubcribe all handlers for all events.