I have a Backbone model object which I successfully save. However the response from the server once the object is saved is another object (not the same object I am saving (not my decision, it's a system I have to deal with)):
var userActivity = new UserActivity();
...some other logic here...
userActivity.save(null, {
wait: true,
success: function(model, response, options) {
dataLoader.getCachedObject(
function(cachedObject) {
// I want to update cachedObject object with new data coming back from the server, in a way that my views get updated on change event.
});
},
error: function(model, xhr, options){
}
});
I want to update cachedObject object with new data coming back from the server, in a way that my views get updated on change event.
How can I accomplish that? Do I call
cachedObject.parse(response)
Any help is greatly appreciated.
If I understand you correctly, each time your model successfully syncs with the server, you want to update the client-side attributes with the data coming from the server, right? You should be able to do it with something like this:
In your model:
initialize: function(){
this.on("sync", function(model, response, options){
this.set(response.newData);
}
}
Of course, change the newData key to whatever object the server uses to return the dat you want...
Now, each time the model successfully sync with the server, it will set its attributes to what is returned by the server, which will trigger a "change" event.
In your view, you can then have
initialize: function(){
this.listenTo(model, "change", this.render);
}
And the view wil rerender itself each time the models changes (which will happen after each save on the server).
Related
I am using Backbone in my simple web application to manage collections and models.
Application has an event manager who listens for some of Backbone events and display proper messages. For example, when model was added to collection or edited I show message "Model was added" and "Model was edited".
But I have an issue when I delete model from collection. Backbone generates next events during deleting process:
1) request - event after "DELETE" request to server
2) destroy - model was deleted.
3) remove - model was removed from all collections
4) sync - model was synchronized
I have code like this one:
onSave: function () {
console.log("Model was saved");
},
onRemove: function() {
console.log("Model was removed");
}
Backbone.Notifications.on('sync', this.onSave, this);
Backbone.Notifications.on('remove', this.onRemove, this);
So when I delete model from collection I got 2 messages. First one is "Model was removed" and second one is "Model was saved". How to prevent messages duplication or what code structure use to show messages to user for all CRUD operations ? Thank you.
sync is called for any CRUD operation. When you destroy the model, the model is first removed from the collection, firing the onRemove handler, then sync is called to DELETE the model from the server, which fires the second event handler.
You could overload sync to listen to which CRUD operation is being performed and log this:
var sync = Backbone.prototype.sync;
Backbone.prototype.sync = function (method, item, options) {
console.log('Method called was: ', method);
sync.apply(item, arguments);
};
One to do it would be to go the successhandler route:
model.destroy({
success: function(){
alert('Model deleted');
}
});
This will only trigger when the server sends a 200 after the DELETE has been processed.
Context
The situation as follows: Users can upload files in an application. They can do this at any time (and number of times).
I would like to show a spinner when any uploading is being done, and remove it when no uploading is happening at the moment.
My approach
The uploads are handles by an external file upload plugin (like blueimp) and on it's add method I grab the jqXHR object and add these to a backbone collection (which are images in my application, so I use this in combination with Marionette's collectionviews).
The following is part of a function called in an onRender callback of a Marionette Itemview:
// Get the file collection
var uploadFiles = SomeBackBoneCollection;
// Track how many deferreds are expected to finish
var expected = 0;
// When an image is added, get the jqXHR object
uploadFiles.bind('add', function(model) {
// Get jqXHR object and call function which tracks it
trackUploads(model.get('jqXHR'));
// Do something to show the spinner
console.log('start the spinner!');
// Track amount of active deferreds
expected++;
}, this);
// Track the uploads
function trackUploads(jqXHR) {
$.when(jqXHR).done(function(){
// A deferred has resolved, subtract it
expected--;
// If we have no more active requests, remove the spinner
if (expected === 0) {
console.log('disable the spinner!');
}
});
}
Discussion
This method works very well, although I'm wondering if there are any other (better) approaches.
What do you think about this method? Regarding this method, do you see any up- or downsides? Any other methods or suggestions anyone?
For example, it might be great to have some kind of array/object to which you can keep passing deferreds, and that a $.when is somehow monitoring this collection and resolves if at any moment everything is done. However, this should work such that you can keep passing deferred objects at any given time.
you can do this via events.
I am assuming each file is an instance of this model:
App.Models.File = Backbone.Model.extend({});
before the user upload the file, you are actually creating a new model, and save it.
uploadedFiles.create(new App.Models.File({...}));
so in your upload view...
//listen to collection events
initialize: function() {
//'request' is triggered when an ajax request is sent
this.listenTo(this.collection, 'request', this.renderSpinner);
//when the model is saved, sync will be triggered
this.listenTo(this.collection, 'sync', this.handleCollectionSync);
}
renderSpinner: function() {
//show the spinner if it is not already being shown.
}
ok, so, in 'handleCollectionSync' function, you want to decide if we wanna hide the spinner.
so how do we know if there're still models being uploaded? you check if there're new models in the collection (not saved models)
so in your collection, add a helper method:
App.Collections.Files = Backbone.Collection.extend({
//if there's a new model, return true
hasUnsavedModels: function() {
return this.filter(function(model) {
return model.isNew();
}).length > 0;
}
});
back to your view:
handleCollectionSync: function() {
//if there's no unsaved models
if(!this.collection.hasUnsavedModels()){
//removespinner
}
}
this should solve your problem assuming all the uploads are successful. you may want to complete this with error handling cases - it depends on what you wanna do with error case, but as long as you are not retrying it right away, you should remove it from the collection.
==========================================================================================
Edit
I'm thinking, if you allow the user to upload a file multiple times, you are not really creating new models, but updating existing ones, so the previous answer would not work. to work around this, I would track the status on the model itself.
App.Models.File = Backbone.Model.extend({
initialize: function() {
this.uploading = false; //default state
this.on('request', this.setUploading);
this.on('sync error', this.clearUploading);
}
});
then setUploading method should set uploading to true, clearUploading should change it to false;
and in your collection:
hasUnsavedModels: function() {
return this.filter(function(model) {
return model.uploading;
}).length > 0;
}
so in your view, when you create a new file
uploadNewFile: function(fileAttributes) {
var newFile = new App.Model.File(fileAttributes);
this.collection.add(newFile);
newFile.save();
}
I believe 'sync' and 'request' events are triggered on the collection too when you save models inside of it. so you can still listenTo request, sync, and error events on the collection, in the view.
I'm trying to fetching data from the server with backbone, this is my request:
I initialize the view with this:
// Backbone view initialize
this.collection = new Dropdown.Collections.Brands();
this.listenTo(this.collection, 'reset', this.render);
I send the data to my server from my Backbone View:
// after the input text fires keypress event
this.collection.fetch({data: {limit: 10, term:value}, reset: true});
I response this from PHP:
return json_encode($data);
This is what's inside the response:
"{\"brands\":{\"0\":{\"type\":\"brand\",\"id\":\"3\",\"text\":\"Smith\",\"manufacturer\":{\"text\":\"Proctor\",\"id\":\"1\"},\"image\":null}}}"
In the render function i log this to see what I get:
console.log(this.collection.toJSON());
This is the Chrome debugger that gives me this:
I'm not sure where is the error, I've tried different working ways by skipping the fetch method and using a jquery plugin handler for the input text, but i would do it with the right way, where is the problem?
Also Response Headers are the same for both methods.
I've solved the problem by overriding the parse method of the collection:
parse:function (response) {
return jQuery.parseJSON(response);
}
With that it works, but shouldn't it works correctly by default?
Lol the problem was a json_ecode called twice in different places, now everything works perfect.
i am very confuse about using backbone.js model fetch method. See the following example
backbone router:
profile: function(id) {
var model = new Account({id:id});
console.log("<---------profile router-------->");
this.changeView(new ProfileView({model:model}));
model.fetch();
}
the first step, the model account will be instantiated, the account model looks like this.
define(['models/StatusCollection'], function(StatusCollection) {
var Account = Backbone.Model.extend({
urlRoot: '/accounts',
initialize: function() {
this.status = new StatusCollection();
this.status.url = '/accounts/' + this.id + '/status';
this.activity = new StatusCollection();
this.activity.url = '/accounts/' + this.id + '/activity';
}
});
return Account;
});
urlRoot property for what is it? After model object created, the profileview will be rendered with this this.changeView(new ProfileView({model:model}));, the changeview function looks like this.
changeView: function(view) {
if ( null != this.currentView ) {
this.currentView.undelegateEvents();
}
this.currentView = view;
this.currentView.render();
},
after render view, profile information will not display yet, but after model.fetch(); statement execute, data from model will be displayed, why? I really don't know how fetch works, i try to find out, but no chance.
I'm not entirely sure what your question is here, but I will do my best to explain what I can.
The concept behind the urlRoot is that would be the base URL and child elements would be fetched below it with the id added to that urlRoot.
For example, the following code:
var Account = Backbone.Model.extend({
urlRoot: '/accounts'
});
will set the base url. Then if you were to instantiate this and call fetch():
var anAccount = new Account({id: 'abcd1234'});
anAccount.fetch();
it would make the following request:
GET /accounts/abcd1234
In your case there, you are setting the urlRoot and then explicitly setting a url so the urlRoot you provided would be ignored.
I encourage you to look into the Backbone source (it's surprisingly succinct) to see how the url is derived: http://backbonejs.org/docs/backbone.html#section-65
To answer your other question, the reason your profile information will not display immediately is that fetch() goes out to the network, goes to your server, and has to wait for a reply before it can be displayed.
This is not instant.
It is done in a non-blocking fashion, meaning it will make the request, continue on doing what it's doing, and when the request comes back from the server, it fires an event which Backbone uses to make sure anything else that had to be done, now that you have the model's data, is done.
I've put some comments in your snippet to explain what's going on here:
profile: function(id) {
// You are instantiating a model, giving it the id passed to it as an argument
var model = new Account({id:id});
console.log("<---------profile router-------->");
// You are instantiating a new view with a fresh model, but its data has
// not yet been fetched so the view will not display properly
this.changeView(new ProfileView({model:model}));
// You are fetching the data here. It will be a little while while the request goes
// from your browser, over the network, hits the server, gets the response. After
// getting the response, this will fire a 'sync' event which your view can use to
// re-render now that your model has its data.
model.fetch();
}
So if you want to ensure your view is updated after the model has been fetched there are a few ways you can do that: (1) pass a success callback to model.fetch() (2) register a handler on your view watches for the 'sync' event, re-renders the view when it returns (3) put the code for instantiating your view in a success callback, that way the view won't be created until after the network request returns and your model has its data.
I'm trying to get the backbone.js DELETE request to fire, but don't see any requests being made in my console.
I have collection model like so:
var Model = Backbone.Model.extend(
{
urlRoot: '/test',
defaults:{}
});
var TableList = Backbone.Collection.extend(
{
url: '/test',
model: Model
});
In my view I'm running this:
this.model.destroy();
Everything seems to be running fine, I can see output coming from the remove function that calls the destroy so I know it's getting there plus it also successfully runs an unrender method that I have. Can't see any requests being made to the sever though?
If I am not mistaken, you have to have an id property on your model to ensure that it hits the correct url. IE if your model was...
var Model = Backbone.Model.extend({
url: '/some/url'
});
var model = new Model({
id: 1
});
model.destroy(); // I THINK it will now try and DELETE to /some/url/1
Without an id it doesn't know how to build the url correctly, typically you'd fetch the model, or create a new one and save it, then you'd have a Url...
See if that helps!
I found the issue to my problem, thought not a solution yet. I'm not sure this is a bug with backbone or not, but I'm using ajaxSetup and ajaxPrefilter. I tried commenting it out and it worked. I narrowed it down to the ajaxSetup method and the specifically the use of the data parameter to preset some values.
Have you tried using success and error callbacks?
this.model.destroy({
success : _.bind(function(model, response) {
...some code
}, this),
error : _.bind(function(model, response) {
...some code
}, this);
});
Might be instructive if you're not seeing a DELETE request.