Defining the DOM element of a Backbone view right in the template.
With Backbone.Declarative.Views, you can read the markup for the container element of a view directly from its template. Keep the tag name, class name and other attributes of
If you use other components which extend Backbone.View, load those components after Backbone.Declarative.Views. (Even if you load them too early, Backbone.Declarative.Views does its job as intended. But you might have to deal with a corner case.)
The stable version of Backbone.Declarative.Views is available in the
dist directory (dev, prod), including an AMD build (dev, prod). If you use Bower, fetch the files with
bower install backbone.declarative.views. With npm, it is
npm install backbone.declarative.views.
Backbone.Declarative.Views augments the Backbone.View base type, so its functionality will be available in every view throughout your code.
Markup, styling and behaviour should be kept separate - we all know that. Yet with Backbone views, it is common to mix them up.
className and other properties.
It doesn't belong there.
Backbone views use a couple of properties to describe the container element:
Lets begin with an example. Consider the following template snippet.
Now, if your view has a
template: "#my-template" property, its
el is set up as
<p id="myContainer" class="someClass orOther"></p>, right out of the box.
So here is how it works. Backbone.Declarative.Views expects to find a
template property on the view, with a selector as its value. When the Backbone view is initialized, it will automatically locate the template and use the data attributes, if there are any, to set up the
el of the view.
(You still have to read and apply the template in
render() to actually, well, render the content - or whatever else you do to get your templates into the DOM.)
The names of the Backbone.View properties have to change a bit when they are written as HTML-compliant data attributes. So
tagName turns into
data-class-name. Likewise, there are a
You can override part or all of the
el properties which are declared in data attributes.
In the example above, the data attribute defines the tag as a
p. Suppose you add a
tagName: "section" property to the view, or pass it to the constructor as an option. The tag name applied by your script,
section, will trump the
p you defined statically in the template.
Among the properties describing the
el of the view, one warrants a closer look: the
(Note to self: When hand-writing JSON, remember to quote property names as well as their values. And those quotes must be double quotes.)
There are two ways to let a view know about the template:
You can set the template property of the class with
You can also pass the template in as an option when you create the view.
(If you have loaded other components before Backbone.Declarative.Views, and if those components extend Backbone.View for their own view types, you likely cannot pass the template as an option to these types. See the edge case, below).
On the other hand, this is how it won't work:
If you modify the template property in
initialize(), it will not affect the
elof the view. The
elhas already been set up at this point. So you can't set the template property to a selector in
This behaviour is a feature, not a bug. It is common to compile a template in
initialize, along the lines of
this.template = _.template( $( this.template ).html() ), and overwrite the template property in the process. This pattern will continue to work, and it won't break the functionality of Backbone.Declarative.Views, either.
If you set the
elof the view to an existing DOM element, the data attributes of a template don't get applied to it. That matches the default Backbone behaviour. Backbone ignores
el-related view properties, like
elis set to an existing DOM element.
Yes, that works as well. The template property of a view can be set to an HTML string instead of a selector, as in the following example:
var templateHtml = '<li class="bullet" data-tag-name="ul" data-class-name="list">template stuff</li>'view = template: templateHtml ;console.log vieweltagName // => prints "UL"console.log viewelclassName // => prints "list"
If the template HTML doesn't have a single top-level element, but multiple ones, then the data attributes defining the
el must be on the first top-level element.
Looking up DOM elements is relatively slow. Backbone.Declarative.Views pulls the
el data from the template script tag in the DOM, so it makes sense to cache the DOM node - or more specifically, the jQuery object representing it. You can access the cached element and save yourself future look-ups.
You'll find the cached template in the
backboneDeclarativeViews.$template property of a view. The property is available by the time
initialize() is run, so you can access it there. If the template has not been specified by a selector, or if it did not exist, the
$template property is undefined.
If you handle multiple templates, please note that the template cache is set once and for all when the view is instantiated, and not updated if you change templates later on. So the cache always holds the template node which was accessed first.
With Marionette, it does. The unit tests cover Marionette, too.
With other frameworks, it should work just as well. But then again, what do I know? ;) Feedback welcome.
No. You can continue to define
Equally, you can omit the
template property, or assign a value to it which is a function, rather than a selector. Obviously, the
el of the view won't be set up from data attributes this way, but rest assured that nothing will break.
Mostly, yes. It depends on how you set up your views. If you define the template property with
extend(), before instantiating the view, things will just work.
But you can run into an edge case if you
- load a component which creates its own Backbone.View subtypes
- load the component too early, ie before Backbone.Declarative.Views
- while instantiating a view which is supplied by the component, pass it a template selector as an option.
In that case, and in that case only, the data attributes of the template won't get applied to the
el of the view.
So to be on the safe side, load your view-related components after Backbone.Declarative.Views.
Incidentally, Marionette is not affected by that edge case. You can load Marionette as you please, before or after Backbone.Declarative.Views.
An example of such a view is the Marionette.CollectionView type. Its content is entirely made up of iterated child views. Its own markup consists of nothing more than the containing
And yes, that
el can be defined with a template, so the properties of
On the face of it, using data attributes on one tag to describe another tag seems nonstandard and indirect. You may wonder why the markup for
el can't just be part of the HTML in the template.
If you'd like to fix, customize or otherwise improve the project: here are your tools.
- The only thing you've got to have on your machine is Node.js. Download the installer here.
- Open a command prompt in the project directory.
npm install. (Creates the environment.)
bower install. (Fetches the dependencies of the script.)
Your test and build environment is ready now. If you want to test against specific versions of Backbone, edit
A handful of commands manage everything for you:
- Run the tests in a terminal with
- Run the tests in a browser interactively, live-reloading the page when the source or the tests change:
- Build the dist files (also running tests and linter) with
grunt build, or just
- Build continuously on every save with
- Change the version number throughout the project with
grunt setver --to=1.2.3. Or just increment the revision with
grunt setver --inc. (Remember to rebuild the project with
grunt getverwill quickly tell you which version you are at.
Finally, if need be, you can set up a quick demo page to play with the code. First, edit the files in the
demo directory. Then display
demo/index.html, live-reloading your changes to the code or the page, with
grunt demo. Libraries needed for the demo/playground should go into the Bower dev dependencies, in the project-wide
bower.json, or else be managed by the dedicated
bower.json in the demo directory.
grunt interactive and
grunt demo commands spin up a web server, opening up the whole project to access via http. By default, that access is restricted to localhost. You can relax the restriction in
Gruntfile.js, but be aware of the security implications.
In case anything about the test and build process needs to be changed, have a look at the following config files:
karma.conf.js(changes to dependencies, additional test frameworks)
Gruntfile.js(changes to the whole process)
web-mocha/_index.html(changes to dependencies, additional test frameworks)
New test files in the
spec directory are picked up automatically, no need to edit the configuration for that.
Copyright (c) 2014 Michael Heim.