Using External Views with Backbone.js - backbone.js

So I have seen other posts pertaining to this question but regardless of my efforts to incorporate their suggestions, I don't seem to be able to solve the issue. So here is the code:
What am I doing wrong here and what is the best way to get the application started?
Entire Application:
var Person = Backbone.Model.extend({
defaults : {
name : ""
}
});
var PersonView = Backbone.View.extend({
el : $('body'),
tagName: 'li',
initialize : function() {
this.render();
},
render : function(){
var template = _.template( $("#person").html(), {} );
this.$el.html(template);
return this;
}
});
HTML w/ Template:
<script type="text/template" id="person">
<h1><%= name %></h1>
</script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script type="text/javascript" src="js/underscore.js"></script>
<script type="text/javascript" src="js/backbone.js"></script>
<script type="text/javascript" src="app.js"></script>

At first glance, it looks like you're defining the DOM location incorrectly.
el : $('body'),
tagName: 'li',
el should be the jQuery selector string rather than the object
el : 'body'
but then, this.$el will be $('body') effectively, so this.$el.html(template) will try to put data into the <body> tag.
It looks like, from the tagName: 'li' that you want to have multiple PersonViews, so I suggest not defining the el property at all, then appending this.$el once the view has been rendered to whatever parent element you need it in.

On further review, "entire application" suggests that you're not initialising anything.
The Javascript you have there is defining a Person class and a PersonView class. Presumably, you will want to instantiate one or more objects of the Person class and then also a PersonView for each Person, this is achieved by using new, as mu is too short mentions.
Effectively, the code you have is the setup, now you need to start it.
var joe = new Person;
var joeView = new PersonView({ person: joe });
That snippet assumes that you'll be expecting { person: joe } in PersonView, which you're not, but the errors thrown should point you in a better direction.

Related

Backbone.js: defining el manually vs. automatically with id

My question is simple. Why does the following work (i.e. show an alert when the black square is clicked):
$(document).on('ready', function() {
var MainView = Backbone.View.extend({
el : "#id",
events : {
"click" : function() {
alert("this works");
}
},
});
var main_view = new MainView();
});
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.1/backbone-min.js">
</script>
<style>
#id {
width : 10vw;
height : 10vh;
background-color : #000000;
}
</style>
</head>
<body>
<div id="id">
</div>
</body>
</html>
but the following doesn't:
$(document).on('ready', function() {
var MainView = Backbone.View.extend({
id : "id",
events : {
"click" : function() {
alert("this works");
}
},
});
var main_view = new MainView();
});
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.1/backbone-min.js">
</script>
<style>
#id {
width : 10vw;
height : 10vh;
background-color : #000000;
}
</style>
</head>
<body>
<div id="id">
</div>
</body>
</html>
From the backbone documentation website:
"this.el can be resolved from a DOM selector string or an Element; otherwise it will be created from the view's tagName, className, id and attributes properties".
Nothing here suggests that events will fail to bind if I don't set the el manually...
Moreover, inspecting the main_view objects after creation show that the el in both cases is ostensibly the same. Why doesn't the click get bound in the second example, when the el is extrapolated from the id? Is there some philosophy of backbone that I'm missing here?
The documentation for el says this:
this.el can be resolved from a DOM selector string or an Element; otherwise it will be created from the view's tagName ...
Emphasis mine.
So if you have this in your view:
el: 'some-selector-string'
then Backbone will look up that selector in the DOM and use what it finds as the el: i.e. it will resolve the selector to a DOM node and use that node as the final el and Backbone assumes that it is working with an existing node.
If you have this:
id: 'id',
tagName: 'div',
...
in your view, then Backbone will create the el based on those properties. Note that it only says that it will create the el, nowhere does it say that that el will be added to the DOM. If Backbone creates the el then it expects you to add it to the page.
In both cases you'll have an el in your view but only the first case (el: 'selector') will give you an el that is actually on the page.
If you're going to let Backbone create the el from id, tagName, and friends, then you'll need to add the el to the page yourself. The common pattern looks like this:
// In the view...
render: function() {
this.$el.html('whatever goes inside the el');
return this;
}
// And then outside the view
var v = new YourView;
$('some-container-element').append(v.render().el);
// Now the el is on the page and usable ------^^
If you ask Backbone to create the el with id: 'some-id' in your view and you already have <div id="some-id"> on the page, then you end up with two #some-id nodes: one on the page and one that is only in your view. The view's events are always bound through the view's el so there won't be any events on the #some-id on the page.

template not loading in backbone.js ( TypeError: text is undefined )

I'm learning backbone.js and I'm pretty much in the beginning. I want to add a template through underscore template method but it's not working for me. I searched for this error but couldn't fix it myself. How can I move forward if it's not showing the template. Need some help guys.
Here is the code (this code is from addyosmani's book backbone-fundamentals):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>testing</title>
</head>
<body>
<script src="scripts/jquery.js"></script>
<script src="scripts/underscore.js"></script>
<script src="scripts/backbone.js"></script>
<script>
var TodoView = Backbone.View.extend({
tagName: 'li',
// Cache the template function for a single item.
todoTpl: _.template( $('#item-template').html() ),
events: {
'dblclick label': 'edit',
'keypress .edit': 'updateOnEnter',
'blur .edit': 'close'
},
// Re-render the titles of the todo item.
render: function() {
this.$el.html( this.todoTpl( this.model.toJSON() ) );
this.input = this.$('.edit');
return this;
},
edit: function() {
// executed when todo label is double clicked
},
close: function() {
// executed when todo loses focus
},
updateOnEnter: function( e ) {
// executed on each keypress when in todo edit mode,
// but we'll wait for enter to get in action
}
});
var todoView = new TodoView();
// logs reference to a DOM element that cooresponds to the view instance
console.log(todoView.el);
If the template is defined after your script it will not work.
wrap your entry point into
$(function(){
var todoView = new TodoView();
});
so you dont get this kind of error.
I got the same error. Make sure that template with defined id exists on the page.
In my case I used wrong id for template, and this was a reason of error "TypeError: n is undefined".

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/

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.

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