Uncaught Error: Cannot read property 'replace' of undefined - Backbone.js [duplicate] - backbone.js

I 'm trying to develop a simple RSS app using backbone.js. I 'm using this backbone.js tutorial. I 'm getting the following error, on line 2(template), when defining the template.
Can someone also tell me why is tagName: "li" defined in the tutorial?
uncaught TypeError: Cannot call method 'replace' of undefined
backbone.js
Javscript
window.SourceListView = Backbone.View.extend({
tagName:"li",
template: _.template($('#tmpl_sourcelist').html()),
initialize:function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render:function (eventName) {
$(this.$el).html(this.template(this.model.toJSON()));
return this;
},
close:function () {
$(this.el).unbind();
$(this.el).remove();
}
});
HTML
<script type="text/template" id="tmpl_sourcelist">
<div id="source">
<a href='#Source/<%=id%>'<%=name%></a>
</div>
</script>
thanks

You're getting your error right here:
template: _.template($('#tmpl_sourcelist').html()),
Part of _.template's internals involves calling String#replace on the uncompiled template text on the way to producing the compiled template function. That particular error usually means that you're effectively saying this:
_.template(undefined)
That can happen if there is no #tmpl_sourcelist in the DOM when you say $('#tmpl_sourcelist').html().
There are a few simple solutions:
Adjust your <script> order so that your #tmpl_sourcelist comes before you try to load your view.
Create the compiled template function in your view's initialize instead of in the view's "class" definition:
window.SourceListView = Backbone.View.extend({
tagName:"li",
initialize:function () {
this.template = _.template($('#tmpl_sourcelist').html());
//...
As far as tagName goes, the fine manual has this to say:
el view.el
[...] this.el is created from the view's tagName, className, id and attributes properties, if specified. If not, el is an empty div.
So having this in your view:
tagName: 'li'
means that Backbone will automatically create a new <li> element as your view's el.

Related

JST with Backbone and Underscore

I am using Backbone and Underscore to create a small test site.
I am compiling all my html template files into one JST javascript file as suggested here and here.
It isn't however very obvious how to use this with template files. I have tried this:
App.HeaderView = Backbone.View.extend({
el: '#headerContent',
template: JST['header.html'](),
//template: window["JST"]["header.html"],
//template: _.template("<h1>Some text</h1>"),
initialize: function () {
this.render();
},
render: function() {
//var html = this.template();
//// Append the result to the view's element.
//$(this.el).append(html);
this.$el.html(this.template());
return this; // enable chained calls
}
});
The error I get is JST.header.html is not a function.
(The final commented out bit works by the way template: _.template("<h1>Some text</h1>") so I know the problem isn't with anything else).
It might be because I am using browserify (so have tried 'requiring' the file), but I have tried several different ways of 'including' the template file, including adding it directly:
<script src="templates/_templates.js"></script>
<script src="js/functions.js"></script>
</body>
</html>
Any ideas what needs to be done to get this to work?
On line 3 you don't want to invoke the template, rather just use:
template: JST['header.html'].
Currently you're setting template to equal the return value of the function and then trying to invoke that return value instead of the actual function, so it is raising a "is not a function" error.

How to update data after model fetch in Handlebars template with Backbone and Marionette?

Trying to have a data field in a Handlebars template update after the model that is assigned to the Marionette CompositeView is fetched, but the HTML in the page is not getting updated.
My code looks like this:
Model:
B.Page.Model = Backbone.Model.extend({
url: function () {
return 'my/resource/';
},
});
View:
B.Page.CompositeView = Backbone.Marionette.CompositeView.extend({
template: Handlebars.compile(templates.find('#my-template').html()),
initialize: function(options) {
_.bindAll(this);
this.model.fetch();
},
)};
Template:
<script id="my-template" type="text/x-handlebars-template">
Date: <span id="my-data-field">{{data}}</span>
</script>
I have checked the resource and it does return proper JSON with the data field set. Also, the model is getting passed in to the view.
I suspect that this is due to the render function not getting called after the data is retrieved; however, I would like to get feedback on how it should be done.
What is a good way to do this?
Thanks!
EDIT: This CompositeView does have a Collection that is associated with it (which renders just fine when I trigger the appropriate event). I purposefully left out that part of the code to avoid muddying up the problem.
Your are right, since a CompositeView extends from CollectionView, it only re-renders on collection events by default. To make it re-render on changes on your model, you could do something like this in your CompositeView:
initialize: function(){
this.listenTo(this.model, "change", this.render);
}
All Marionette views have a modelEvents object that is bound to the passed in model. So you could clean the accepted answer up slightly by doing:
template: Handlebars.compile(templates.find('#my-template').html()),
modelEvents: {
'change': 'render'
}
rather than binding manually in initialize.

Backbone Underscore Template

So I'm checking out the changes related to the latest backbone/underscore version. Prior I have a project running with BB 0.5.2 and underscore 1.1.7. I'm noticing something strange with regards to defining a template property within a view in the new version, which gives me reservations in going forward with upgrading.
In my current version I would define a view as such:
var MyView = Backbone.View.extend({
template: _.template($('#exampleTemplate').html()),
initialize: function() {...},
render: function() { $(this.el).html(this.template(someObjectParam)); },
});
However, if I attempt to work in the same manner, using a simplified todo clone attempt as an example, I setup an html with an inline script template as such:
<script>
$(document).ready(function() {
app.init();
});
</script>
<script type="text/template" id="itemViewTemplate">
<div class="item">
<input type="checkbox" name="isComplete" value="<%= item.value %>"/>
<span class="description"><%= item.description %></span>
</div>
</script>
In my included JS file I have:
var ItemView = Backbone.View.extend({
el: 'body',
// Below causes error in underscore template, as the jquery object .html() call
// returns null. Commenting will allow script to work as expected.
templateProp: _.template($('#itemViewTemplate').html()),
initialize: function() {
// Same call to retrieve template html, returns expected template content.
console.log($('#itemViewTemplate').html());
// Defining view template property inside here and calling it,
// properly renders.
this.template = _.template($('#itemViewTemplate').html());
this.$el.html(this.template({item: {value: 1, description: 'Something'}}));
},
});
var app = {
init: function() {
console.log('Fire up the app');
var itemView = new ItemView();
}
}
So I'm left confused as to why defining the template property directly causes the call to retrieve the template html to return a null value, thus breaking the attempt to define an underscore template object (mouthful). However, if the definition is done within the initialize function, the call to retrieve the template html properly finds the template so its contents can be passed to the underscore template. Anyone see what I'm potentially missing?
Thanks in advance!
If this:
var ItemView = Backbone.View.extend({
//...
templateProp: _.template($('#itemViewTemplate').html()),
//...
});
is failing because $('#itemViewTemplate').html() is null, then you have a simple timing problem: you're trying to read the content of #itemViewTemplate before it exists. Your old version should suffer from exactly the same problem.
Either make sure everything is loaded in the right order (i.e. your views after your template <script>s) or compile the template in your view's initialize. You can check for the templateProp in your view's prototype and only compile it on first use if you want:
initialize: function() {
if(!this.constructor.prototype.template)
this.constructor.prototype.template = _.template($('#itemViewTemplate').html());
//...
}
Demo: http://jsfiddle.net/ambiguous/HmP8U/

Using Handlebars with Backbone

I am learning Backbone/Handlebars/Require. I have looked all over online and on SO - are there any tutorials or websites that you can direct me to that would provide helpful information for using using handlebars instead of underscore?
Using handlebars.js instead of underscore templating is pretty straightforward. Check out this example:
https://cdnjs.com/libraries/backbone.js/tutorials/what-is-a-view
(scroll to the "Loading a Template" section)
SearchView = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
// Compile the template using underscore
var template = _.template( $("#search_template").html(), {} );
// Load the compiled HTML into the Backbone "el"
this.el.html( template );
}
});
Basically, the convention in backbone is to build your html in a render function. The use of templating engine is left completely up to you (which I like about Backbone). So you'd just change it to:
SearchView = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
// Compile the template using Handlebars
var template = Handlebars.compile( $("#search_template").html() );
// Load the compiled HTML into the Backbone "el"
this.el.html( template );
}
});
Since you're using require.js, you can make Handlebars a dependency at the top of your module. I'm pretty new to this, but it sounds like the learning to focus on would be Backbone.js patterns and require.js usage.
I would prefer to compile the template once (during initialize), that way you avoid to recompile the template with every render. Also, you need to pass the model to the compilated template in order to generate the HTML:
SearchView = Backbone.View.extend({
initialize: function(){
// Compile the template just once
this.template = Handlebars.compile($("#search_template").html());
this.render();
},
render: function(){
// Render the HTML from the template
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
If you are using require.js you wont be able to use the current Handlebars file. I used the following Handlebars Plugin and it seems to be kept up to date with the current version. Just replace your Handlebars file with the plugin above if Handlebars is returning null in your module.
define(["app", "handlebars",
"text!apps/templates/menu.tpl"
], function (app, Handlebars, template) {
return {
index: Marionette.ItemView.extend({
template: Handlebars.compile(template),
events: {
'click .admin-menu-ref': 'goToMenuItem'
},
goToMenuItem: function (e) {
//......
}
})
}
});
new view.index({model: models});

Rendering problem

From the backbone documentation:
All views have a DOM element at all times (the el property), whether they've already been inserted into the page or not.
I have following very simple javascript file:
CBBItem = Backbone.Model.extend(
{
});
CBBTrackItem = Backbone.View.extend(
{
template: _.template("<span><%= title %></span>"),
initialize: function()
{
_.bindAll(this, "render");
},
render: function()
{
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
And a html page like this:
<script type="text/javascript">
$(function()
{
var itm1 = new CBBItem({ title: 'track 1'});
var itmUI1 = new CBBTrackItem({ model: itm1, id: "kzl" });
itmUI1.render();
});
</script>
<body>
<div id="kzl"></div>
</body>
My view item doesn't want to render although there is a created div on the page. I can trick the situation in many ways. For example doing something like this
var itm1 = new CBBItem({ title: 'track 1'});
var itmUI1 = new CBBTrackItem({ model: itm1, id: "big_kzl" });
$(itmUI1.render().el).appendTo("#kzl");
But, why is the main case not working?
Here's one possibility: you aren't setting the el for the view, so it doesn't know what to do with your template. Could you modify your view-calling code to look like this?
var itmUI1 = new CBBTrackItem({
model: itm1,
id: "big_kz1",
el: "#kz1"
});
itmUT1.render();
Alternatively, you could set the el value within the initialize of the view if the value never varies. The advantage to doing so is that callers of the view don't have to know this information and thus the view is more self-contained.
If the document already has the element you want to use as el for a particular view, you have to manually set that dom element as the el attribute when the view is initialized. Backbone provides you no shortcut for doing that.
I've experienced problems when passing values like ID and events in during construction as opposed to defining them during extension. You may want to check and see if that's the difference you're looking for.

Resources