Should model know its collection? - backbone.js

A collection knows its models, but is there a way for the model to get to its collection?
I would need a way to get to the "adjacent" models in a collection. The following may give a better idea of the problem:
I'm trying to implement a simple app that ranks items based on an "priority" attribute in the model and maintains a list of them. Each model view has buttons for increasing or decreasing the priority, which should alter the ordering of the list accordingly.
Simply adding or subtracting one from the current value will work as long as the priority-attributes are continuous, for example:
1,2,3,4,5,6
But there is also a delete button, which allows deleting any model from the list. It can lead to priority-attributes like:
1,2,5,6
Now changing the priority of 5 to 4 wont change the order of the list as it's still greater than 2.

It's undocumented, but the models will automatically have a collection property that points to the collection. I don't know the details of how that works if a model is in multiple collections (perhaps the collection property is not changed once it's set).
To solve the specific problem you mentioned, you can listen for remove events on the collection and adjust things accordingly. In this case you don't even need that collection property, because both the model and collection will be passed to the remove listener.
var handler = function ( model, collection, options ) {
// ...
};
collection.on( 'remove', handler );
The Backbone.Collection docs also say this:
the index at which the model is being removed from the collection is available as options.index
The options argument to remove listeners is not documented where the signature for remove listeners is documented, but it is passed (in 0.9.2 anyway).

Related

Updating a model attribute

I am practising Backbone js. I have a collection consisting of 10 models. In just one model, I want to change 2 of the attributes. I am using the device local storage. I have tried the following code (this is just part of the larger script):
this.collection.forEach(function(user) {
if (user.get('subject') === 'Physics') {
user.set({'title': 'hdrodynamics'});
user.save();
console.log(JSON.stringify(user));
return;
}
});
I consider this method inefficient. What happens as the collection length increases. I believe there is a better way than this.
You can use collection.findWhere, which:
directly returns only the first model in the collection that matches the passed attributes.
Which can reduce the number of iterations run.
You can also use model.save with attributes to avoid doing the extra 'set'.
// Stops when matching model is found
var physicsModel = this.collection.findWhere({ subject: 'Physics' });
// might want to do some null-checking...
// Performs a set prior to POSTing, firing correct 'change' events
physicsModel.save({ title: 'hydrodynamics' });
Note: A more efficient way to do this would be to avoid the lookup in the collection. For example, if this model should 'save' by some action from the user, you could leverage the model's view to gain access to the correct model (i.e. this.model.save()). This of course is implementation dependent.

Update model within collection where model attribute is x

Within backbone I have a collection. Where I want to update a model or models within it if a model has a particular attribute value.
I know I can do something to the extent of
var model = myCollection.where({some_attr:'something'});
where model becomes the child object from the collection. However beyond that I seem to get lost. As none of the backbone functions seem to work on it. Example, I can't:
model.set({other_attr: 'changed value'});
model.save();
I just get an Uncaught TypeError. model.set is not a function
again.. Example sake.
So overall I want to find the model, update it, and then save it and its changes to the server? Idea's? Assume I have no workable ID to make getting and setting a little easier, hence why I need to do a where and update like that.
I would recommend using where and always working with the array of models it passes. When you're done updating the models you can make a sync call on the collection to bulk update those changes back to the server.
myCollection.
where({some_attr:'something'}).
map(function(model) {
model.set({other_attr: 'changed value'});
});
myCollection.sync('update');
The statement
var model = myCollection.where({some_attr:'something'});
returns an array of models, not a single model. Assuming only one model will match your criteria, you could use
var model = myCollection.findWhere({some_attr:'something'});
If your collection could have multiple matching models, then you'll want to stick with .where but iterate over the array.

Pagination with collection.fetch() in backbone.marionette

I am trying to implement pagination with backbone.marionette's
compositeView by first doing an initial fetch:
collection.fetch({reset: true})
To load additional models into the same collection,
collection.fetch({reset: false, remove: false})
is used.
I saw that on each use of fetch() with {reset: false}
muliple add events are triggered (for every model added.)
Thats fine basically, but when looking into the annotated source
of the compositeView's appendHtml function in marionette the follwing
statement is given:
buffering happens on reset events and initial renders in order to
reduce the number of inserts into the document, which are expensive.
While this is great for the initial fetch (only 1 browser reflow), every
subsequent fetch with {reset: false} could get pretty expensive
(reflow on every single model thats added). It really seems to
append() the itemView every single time - causing a reflow
(compositeView.isBuffering is false).
Is there any way to "batch" process the models and do a single insert
(same as done on the reset event)?
Does this need an overwrite in the appendHtml implementation
and implement the batch logic by myself?
(I assume you don't want to use Backbone.paginator, for some reason. If that isn't the case, use the plugin ;-D).
You could use a technique similar to the filtered collection used here: https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/entities/common.js#L2 (instanciation at https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/apps/contacts/list/list_controller.js#L13).
Basically: you provide a "paginated collection" to the view, and what the paginated collection does is:
initially, return a "cloned" copy of the original collection
when you fetch on the original collection, add the fetched events in "batch" to the cloned collection, which will trigger the view to refresh (because it's listening to the cloned collection, not the original)
To minimize reflows, you can try the following:
silence the events that the collection listens to ("add", etc.)
when a group of models gets added, create a new instance of a collection view with only the new models
render the new collection view (but don't show it anywhere), and manually append the newView.el to the right place in the DOM
destroy the newView
Unfortunately, once you need to start optimizing, you tend to need to handle a bunch of things by hand...

Backbone keeps creating new models

Everytime I do a "fetch" of my collection, backbone creates new models for every item. The old models stick around in memory, causing a big memory leak.
There are no changes of the data between "fetch" calls, should backbone not recognize that there are no changes and carry on?
Backbone collection will use "set" method to update model data in collection.
Set is a smart method and it will perform these checks when collection fetch data from server:
Add: If a model in the list isn't yet in the collection it will be added.
merge: if the model is already in the collection its attributes will be merged
remove: if the collection contains any models that aren't present in the list, they'll be removed
Your problem is the collection will continuously add new model when you fetch data from server even though you just try to refresh data which is updated. I think your data don't have unique "id" attribute. So the collection can't perform "smart update" when it fetch data from server.
{id:"1234",name:"blabla","tel:0600000000"}
I hope this is helpful for you.
The problem is different than I thought.
I keep a list of sub-view in my view so I can remove them. However, there appear to be 2 different properties with the same name. One keeping a hold of the views.

How does collection.fetch({add:true}) work?

I added a model to my collection and did model.save The data gets saved on the server.
After this I call collection.fetch({add:true}). Ideally only one model should be returned from server ie the newly added one but I'm seeing the entire collection being propagated back in chrome developers tool.
I'm confused. how does this actually work ?
As an add-on to Cyril N.'s answer (which describes essentially the default behavior, and should be enough I guess), I'd like to explain more thoroughly the role of the 3 flags:
add: whether or not Backbone should create and add to the collection the models which don't exist yet (ie, whose id are not in the collection)
remove: whether or not Backbone should remove from the collection the models which were not brought back from the server (ie, whose id were not is the brought back data)
merge: whether or not Backbone should update the models which are not in the first two categories (already in the collection, and brought back from the server)
However, I like to expand a bit further about the general behavior of the method (it uses the Collection#set method as a callback, so it's more the Collection#set's behavior). Backbone prepares the models, which means it creates fake, volatile models at the beginning of the method (they're only volatile if they're not added). This can lead to unexpected behaviors: because it creates models, the initialize method of those are executed.
Also, as a side-note, since Backbone 1.0, the Collection#reset flag was introduced if you want to reset your collection when fetching (therefore it uses this method as a callback). Before that, it was the default behavior of the Collection#fetch method.
Well, with both answers you should have every info you need I guess.
When you call collection.fetch({add:true}, Backbone will request the server with the complete list of items in the collection (hence the entire collection you see in the Chrome developers tool), but then, instead of reloading the entire collection in the Collection Object in Backbone (Javascript), Backbone will performs a "smart" update of the collection.
The set method performs a "smart" update of the collection with the
passed list of models. If a model in the list isn't yet in the
collection it will be added; if the model is already in the collection
its attributes will be merged; and if the collection contains any
models that aren't present in the list, they'll be removed. All of the
appropriate "add", "remove", and "change" events are fired as this
happens. If you'd like to customize the behavior, you can disable it
with options: {add: false}, {remove: false}, or {merge: false}.
Source: http://backbonejs.org/#Collection-set
All the smart work is made client side, in Javascript, but for that, Backbone request the entire collection from the server to compare it with what it has in local (client side).
(I hope I'm clear ;)).

Resources