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

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

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 ;)

what is not making that my code render the collection?

I'm having a hard time trying to figre out what's wong with my code?
the thing that i'm tryng to make is get a collection o models and display them
here are my models
var MessageModel = Backbone.Model.extend({
defaults : {
id: "",
from:"",
titleMessage:"",
bodyMessage:"",
bodyMessageTrim:""
}
});
My Collection
var MessageListCollection = Backbone.Collection.extend({
url: '../js/dataDummy/populateJsonMessages.json',
model: MessageModel
});
My Views
var MessageListItemView = Backbone.View.extend({//view for a row in the message list
template: _.template($('#tpl-message-item-list').html()),
render: function(eventName){
this.$el.html(this.template(this.model.toJSON() ));
return this;
},
});
var MessageListView = Backbone.View.extend({//view por all messages listed
className:'messages',
render: function(){
this.collection.each(function(model){
var msgListAll = new MessageListItemView({model:model});
console.log(msgListAll.el);
this.$el.append(msgListAll.render().el);
}, this);
return this;
});
Finally my Routes
//global model variables so i can interact with the different views
var myMessageModelAction = new MessageModel();//whole message information
var myMessageListAction = new MessageListCollection();//all the messages to be listed
var AppRouter = Backbone.Router.extend({
routes:{
"messages": "messagesList"
},
messagesList: function(){
var myMessageList = new MessageListCollection();
myMessageList.fetch();
console.log(myMessageList);
var myMessageListView = new MessageListView({collection:myMessageList});
console.log(myMessageListView);
myMessageListView.render();
console.log("dame esto");
console.log(myMessageListView.el);
$('#rendered').html(myMessageListView.render().el);
}
});
var appRouter = new AppRouter();
Backbone.history.start();
The File that is been called inside the collection code is just a json plain text but if it helps here it is
[
{"id": "1", "from":"user1", "titleMessage":"Welcome to the Team", "bodyMessage":"Congratulations you passed the selection tests", "bodyMessageTrim": "Congratulations you passed..."},
{"id": "2", "from":"user2", "titleMessage":"First Task", "bodyMessage":"Hello you have to make some changes in the UI", "bodyMessageTrim": "Hello you have to..."},
{"id": "3", "from":"user2", "titleMessage":"Re:Welcome to the Team", "bodyMessage":"No problem if it's anything you might need just let me know", "bodyMessageTrim": "No problem if it's..."},
{"id": "4", "from":"user2", "titleMessage":"Re:First Task", "bodyMessage":"Ok i am going to talk to the design team to give you all the assets", "bodyMessageTrim": "Ok i am going to talk..."},
{"id": "5", "from":"user2", "titleMessage":"Re:Re:First Tak", "bodyMessage":"Ok that is it great work", "bodyMessageTrim": "Ok that is it..."},
{"id": "6", "from":"user1", "titleMessage":"Meeting Tomorrow", "bodyMessage":"Hi this is just a notice that tomorrow we will have a meet with all new members", "bodyMessageTrim": "Hi this is just a..."}
]
The index looks like this
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>test</title>
<link rel="stylesheet" href="../assets/bootstrap.min.css">
<link rel="stylesheet" href="../assets/index.css">
</head>
<body>
<!-- Templates -->
<script type="text/template" id="tpl-message-item-list" >
<div class="messageItem">
<div><%= from %></div>
<div><%= titleMessage %></div>
<div><%= bodyMessageTrim %></div>
</div>
</script>
<div class="jumbotron">
<div class="container" id="rendered">
<p>Looks like you are in the wrong place run now to a safe place</p>
</div>
</div>
<!-- Libraries -->
<script src="../js/lib/jquery-2.1.4.min.js"></script>
<script src="../js/lib/underscore-min.js"></script>
<script src="../js/lib/backbone-min.js"></script>
<!-- Relevant Scripts -->
<!--script src="../js/app.js"></script-->
<script src="../js/views/appIndex.js"></script>
<script src="../js/models/appIndex.js"></script>
<script src="../js/collections/appIndex.js"></script>
<script src="../js/routers/routes.js"></script>
</body>
</html>
Any help is good due i'm lost and i have like 3 days playing with backbone
and all that i'm having in response is a blank screen where it should be loaded my data.
Also the collections is reached and all the data is in place just the problem is the render of it
The reason is that Backbone View, by default, expects an el parameter when you initialize it. meaning the render will write the stuff inside that el.
if you already have some div in html page, then you can initialize backbone view like this:
var messageItemView=new MessageItemView({model:model,el:'#somediv'});
Then when render, it will write to that div.
However, it looks like you are creating a view without an element in the page, which makes sence if you do render().el, this will return the html text, and you append to main html.
while this makes sense, the backbone constructor does not know this. if you see this fiddle, you can see, after you initialize the MessageListItemView, the $el is null. It needs to be constructed before you do
this.$el.html(xxxx);
A hacky fix is to add a
render: function(eventName){
this.$el=$(this.el);// this create the $el element;
this.$el.html(this.template(this.model.toJSON() ));
return this;
},
A more standard is to do it by overwrite the initialize function and do setElement();
Same thing needs to be done for MessageListView too
You are calling colleciton.fetch but not waiting for it to get data before you create collectionView.
var def = myMessageList.fetch(); return a deferred. def.done(function(){ /* rest of the code here */}) should fix the problem.

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

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