-
Notifications
You must be signed in to change notification settings - Fork 172
Template rendering
One of the best parts about LayoutManager is working with the super
flexible sync/async friendly fetchTemplate
and renderTemplate
overridable methods.
There are several ways of loading your templates. If you can, try to abstract
as much into the fetchTemplate
method to make your template
properties lean and
descriptive.
Out of the box LayoutManager has defaults in place to easily fetch templates from script tags containing template contents.
If this is slightly confusing, check out the Overview section for more information on how these work.
<script type="template" id="my-template">
Hi <%= name %>!
</script>
You can now use this template easily by doing:
Backbone.Layout.extend({
template: "#my-template"
});
This is not recommended for production applications. Check out the recommended best practices below for more information on alternative methods.
When the template property is not null
, undefined
, or a function
it will
be passed to a configurable method fetchTemplate
. You can read more about this in
Properties and Methods.
If the template is not a string, it will be passed unchanged to fetchTemplate
. If
the template property is a string and you have a prefix
property set, the
template will be prefixed with that properties value.
Path based string template example:
Backbone.Layout.extend({
// Remember the prefix needs to have a trailing `/`.
prefix: "/templates/", template: "my-template",
// This method would be called with the `prefix` + `template`.
fetchTemplate: function(path) {
// Path would be `/templates/my-template` here.
}
});
Selector based string template example:
Backbone.Layout.extend({
// Remember the prefix needs to have a trailing `/`.
prefix: "script#", template: "my-template",
// This method would be called with the `prefix` + `template`.
fetchTemplate: function(path) {
// Path would be `script#my-template` here.
}
});
If you have access to the template function directly, perhaps through RequireJS, or a global template namespace, you can directly assign it to the template property.
// Assume `window.JST` is an object containing all your templates.
window.JST = {
// Basic template demonstration.
"some-template": _.template("<%= some template %>")
};
// Create a new Layout that uses the previously defined template function.
Backbone.Layout.extend({
template: window.JST["some-template"]
});
The default implementation is not an ideal way of loading templates and we highly recommend you explore other options, such as AJAX loading during development and compiling for production.
Here is an example:
Backbone.Layout.configure({
// Set the prefix to where your templates live on the server, but keep in
// mind that this prefix needs to match what your production paths will be.
// Typically those are relative. So we'll add the leading `/` in `fetch`.
prefix: "templates/",
// This method will check for prebuilt templates first and fall back to
// loading in via AJAX.
fetchTemplate: function(path) {
// Check for a global JST object. When you build your templates for
// production, ensure they are all attached here.
var JST = window.JST || {};
// If the path exists in the object, use it instead of fetching remotely.
if (JST[path]) {
return JST[path];
}
// If it does not exist in the JST object, mark this function as
// asynchronous.
var done = this.async();
// Fetch via jQuery's GET. The third argument specifies the dataType.
$.get(path, function(contents) {
// Assuming you're using underscore templates, the compile step here is
// `_.template`.
done(_.template(contents));
}, "text");
}
});
Once you have loaded your template function into the View using an above method, you'll want to be able to provide the correct data to render.
The serialize
property is named the same as the example in the Backbone
documentation to reduce confusion.
Use a function for serialize
when you have data changing dynamically such as
models, collections, etc.
Backbone.Layout.extend({
serialize: function() {
return { user: this.model };
}
});
This will provide this.model
as user
inside of your templates. If you are
not using a template engine that supports inline JavaScript, you want to pass
the raw attributes instead.
Backbone.Layout.extend({
serialize: function() {
return { user: _.clone(this.model.attributes) };
}
});
When you provide a collection or anything iterable, it is considered a good practice to pass it as a wrapped (chained) underscore object.
Backbone.Layout.extend({
serialize: function() {
return {
// Wrap the users collection.
users: _.chain(this.collection)
};
}
});
The benefit here is that you no longer depend on an _
variable inside your
templates and it becomes easier to read:
<% users.each(function(user) { %>
instead of:
<% _.each(users, function(user) { %>
If your content never changes, or you want to assign the context manually at
run-time, you can use an object instead of a function. This is very similar
to how Backbone handles almost all dynamic values (think Model#url
).
// Create a demo Layout.
var myLayout = new Backbone.Layout({
template: _.template("<%= msg %>")
});
// Set the data.
myLayout.serialize = { msg: "Hello world!" };
If you nest a View and call render
on the parent, you will not need to call
render on the nested View. The render
logic will always iterate over nested
Views and ensure they are rendered properly.
Sometimes it's confusing as to when you should trigger render. Take this common scenario:
Backbone.Layout.extend({
addView: function(model, render) {
// Insert a nested View into this View.
var view = this.insertView(new ItemView({ model: model }));
// Only trigger render if it not inserted inside `beforeRender`.
if (render !== false) {
view.render();
}
},
beforeRender: function() {
this.collection.each(function(model) {
this.addView(model, false);
}, this);
},
initialize: function() {
this.listenTo(this.collection, "add", this.addView);
}
});
There is special logic inside of addView
that conditionally determines if you
should render or not. This is semi-fragile and hopefully a more
straightforward method will arise.
Whenever you call render
you will receive back a Deferred object, which can
be used to know when the render has completed.
myLayout.render().then(function() {
/* Rendering has completed. */
});
If you want access to the View, the Deferred returns with a property view
that you can access to continue chaining.
myLayout.render().view.$el.appendTo("body");
If you wish to prevent a render from happening, you can cancel the render
by returning false
or a rejected Promise from within the beforeRender
method.
Example:
var CancelledView = Backbone.Layout.extend({
beforeRender: function() {
return false;
},
afterRender: function() {
window.alert("This will never alert!");
}
});
Whenever you insert a View and render it, if it has already rendered it will
be deleted. This prevents duplicates in lists. Since this is not always
desired behavior, you may want to add a property to the view: keep: true
.
This property will keep the View from being removed.
Example:
var AppendedView = Backbone.Layout.extend({
keep: true
});
// Insert into my layout.
myLayout.insertView(".insert-region", new AppendedView());
// Render.
myLayout.getView(".insert-region").render();
// Render twice! Doesn't get removed.
myLayout.getView(".insert-region").render();
Getting started
Usage
- Overview
- Configuration
- Basics
- Example usage
- Nested views
- Template rendering
- LayoutManager in Node.js
API reference
Migration guides
Legacy
How to contribute