Marionette.Handlebars does exactly what its name suggests: it adds support for Handlebars and Mustache templates to Marionette.
Marionette.Handlebars supports precompiled templates as well. It does its job entirely behind the scenes – load it, and you are all set.
There really isn't much in terms of an API – nothing, in fact, except for an extension point in case you want to lazy-load some templates.
If you are a happy user of this project already, you can support its development by donating to it. You absolutely don't have to, of course, but perhaps it is something you might actually want to do.
Marionette.Handlebars, somewhat unsurprisingly, depends on the Marionette stack (Underscore, Backbone, Marionette) and Handlebars. Include marionette.handlebars.js after those are loaded.
When loaded as a module (e.g. AMD, Node), Marionette.Handlebars does not export a meaningful value. It solely lives in the Marionette namespace.
The stable version of Marionette.Handlebars is available in the dist
directory (dev, prod). If you use Bower, fetch the files with bower install marionette.handlebars
. With npm, it is npm install marionette.handlebars
.
If you have precompiled your templates, Marionette.Handlebars retrieves them from the Handlebars cache. You just need to set the template
property of a Marionette view to the ID of the compiled template (derived from the name of the template file).
If you want to lazy-load some of your templates, you must implement a loader which works for your requirements – such as your URL scheme, for instance. Marionette.Handlebars just provides an extension point for you.
A word of caution: Loading templates on demand is terrible for the performance of an application. The additional overhead of an HTTP request, particularly on mobile, is way beyond acceptable levels for UI elements which the user is already waiting for. Lazy template loading can make sense, though, for elements which are pre-rendered, but not yet exposed to the user. Use it judiciously.
You need to override Marionette.TemplateCache.prototype.lazyLoadTemplate
. Out of the box, the method doesn't do anything. Replace it with your own implementation. Here is what you need to know.
-
Your loader is called with the template ID as the first argument.
The template ID is the value of the
template
property, ortemplate
constructor option, in your Marionette views. The template ID would be a selector if your templates were read from the DOM. But because you provide your own loader, it can be any string you choose. -
Your loader also receives an options argument which is passed around by Marionette.
That argument can initially be provided to
Marionette.TemplateCache.get()
, but is usuallyundefined
. Have a look at the few bits of information in the Marionette documentation to get started.Overall, though, the
options
argument is not very useful. Marionette views never pass it in when requesting a template. At a minimum, you'd have to overrideMarionette.Renderer.render()
to make it work at all. -
The loader must return the raw template HTML if successful, or
undefined
if it fails to fetch the template.If you want to fetch and return compiled templates, you need to enable it explicitly.
-
Your loader must not be async.
Yes, synchronous loading is terribly inefficient. But asynchronous template loading is beyond what a generic Handlebars integration can provide to Marionette views.
That said, you can certainly fill in the blanks. If the templates are lazy-loaded asynchronously, then the views must be rendered async as well. Also, there should probably be a mechanism to prevent multiple, simultaneous requests for the same template URL. (I have seen Traffic Cop being mentioned as a helpful tool, but it seems abandoned. More here.)
A very basic implementation of async loading can be seen in the AMD demo of Marionette.Handlebars.
Please be aware that the lazy loader is only called as a last resort. The search for a matching template begins in the Handlebars cache of precompiled templates, then moves on to the DOM. Only if the template is not found in any of these places, the lazy loader gets its turn.
For security reasons, you cannot lazy-load compiled templates, ie executable Javascript code. If you want to go down that route, you must explicitly enable it:
Marionette.TemplateCache.allowCompiledTemplatesOverHttp = true;
This is a global setting. The lazyLoadTemplate()
method is allowed to return a function then, in addition to strings.
A basic loader might look like this:
Marionette.TemplateCache.prototype.lazyLoadTemplate = function ( templateId, options ) {
var templateHtml,
templateUrl = "templates/" + templateId + ".hbs";
Backbone.$.ajax( {
url: templateUrl,
success: function( data ) {
templateHtml = data;
},
async: false
} );
return templateHtml;
};
The loader in the AMD demo might give you some inspiration, too.
If you'd like to fix, customize or otherwise improve the project: here are your tools.
npm sets up the environment for you.
- The only thing you've got to have on your machine (besides Git) is Node.js. Download the installer here.
- Clone the project and open a command prompt in the project directory.
- Run the setup with
npm run setup
. - Make sure the Grunt CLI is installed as a global Node module. If not, or if you are not sure, run
npm install -g grunt-cli
from the command prompt.
Your test and build environment is ready now. If you want to test against specific versions of Backbone, edit bower.json
first.
To run the tests on remote clients (e.g. mobile devices), start a web server with grunt interactive
and visit http://[your-host-ip]:9400/web-mocha/
with the client browser. Running the tests in a browser like this is slow, so it might make sense to disable the power-save/sleep/auto-lock timeout on mobile devices. Use grunt test
(see below) for faster local testing.
The test tool chain: Grunt (task runner), Karma (test runner), Mocha (test framework), Chai (assertion library), Sinon (mocking framework). The good news: you don't need to worry about any of this.
A handful of commands manage everything for you:
- Run the tests in a terminal with
grunt test
. - Run the tests in a browser interactively, live-reloading the page when the source or the tests change:
grunt interactive
. - If the live reload bothers you, you can also run the tests in a browser without it:
grunt webtest
. - Run the linter only with
grunt lint
orgrunt hint
. (The linter is part ofgrunt test
as well.) - Build the dist files (also running tests and linter) with
grunt build
, or justgrunt
. - Build continuously on every save with
grunt ci
. - Change the version number throughout the project with
grunt setver --to=1.2.3
. Or just increment the revision withgrunt setver --inc
. (Remember to rebuild the project withgrunt
afterwards.) grunt getver
will 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.
The grunt interactive
and grunt demo
commands spin up a web server, opening up the whole project to access via http. So please be aware of the security implications. You can restrict that access to localhost in Gruntfile.js
if you just use browsers on your machine.
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.
To my own surprise, a kind soul wanted to donate to one of my projects, but there hadn't been a link. Now there is.
Please don't feel obliged in the slightest. The license here is MIT, and so it's free. That said, if you do want to support the maintenance and development of this component, or any of my other open-source projects, I am thankful for your contribution.
Naturally, these things don't pay for themselves – not even remotely. The components I write aim to be well tested, performant, and reliable. These qualities may not seem particularly fascinating, but I put a lot of emphasis on them because they make all the difference in production. They are also rather costly to maintain, time-wise.
That's why donations are welcome, and be it as nod of appreciation to keep spirits up. Thank you!
- Removed the separate AMD/Node builds in
dist/amd
. Module systems and browser globals are now supported by the same file,dist/marionette.handlebars.js
(or.min.js
) - Updated Marionette dependency to include Marionette 3
- Exposed version in
Marionette.TemplateCache.MarionetteHandlebarsVersion
- Updated Backbone and Handlebars dependencies
- Updated Backbone dependency
- Added support for lazy-loading precompiled templates
- Exposed getPrecompiledTemplate
- Added lazy-loaded, precompiled templates to demo
- Initial development, tests, documentation
MIT.
Copyright (c) 2015-2017 Michael Heim.