backbone.js collection events - backbone.js

I develop a jquery & backbone.js web app.
One component has an html table and behind this table is a backbone.js collection.
Any change in this collection should lead to an update of the html table, so I write
this.collection.bind("reset add remove", this.renderRows, this);
So I update the html table, when the whole collection gets new, when a new model gets added and when a model gets removed.
There's also a detail view component that gets called when the user hovers and clicks over a certain row of the html table. At the beginning of this component I get the right model out of the collection
changeModel = this.collection.get(id);
After the user has changed some attributes, I do
changeModel.set(attrs);
and return to the html table. The model in the collection has the correct changed values.
But the html table is not updated as none of the 3 events (reset, add, remove) was triggered.
So I added "replace" to the collection binding
this.collection.bind("replace reset add remove", this.renderRows, this);
and before returning from the detail view I called
this.collection.trigger("replace");
My solution works, but my question is:
Is there any "native" backbone.js solution that is already there and that I have missed and where I do not have to trigger something by myself?

The change events from models bubble up to the collection. (Collection's _onModelEvent -function in the annotated source. The method just basically takes all events from models and triggers them on the collection.
This leads to
Model attribute is set
Model triggers change
Collection catches change
Collection triggers change
So
this.collection.bind("replace reset add remove", this.renderRows, this);
has to be replaced with this
this.collection.bind("change reset add remove", this.renderRows, this);
P.S.
My personal opinion is that you shouldn't redraw the whole table if just one model is changed. Instead make each table row a view in itself that has the corresponding model as its model and then react to attribute changes there. There is no point in redrawing 500 table cells if you're targeting just one.
UPDATE
And nowadays you should use the on -method for binding to events.
collection.on("change reset add remove", this.renderRows, this);
If you're using BB 1.0, and this event is being listened to within a View, I suggest moving to use the new listenTo to bind into events, which also allows for easy unbinding when calling view.remove(). In that case you should do:
// assuming this is the view
this.listenTo(collection, "change reset add remove", this.renderRows);

Related

How to update the view without re-rendering in marionette.js

How to update the view with the model.fetch(), i.e when i fetch the model, the view shouldn't be destroyed or re-rendered. It should just update the new model into view with reseting the previous model.
this.model.fetch({success: this.render.bind(this)});
This code is re-rendering my view..How can i just update the view with new model
Thanks.
There are multiple ways of updating the view based on your need.
If the view is fairly simple, and all that is needed is to automatically update the view on model's fetch, rendering the view again is the simplest(Backbone's sync event on the model can be used, and model events can be handled declaratively using Marionette View's modelEvents hash -http://marionettejs.com/docs/marionette.view.html#viewmodelevents-and-viewcollectionevents)
modelEvents: {'sync': 'render'}
If you have few model attributes that change in a complex view, you could directly update the dom elements via jquery by listening to change events on the model attributes:
modelEvents: {'change:name':'nameChanged'},
nameChanged: function(model, value){ this.$('#name').val(value);}
If Two way data binding between view and model is needed, frameworks like Backbone.stickit can be used to this purpose - https://github.com/NYTimes/backbone.stickit#usage
Whenever you establish a double binding with your model attributes to the templates, your views need to be rendered again to show the changes. So, you can't avoid rendering to show the updated status of your model.
But what I would suggest you to do is to divide your view into subviews. Since, you are using marionette, you can create a main layout which contains all the regions and for each small region, you can define a view .
For example , suppose I have a basic form with my name, current time and age . All of these variables are stored in a model . So, you have a scenario where your name and age hardly changes but the current time is changing every millisecond causing your whole view to re-render which is bad in terms of performance as well as to the eyes.
So, in order to solve the above scenario, if you could create a separate view for the current-time element, you can render is separately and other elements don't need to be rendered again and again. You can always define a separate view for a single button element if you think that its functionality can be abstracted.

Change details on Backbone.JS collection change event

Is there a way to gather more information about the changes from within the Backbone.JS collection change event. What I would like to figure out is whether or not it possible to to know what the collection change was; was a model updated, was one added or removed from the collection and also, for every of these, figure out which one.
Different events
When model is added to collection
collection.on('add',this.someFunc,this);
When a model is removed from a collection.
collection.on('remove',this.someFunc,this);
There is no change event on collection but you need to listen on models change event for change
in model:
initialize:function(){
this.on('change',function(){
this.collection.trigger('change');
});
}
Now on collection you can hear for change event
collection.on('change',this.someFunc,this);
refer this for list of all backbone built-in events

Backbone: firing an event only once on a Collection 'change'

Simple question: What is the best way to fire an event 'only once'?
I have a Collection in backbone with multiple models. Sometimes multiple models' attributes are updated at once, firing multiple 'change' events on the Collection.
What is the best way to fire the 'change' event only once, grouping all attribute changes together?
My best idea at the moment is using a timer but this will only capture the first model attribute change.
All suggestions, ideas and solutions are valued. Thank you :).
Context:
In this case the event fires a very computationally intensive function, therefore multiple runs must be avoided.
Whenever you are updating the attributes on the models in bulk, pass {silent:true} as an option, which will suppress any events from being fired.
Then trigger a custom event for which a view or multiple views listening to the same.
You can "debounce" the method in your collection that responds to change events, though you'll have to manually figure out which models changed:
Col = Backbone.Collection.extend({
modelChanged: _.debounce(function() {
// handle model changes
}, delayInMs),
initialize: function() {
this.collection.on('change', this.modelChanged, this);
}
})
You can try resetting the collection, rather than adding the elements. See Collection reset method description:
reset collection.reset([models], [options])
Adding and removing models one at a time is all well and good, but sometimes you have so many models to change that you'd rather just update the collection in bulk. Use reset to replace a collection with a new list of models (or attribute hashes), triggering a single "reset" event at the end. Returns the newly-set models. For convenience, within a "reset" event, the list of any previous models is available as options.previousModels.
As documentation says, if you use reset, instead of add you will get a single reset event in the end.

Parsing updated collection content, hiding new model view instances

I have a Backbone view which represents a collection. When this collection is synced with the server, and new models are added to the collection, I would like to hide all the view instances that represent these new models in the collection, whilst continuing to display the old models' view instances. How can I do this?
I'm assuming you're using a Marionette.CollectionView or Marionette.CompositeView here, right? That being the case, you are trying to prevent these from showing the newly added models, when the come back from the server and are added to your collection, right?
I can see a few different ways of doing this, most of which begin at the same place: a collection that filters based on an attribute that tells you whether or not to show the model. You would need to have some piece of data on the models that tells you wether to show them or not... I'm not sure what this would look like off-hand, but once you have the logic set up for this, I think the rest of it becomes easier.
A Proxy Collection
My preferred method of handling this in the CompositeView or CollectionView would be to create the view instance with a proxy collection - one that is filtered based on the value to show or hide the models.
I've talked about proxy collections before, and I have a working JSFiddle to show how to build them, here: http://jsfiddle.net/derickbailey/7tvzF/
You would set up a filtered collection like this, and then send the filtered collection to your actual view instance:
var filtered = new FilteredCollection(myOriginalCollection);
filtered.filter({
showThisOne: true
});
var view = new MyCompositeView({
collection: filtered
});
myOriginalCollection.fetch();
From here, it comes down to how you want to manage the filtering and fetch/sync of the original collection. But I think the over-all solution hinges on the proxy/decorator collection and being able to filter the collection down to only the list of items that you want.
Views in Bbone are not automatically updated when the underlying model/collection is changed. You have to explicitly listen for events: change/destroy for models and add/change/remove/reset for collections and call render().
As WiredPrairie suggests, if you've registered during view initialization for example to listenTo() any of these events and explicitly render(), you can always use stopListening() to reverse the effect.
An alternative, in case it's a one-of case of suppressing the view from showing the changes, is to check manually in your view's render() which models have been changed and use the previous state of the changed attributes to avoid showing the new values. Check out: model.hasChanged() and model.previousAttributes() as well as the options.previousModels in case of a collection reset.

Adding to a collection from a popup modal with Backbone Marionette

I'm pretty new to Backbone and Marionette and am having a tough time getting my views to communicate.
I've got a composite view that displays a list of items. To add a new item to the list, I have a popup modal that opens in a new item view that is separate from the composite view.
I'm not sure that this is the best way to do this, but in the modal, I created a new instance of the collection with all of the items and added the new item to that collection. This new item shows up in the original composite view, but only after I refresh the page.
I can't seem to figure out how to get the composite view to listen for the add event and render the new model after it is added.
Am I on the right track here? If not, what should I be doing to add to a collection from a modal?
I think I got this figured out. Instead of creating a new collection in the modal view, I passed in the collection from the composite view as a parameter when I created the modal view. Now, when I add a new model in the modal, the 'add' event is automatically triggered on both versions of the collection and the view automatically renders the new model. No need to bind any extra events like I was thinking.
Your solution will work, but means your views are pretty tightly coupled. You might want to look into using events instead (see How do I send a model that was clicked on within a ItemView to another ItemView that is on the same page?)
How your functionality would work with events:
Within the modal, you enter the data for the model to create
When you press the "save" button,
you validate and save the model var myNewModel = ...
you trigger an event: MyApp.MySubApp.trigger("item:add", myNewModel)
In the controllerfor the list view, you listen to that event, and add the new model to the collection.
The handler code in your controller would look something like:
MyApp.MySubApp.on("item:add", function(model){
this.myCollection.add(model);
});
If you'd like to learn more about using events, check out 2 Marionette tutorials I wrote:
http://davidsulc.com/blog/2012/04/15/a-simple-backbone-marionette-tutorial/
http://davidsulc.com/blog/2012/05/06/tutorial-a-full-backbone-marionette-application-part-1/
Both use events to communicate information within the apps.
In addition, basic events are also explained here: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf

Resources