undefined local variables in templates - backbone.js

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 %>.

Related

If the service returns JSON data, should I still use this.collection. toJSON()?

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.

how to parse a backbone collection in a template view

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.

How to get Angular to work for dynamically added object

I have two files as described below. I am defining the controller in file_1.php
In file_2.php, I am 'require'ing the file_1.php, and then moving the ul into the div that is described in file_1.php
What I want to be able to do is - get the functions within the controller to work for the ul which was dynamically added. My guess is that, when the page was loaded, the ul block is seen as being outside the controller and so it doesn't work. On searching, I was able to see a solution that involved $compile, but that works for ng-model, and not for repeat or {{}} either. I am new to Angular and would appreciate any help.
file_1.php
<?php
<div id="box_1" ng-controller="MyCtrl"></div>
?>
file_2.php
<?php
require 'file_1.php';
?>
<ul>
<li ng-repeat="item in items">item.text</li>
</ul>
{{items}}
<script>
function MyCtrl($scope) {
$scope.items = [{text: "Item 1", text: "Item 2"}];
}
$(document).ready(function() {
$('#box_1').append($('ul'));
})
</script>
Some information that I found:
In the documentation here, under section "Reasons behind the compile/link separation", they have explained why compiling is different for ng-repeat. Could anyone explain what it means exactly and/or the way to go about it? I tried compile - anything that is not in an ng-repeat works, but anything inside ng-repeat doesn't.
Are you wedded to this file structure for some reason? What you're trying to do goes against the grain of angular and will thus be a pain. Angular is supposed to free you from the pain of DOM manipulation with jQuery.
It seems like using $compile should work, though. Can you show us what you're trying to do with it?
Why is it that you can't do something like this?
<div id="box_1" ng-init="items = [{text: "Item 1", text: "Item 2"}]">
<ul>
<li ng-repeat="item in items">{{item.text}}</li>
</ul>
{{items}}
</div>
(The syntax might be a bit off on ng-init.)
It is not good practice to manipulate the DOM in a controller. Aside of that, doesn't $scope.$apply() help?
$(document).ready(function() {
$('#box_1').append($('ul'));
$scope.$apply();
})

Backbone, not "this.el" wrapping

I do an extensive use of templates, and I like to use full contained templates. I mean that I want to see in the template code all the DOM elements including the root one, like this:
<script type="text/template" id="template-card">
<div class="card box" id="card-<%= id %>">
<h2><%= title %></h2>
<div><%= name %></div>
</div>
</script>
But what Backbone likes is having a template like this:
<script type="text/template" id="template-card">
<h2><%= title %></h2>
<div><%= name %></div>
</script>
And defining the root element and its attributes in the JS code. What I think is ugly and confusing.
So, any good way to avoiding my Backbone View to wrapper my template with an extra DOM element?
I have been checking this issue thread: https://github.com/documentcloud/backbone/issues/546 and I understand there is not any official way to do it.. but maybe you can recommend me a non official way.
You can take advantage of view.setElement to render a complete template and use it as the view element.
setElement view.setElement(element)
If you'd like to apply a Backbone view to a different DOM element, use setElement, which will
also create the cached $el reference and move the view's delegated
events from the old element to the new one
Two points you have to account for:
setElement calls undelegateEvents, taking care of the view events, but be careful to remove all other events you might have set yourself.
setElement doesn't inject the element into the DOM, you have to handle that yourself.
That said, your view could look like this
var FullTemplateView = Backbone.View.extend({
render: function () {
var html, $oldel = this.$el, $newel;
html = /**however you build your html : by a template, hardcoded, ... **/;
$newel = $(html);
// rebind and replace the element in the view
this.setElement($newel);
// reinject the element in the DOM
$oldel.replaceWith($newel);
return this;
}
});
And a working example to play with http://jsfiddle.net/gNBLV/7/
Now you can also define a view's tagName as a function and create a class like this:
var MyView = Backbone.View.extend({
template: '#my-template',
tagName: function() {
// inspect the template to retrieve the tag name
},
render: function() {
// render the template and append its contents to the current element
}
});
Here's a working example
Backbone.Decarative.Views provides you with an alternative way to do this, without having to rely on setElement. For more, check out my answer here.

underscore template is removed from the DOM so can't be re-used

I'm trying to use a simple inline underscore.js template with backbone, and pulling the template from a <script> tag via jQuery's html() method.
The page is supposed to render multiple Recipe callouts for each returned by the Recipe Model. It works on the first thumbnail, but on the second, it seems the <script> tag has been removed from the DOM, so underscore fails to render it and bails with str is null on line 913
For the template, I have
<script type="text/html" id="user-recipe-rated-template">
<div class="user-recipe-rated">
<a class="thumb" href="<%= href %>"></a>
<p><%= title %></p>
</div>
</script>
And the backbone view looks like:
var UserRecipeView = Backbone.View.extend({
initialize : function() {
this.template = _.template($("#user-recipe-rated-template").html());
},
render : function()
{
this.el = this.template({
title: this.model.get("Title"),
href: '#'+this.model.get('UserContentId'),
image_src: this.model.get('ThumbnailSrc')
});
return this;
}
});
So, what I am seeing is that $("#user-recipe-rated-template") exists on the first thumbnail and all is well. On the second, it returns an empty array and underscore can't proceed.
I've trying memoizing the string of html and such, and that might work, but it seems there should be a cleaner way of doing this. What am I doing wrong?
(I'd like to use templates inlined as opposed to external JST for now to keep things simple - it seems nicer to have the template embedded in the page near where it will be loaded when the data comes in)

Resources