This is a 5 part backbone.js hello world tutorial/application. https://web.archive.org/web/20180317062059/http://arturadib.com/hello-backbonejs/docs/3.html In part 3, the author's illustrating how to use collections and models to store data and how to tie changes to the views.
I understand most of it, except this line
this.collection.bind('add', this.appendItem); // collection event binder
Is this 'bind' just binding context, or is it functioning as an 'event' such appendItem is called anytime that a model has been added?
I ask, because in the render method of the ListView, it's explicitly calling appendItem method, so why is it bound to 'add'
_(this.collection.models).each(function(item){ // in case collection is not empty
self.appendItem(item);
}, this);
Can someone please explain a little how that code is working. i looked through the documentation but couldn't find an explanation of bind used in this way.
Full code
(function($){
¶
Item class: The atomic part of our Model. A model is basically a Javascript object, i.e. key-value pairs, with some helper functions to handle event triggering, persistence, etc.
var Item = Backbone.Model.extend({
defaults: {
part1: 'hello',
part2: 'world'
}
});
¶
List class: A collection of Items. Basically an array of Model objects with some helper functions.
var List = Backbone.Collection.extend({
model: Item
});
var ListView = Backbone.View.extend({
el: $('body'),
events: {
'click button#add': 'addItem'
},
¶
initialize() now instantiates a Collection, and binds its add event to own method appendItem. (Recall that Backbone doesn't offer a separate Controller for bindings...).
initialize: function(){
_.bindAll(this, 'render', 'addItem', 'appendItem'); // remember: every function that uses 'this' as the current object should be in here
this.collection = new List();
this.collection.bind('add', this.appendItem); // collection event binder
this.counter = 0;
this.render();
},
render: function(){
¶
Save reference to this so it can be accessed from within the scope of the callback below
var self = this;
$(this.el).append("<button id='add'>Add list item</button>");
$(this.el).append("<ul></ul>");
_(this.collection.models).each(function(item){ // in case collection is not empty
self.appendItem(item);
}, this);
},
¶
addItem() now deals solely with models/collections. View updates are delegated to the add event listener appendItem() below.
addItem: function(){
this.counter++;
var item = new Item();
item.set({
part2: item.get('part2') + this.counter // modify item defaults
});
this.collection.add(item); // add item to collection; view is updated via event 'add'
},
¶
appendItem() is triggered by the collection event add, and handles the visual update.
appendItem: function(item){
$('ul', this.el).append("<li>"+item.get('part1')+" "+item.get('part2')+"</li>");
}
});
var listView = new ListView();
})(jQuery);
Say your collection already has 10 models. Then you pass it to your view. You'd call render() which triggers a loop of appendItem() or what not. Your view is happy.
Then you add a model to your collection. (Model 11)
Rather than re-render the whole thing, the this.collection.on('add', this.appendItem, this) executes the function that adds a single item view to the already existing view list.
That's probably why it's bound to the add event AND included in the render as a loop. One loops through an existing collection to generate views at the start. One takes care of any new models that are added after the view is initialized and rendered the first time.
Related
I want to perform an action, clearing parent element, after a collection has fetched his models but prior to the models rendering.
I've stumbled upon before and after render methods yet they are model specific, which will cause my parent element to clear before every model rendering.
I'm able of course to perform the action pre-fetching yet I want it to occur when fetch is done and before models are rendered.
I tried using reset and change events listening on the collection yet both resulted unwanted end result.
Reset event seamed to go in that direction yet the passed argument was the entire collection and not a single model from the collection, therefore using the add event callback wasn't possible due to difference in argument type (collection and not a model as required)
Any ideas how to invoke a callback when fetch a collection fetch is successful yet models are yet to be rendered?
The model contains the returned attributes while collection contains url for fetching and parse method to return argument wrapped object.
Below is the code I use to render the collection view, which is basically rendering each model's view within the collection.
Collection View
---------------
var FoosView = Backbone.View.extend({
el: '#plans',
events: {
//'click tr': 'rowClick'
},
initialize: function() {
this.listenTo(this.collection, 'add', this.renderNew);
_.bindAll(this, "render");
this.render();
},
renderNew: function(FooModel) {
var item = new FooView({model: FooModel});
this.$el.prepend(item.render().$el);
}
...
});
The model view
--------
var FooView = Backbone.View.extend({
tagName: 'li',
initialize: function(options) {
this.options = options || {};
this.tpl = _.template(fooTpl);
},
render: function() {
var data = this.model.toJSON();
this.$el.html(this.tpl(data));
return this;
}
});
Thanks in advance.
OK, I think I understand your question and here is a proposed solution. You are now listening to the reset event on your collection and calling this.renderAll. this.renderAll will take the list of models from the collection and render them to the page, but only AFTER the list element has been emptied. Hope this helps :).
var FoosView = Backbone.View.extend({
el: '#plans',
collection: yourCollection, // A reference to the collection.
initialize: function() {
this.listenTo(this.collection, 'add', this.renderNew);
this.listenTo(this.collection, 'reset', this.renderAll);
},
renderAll: function() {
// Empty your list.
this.$el.empty();
var _views = []; // Create a list for you subviews
// Create your subviews with the models in this.collection.
this.collection.each(function(model) {
_views.push(new FooView({model: model});
});
// Now create a document fragment so as to not reflow the page for every subview.
var container = document.createDocumentFragment();
// Append your subviews to the container.
_.each(_views, function(subview) {
container.appendChild(subview.render().el);
});
// Append the container to your list.
this.$el.append(container);
},
// renderNew will only run on the collections 'add' event.
renderNew: function(FooModel) {
var item = new FooView({model: FooModel});
this.$el.prepend(item.render().$el);
}
});
I am forced to assume a few things about you html, but I think the above code should be enough to get you up and running. Let me know if it works.
I'm not totally sure about what you are asking but have you tried:
MyCollection.fetch({
success: function(models,response) {
//do stuff here
}
});
Also you may be interested taking a look at http://backbonejs.org/#Model-parse
Hope it helps!
Edit: there is no direct link between fetching and rendering my bet is that you binded rendering to model change.
USE===============>>>> http://backbonejs.org/#Model-parse
I am new to Marionette. So may be its pretty basic.
I have a collection initialized globally and is empty during creation.
App.Colors= Backbone.Collection.extend({
model : App.Color,
comparator : "price"
});
var appColors = new App.Colors();
If I create a Marionette collection view with this empty collection.
var colorview= new App.ColorView({collection:appColors});
and later on add to the collection appColors.
appColors.add({code:'red'})
Shouldn't the view be updated automatically since it listens to collection.add. I know it will work fine if I type:
colorview.collection.add({code:'red'})
In order for it to work, you must bind the view rendering to the collection add event, so basicly in your view declaration you must have something like this :
App.ColorView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.collection, 'add', this.render);
...
}
...
});
I am following the tutorial at:
http://arturadib.com/hello-backbonejs/docs/3.html
This is a piece of code I am stuck on:
initialize: function(){
_.bindAll(this, 'render', 'addItem', 'appendItem'); // remember: every function that uses 'this' as the current object should be in here
this.collection = new List();
this.collection.bind('add', this.appendItem); // collection event binder
this.counter = 0;
this.render();
},
The line of code that I am having a hard time understanding is:
this.collection.bind('add', this.appendItem);
I know there is a bind method in underscore, but I don't think that is the same bind function.
Could you explain what the above line is for and where I can read more about it?
In backbonejs, a collection can trigger events. Example:
this.collection.trigger('myEvent');
In addition, you can also bind a collection to some events. Example:
this.collection.bind('myEvent', function() { ... });
Backbone.Collection.bind() method comes from Backbone.Events. Note that Backbone.Collection has all the methods of Backbone.Events mixed in (like all the other backbone.js objects including Backbone itself).
Backbone.Events.bind() is an alias for Backbone.Events.on().
Again some simple problem with backbone. Instead of long description here is some sample code:
var Group = Backbone.Model.extend({
defaults: {
Product: new Products()
},
initialize: function(data){
var _this = this;
var products = new Products(data.Product);
this.set('Product', products);
this.get('Product').each(function(product){
product.on('destroy', function(){
_this.trigger('change');
}, _this);
});
this.bind('change', this.update, this);
},
update: function(){
console.info("something changed");
console.log(this.get('Product').toJSON());
},
});
So group model contains Product-collection, which obviously contains products. In initialize i am trying to make sure that the update method of the group is called example when product is changed and destroyed. All seems to be working nicely, events get called, attributes look great but it fails when I call destroy method in product model. In update I try to print the contents of product collection and what I get is products BEFORE remove is done. If I call this debug line after 500ms timeout, the contents are ok. Product is removed etc.
So according to my understanding the destroy event of product is called and then passed to group before actual removal from collection is done. What am I doing wrong?
Backbone handles the removal of a destroyed model in a collection by listening to the destroy event on the models: see the source code for Backbone.Model - destroy and Backbone.Collection - _onModelEvent.
The order in which the handlers will be executed is not guaranteed, you will have to use something else. For example, listen to the destroyevent on the collection which will fire after the model is actually removed:
initialize: function(data){
var _this = this;
var products = new Products(data.Product);
this.set('Product', products);
this.get('Product').on("destroy", this.update, this);
this.bind('change', this.update, this);
},
Check this Fiddle http://jsfiddle.net/NUtmt/ for a complete example.
I have a collection of models, each of which is attached a view. The collection also globally has a view. Is it best for the model views to take care of deleting the corresponding model, or should the collection (or collection view) do that?
Collections have a .add and .remove method, when you use one of them, they fire an add or remove event on the collection. This is how you can bind without using a view at all.
var ships = new Backbone.Collection();
ships.bind('add', function(ship) {
alert('Ahoy ' + ship.get('name') + '!');
});
ships.add([
{name: 'Flying Dutchman'},
{name: 'Black Pearl'}
]);
So attach the collection to the view, inside of the constructor. This simply makes the collection available via this.collection
var ShipView = Backbone.View.extend({
collection: ships
});
This is how you can bind using the view.
// Create collection instance.
var ships = new Backbone.Collection();
// Create view class.
var ShipView = Backbone.View.extend({
collection: ships,
initialize: function() {
// This is binding add, to the view function.
this.collection.bind('add', this.add);
},
add: function(ship) {
alert('Added ' + ship.get('name') + '!');
}
/*Optional for DOM.
events: {
'click .addMyShip': 'addShip'
},
addShip: function(eventObject) {
this.collection.add([models]);
}*/
});
// Create view instance.
var shipView = new ShipView();
// Add two ships.
ships.add([
{name: 'Flying Dutchman'},
{name: 'Black Pearl'}
]);
When the view initializes, it binds the collection's add event to run this.add which is a function of the view. Optionally, you can use the delegateEvents API to handle mapping DOM elements event selector to function that runs the function in the view. That function can call this.collection.add, of which will create the domino effect.
The way the view interacts with the collection, or model, is by binding to events, you can define those events and handle them inside the view. There are several special options that, if passed, will be available to the view: model, collection, el, id, className, and tagName.