I am using backbone and i would like to parse my first collection in my view.
My first question, is undescore really the best way to do it? I have heard about mustache.js
The next thing is, i don't know how to do is:
var A = new Model();
var B = new Model();
var Mole = new Collection([A, B]);
var View = View({model: A, collection: Mole });
View.render();
Here is my render method:
render: function(){
this.template = _.template($('template').html());
$(this.el).html(this.template(this.collection.models)); //I don't know what to send here
}
Here is my template
<script type="text/template" id="template">
<% _.each(collection, function(model){ %> //I don't know what to get here
<% model.name %>
<% }); %>
</script>
First of all, _.template wants the text from the template, not a jQuery object. That means that this:
this.template = _.template($('template'));
should be this:
this.template = _.template($('template').html());
Then the compiled template function will want to see key/value pairs for the data; from the fine manual (this applies to Mustache, Handlebars, and Underscore BTW):
When you evaluate a template function, pass in a data object that has properties corresponding to the template's free variables.
So you want to say this:
this.$el.html(this.template({
collection: this.collection.toJSON()
}));
and then you can say this in your template:
<% _.each(collection, function(model) { %>
<%= model.name %>
<% }); %>
A couple points to consider:
Backbone views already have a jQuery wrapped this.el in this.$el so there's no need to $(this.el).
Serialized data is generally passed to templates using toJSON, this applies doubly so to Mustache and Handlebars since they won't understand anything else.
You need to say <%= ... %> to get some output in your template; <% ... %> simply evaluates a bit of JavaScript code, it won't leave anything behind, you have to use interpolation delimiters (<%= and %> by default) for that.
Related
Below code is my backbone view. :
app.WorkerView = Backbone.View.extend({
tagName: 'tr',
className: 'odd',
template: _.template(templat1e),
render: function() {
//this.el is what we defined in tagName. use $el to get access to jQuery html() function
window.alert("I am in render function of WorkerView");
this.$el.html(this.template(this.model.attributes));
return this;
}
});
But this can produce html with class odd only. My goal is to produce html with different class for odd and even row. What I need to do?
I never worked with backbone but if you are using template to render your html fragment then you may try this out
<% _each(list, function(index) { %>
<tr <% if (index%2 == 0) { %>class="even" <% } else { %>class="odd" <% } %> ></tr>
<% }); %>
In my backbone application the REsT service always returns JSON data, so should I always use this.collection.toJSON() or can I use this.collection directly after collection fetch to bind the data to the underscore template?
It is better to pass Backbone's collection/model objects directly to underscore template, because its handy to add methods to models that output formatted values of the attributes. Check this example (assuming model has custom method getFormattedDate)
this.$el.html(this.template({collection: this.collection}))
<ul>
<% collection.each(function (model) { %>
<li><%= model.getFormattedDate() %> — <%= model.escape('title') %></li>
<% }); %>
</ul>
But if you decide to pass JSON data directly, then you can't output formatted date:
this.$el.html(this.template({collection: this.collection.toJSON()}))
<ul>
<% _.each(collection, function (model) { %>
<li><%= model.date %> — <%- model.title %></li>
<% }); %>
</ul>
UPD: I was wrong about escaping values in underscore templates, because you can use <%- content %> syntax to output escaped content. So the only benefit of passing models and collections directly to underscore template is ability to use custom getters of formatted data.
I'm following along with this tutorial (in french only) https://github.com/k33g/articles/blob/master/2011-08-14-BB-VIEWS.md which uses underscore templates with Backbonejs.
The tutorial says to put this template below in index file.
<script type="text/template" id="doc-template">
<span><%= id %></span>
<span><%= title %></span>
<span><%= test %></span>
<span><%= keywords %></span>
</script>
<div id='doc-container'></div>
I'm putting it in index.html.erb, however, the tutorial author is not using rails. It's necessary for me to use erb because I also include page specific content using rails content_for helpers.
When I try to view the page, I get an undefined local variable or method error
undefined local variable or method `id' for #<#<Class:0x007fd9c3a133b8>:0x007fd9c5066d90>
If I remove those variables from the templates it's still not rendering content to the page.
Can anyone explain what I'm doing wrong to render the data?
Other Backbone view related code
The tutorial initializes and renders a view in the appropriate container...
el : $('#doc-container'),
initialize : function() {
this.template = _.template($('#doc-template').html());
_.bindAll(this, 'render');
this.model.on('change', this.render);
},
render : function() {
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
return this;
}
Update
I'm having the same problem when I follow the tutorials instructions for a collection view. It throws an error for underscore's each method
undefined local variable or method `_' for #<#<Class:0x007fd9c3a133b8>:0x007fd9c2c78a78>
template in index.html.erb
<script type="text/template" id="docs-collection-template">
<ol>
<% _.each(docs, function(doc) { %>
<li><%= doc.id %> : <%= doc.title %></li>
<% }); %>
</ol>
</script>
The problem is that Underscore is using the same syntax for templating as ERB, so it is conflicting. You need to tell Underscore to use a different syntax. From the Underscore docs:
If ERB-style delimiters aren't your cup of tea, you can change
Underscore's template settings to use different symbols to set off
interpolated code. Define an interpolate regex to match expressions
that should be interpolated verbatim, an escape regex to match
expressions that should be inserted after being HTML escaped, and an
evaluate regex to match expressions that should be evaluated without
insertion into the resulting string. You may define or omit any
combination of the three. For example, to perform Mustache.js style
templating.
So, somewhere in your JavaScript code, before you compile the template, add the following code:
_.templateSettings = {
interpolate : /\{\{=(.+?)\}\}/g,
escape : /\{\{-(.+?)\}\}/g,
evaluate: /\{\{(.+?)\}\}/g,
};
Then, anywhere in your template where you have <% %>, change it to {{ }}, change <%= %> to {{= }}, and change <%- %> to {{- }}.
You can use <%%= variable %>. Rails output for this will be <%= variable %>.
I'm using the backbone-on-rails gem with javascript (as opposed to coffeescript) to render views for a model. Everything works fine, but my view code is kind of long so I'd like to refactor things out to use includes/partials.
Heres' my code:
in my views/model_show.js file
AppName.Views.ModelNameShow = Backbone.View.extend({
template: JST['main'],
initialize: function() {
this.render();
},
render: function() {
this.model.fetch({async:false});
$(this.el).html(this.template({ model: this.model }))
$('#main_content').html(this.el);
}
});
in my templates file I'd like to so something like this in templates/main.jst.ejs
<% if(this.model) { %>
<h2> model found</h2>
<%- include top_section %>
<%- include middle_section %>
<%- include bottom_section %>
<% } else { %>
<h3>Claim not found <a href='#'>Back</a></h3>
<% } %>
where top_section would be a file under templates (like templates/top_section.jst.ejs)
Problem is I keep getting the following errors (firebug output).
SyntaxError: missing ) in parenthetical [Break On This Error]
...2> model found \n\t',(''+ include top_section ).replace(/&/g,
'&').replace(/
which causes this error because the template isn't rendered
TypeError: this.template is not a function
[Break On This Error]
$(this.el).html(this.template({ model: this.model }))
I should mention that I've tried a few different syntaxes like the ones below, but those just throw different errors.
<%= include top_section %>
<%- include(top_section) %>
Thanks in advance
In EJS the correct syntax for a partial is this:
<%= this.partial({url: 'templates/partial.ejs'}) %>
As a stop-gap for now I just created a hash of templates and enumerate over them in the render. Not ideal, but works for now. Here's what the code looks like now.
views/model_show.js file
AppName.Views.ModelNameShow = Backbone.View.extend({
templates: {
top_section: JST['top_section'],
middle_section: JST['middle_section'],
bottom_section: JST['bottom_section']
},
initialize: function() {
this.render();
},
render: function() {
this.model.fetch({async:false});
var zuper = this;
$.each(this.templates, function(key, value) {
//iterate over each template and set the html for the div with id=key with template
//For example $('#top_section).html(JST template for top_section)
$('#' + key).html(zuper.templates[key]({ model: zuper.model }))
})
}
});
template file looks like this now.
<% if(this.model) { %>
<h2> model found</h2>
<div id="top_section"/>
<div id="middle_section"/>
<div id="include bottom_section"/>
<% } else { %>
<h3>Model not found <a href='#'>Back</a></h3>
<% } %>
As I said -- not ideal, but a functional workaround for now.
I have a few different problems going on, I hope though this example is easy to follow. The code uses an HTML template with elements hidden by default (using CSS). The Backbone View uses data in a Model to display appropriate values OR hide the UI element if no value is present in the Mode.
Given a template where everything is hidden by default (using CSS), for example:
<script type="text/template" id="Person-Template">
<span class="fname" title="FirstName"></span>
<span class="lname" title="LastName"></span>
<span class="age" title="Age"></span>
</script>
To hide each UI element the CSS is:
span.fname,
span.lname,
span.age {
display:none;
}
My Backbone.js Model would therefore be:
PersonModel = Backbone.Model.extend({
defaults: {
fname: undefined,
lname: undefined,
age: undefined
}
});
The View (simplified) would be:
PersonView = Backbone.View.extend({
tagName: 'div',
initialize: function() {
this.model.on("fname", this.updateFName, this);
this.model.on("lname", this.updateLName, this);
this.model.on("age", this.updateAge, this);
},
updateFName: function() {
// Pseudo code
Get 'new' value from Model
Obtain reference to DOM element span.fname
Update span.fname
if (model value is empty) {
Hide UI element.
}
},
updateLName: function() {
// Same as above
},
updateAge: function() {
// Same as above
},
render: function() {
// Get model values to display
var values = {
FirstName : this.model.get('fname'),
LastName : this.model.get('lname'),
Age: this.model.get('age'),
};
// Load HTML template
var template = $('#Person-Template').html();
// Populate template with values
var t = _.template(template, values);
// Show / hide UI elements
this.updateFname();
this.updateLName();
this.updateAge();
}
}
Finally, the question: It seems hacky calling each updateXYZ() method from render() just to determine whether the UI element should be set to hidden or visible. I have a lot of attributes in my model and the code just seems a little absurd really.
I have been told on SO that the View should not be responsible for determining what should or should be displays. My questions is, well then what is responsible? The user may perform some (valid) aciton which clears the First Name, in which case I don't want my View displaying 'First name:' followed by no value.
First of all, you don't need to build your values by hand, just use toJSON:
var values = this.model.toJSON();
Then, you have to add your filled in template to your view's el:
this.$el.html(_.template(template, values));
and your template should probably include something to display in your template:
<script type="text/template" id="Person-Template">
<span class="fname" title="FirstName">F: <%= fname %></span>
<span class="lname" title="LastName">L: <%= lname %></span>
<span class="age" title="Age">A: <%= age %></span>
</script>
You don't separate functions for each of the three parts, you could just loop through them in your render:
_(values).each(function(v, k) {
var m = _(v).isUndefined() ? 'hide' : 'show';
this.$('.' + k)[m]();
}, this);
Now back to your events. There is no such thing as an "fname" event unless you've added a custom one. But there's no need for that, the model will trigger "change" and "change:fname" events when the fname is changed; you only need to care about "change" though:
initialize: function() {
_.bindAll(this, 'render');
this.model.on("change", this.render);
},
I've also bound render to your view instance using _.bindAll so that you don't have to worry about the third argument to this.model.on.
Now you have something that works: http://jsfiddle.net/ambiguous/46puP/
You can also push the "should this be displayed" logic into the template:
<script type="text/template" id="Person-Template">
<% if(fname) { %><span class="fname" title="FirstName">F: <%= fname %></span><% } %>
<% if(lname) { %><span class="lname" title="LastName">L: <%= lname %></span><% } %>
<% if(age) { %><span class="age" title="Age">A: <%= age %></span><% } %>
</script>
and simplify your render:
render: function() {
var values = this.model.toJSON();
var template = $('#Person-Template').html();
this.$el.html(_.template(template, values));
return this;
}
Demo: http://jsfiddle.net/ambiguous/W9cnJ/
This approach would probably be the most common and there's nothing wrong with it. I think you're misunderstanding what the previous answer was trying to tell you. The template chooses what pieces of information to display through <%= ... %> already so there's no good reason that it shouldn't see if fname, for example, is set before trying to display it. Depending on the nature of your data, you might want to use if(!_(fname).isUndefined()) and such in your template but a simple truthiness check is probably fine; the age might be an issue in some cases though so you might want to be a bit stricter with that.