add updated model into Backbone Collections - backbone.js

I used collection create method to save the model into the server. But this create method automatically adds the stale model into the collection.
I am trying to do the following things.
After the inserting process success, I would like to add updated
model into the collection. The updated info is returned from server
as a json.
If the server responses error, I don't want to add the model into the
collection.
Do I need to extend the collection create method? Or is there another way?

From the docs:
Creating a model will cause an immediate "add" event to be triggered on the collection, a "request" event as the new model is sent to the server, as well as a "sync" event, once the server has responded with the successful creation of the model. Pass {wait: true} if you'd like to wait for the server before adding the new model to the collection.
(emphasis mine)
That'll look like:
collection.create({ // attributes
},{wait: true});

Related

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

The best way for extending a model

According the Backbone.js documentation Model-parse does the following:
parse is called whenever a model's data is returned by the server, in
fetch, and save.
To augment models I've already loaded I use Model.parse(). I accomplish this by using fetch to make an additional request for data, then use that data to add properties to an existing model.
Example:
the fetch object is {age: 19}
after the parser will be {age: 19, isAdult: true}
When I perform the save request, in the PUT request I also have other parameters not needed (for example isAdult). I would like to have the original model (without additional parameters in PUT request).
What is the best way to achieve my goal in Backbone?
If I understand your question correctly ....
When backbone talks to a server using a save it sends a complete respresentation of the model. As the docs put it :
The attributes hash (as in set) should contain the attributes you'd
like to change — keys that aren't mentioned won't be altered — but, a
complete representation of the resource will be sent to the server.
So the default behavior is to send the complete model. If you want to implement you're own logic you're going to have to override the sync method. Dig through the expanded backbone code a bit and you'll see this comment above sync :
// Override this function to change the manner in which Backbone persists
// models to the server. You will be passed the type of request, and the model in question.
I would use the default implementation of sync as my starting point.

Backbone.js model validation only on set->save (not on fetch)

Caveat: I'm working with a backend that I don't have full control over, so I'm wrestling with a few considerations within Backbone that might be better addressed elsewhere...unfortunately, I have no choice but to handle them here!
So, my problem is that I'd really like to validate user input from a form (when I set values with it on Backbone models), but the models I receive from the API on newly created objects (via posts that ONLY accept a name, and ONLY return a name and object id) will fail my validation checks.
As example, when a new object is created in the database, two key fields are populated as empty strings (and so when Backbone hits the API and populates the models, it populates those keys with empty strings). When the user saves these objects back, post-edit, I'd like to force them to enter values for these two keys -- which is very easy, given Backbone's built in validation method.
The problem, of course, is that the validation is firing on both fetch->set (unwanted behavior) and set->save (desired behavior) -- and so newly created models won't load at all...Backbone collects them, validation fails, and errors fire.
So, my question is: is there a "Backbone-y" way to only validate the models on set->save, not on fetch->set? Could I use a specific trigger to work around this?
Any ideas would be greatly appreciated.
Backbone.Model.set won't perform validation if you pass in { silent: true }, and fetch will pass any options through to set, so you could either override fetch or write your own fetchSilent method that passes that in an options object.
However, you might run into a slight gotcha with Backbone.Collection.fetch, because when it receives attributes from the server, it doesn't create the new models with set. Instead, it creates a new model with model = new this.model(attrs, {collection: this}); and then performs validation if there's a validate method on the object.
This is a little annoying. You can get around it by defining a parse method on your collection (if you're using one) that creates a model silently (using {silent: true}), because when Backbone.Collection.add receives a fully formed Backbone model, it won't run the validation. (see the _add and _prepareModel methods in the annotated source).
It's a little annoying that the collection works that way, but (for now at least) it is what it is.
Instead of overriding fetch you can do another thing:
When you validate your model, check for model.silent and only validate if that doesn't exist.
So you do the following when you want to fetch a model:
var test = new MyModel({ id: '123', silent: true });
// in your Model validate function
validate: function(attrs) {
if (!attrs.silent) {
// validate logic here
}
}
Then you can fetch the model. After you get your model you can unset silent, for future validation.

Resources