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

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.

Related

Backbone Marionettejs view does not change when model changed

I follow the example from this book https://leanpub.com/marionette-gentle-introduction. My problem is that the view does not rerender when i change the model by clicking on the button. As the answer from this question , i don't need to do anything because Backbone/MarionetteJS smart enough to change the view.
Here is the code
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demo marionettejs</title>
<script src="./vendors/jquery/dist/jquery.js" type="text/javascript"></script>
<script src="./vendors/underscore/underscore.js" type="text/javascript"></script>
<script src="./vendors/backbone/backbone.js" type="text/javascript"></script>
<script src="./vendors/backbone.marionette/lib/backbone.marionette.js" type="text/javascript"></script>
</head>
<body>
<div id="main-region" class="container">
<p>Here is static content in the web page. You'll notice that it gets
replaced by our app as soon as we start it.</p>
</div>
<script type="text/template" id="contact-template">
<p><%- firstName %> <%- lastName %> : <%- time %> </p> <br />
<button>Change model</button>
</script>
<script type="text/javascript">
var ContactManager = new Marionette.Application();
ContactManager.Contact = Backbone.Model.extend({});
ContactManager.ContactView = Marionette.ItemView.extend({
template: "#contact-template",
initialize: function () {
this.currentMeterId = null;
},
events: {
"click button": "changeModel"
},
modelEvents: {
"change": "modelChanged"
},
changeModel: function() {
this.model.set("time", (new Date()).toString());
},
modelChanged: function() {
console.log("Model changed : " + this.model.get('time'));
},
//EDIT
onRender: function() {
//Create jsTree here.
}
});
ContactManager.on("before:start", function () {
var RegionContainer = Marionette.LayoutView.extend({
el: "#app-container",
regions: {
main: "#main-region"
}
});
ContactManager.regions = new RegionContainer();
});
ContactManager.on("start", function () {
var alice = new ContactManager.Contact({
firstName: "Alice",
lastName: "Arten",
time: "#"
});
var aliceView = new ContactManager.ContactView({
model: alice
});
ContactManager.regions.main.show(aliceView);
});
ContactManager.start();
</script>
</body>
</html>
#Edit
This code is just sample. In my real app, I have an ajax task that changes DOMs in the view. This ajax task creates a tree (jsTree) in onRender event. If i use modelEvents: {"change": "render"}, my jsTree will be reload and lost its state. So I want only update the model values in the view, others DOMs is retain.
The accepted answer to the question you pointed points to another question which has the following:
modelEvents: {
'change': "modelChanged"
},
modelChanged: function() {
console.log(this.model);
this.render();
}
And the most upvoted answer suggests the same:
modelEvents: {
'change': 'fieldsChanged'
},
fieldsChanged: function() {
this.render();
}
a comment to the most upvoted answer suggests
just {'change': 'render'} does the trick too
Which means you can do
modelEvents: {
'change': 'render'
}
So somehow you need to tell marionette invoke render on model changes.
I don't think backbone and marionette couple is smart enough to know whether you need to render view on model changes or you don't want to unless you tell them ;)

Why doesn't this.$el.append() work?

I'm trying to follow along http://addyosmani.github.io/backbone-fundamentals. I'm not getting how $el is supposed to work in a view.
Here's my HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Dashboard</title>
</head>
<body>
<h1>Dashboard</h1>
<ol class="foo" id="recent-station">
</ol>
<!-- Templates -->
<script type="text/template" id="station-template">
<li><%= station %></li>
</script>
<!-- Javascript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script src="static/js/script.js"></script>
</body>
</html>
And script.js is:
var RecentStation = Backbone.Model.extend( {
defaults: {
station: "",
},
initialize: function() {
console.log('initialized: ' + JSON.stringify(this));
this.on('change', function() {
console.log('changed: ' + JSON.stringify(this));
})
}
});
var RecentStationView = Backbone.View.extend( {
tagName: 'ol',
id: 'recent-station',
initialize: function() {
this.model.bind('change', _.bind(this.render, this));
},
render: function() {
console.log('render');
this.$el.append('<li>foo</li>');
$('ol#recent-station').append('<li>bar</li>');
return this;
},
});
var recent = new RecentStation();
var recentView = new RecentStationView({model: recent});
recent.set('station', 'My Station');
The interesting stuff is happening in the render function. I can see "render" logged to the console, and the "bar" text gets appended to the node, but not the "foo" text. I thought this.$el and $('ol#recent-station') were the same thing, but obviously not. What am I missing?
If you don't specify a dom element using el attribute, one will be created using tagName,id,className, and attributes from the view.
In your case you don't specify an el attribute in your view so you create an element that looks like:
<ol id='recent-station'></ol>
You then append <li>foo</li> into it, but your view element is still not in the DOM.
$('ol#recent-station') returns the dom element included in your html which is different than your view element, but has the same attributes.
So, in your example you would need to specify an existing element by supplying an el attribute.
var RecentStationView = Backbone.View.extend( {
// remove tagName and id
el:'#recent-station',
/* rest of your code below */
A fiddle with the changes, http://jsfiddle.net/DsRJH/.

Using External Views with 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.

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

Losing Scope when Repeating using View Render in Backbone.js

I'm a newbie at Backbone.js and am coming across a scope issue with a simple view & model scenario.
I've created a simple model with a single default "score" value. I also created a simple view containing a template rendered value of "score" and a button to increment score by one on each press. The view repeats the render every time the score value is changed.
I've got this to work but in a way that I think may be a botch. The template will only render the first time unless I cache the value of "this" in view variable "thisView". If I don't it seems to lose focus and the rendering errors. Is this a good idea? Or am I missing something about repeatedly applying the render.
Thanks for any advice
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<style>
#view_container{background-color: rgba(12, 5, 11, 0.14);width: 100px;height: 100px;padding: 10px;}
</style>
</head>
<body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/underscore.js/1.1.4/underscore-min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/backbone.js/0.3.3/backbone-min.js"></script>
<!-- View Template -->
<script type="text/template" id="view-template">
<div class="profileSpace">
<p>Score: <%= score %></p>
</div>
<button id="increaseScoreButton">Increase Score</button>
</script>
<div id="view_container"></div>
<script type="text/javascript">
(function ($) {
MyModel = Backbone.Model.extend({
defaults:{
score:0
},
initialize: function(){
},
increaseScore: function(){
//Increase Score by 1
var currentScore = this.get("score");
var newScore = currentScore +1;
this.set({score:newScore});
}
});
MyView = Backbone.View.extend({
el: $("#view_container"),
template: _.template($('#view-template').html()),
initialize: function(model){
thisView =this;
this.model.bind('change', this.render, this);
this.render();
},
events: {
"click #increaseScoreButton": "increaseScore"
},
increaseScore: function(){
this.model.increaseScore();
},
render: function(){
var currentScore = thisView.model.get("score");
var html = thisView.template({"score":currentScore});
$(thisView.el).html( html );
return thisView;
}
});
myModel = new MyModel;
myApp = new MyView({model:myModel});
})(jQuery);
</script>
</body>
</html>
You bind the change event via this.model.bind('change', this.render, this);
This syntax was introduced in Backbone 0.5.2 but you use Backbone 0.3.3 in your example.
0.5.2 — July 26, 2011
The bind function can now take an optional third argument to specify the this of the callback function.
Upgrade Backbone to a more recent version (0.9.2 as of today) and you should get the expected behaviour.
Or, as CoryDanielson pointed out in the comments, you could use _.bindAll to have a guaranteed context:
MyView = Backbone.View.extend({
initialize: function(model) {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.render();
},
render: function(){
var currentScore = this.model.get("score");
var html = this.template({"score":currentScore});
$(this.el).html( html );
return this;
}
});

Resources