Backbone.js: what is "model" inside "render: function(model){...}"? - javascript

I'm asking this based on a blog I read...
backbone.js simple inheritance...
Where does the parameter model come from in render: function(model){...}? I know that this.render is called for every new item within the collection, but where does function(model) come from? And how can that be passed as model for SingleAnimalView like so: new SingleAnimalView({model: model})?
var AnimalView = Backbone.View.extend({
el: "#demo",
initialize: function(){
window.animals.bind("add", this.render, this);
},
render: function(model){
var singleAnimalView = new SingleAnimalView({model: model});
$(this.el).append(singleAnimalView.el);
}
});
Here's the jsFiddle link: http://jsfiddle.net/HVK7F/

When the add event is fired from the animals collection, the callback method's first argument will be item that was added to the collection.
In this case, render(model) is being used as the event handler, and model will be the item added.
Have a look at the add method of the annotated source to see how it's invoked. Ultimately it's a result of this line:
if (!options.silent) model.trigger('add', model, this, options);
trigger() takes all of the arguments except the first one and passes them to the callback.

Related

Backbone View instance not working as expected

I think I am missing something very trivial here. I have created a Backbone View as follows(without extending Backbone.View):
var PlayersView = new Backbone.View({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
But it doesn't log anything when I run this code. It doesn't work when I explicitly do: PlayersView.render(); as well.
But the following code works :
var PlayersView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
var playersview = new PlayersView();
The View constructor does not accept properties to add to the constructed object. It accepts a few special options like 'model', 'tagName', and so on. But the initialize(...) and render(...) properties in your first code snippet are effectively ignored.
The proper way to provide initialize, render, is to use Backbone.View.extend({...}).
From http://backbonejs.org/#View-extend
extend
Backbone.View.extend(properties, [classProperties]) Get started with
views by creating a custom view class. You'll want to override the
render function, specify your declarative events, and perhaps the
tagName, className, or id of the View's root element.
In other words, your first view's render method was not overridden by your custom render/initialize function.
When using extend you actually let Backbone understand you wish to use your own methods instead of the "default" ones.

Backbone Marionette ItemView return html

I want to render marionette ItemView and append result html() to my div.
My code:
var ProjectItemView = Backbone.Marionette.ItemView.extend({
template: "#ProjectItem",
tagName: 'div',
initialize: function () {
this.model.on('change', this.life, this);
this.model.on('change', this.render, this);
},
life: function () {
alert(JSON.stringify(this.model));
}
});
var tmp = new Project({project_id: 1});
tmp.fetch();
$('#container').append(new ProjectItemView({model: tmp}).$el);
alert in life: function shows model right. It means fetch works fine.
The question is - how to get html as result of view.
I also tried $('#container').append(new ProjectItemView({model: tmp}).render().el);
The problem was with the REST service that I use to populate collections/models. It returns array that contains one element - not plain object directly.
You have to react to render event from marionette.
...
onRender : function() {
$('#container').append(this.$el);
}
...
new ProjectItemView({model: tmp});
tmp.fetch();
If you want to get uncoupled, fire a distinct app event from within your view handler to the outside world. Radio might be worth considering if you're not already.
I think your problem is only the order of operations. Try this:
$('#container').append((new ProjectItemView({model: tmp})).render().el);
The way you had it before, you were invoking .render() on the constructor. With the extra parenthesis above, .render() is called on the instance.
pass element to view:
new ProjectItemView({model: tmp, el:'#container'}).render();

Best method for rendering collection in Backbone

I'm using Backbone and I have the following model and collection
App.Models.Person = Backbone.Model.extend({
});
App.Collections.People = Backbone.Collection.extend({
model: App.Models.Person,
url: 'api/people',
});
However what I'm struggling on is the best way to render this collection. Here's how I've done it so far which works but doesn't seem to be the most elegant solution
App.Views.PeopleView = Backbone.View.extend({
el: $(".people"),
initialize: function () {
this.collection = new App.Collections.People();
//This seems like a bad way to do it?
var that = this;
this.collection.fetch({success: function(){
that.render();
}});
},
render: function () {
var that = this;
_.each(this.collection.models, function (item) {
that.renderPerson(item);
}, this);
},
I'm fairly new to Backbone but have to assign this to a different variable to I use it inside of the success function just seems like a bad way of doing things? Any help on best practices would be appreciated.
Backbone allows you to register for events that you can react to. When the collection is synchronized with the server, it will always fire the sync event. You can choose to listen for that event and call any given method. For instance ...
initialize: function () {
this.collection = new App.Collections.People();
this.listenTo(this.collection, "sync", this.render);
// Fetch the initial state of the collection
this.collection.fetch();
}
... will set up your collection so that it would always call this.render() whenever sync occurs.
The docs on Backbone Events are succinct but pretty good. Keep in mind a few things:
The method you use to register event listeners (i.e. listenTo or on) changes how you provide the context of the called function. listenTo, for instance, will use the current context automatically; on will not. This piece of the docs explains it pretty well.
If you need to remove a view, you will need to disconnect event listeners. The easiest way to do that is to use listenTo to connect them in the first place; then when destroying the view you can just call view.stopListening().
For rendering, there are a lots of suggestions for how to do it. Generally having a view to render each individual model is one way. You can also use Backbone.Collection#each to iterate over the models and control the scope of the iterating function. For instance:
render: function() {
this.collection.each(function(model) {
var view = new App.Collections.PersonView({ model: model });
view.render();
this.$el.append(view.$el);
}, this);
}
Note the second argument to .each specifies the scope of the iterator. (Again, have a look at the docs on controlling scope. If you'd rather have a framework help out with the rendering, check out Marionette's CollectionView and ItemView.
If your view is supposed to just render the collection you can send the collection to temnplate and iterate through in template, otherwise you can create another subView for that purpose or send the individual models of the collection to another subview and append to the container, hope it was helpful.

Backbone.js "Todos" sample - Unsure why a certain fragment of code works

This question is about the "Todos" Backbone.js sample, which is at:
http://documentcloud.github.com/backbone/docs/todos.html
The following block of code is in "The Application" section, and iterates through the Todos collection. My issue is that the addOne function is passed as a reference to the Todos collection, but that function has a reference to this, which is not the same as what this would refer to when the function is called by the Todos collection object.
addOne: function(todo) {
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
},
addAll: function() {
Todos.each(this.addOne);
},
Why does the function execute correctly when the caller is not calling it in the context of the instantiated AppView object?
I just worked it out. It occurred to me that this refers to the window object by default and seeing as jQuery registers $ globally, the function will work even if called with no context object.
"If jQuery or Zepto is included on the page, each view has a $ function that runs queries scoped within the view's element."
http://documentcloud.github.com/backbone/#View-dollar
Context is the same (view) for both addOne and addAll and it is achieved with the third parameter in bind calls.
Todos.bind('add', this.addOne, this);
Todos.bind('reset', this.addAll, this);
http://documentcloud.github.com/backbone/#Events-bind
--edit
Hmm.. then again do those binds ensure the context when addOne is ran with each?
addAll: function() {
Todos.each(this.addOne);
},
For future Googlers, this issue was submitted on GitHub and a fix was applied, but it doesn't really address the issue.
I think the best way to solve this problem is have the addAll function bind its own this to the .each iteration, i.e.
addOne: function(todo) {
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
},
addAll: function() {
Todos.each(this.addOne, this); // notice the second parameter
},
We can do this because Underscore.js's _.each function has an option third (second, in our case) parameter to specify the context for the iteration.
Take a look at the initialize method:
initialize: function() {
this.input = this.$("#new-todo");
Todos.bind('add', this.addOne, this);
Todos.bind('reset', this.addAll, this);
Todos.bind('all', this.render, this);
Todos.fetch();
}
That third (optional) argument to bind is the context with which the callback gets called. It's documented here. The implementation in Backbone is here:
callback[0].apply(callback[1] || this, args);

What can my models bind to when a collection is reset?

Are there any events my models can bind to, to know their collection has been reset?
When I call:
collection.reset()
I want those removed models to be destroyed and in turn any views to know they are gone. What should I bind to here?
From the fine manual:
reset collection.reset(models, [options])
[...] triggering a single "reset" event at the end.
So bind to the collection's reset event and hope that no one uses the {silent: true} option to do things behind your back.
#mu's answer is correct, but you might also need to know that a model that is added to a collection has the .collection property, which points to the parent collection. So if you are instantiating your models manually, you can just do this:
var myModel = new MyModel();
collection.add(myModel);
collection.bind('reset', model.cleanUp(), model);
But if you're instantiating your models via the collection, e.g. with collection.fetch(), you need to bind to the collection in the initialize() method of the model:
var MyModel = Backbone.Model.extend({
initialize: function() {
if (this.collection) {
this.collection.bind('reset', this.cleanUp(), this);
}
}
// etc
});

Categories