As you can see, I am very new at backbone, and I can't work out why this is not working???
I am getting this error?
Uncaught TypeError: Cannot call method 'on' of undefined
CODE
TodoItem = Backbone.Model.extend({});
TodoList = Backbone.Collection.extend({
model: TodoItem,
url: "todo"
});
var TodoView = Backbone.View.extend({
el: '#content',
initialize: function() {
this.model.bind('change', this.render, this);
},
render: function(model) {
this.$el.html('test');
}
});
$(function() {
var todoList = new TodoList();
todoList.fetch();
new TodoView();
});
URL TODO - JSON
[ {description: 'Pick up milk.', status: 'incomplete', id: 1},
{description: 'Get a car wash', status: 'incomplete', id: 2} ]
You're using this.model in your view:
initialize: function() {
this.model.bind('change', this.render, this);
},
but you don't specify a model when you create the view:
new TodoView();
That should look more like:
new TodoView({ model: some_model })
If you want the TodoView to look at the whole collection then use this.collection:
initialize: function() {
this.collection.on('change', this.render, this);
}
and supply the collection option when creating the view:
new TodoView({ collection: todoList });
Also note that todoList.fetch(); is an AJAX call so you probably won't have anything in todoList when you create your view, you can bind to the 'reset' event to re-render when something shows up:
initialize: function() {
_.bindAll(this, 'render');
this.collection.on('change', this.render);
this.collection.on('reset', this.render);
}
You can also use _.bindAll to bind a function to this, that way you don't need to use the third parameter to on.
BTW, bind is an alias for on, bind still works but on is the preferred method for new code; AFAIK, the name was changed to on to better match the newer jQuery naming scheme.
Related
Generally when design view for Collection(s), I would bind the collection to the view, and register related events to the collection like this:
var Book = Backbone.Model.extend({});
var BookList = Backbone.Collection.extend({
model: Book,
url: "/books"
});
var BookListItemView = Backbone.View.extend({
mtemplate: _.template($('#tpl_book_item').html()),
render: function () {
this.$el = $(this.mtemplate(this.model.toJSON()));
return this;
}
});
var BookListView = Backbone.View.extend({
el: '#content',
initialize: function () {
this.listenTo(this.collection, 'add', this.render);
this.listenTo(this.collection, 'remove', this.render);
},
render: function () {
this.$el.empty();
this.collection.each(function (item) {
this.$el.append(new BookListItemView({model: item}).render().$el);
}, this);
return this;
}
});
Use:
var books = new BookList();
var bookListView = new BookListView({
collection: books
});
books.fetch();
It worked as expected: render every book as defined in the template. However I found that there is a slight stuck in the page.
I am not sure if this is caused by the re-rendering the view? As shown, when the books.fetch complete, it will add books to the collection of books, for each book item, an add event will be triggered, then I will re-render the page by removing the exist item and iterate the collection.
Which means once there are 10 books, there will be 1+2+3+4...+10 loops for the BookListView.
I my opinion, once the add event triggered, I should not refresh the whole list but just add a new view to the BookListView, but how about the remove event, it seems that Backbone does not provide any internal method to get the view from the model, so once a model to be removed, I can not get the related view.
How do you handle this kind of suitation?
Do not bind your add to the render function. Instead create a dedicated add method for that.
var Book, BookList, BookListItemView, BookListView;
Book = Backbone.Model.extend({});
BookList = Backbone.Collection.extend({
model: Book,
url: "/books"
});
BookListItemView = Backbone.View.extend({
mtemplate: _.template($("#tpl_book_item").html()),
initialize: function() {
this.model.on("remove", this.remove);
},
render: function() {
this.$el = $(this.mtemplate(this.model.toJSON()));
return this;
}
});
BookListView = Backbone.View.extend({
el: "#content",
initialize: function() {
this.listenTo(this.collection, "add", this.addItem);
},
render: function() {
this.$el.empty();
this.collection.each((function(item) {
this.addItem(item);
}), this);
return this;
},
addItem: function(item) {
this.$el.append(new BookListItemView({
model: item
}).render().$el);
}
});
Let the models own View handle its own remove event.
I'm using Backbone.js 1.1.2 with jQuery 1.11.
I think I'm missing something very simple.
I've set up a model: Contact and a collection: ContactList.
I have success rendering ContactView views with models obtained from ContactList.fetch()
But when I try to bind a change event in the view initializer:
this.model.on('change', this.render, this);
... I get:
backbone Uncaught TypeError: Object #<Object> has no method 'on'
So I go back to the documentation and read that .on() is part of Backbone.Events, and somehow I need to extend my model to use this (and why is this not part of the out-of-the-box functionality for models??)
I've tried declaring
_.extend(this, Backbone.Events);
... inside of the initialize function for ContactView before declaring the binding, but no dice.
What do I need to do to get the Backbone.Events functionality working??
update: all relevant code (sans template)
var Contact = Backbone.Model.extend({
defaults: {
FirstName: '',
...[etc]
},
initialize: function() {
}
});
var ContactList = Backbone.Collection.extend({
model: Contact,
url: '/api/Contacts/getall'
});
var ContactView = Backbone.View.extend({
initialize: function() {
this.render();
this.model.on('change', this.render, this);
},
render: function () {
var template = _.template($('#contact_template').html(), this.model);
this.$el.html(template);
}
});
var contactList = new ContactList();
contactList.fetch({
success: function (collection, response, options) {
contactList.models.forEach(function (m) {
var $el = $('<li />')
.addClass('adminItem clearfix')
.appendTo($('#contactList'));
new ContactView({ el: $el, model: m.attributes });
});
}
});
This line is the source of trouble
new ContactView({ el: $el, model: m.attributes });
m.attributes is a plain js object. You want to pass along the actual model object:
new ContactView({ el: $el, model: m});
And because the collection has underscore methods mixed in you should be able to simplify it a bit to this:
contactList.each(function (m) {
var $el = $('<li />')
.addClass('adminItem clearfix')
.appendTo($('#contactList'));
new ContactView({ el: $el, model: m});
});
I have a collection, and I'm trying to share information concerning the current model being selected between two different views
-->TO PUT IT SIMPLY, I WANT TO BE ABLE TO ACCESS THE MODEL SELECTED IN ONE VIEW FROM ANOTHER VIEW AND CHANGE/ASSIGN THE ATTRIBUTES OF THE MODEL
The first view is defined with:
App.Views.Person = Backbone.View.extend({
tagName: 'a',
template: template('personTemplate'),
initialize: function(){
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this);
},
render: function() {
this.$el.html( this.template(this.model.toJSON()) );
this.input = this.$('.view');
return this;
},
the second view is defined with:
App.Views.App = Backbone.View.extend({
el: 'html',
initialize: function(){
_.bindAll(this,"render");
},
render: function() {
return this;
},
and I created my views with the following
addPersonView = new App.Views.AddPerson({ collection: peopleCollection });
appView = new App.Views.App({ model: person, collection: peopleCollection });
How do I make it so that the model selected in the 2nd view is the same as the model in the first view as pulled from my collection --> for example, when I type something into the input box on my bottom view, I want to be able to use: this.set.model({name:title}) and for this to set the model attribute for the element (associated with a model) that is selected in my top view, but using this.set.modelis not choosing the correct model that is selected in my first view
for further reference and confusion: my models are being added to my PeopleView with the following code which i'm loading from an array;
App.Views.People = Backbone.View.extend({
// tagName: '',
initialize: function() {
var i = 1;
while(i < size)
{
var person = new App.Models.Person({ url: jsArray[i] });// creating a new person object..
this.collection.add(person);
i++
}
this.collection.on('add', this.addOne, this);
console.log(jsArray[1]);
// listeners/anouncers for the collection on add..
},
// refactored render method...
render: function() {
this.collection.each(this.addOne, this);
return this;
},
// called from render method of collection view..
addOne: function(person) {
var personView = new App.Views.Person({ model: person, vent: vent });
this.$el.append(personView.render().el);
}
});
I resolved this by using 'vents' so to speak --> (awesome link) the vents provide ways for views to communicate etc
http://lostechies.com/derickbailey/2012/04/03/revisiting-the-backbone-event-aggregator-lessons-learned/, or an application level aggregator like so:
MyApp.vent = _.extend({}, Backbone.Events);
MyApp.vent.on("some:event", function(){
alert("some event was fired!");
});
MyApp.vent.trigger("some:event");
Looking for the absolute minimum script to get Backbone working. Tried piecing various tutorials and sample together, but having problems getting views to work. Nothing fancy, I'll take raw json in the browser right now. Just a basic skeleton to help connect the dots and build on. I've tried various variations on the following:
(function ($) {
var model = Backbone.Model.extend({
idAttribute: 'custId'
});
var collection = Backbone.Collection.extend({
initialize: function(){
},
model: model,
url: '/cust'
});
var view = Backbone.View.extend({
initialize: function(){
_.bindAll(this, 'render'); // fixes loss of context for 'this' within methods
this.collection.bind("reset", this.render);
this.render();
},
el: $('#content'),
template: Handlebars.compile($("#contentTemplate").html()),
render: function(){
$(this.el).html( this.template(this.model.toJSON()));
},
tagName: "li"
});
var router = Backbone.Router.extend({
initialize: function(){
var newCollection = new collection;
newCollection.fetch();
},
route: {
"": "home"
},
home: function(){
this.view = new view({collection: newCollection});
$('#content').html(this.view.el);
}
});
var app = new router();
}(jQuery))
Thanx.
You are misusing the el attribute. $('#content').html(this.view.el) will result in copying the $('#content') element inside itself (because view.el is equal to $('#content')).
You should try removing the el attribute from the view object and let it generate itself. Then $('#content').html(this.view.el); should work.
One other possible problem is that you are rendering the entire collection inside a li element - was this what you are going for? The best way to go about this would be to have each model in the collection represent a li tag and the collection a ul tag.
Other issues:
the view element is receiving a collection but you are trying to render a model
in the router, newCollection is not accessible in the home method
You are not calling Backbone.history.start()
Here is how i would rewrite the code:
var model = Backbone.Model.extend({
idAttribute: 'custId'
});
var model_view = Backbone.View.extend({
template: Handlebars.compile($("#modelTemplate").html()),
tagName: 'li',
initialize: function() {
_.bindAll(this, 'render');
this.render();
this.on('change',this.render);
},
render: function() {
$(this.el).html( this.template(this.model.toJSON()) );
return this;
}
});
var collection = Backbone.Collection.extend({
initialize: function(){
},
model: model,
url: '/cust'
});
var collection_view = Backbone.View.extend({
tagName: "ul",
initialize: function(){
_.bindAll(this, 'render','renderModels');
this.render();
this.renderModels();
this.collection.bind("reset", this.render);
this.collection.bind("reset", this.renderModels);
},
render: function(){
// just create the 'ul' tag; we will populate it with model view elements; a collection template is no longer needed
return this;
},
renderModels: function() {
this.collection.each(function(obj){
var view = new model_view({
model: obj
});
$(this.el).append(view.el);
},this);
}
});
var router = Backbone.Router.extend({
initialize: function(){
this.newCollection = new collection();
this.newCollection.fetch();
},
route: {
"": "home"
},
home: function(){
this.view = new collection_view({collection: this.newCollection});
$('#content').html(this.view.el); // #content should not be a 'ul' tag, the 'ul' is generated by the collection_view
}
});
var app = new router();
Backbone.history.start();
Make sure you update your templates accordingly.
Please excuse possible errors, i had no means to test the code but i believe it points out the logic you should use.
Cheers!
My collection is not rendering for some reason. Cannot find out why.
TreeItem = Backbone.Model.extend({
});
TreeList = Backbone.Collection.extend({
model: TreeItem,
url: "/get_tree_list"
});
window.tree_list = new TreeList();
// VIEW
window.TreeItemView = Backbone.View.extend({
tagName: 'li',
initialize: function(){
_.bindAll(this, 'render');
},
render: function(){
$(this.el).html('<span>'+this.model.get('title')+'</span>');
return this;
}
});
window.TreeListView = Backbone.View.extend({
el: "#tree-structure",
events: {
},
initialize: function() {
_.bindAll(this, 'appendItem', 'render');
tree_list.bind('add', this.appendItem);
tree_list.fetch();
this.render();
},
render: function() {
tree_list.each(this.appendItem);
return this;
},
appendItem: function(item){
var tree_item_view = new TreeItemView({
model: item
});
$(this.el).append(tree_item_view.render().el);
}
});
var tree_list_view = new TreeListView;
Backbone.js provides a lot to be interpreted that's where people new go wrong. Your mistake is fundamental in nature. You tie the View directly to the model
see initialize function where a instance of collection is rendered!!
Always and anywhere you create model, collection pass then as parameters to the constructor of views. Check my fiddle
Never call render inside model, view or collection. They must be inside application file
JsFiddle
http://jsfiddle.net/35QGM/