Marionette collection view does not update when model added - backbone.js

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

Related

Backbone pre rendering of collection models

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

Backbone Nested Views & Nested Models

I am a newbie to backbone and trying to start using in our projects.
My requirement is I have something like this
var TextFields = Backbone.Model.extend({});
var TextFieldsCollection = Backbone.Collection.extend({});
var Module = Backbone.Model.extend({
defaults: {
fields: new TextFieldsCollection()
});
var ModuleCollection = Backbone.Collection.extend({
model: CTModule
});
Now I have views defined for TextFields & Modules.
If I change a value in TextFields a event gets fired for the model and I am changing the value in the model but the collection is not getting updated.
I tried to trigger backbone events in the child model but at the collection view I am not able to map to the correct model that triggered the change event.
Any comments are helpful. I am not in a position to use more libraries. Can this be done in Backbone?
I do not know what is full code, but if you do it in a fashion that Backbone apps are usually written everything should work.
Quick tutorial:
var TestModel = Backbone.Model.extend({});
var TestModelCollection = Backbone.Collection.extend({ model: TestModel });
// now if you have a view for collection
var TestModelCollectionView = Backbone.View.extend({
initialize: function(opts) {
this.collection = opts.collection;
this.collection.on('change', this.render, this) // so - rerender the whole view
}
});
//execution:
var modelData = { foo: 'bar' };
var collection = new TestModelCollection([modelData]);
var view = new TestModelCollectionView({collection: collection});
view.render();
collection.first().set('your_attr', 'new_value'); // will cause rerender of view
//////
One more thing - if you have collection, which is kept by the model (in your case Modules model, which have collection as one of its attributes), and your view keeps that model, you will have to either bind directly from view to collection via model (this.model.get('fields').on( .... )), or rethrow collection event in your model
I hope I helped at least a bit.
//////
There might be some syntax errors in my code - didn't run it on js fiddle.

Initialize CollectionView with a collection reference

In my initialize function of a CollectionView Backbone Marionette object, I'm doing the following:
this.collection_addresses = new AddressCollectionView({
el: 'ul.addresses',
collection: options.user.get("addresses")
});
However the AddressCollectionView is never updated whenever the object referenced in options.user.get("addresses") is changed, I thought Marionnette is handling this automatically. The user object is updated using a fetch. Any thoughts?
Edit #1
Just to clarify the collection view is like this
var AddressCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: AddressItemView,
tagName: 'ul'
});
Thanks.
from the v1.0.3 source code, here are the events the collectionview listens to
_initialEvents: function(){
if (this.collection){
this.listenTo(this.collection, "add", this.addChildView, this);
this.listenTo(this.collection, "remove", this.removeItemView, this);
this.listenTo(this.collection, "reset", this.render, this);
}
},
A collectionView accepts an itemView view object definition, (not an instance). This itemView is initialized with the model for each item in the collection by the collectionview. I would extend marionette's itemview class, and listen to your change event on your model there, and inject that itemview view object definition, (not an instance) into the collectionview.

Backbone: collection event binder

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.

How should a Backbone model or collection handle views?

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.

Resources