Object [object Object] has no method 'on' - backbone.js

I'm pretty new to backbone and I'm reading, following its example to create a todo list. I can't figure out why this piece of code does not work:
var Todo= Backbone.Model.extend({
defaults: {
title : "",
completed : false
},
initialize: function(){
console.log("model initialized");
this.on("change", function(){
console.log("values for this model have changed.");
});
}
});
var todo1= new Todo();
the libraries I've included are jquery, underscore and backbone. What's wrong with this? Why ".on" is not available? Thanks

on was only included in Backbone 0.9.0. You need to update it.

Related

Backbone.js .on('add') to Collection is not causing a render

I am following CodeSchool's 'Anatomy of Backbone.js' and cannot get this to work on my machine. There are similar questions, but they have a lot of extra stuff going on and, for someone brand-new like me, it's making it hard to learn.
Here's the code as simple/universal as possible:
var WorldEvent = Backbone.Model.extend({});
var WorldEventView = Backbone.View.extend({
events: {
'click' : 'focusEvent'
},
focusEvent: function(){
alert('great.');
},
className : 'pin bounce',
render : function () {
console.log('did something');
this.$el.html("rendered");
return this;
}
});
var WorldEventCollection = Backbone.Collection.extend({
model: WorldEvent,
url: '/events'
});
var worldEventCollection = new WorldEventCollection();
var worldEventCollectionView = new WorldEventView({
collection: worldEventCollection,
initialize: function(){
this.collection.on('add', this.addOne, this);
this.collection.on('reset', this.addAll, this);
},
addOne: function(myEvent){
var worldEventView = new WorldEventView({ model: myEvent });
this.$el.append(worldEventView.render().el);
},
addAll: function(){
this.collection.forEach(this.addOne, this);
},
render: function(){
this.addAll();
}
});
The good news is that if I call
worldEventCollection.add(new WorldEvent( {<my data>} ));
... the new model is added to worldEventCollection - I've logged worldEventCollection and worldEventCollection.length to verify.
The bad news is that "did something" doesn't appear in the console and I see no evidence of a render.
Please help, I've wasted a ton of time on what is probably super simple. Thank you.
UPDATE
Okay, I found one of my issues. I needed to define a separate WorldEventCollectionView class altogether, so this was NOT correct:
var worldEventCollectionView = new WorldEventView({
collection: worldEventCollection,
...
Instead, I believe one correct approach is:
var WorldEventCollectionView = Backbone.View.extend({
initialize: function(){
this.collection.on('add', this.addOne, this.collection);
...
And then:
var worldEventCollectionView = new WorldEventCollectionView({ collection: worldEventCollection });
WorldEventView.render must end with return this; as per backbone view convention, otherwise chaining such as worldEventView.render().el will not work. Specifically, that will throw an exception since render() returns undefined and you try to access the .el property of undefined.
There's several other things that are not quite right about your snippet as well, but fix that first and see if you can take it from there. Generally in a view's render method, you want to populate HTML inside the view's this.$el and return this; at the end of render and really that's all you should be doing. Render has a very specific purpose and code that isn't following that basic idea and semantic belongs elsewhere.
Oh so this:
var worldEventCollectionView = new WorldEventView({
should be:
var WorldEventCollectionView = Backbone.View.extend({

Backbone.js, using urlRoot result in view

I am a Backbone.js newbie and I'm just playing around with it. I would like to know how the model is related with the View when using urlRoot. I'm using a local restful service. When calling 'api/feed/57' I get the following JSON result:
{"id":"57","name":"Speakers","name_desc":null,"url":"http:\/\/speakers.com\/feed\/","category":"1","favicon":"http:\/\/g.etfv.co\/http%3A%2F%2Fspeakers.com%2F","last_update":"2013-09-20 12:57:25","insert_date":"0000-00-00 00:00:00"}
What I want to achive is to have the values retrieved displayed by the view. When trying so, the default values are displayed and not the values retrieved from the urlRoot. I used a console.log(name) to verify if the json service is working properly. It seems so, because "speakers" is shown in the debug. Any idea what I'm doing wrong? The following code is used:
var Feed = Backbone.Model.extend({
urlRoot: 'api/feed',
defaults: {
name: 'Test',
name_desc: 'Test',
url: ''
}
});
var feedItem = new Feed({id: 57});
feedItem.fetch({
success: function (feedItem) {
var name = feedItem.get('name');
console.log(name);
}
})
var FeedView = Backbone.View.extend({
tagName: 'li',
initialize: function(){
this.render();
},
render: function(){
this.$el.html( this.model.get('name') );
}
});
var FeedView = new FeedView({ model: feedItem });
FeedView.el;
$(document.body).html(FeedView.el);
First, you are overriding your View, choose a different name to store the instance,
var feedView = new FeedView({ model: feedItem });
feedView.render();
$(document.body).html(feedView.el);
Second, the fetch is an asynchronous call so you need to wait for it to complete before rendering,
initialize: function(){
this.listenTo(this.model, 'change', this.render);
},
Now, when your model changes, your render function will be called and update the view with the correct values.

Backbone localstorage A "url" property or function must be specified

I am creating a small application based on a backbone example with the backbone-localstorage plugin.
When saving the data for a new model I always get the error "A "url" property or function must be specified"
After reading through several simular topics I'm still not able to find the cause for this.
Model:
directory.models.EmployeeCollection = Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage("EmployeeCollection"),
model: directory.models.Employee,
store: directory.utils.store,
findByName: function(key) {
this.reset(this.store.findByName(key));
}
});
The view:
directory.views.newEmployeeView = Backbone.View.extend({
tagName: "div",
initialize: function() {
this.template = _.template(directory.utils.templateLoader.get('new-employee'));
},
events: {
"click .save": "saveEmployee"
},
render: function(eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
saveEmployee: function(event){
this.model.set({
firstName:$('#newFirstName').val(),
lastName:$('#newLastName').val(),
title:$('#newTitle').val(),
city:$('#newCity').val(),
officePhone:$('#newOfficePhone').val(),
cellPhone:$('#newCellPhone').val(),
email:$('#newEmail').val()
});
this.model.save();
window.history.back();
return false;
}
});
I think you need the new model to be a member of your collection before you attempt to persist it. Try creating a new instance of your collection and passing it to the view (probably in your router) like this:
new newEmployeeView({ collection: new EmployeeCollection() });
In your view you can use Backbone's create convenience method (see docs) to add a new instance of the model to the collection and persist it:
this.collection.create({
firstName:$('#newFirstName').val(),
lastName:$('#newLastName').val(),
title:$('#newTitle').val(),
city:$('#newCity').val(),
officePhone:$('#newOfficePhone').val(),
cellPhone:$('#newCellPhone').val(),
email:$('#newEmail').val()
});

Backbone - testing a simple model using Jasmine results in TypeError

I was trying to learn the ways of testing Backbone-based app using Jasmine. For this, I picked up some sample code from here: http://msdn.microsoft.com/en-us/scriptjunkie/hh377172.
Photo = new Backbone.Model.extend({
defaults:{
title: 'Another photo!',
tags: ['untagged'],
location: 'home',
src: 'placeholder.jpg'
},
initialize: function(){
console.log('this model has been initialized');
this.bind("change:title", function(){
var title = this.get("title");
console.log("My title has been changed to.." + title);
});
},
setTitle: function(newTitle){
this.set({ title: newTitle });
}
});
Then wrote the test spec as follows:
describe("Photo Model", function() {
it("verifies title", function() {
var myPhoto = new Photo();
myPhoto.set({ title: "On the beach" });
expect(myPhoto.get("title"))
.toEqual("On the beach");
});
});
While running it, the test fails with TypeError
TypeError: Object [object Object] has no method 'apply'
at new <anonymous> (http://localhost:88/backbone/WebClient-Backbone2/js/backbone.js:1103:41)
at [object Object].<anonymous> (http://localhost:88/backbone/WebClient-Backbone2/test/spec.js:28:16)
at [object Object].execute (http://localhost:88/backbone/WebClient-Backbone2/test/lib/jasmine/jasmine.js:1001:15)
at [object Object].next_ (http://localhost:88/backbone/WebClient-Backbone2/test/lib/jasmine/jasmine.js:1790:31)
at [object Object].start (http://localhost:88/backbone/WebClient-Backbone2/test/lib/jasmine/jasmine.js:1743:8)
at [object Object].execute (http://localhost:88/backbone/WebClient-Backbone2/test/lib/jasmine/jasmine.js:2070:14)
at [object Object].next_ (http://localhost:88/backbone/WebClient-Backbone2/test/lib/jasmine/jasmine.js:1790:31)
at [object Object].start (http://localhost:88/backbone/WebClient-Backbone2/test/lib/jasmine/jasmine.js:1743:8)
at [object Object].execute (http://localhost:88/backbone/WebClient-Backbone2/test/lib/jasmine/jasmine.js:2215:14)
at [object Object].next_ (http://localhost:88/backbone/WebClient-Backbone2/test/lib/jasmine/jasmine.js:1790:31)
As mentioned in the comments, I found out that, this is due to the "new" in the model definition.
So, after changing,
var Photo = new Backbone.Model.extend({
to
var Photo = Backbone.Model.extend({
The error disappeared.
There is a good set of tutorials for testing a backbone based application with Jasmine here:
http://tinnedfruit.com/2011/03/03/testing-backbone-apps-with-jasmine-sinon.html
The code snippet below is probably the culprit.
this.bind("change:title", function(){
var title = this.get("title");
console.log("My title has been changed to.." + title);
});
In Javascript, the scope context of the this keyword is based upon the function it is in. Since this is inside an anonymous function (this.bind("change:title", function()), then this has changed its scope.
The easy solution is to use a closure to set this to another variable outside the anonymous function, and then you can use the variable inside the anonymous function.
Showing a code example will probably better explain it. Update your initialize method to the following.
initialize: function(){
console.log('this model has been initialized');
var self = this;
this.bind("change:title", function(){
var title = self.get("title");
console.log("My title has been changed to.." + title);
});
},
You will also have the same problem in the setTitle() method. You will need to add this line of code in the initialize() method to correctly set the context of this.
_.bindAll(this, "setTitle");

Failing to pass models correctly from collection view?

I've been staring at this for a while and trying various tweaks, to no avail.
Why am I getting a "this.model is undefined" error at
$(function(){
window.Sentence = Backbone.Model.extend({
initialize: function() {
console.log(this.toJSON())
}
});
window.Text = Backbone.Collection.extend({
model : Sentence,
initialize: function(models, options){
this.url = options.url;
}
});
window.SentenceView = Backbone.View.extend({
initialize : function(){
_.bindAll(this, 'render');
this.template = _.template($('#sentence_template').html());
},
render : function(){
var rendered = this.template(this.model.toJSON());
$(this.el).html(rendered);
return this;
}
})
window.TextView = Backbone.View.extend({
el : $('#notebook') ,
initialize : function(){
_.bindAll(this, 'render');
},
render : function(){
this.collection.each(function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
return this;
}
});
function Notebook(params){
this.text = new Text(
// models
{},
// params
{
url: params.url
}
);
this.start = function(){
this.text.fetch();
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
};
}
window.notebook = new Notebook(
{ 'url': 'js/mandarin.js' }
);
window.notebook.start();
})
There's an online version wher eyou can see the error in a console at:
http://lotsofwords.org/languages/chinese/notebook/
The whole repo is at:
https://github.com/amundo/notebook/
The offending line appears to be at:
https://github.com/amundo/notebook/blob/master/js/notebook.js#L31
I find this perplexing because as far as I can tell the iteration in TextView.render has the right _.each syntax, I just can't figure out why the Sentence models aren't showing up as they should.
var view = new SentenceView({model: sentence});
I'm pretty sure when you pass data to a backbone view constructor, the data is added to the Backbone.View.options property.
Change this line
var rendered = this.template(this.model.toJSON());
to this
var rendered = this.template(this.options.model.toJSON());
and see if it works
UPDATE:
From the doco:
When creating a new View, the options you pass are attached to the view as this.options, for future reference. There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, and tagName
So, disregard the above advice - the model should by default be attached directly to the object
Things to check next when debugging:
confirm from within the render() method that this is actually the SentenceView object
confirm that you are not passing in an undefined sentence here:
var view = new SentenceView({model: sentence});
UPDATE2:
It looks like the collection is borked then:
this.textView = new TextView({
collection: this.text
});
To debug it further you'll need to examine it and work out what's going on. When I looked in firebug, the collection property didn't look right to me.
You could have a timing issue too. I thought the fetch was asynchronous, so you probably don't want to assign the collection to the TextView until you are sure it has completed.
Backbone surfaces underscore.js collection methods for you so you can do this. See if this works for you:
this.collection.each(function(sentence) {
// YOUR CODE HERE
});
I think the problem is on line 48 of notebook.js shown below:
render : function(){
_(this.collection).each(function(sentence){
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
The problem is you are wrapping the collection and you don't have to. Change it to
this.collection.each(function(sentence){ ...
hope that fixes it
EDIT:
OK i'm going to take another crack at it now that you mentioned timing in one of your comments
take a look at where you are fetching and change it to this:
this.start = function(){
this.text.fetch({
success: _.bind( function() {
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
}, this)
);
};
I typed this manually so there may be mismatching parentheses. The key is that fetch is async.
Hope this fixes it
try using _.each
_.each(this.collection, function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
},this);

Resources