Created by Tim Branyen @tbranyen with contributions from @nedcampion
Provides a logical structure for assembling layouts with Backbone Views. Designed to be adaptive and configurable for painless integration.
Depends on Underscore, Backbone and jQuery. You can swap out the jQuery dependency completely with a custom configuration.
Development is fully commented source, Production is minified and stripped of all comments except for license/credits.
Include in your application after jQuery, Underscore, and Backbone have been included.
<script src="/js/jquery.js"></script>
<script src="/js/underscore.js"></script>
<script src="/js/backbone.js"></script>
<script src="/js/backbone.layoutmanager.js"></script>
This example renders a View into a template which is injected into a layout.
This code typically resides in a route callback. If you want to provide a
custom object for your template engine to the layout, use the serialize
property.
// Create a new layout using the #main template.
var main = new Backbone.LayoutManager({
template: "#main-layout",
// In the secondary column, put a new Login View.
views: {
".secondary": new LoginView()
}
});
// Render into <body>.
main.render(function(el) {
$("body").html(el);
});
Views may also be alternatively defined later:
main.view(".header", new HeaderView);
main.view(".footer", new FooterView);
Use the above syntax to change out views at a later time as well, remember to
call the View's render
method after swapping out to have it displayed.
You may have a situation where a View is defined that encapsulates other nested Views. In these cases you should use nested views inside your LayoutManager View assignments.
Check out this example to see how easy this is:
var main = new Backbone.LayoutManager({
template: "#some-layout",
views: {
".partial": new PartialView({
views: {
".inner": new InnerView()
}
})
}
});
You can nest Views infinitely.
Each View needs to have a template associated, via the template
property.
This name by default is a jQuery selector, but if you have a custom
configuration this could potentially be a filename or JST function name.
var LoginView = Backbone.View.extend({
// Tell LayoutManager what template to associate with this View.
template: "#login-template",
// The render function will be called internally by LayoutManager.
render: function(layout) {
// Wrap the layout with this View and call render.
return layout(this).render();
}
});
Optionally, you can extend from LayoutManager.View
and omit the render
method. If you need to do custom logic in render
, you should use the
other pattern above.
var LoginView = Backbone.LayoutManager.View.extend({
template: "#login-template"
});
Instead of re-rendering the entire layout after data in a single View changes,
you can simply call render()
on the View and it will automatically update
the DOM. You cannot bind to the initial render reference, like so
Assume that you have a model that when changed, causes a redraw.
var MyView = Backbone.LayoutManager.View.extend({
initialize: function() {
this.model.bind("change", this.render, this);
}
});
You must use this syntax instead, calling it from a function:
var MyView = Backbone.LayoutManager.View.extend({
initialize: function() {
this.model.bind("change", function() {
this.render();
}, this);
}
});
This necessity may be alleviated in a future version.
Template engines bind data to a template. The term context refers to the data object passed.
LayoutManager
will look for a serialize
method or object automatically:
var LoginView = Backbone.LayoutManager.View.extend({
template: "#login-template",
// Provide data to the template
serialize: function() {
return this.model.toJSON();
}
});
You can also pass the context object inside the render
method:
var LoginView = Backbone.View.extend({
template: "#login-template",
render: function(layout) {
// Provide data to the template
return layout(this).render(this.model.toJSON());
}
});
These example templates are defined using a common pattern which leverages
how browsers treat <script></script>
tags with custom type
attributes.
This is how LayoutManager
expects templates to be defined by default (using script tags).
<script id="main-layout" type="layout">
<section class="content twelve columns"></section>
<!-- Template below will be injected here -->
<aside class="secondary four columns"></aside>
</script>
<script id="login-template" type="template">
<form class="login">
<p><label for="user">Username</label><input type="text" name="user"></p>
<p><label for="pass">Password</label><input type="text" name="pass"></p>
<p><input class="loginBtn" type="submit" value="Login"></p>
</form>
</script>
Attaching jQuery plugins should happen inside the render
methods. You can
attach at either the layout render or the view render. To attach in the
layout render:
main.render(function(el) {
$(el).find(".some-element").somePlugin();
$(".container").html(el);
});
In the above example its entirely possible the elements are not in the DOM yet.
This happens when you fetch templates asynchronously. Using the following
method, elements will be added into the DOM. To attach in the layout render,
you will need to override the render
method like so:
render: function(layout) {
return layout(this).render().then(function(el) {
$(el).find(".some-element").somePlugin();
});
}
This is a very cool example of the power in using deferreds. =)
Overriding LayoutManager
options has been designed to work just like
Backbone.sync
. You can override at a global level using
LayoutManager.configure
or you can specify when instantiating a
LayoutManager
instance.
Lets say you wanted to use Handlebars
for templating in all your Views.
Backbone.LayoutManager.configure({
// Override render to use Handlebars
render: function(template, context) {
return Handlebars.compile(template)(context);
}
});
In this specific layout, define custom prefixed paths for template paths.
var main = new Backbone.LayoutManager({
template: "#main",
// Custom paths for this layout
paths: {
template: "/assets/templates/"
}
});
- Paths:
An empty object. Two valid property names:
template
andlayout
.
paths: {}
- Deferred: Uses jQuery deferreds for internal operation, this may be overridden to use a different Promises/A compliant deferred.
deferred: function() {
return $.Deferred();
}
- Fetch:
Uses jQuery to find a selector and returns its
innerHTML
content.
fetch: function(path) {
return $(path).html();
}
- Partial: Uses jQuery to find the View's location and inserts the rendered element there.
partial: function(layout, name, template) {
$(layout).find(name).html(template);
}
- Render: Renders a template with Underscore.
render: function(template, context) {
return _.template(template)(context);
}
The fetch
method is overridden to get the contents of layouts and templates.
If you can instantly get the contents (DOM/JST) you can return the
contents inside the function.
Backbone.LayoutManager.configure({
fetch: function(name) {
return $("script#" + name).html();
}
});
If you need to fetch the contents asynchronously, you will need to put the
method into "asynchronous mode". To do this, assign this.async()
to a variable and call that variable with the contents when you are done.
Backbone.LayoutManager.configure({
fetch: function(name) {
var done = this.async();
$.get(name, function(contents) {
done(contents);
});
}
});
You may need to combine a mix of Engines and Transports to integrate.
Custom templating engines can be used by overriding render
.
No configuration necessary for this engine.
Backbone.LayoutManager.configure({
render: function(template, context) {
return Mustache.to_html(template, context);
}
});
Backbone.LayoutManager.configure({
render: function(template, context) {
return Handlebars.compile(template)(context);
}
});
You can swap out how templates are loaded by overriding fetch
.
No configuration necessary for this transport.
Backbone.LayoutManager.configure({
fetch: function(path) {
var done = this.async();
$.get(path, function(contents) {
done(contents);
});
}
});
Whatever you decide to return as a template in fetch
, can be used in the
render
method.
Backbone.LayoutManager.configure({
fetch: function(name) {
return window.JST[name];
},
render: function(template, context) {
return template(context);
}
});