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
Users of Marionette benefit from automatic, integrated management of the template caches which Marionette and Backbone.Declarative.Views provide.
If you use other components which extend Backbone.View, load these components after Backbone.Declarative.Views.
Backbone.Declarative.Views augments the Backbone.View base type, so its functionality is available in every view throughout your code.
Load backbone.declarative.views.js after Marionette.
If you use AMD, please be aware that Marionette is not declared as a dependency in the AMD build of Backbone.Declarative.Views. Declare it yourself by adding the following shim to your config:
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.
Markup, styling and behaviour should be kept separate – we all know that. Yet with Backbone views, it is common to mix them up.
Part of the view markup is often stored in the HTML, wrapped in script/template tags, while another part – the one describing the container
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
The transformation doesn't require any intervention on your part, or additional code. This is the core of what Backbone.Declarative.Views does.
Backbone.Declarative.Views looks for a
template property on the view, with a selector as its value. A
template option passed to the constructor will do as well. As the Backbone view is created, Backbone.Declarative.Views fetches the template and uses the data attributes, if there are any, to set up the
el of the view.
And that is the end of it. Processing the template, feeding template vars to it, appending the final HTML to the DOM – all that remains your responsibility. However, you can speed up the process by fetching the template from the cache of Backbone.Declarative.Views. There is no need to read it from the DOM again.
There is something else you might have noticed in the example above. The names of the Backbone.View properties have changed when they were written as data attributes.
In compliance with the HTML5 data attributes spec,
tagName has turned into
className has become
data-class-name. Likewise, there are a
data-attributes. Use these names when describing an
el in a template.
Among the properties describing the
el of the view, one warrants a closer look: the
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 its template:
You can set the template property of the class with
extend():var View = BackboneViewextend template: "#selector" ;
You can also pass the template in as an option when you create the view:var view = template: "#selector" ;
template option, if provided, is attached to the view directly. It is available to the methods of your view, including
If you want Backbone.Declarative.Views to pick up the properties of your
el, and perhaps cache the template for you, you have to play by the rules.
You can't set the template property to a selector inside
initialize– that is too late. The
elhas already been set up at this point. Modifications of the template property in
initialize()will not affect the
elof the view.
This behaviour is a feature, not a bug. It is common to compile a template in
initialize, along the lines ofthistemplate = _template $ thistemplate html ;
The original value of the template property is overwritten in the process. Backbone.Declarative.Views does not interfere with this pattern, it continues to work. Equally, overwriting the template property in
initializewon't break the functionality of Backbone.Declarative.Views, either.
elproperties in a template are ignored if the view does not create its own, shiny new
el. Backbone allows you to attach a view to an
elwhich already exists in the DOM, rather than create a new one:var view = el: existingElement ;
Even if you specify a
templatealong with it, the data attributes of the template won't get applied to the
el. That is in line with the default Backbone behaviour. Backbone ignores
el-related view properties, like
elis set to an existing DOM element.
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.
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 <%= content %> goes here' +'</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.
Accessing the DOM is rather slow. Ideally, for each template, it should be enough to touch the DOM once. The very first time a template is used, Backbone.Declarative.Views retrieves it from the DOM and checks for
el data on the template tag. From here on out, the data of that template is cached.
It makes overwhelming sense to reuse that data and save yourself future look-ups. Backbone.Declarative.Views tries to be helpful, so it does not just keep the data attributes of the
el in its cache. It will happily hand you the inner HTML of the template, or the outer HTML. And if you tell it which template compiler to use, it will even compile the templates for you and cache the results, too.
Here is a pretty universal duo of snippets for tapping into the cache of Backbone.Declarative.Views.
The snippets assume that you compile your templates with the
_.template() function of Underscore. If you don't, it is pretty easy to see what you need to change.
// Tell the caching mechanism which template compiler to usereturn _template templateHtml ;;
Defining a compiler like that is optional, but gives you a speed boost for free.
// Tap into the cache in a view class.//// Safe to use even if you don't define a template for some of your views.// Also works if you leave out the snippet above, and don't set a compiler.var BaseView = BackboneViewextendvar cachedTemplate = thisdeclarativeViewsgetCachedTemplate;if cachedTemplatethistemplate = cachedTemplatecompiled || _template cachedTemplatehtml ;;
This little bit of code has got you covered. For the fine print, read on.
You can access cached template data easily from inside a view. The necessary methods are tucked away, or rather namespaced, in the
declarativeViews property of a view.
In addition, you can deal with cache entries independently of individual, instantiated views. The global cache API is attached to the
Backbone.DeclarativeViews namespace (note that there is no dot inside
In the context of a view, call
var cachedTemplate = thisdeclarativeViewsgetCachedTemplate;// Do stuff with it, most likely with cachedTemplate.html,// or perhaps with cachedTemplate.compiled.
As you can see in the example, the cached template is available by the time
initialize() is run, so you can use it there.
The link between a view and a template is forged when the view is instantiated, and as far as the cache is concerned, it can never be changed. You can modify or overwrite the
template property as you wish, do whatever you want with it during
render(), even use multiple templates. But
getCachedTemplate() always returns the template you started out with – the one defined by the
template property, or a
template option, at the time the view was created.
If you need to access the cache independently of an individual view, call
getCachedTemplate() via the global API with a template selector.
var cachedTemplate = BackboneDeclarativeViewsgetCachedTemplate "#template" ;
Don't worry about availability. If the template is not yet in the cache, that call will put it in there.
To avoid duplicate cache entries, use the same selector for a
getCachedTemplate() query as in your views. Selectors which are equivalent but not identical, e.g.
"script#template", create two distinct cache entries even though they refer to the same template.
When you pull data from the cache with
getCachedTemplate(), you do not get a string with the template HTML back. Rather, your receive a hash with various properties of the cache entry:
html(string) the actual template content if the template has been specified by a selector.
If you don't define your template with a selector, and rather pass in a raw HTML template string, the
htmlproperty contains the inner HTML of that string. In case you need the string back verbatim, call
outerHtml()instead. Please keep in mind that some HTML strings are uncacheable (see below).
outerHtml(function) a function returning the full (outer) HTML of the template
compiled(function, or undefined) the compiled template (ie, a function returning the final HTML, with the template vars filled in) if a template compiler has been set in
Backbone.DeclarativeViews.custom.compiler. Or undefined, otherwise.
tagName(string or undefined) the tag to be used for the
el, if defined by a data attribute of the template
className(string or undefined) the class name of the
el, if defined by a data attribute
attributes(hash or undefined) hash of
elattributes and their values, if defined by a data attribute
(Oh, and have you spotted the textbook case of bad API design? One way to get back the template HTML is by reading the
html property, while its twin
outerHtml is a function you have to call. Yes, that seems silly, and yes, it can trip you up.
But then again, some templates are rather big, and most people don't need the outer HTML. Given today's memory constraints on mobile devices, it seemed better to reconstruct the outer HTML on demand, with a function call, rather than double the memory consumption of the cache by storing near-identical strings for every template.)
There won't be a cache miss for any template which exists in the DOM. When you call
getCachedTemplate() on either a view or the global
Backbone.DeclarativeViews object, you get the template back. If it is not yet in the cache, it will be put there in the process.
You do get a cache miss in the following cases.
You can set the template property of a view to pretty much anything. It could be a function returning what you need. It could, theoretically, be a hash of things.
Backbone.Declarative.Views does not handle these kinds of template definitions. It simply leaves them alone. Consequentially, the templates do not make it into the built-in cache.
The selector does not match a DOM node.
That only matters on first access. If the DOM node is deleted after its content is already in the cache, you get the cached template back.
The template is not a selector but a raw HTML string, and that string can't be turned into a template element (or a set of elements).
Backbone.Declarative.Views hands the template string over to
Backbone.$(read: jQuery) for processing, or to a custom loader if you have defined one. If jQuery, or your loader, can't handle the string, you get a cache miss.
In practice, that happens when you pass the inner HTML of a template to your view, and parts of the HTML are not wrapped in a tag. Consider a view like this:var view =template: "Template <%= content %> <em>without</em> a tag around it.";
The loader, jQuery, can't deal with the string. There would have to be HTML tags around the plain text, but without them, jQuery throws an error (which is caught, silently). Because the template loader can't handle it, Backbone.Declarative.Views ignores it. This is an uncacheable template as far as Backbone.Declarative.Views is concerned.
In all of these cases,
getCachedTemplate() returns undefined.
Backbone.Declarative.Views handles the template caching, with one exception. Compiled templates are not in the cache, at least by default. You first need to tell Backbone.Declarative.Views which compiler to use.
This is how:
// do stuffreturn yourCompiledTemplate;;
The compiler function receives the inner HTML of the template node as the first argument. As the second argument, it is passed the template node itself, in a jQuery wrapper.
The compiler should return a function which accepts the template vars as an argument and produces the final HTML. But in fact, the compiler is allowed to return anything. Backbone.Declarative.Views doesn't care what your compiled templates are, and what you do with them. It just stores them for you.
The return value of the compiler is stored in the
compiled property of each cache entry.
So in effect, if you define a compiler, this is what Backbone.Declarative.Views does for you:
cacheEntrycompiled = BackboneDeclarativeViewscustomcompiler cacheEntryhtml $template ;
By default, the template property of your view is assumed to be a selector, or perhaps a raw HTML string. For processing, it is handed over to
Backbone.$, which acts as the default loader and fetches your template from the DOM (or creates a node from the raw HTML string).
If that is not how you want to go about loading your templates, define a custom loader instead. It will take the place of
Backbone.$ when the template is fetched.
// do stuffreturn $ nodeOrOuterTemplateHtml ;;
The custom loader is called with the template property as the only argument. That argument is always a string. The custom loader must return a jQuery object (or more precisely an instance of
Backbone.$, which usually means jQuery).
The returned jQuery object is considered to be the template node. The template HTML should best be inside that node (rather than be the node), though it is essentially up to you how you set that up. Inner and outer HTML of the node can be retrieved from the
html property and
outerHtml() method of the cache entry.
But sometimes, things just go wrong. If your loader can't process the template argument, or does not find the template, it is allowed to throw an error. The error is caught and handled silently. Alternatively, the loader can return a jQuery object which does not contain any nodes (length 0). Both cases are treated as a permanent cache miss.
Please be aware that your custom loader will only be called if the template of the view is defined by a string. If it is not, Backbone.Declarative.Views bails out well before attempting to load anything. Non-string template properties are none of its business.
If you modify a template in the DOM, and if that template has already been used, you have to clear the cache. Otherwise, the cache does not pick up the changes and returns an outdated version of the template.
You can clear the cache for a specific template, or a number of them, from the global
BackboneDeclarativeViewsclearCachedTemplate "#template" "#template2" ;BackboneDeclarativeViewsclearCachedTemplate "#template" "#template2" ;
You must use the exact same selectors as when you first used the templates. Selectors which are merely equivalent, e.g.
"script#template" instead of
"#template", don't match the cache entry and leave it in the cache.
Alternatively, you can target the template associated with a specific view, and clear it from there:
Again, this makes sure that the template will be re-read from the DOM on next access. But it does not allow you to re-associate the view with another template (as far as the cache is concerned). That link stays in place for the lifetime of the view.
Finally, if you want to clear the whole cache in one go, do it with
There is a lightweight link between the caches of Marionette and Backbone.Declarative.Views. If you clear an item from one cache, it gets cleared from the other as well. You can call the cache-clearing methods of Marionette and Backbone.Declarative.Views interchangeably.
And that, surprisingly, is where it ends. You might have expected deeper integration, like an actual joint cache, which would have saved memory and reduced DOM access even further.
Indeed, that joint cache has existed briefly. But it turned out that the costs outweighed the benefits. The performance gain was minimal at best, sometimes not even offsetting the additional overhead of integration. And crucially, it didn't work that well with some Marionette customizations. Custom template loaders in Marionette had been trickier to use. In the end, full cache integration had been more trouble than it is worth, and has been removed.
With Marionette, it does. The unit tests cover Marionette, too.
With other frameworks, it should work just as well. Backbone.Declarative.Views is designed to play nice with third-party code of any kind. So go ahead and try it. Feedback is always 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.
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. You don't have to put the
For such a view, only the data attributes matter in the template. The content inside the template tag will simply be ignored.
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 AND
- load the component too early, ie before Backbone.Declarative.Views AND
- while instantiating a view which is supplied by the component, you 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.
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 the
el of a view can't just be part of the HTML inside the template, as an enclosing tag perhaps.
Backbone.Declarative.Views does not make any assumptions about what you keep inside your templates, or how you structure them. It does not break existing code, no matter what. You can include it into any project and use it where it helps you most, without being forced to rework legacy code. Data attributes are the best solution for that kind of approach.
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.
- Added safe and transparent template caching, significant speed gains
- Added a cache management API, opening the cache for access from client code
- Added Marionette cache integration (if Marionette is available)
- Added support for custom template loaders and compilers
- Updated dependencies
- Fixed handling of options.template with undefined value
- Fixed strict mode in AMD build
- Improved build environment
- Made available as an npm install
- Improved documentation
- Added caching for the template element
- Tweaked AMD build
- Clarified documentation
- Fixed AMD build
- Fixed documentation
- Initial public release
Copyright (c) 2014, 2015 Michael Heim.