Backbone: How to update a model that isNew without fetching entire collection? - javascript

I have a form that create a model for a collection. That fires an add event to the collection. I have that binded to a method:
this.collection.bind('add', this.addOne, this)
fires...
addOne: function(tenant) {
var self = this
var collection = this.collection
var view = new TenantView({model: tenant,collection:collection});
self.$el.append(view.render().el);
}
The create syncs it to the database but, the new appended view still isNew to backbone since it hasn't fetched the collection and grabbed the id for the new model.
My question, how can I grab the synced model from the server (that has the id and isn't isNew) without fetching the entire collection then append it?

Use the sync event instead of add...
this.collection.bind('sync', this.addOne, this);
The add event gets fired immediately when calling create; but sync gets fired once the server has responded to the create method, so it should include the correct model id.
You can also wait for the server's response, before adding the model to the collection -- to do that use wait: true in create's options hash: collection.create({ ... }, { wait: true }).
Creating a model will cause an immediate "add" event to be triggered on the collection, as well as a "sync" event, once the model has been successfully created on the server. Pass {wait: true} if you'd like to wait for the server before adding the new model to the collection.

Related

How to re fetch backbone collection after collection create?

Can someone tell me how to re fetch a Backbone collection after calling collection's create function when I create a new model?
When I call fetch on my collection after creating new model, sometimes I'm getting that model and sometimes not.
My problem is when I create a new model in my collection, I'm not getting the id back of my model and then I can't update it immediately, I need to refresh the page and then I got the id of the created model.
I tried with listenTo but I can't use it because I need to send more collections to one function.
And that my view for my bootstrap modal, on save I'm creating my model it persists to database and I'm getting all attributes in my console when I create it except models id.
Backbone view:
app.types.EditView = Backbone.View.extend({
tagName: "div",
$container: $('#containerEdit'),
template: _.template($('#itemEdit-template').html()),
events:
{
"click .save": "save",
},
initialize: function(options)
{
this.options = options;
this.$container.html(this.render());
this.start();
this.end();
},
render: function()
{
this.$el.html(this.template());
return this.$el;
},
save: function()
{
console.log("save");
$('#openModal').modal('hide');
var dan = this.model.dan_u_tjednu_usera.datum;
var mjesec = this.model.dan_u_tjednu_usera.mjesecBrojevi;
var godina = this.model.dan_u_tjednu_usera.godina;
var start = $("#start").val();
var end = $("#end").val();
var user_id = this.model.user.id;
this.model.shifts.create({day: dan, month: mjesec, year: godina, time_from: start, time_to: end, user_id: user_id});
this.options.model.el.html($("<td href='#openModal' width='25%' align='center' class='list-group test' scope='row'>" + start + " - " + end + " " + "Admin" + "</td>"));
this.model.shifts.fetch({sync: true});
console.log("test", this.model.shifts);
}
Here you can see that in my response im not getting the id attribute, on create.
And here you can see when i click on my cell i log my collection and i have not the id attribute of the created model here. And im not getting the id attribute it too when i log this.model
This is because the request sent to the server when you call Collection.create is asynchronous, the Javascript code will continue to execute before the server receives and responds to the request.
If you want to have the Model updated with the ID coming back from the server, you can specify {wait: true} in the Collection.create call. This will mean that the Collection will not have the Model added straight away, but instead only when the server responds (successfully).
In this case you should not run the fetch immediately afterwards, as it will also need to wait for the create operation to complete. You should setup any following actions to trigger when the create operation has completed. Here is an example:
var model = collection.create({field: 'abc'}, {wait: true});
model.once('sync', function() { window.alert(model.id); });
Backbone's create
Convenience to create a new instance of a model within a collection.
Equivalent to instantiating a model with a hash of attributes, saving
the model to the server, and adding the model to the set after being
successfully created.
There's no need to fetch a collection after a create, the model id and any other field are automatically merged within its attributes hash.
Fetch after model creation
While mikeapr4 is not wrong, his example could be improved.
The { wait: true } is unnecessary if the only problem comes from the fetch, not from the model already being inside the collection.
Also, once should be avoided as it's the "old" way, and instead listenToOnce should be used. See Difference between ListenTo and on.
If you really want to fetch once a model is created, using events is overkill here, and instead, using the success callback is best:
save: function() {
// ..snip...
this.model.shifts.create({ /* ...snip... */ }, {
context: this,
success: this.onModelCreated
});
},
onModelCreated: function() {
// the model is now created and its attributes are up-to-date
this.model.shifts.fetch();
}
Other notes on your code
There are no sync option in Backbone. Only a "sync" event and a sync function.
Avoid using the global jQuery selector (like $('.class-name')) and instead, whenever the element is within the view's element, use this.$('.class-name').
Also, cache the jQuery element to avoid the costly search of the find method.
Like $("#start") could be cache and reused. Only reset the cached elements when re-rendering.
The Backbone .render function should return this by convention.
Then, your rendering call should look like:
this.$container.html(this.render().el); // el is enough

Backbone collection.reset()

So there's a piece of functionality with which I've been struggling for a while now. I'm using .where() method in order to retrieve an array of objects from the Collection and then I reset this Collection with this array.
# Fetch the collection
collection = App.request(collection:entites)
console.log collection
>collection {length: 25, models: Array[25] ... }
When the event fires it passes options for .where() method and starts reset process:
# Get new models
new_models = collection.where(options)
# Reset collection with the new models
collection.on 'reset', (model, options) ->
console.log options.previousModels
return
collection.reset(new_models)
console.log collection
>collection {length: 5, models: Array[5] ... }
In the View responsible of rendering this collection I listen to the 'reset' event and render the View accordingly.
initialize: ->
#listenTo(#collection, 'reset', #render)
It works just as expected: event fires, collections undergoes reset and the View re-renders reseted collection. But when the event fires second time the collection doesn't sync with the server and new_models = collection.where(options) receives a collection that was already reseted in a previous event run and returns an empty array.
What are my options here? Each event run I need an initial collection of all models to work with. Should I just request new instance of the collection on the each run or can I make it in a more cleaner manner, i.e. save original state somewhere and pass it for the event run instead of fetching new collection from the server? Please advise.
Yes. Another way to achieve it is, when you filter the collection, using .where(), you can trigger Backbone.Events custom event which you view can listen to. This way, the original collection does not reset, there only is a change in array which will trigger the custom event.
A sample code for using custom events in backbone is:
var object = {};
_.extend(object, Backbone.Events);
object.on("collectionFiltered", function(arrayOfFilteredModels) {
// do stuff to render your view with the new set of array.
// You can use underscore templating to traverse the array for rendering view.
});
object.trigger("collectionFiltered", collection.where(options);

Backbone destroy a model outside of a collection

I have a UserPanel view, which uses a UserModel as its model. In the template for the UserPanel, I have a conditional that checks whether or not the model is undefined. If the model exists, it displays user information. If it doesn't, it displays the "registration" form.
On the user information part of the UserPanel, I have what's essentially an "unregister" button. A user clicks it, and the user information is deleted. The UserPanel responds by re-rendering, allowing them to register a different UserModel.
Common sense tells me to call this.model.destroy. When I use this method, my model is deleted from my data store, but the object still exists in this.model. When the view responds to the model update (by calling render), it still thinks it has a valid model, with all its data and the like. I can call delete on this.model, but that doesn't trigger any events. I can't trigger the event before I delete, because then the view updates before I can delete the model. I have setup the view to respond to model deletions with a method that simply uses delete on the model, then calls render. This works, but bothers me on a conceptual level, since those methods are for handling view updates and not more model manipulation.
In general, what's the proper way to explicitly dispose of a model that is not stored by a collection?
EDIT: I am using Backbone.localStorage as my data store. This might have something to do with it.
Instead of binding to the destroy event I would use the success callback of the model.destroy like so:
this.model.destroy({ success: _.bind(this.onModelDestroySuccess, this) });
Then rename your modelDestroyedView to onModelDestroySuccess:
onModelDestroySuccess: function () {
delete this.model;
this.render();
},
I would also define a cleanupModelEvents method that cleans up your event bindings to the model:
cleanupModelEvents: function() {
this.stopListening(this.model);
},
And call that from onModelDestroySuccess:
onModelDestroySucess: function () {
this.cleanupModelEvents();
delete this.model;
this.render();
},
Hope this helps.
this.model.destroy();
and then on the destroy event
this.model = null;

Update id after collection.create in Backbone.js

On a collection, I am using the create function to save a new instance of a model to the server. This POST request is successful and I return the new model.
{id:135, type:tweet, start:08:00:00, end:14:00:00, text:foo, date:2013-04-01}
However, I need to update the collection with the new model.id that has been returned from the server and it appears that the sync method in create does not do this. Should I add a callback to the create function to update the model with it's changed attributes?
var AddScheduleBlock = Backbone.View.extend({
saveScheduleBlock: function (ev){
var text = "foo"
this.model.set({
text: text
});
var block = blockCollection.create(this.model, {
silent: true,
wait: true
});
});
Backbone's Collection create method calls Model save method underneath (code reference). You don't need to set the id yourself, Backbone does that for you.
The Model save method sets the response attributes into the model (code reference)
If the create is not doing the job, you should try debugging it.

Backbone.js: Model events are not fired while used within a Collection

Here I have a Backbone.js Model - Contact and a Collection - Contacts with Contact as the Model. I've two views where in one I use collection to list all the contacts and in the second the model directly to show a single contact. But when I'm using the model directly I'm able to get the 'change' event fired while using with the collection the 'change' event, (even 'all' events of the model) is not fired. Am I missing some extra bind here to make it work with collection?
var Contact = Backbone.Model.extend({
defaults: {
id: 0,
urlDisplayPicBig: '',
urlDisplayPicSmall: ''
},
initialize: function () {
this.bind('change', this.doSomething);
},
generateUrls: function () { //earlier doSomething()
...
}
...
});
var Contacts = Backbone.Collection.extend({
model: Contact,
...
});
Update
While using both collection & single model instances I have to run generateUrls() to update urlDisplayPicBig & urlDisplayPicSmall based on the 'id' of the model.
When you do fetch on a collection:
the collection will reset
and that will
replace a collection with a new list of models (or attribute hashes), triggering a single "reset" event at the end. [...] Using reset with no arguments is useful as a way to empty the collection.
So a fetch on a collection will remove all the models that are currently in the collection and replace them with brand new model instances. No "change" events will occur, there will only be a single "reset" event.
When you do a fetch on a model:
A "change" event will be triggered if the server's state differs from the current attributes.
So calling fetch is pretty much the same loading the data from the server manually and calling set to change the model.
Summary: Collection#fetch doesn't trigger any "change" events, just a "reset" event; Model#fetch will trigger a "change" event if something changes.
If you just want to add a couple new attributes when creating new model instances then you can add new attributes to the incoming JSON using Model#parse.

Categories