I have a collection: conditions. In a view, after returning an HTTP 403 error response, I want to obviously NOT create the model:
var attributes = ...;
conditions.create(attributes, {
error: function (model, response) {
conditions.trigger('error');
var response = JSON.parse(response.responseText);
console.log(response);
}
});
The error callback is being called correctly. The response is logged correctly. But backbone still adds the (broken) model to the collection! When I look at conditions.toJSON(), there is a new model with some broken attributes.
I returned an error from the server, how do I insist that Backbone not add the new model to the collection?
I can do conditions.remove(model) in the callback, but should I have to?
You can pass {wait: true} to the create method.
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.
http://documentcloud.github.com/backbone/#Collection-create
Related
I know that a fetch is asynchronous and it needs to use "success" in fetch.
But what if I want to use the data out of the success?
I wanna to get the user ID and to use it in a model "Note".
authedUser.fetch({
success: function(){;
window.uid = authedUser.get("id");
}
But if I try to do this console.log(uid); not in success I get undefined.
You should be able to access updated model from success callback.
Like this:
authedUser.fetch({
success: function(model){;
window.uid = model.get("id");
}
When your new models/collections are registered the "change" event is triggered.
So you can use
MyModel.on("change", function() {
MyModel.get('wtv');
});
There's a new behaviour in the latest version of Backbone (1.0.0 in which the reset event is no longer triggered by default after fetching a Collection.
http://backbonejs.org/#changelog
Renamed Collection's "update" to set, for parallelism with the similar
model.set(), and contrast with reset. It's now the default updating
mechanism after a fetch. If you'd like to continue using "reset", pass
{reset: true}.
The problem is that I want to capture the event when the collection has been finally fetched (pretty common case, indeed!)
I could listen to add, remove and change event, but if the collection is empty I don't know how to catch the event.
So, what would be the new, recommended way to catch when the collection request has finalized, or is it passing a { reset = true } the only way to achieve it???
ps: here's the original question, BTW can't catch Backbone Collection reset event
From Backbone.sync doc,
Whenever a model or collection begins a sync with the server, a
"request" event is emitted. If the request completes successfully
you'll get a "sync" event, and an "error" event if not.
For example,
var C = Backbone.Collection.extend({
url: '/echo/json/'
});
var c = new C();
c.on('sync', function() {
console.log('sync');
});
c.fetch();
And a demo http://jsfiddle.net/nikoshr/GLATm/
We can pass a method as a success handler when we call fetch on collection and as you said you just want to do something when everything[add,remove,update or reset] has happened, you can do inside this success handler.
collection.fetch({
success: function() {
// Do Something
// This is called when all add, remove and update operations have been done
}
});
Note: success handler is always executed irrespective of you have passed reset:true or not. Irrespective of your collection gets empty or not and It will be called at the last step when all the add,remove and update events have occurred.
Let me know if it does not solve your problem.
My own solution is indeed pretty simple. I already have a BaseCollection with added features, so in there I just set as default { reset: true }. The code should be something like this (my own BaseCollection has a lot of stuff that is not pertinent here):
var BaseCollection = Backbone.Collection.extend({
fetch: function(options) {
options = options || {};
options.reset = (options.reset === undefined ? true : options.reset);
// just call super.fetch
return Backbone.Collection.fetch.call(this, options);
};
});
Using promises...
// you could use promises as well
// P.S.: pardon jquery promises :)
var C = Backbone.Collection.extend({
url: '/echo/json/'
});
var c = new C();
// c.fetch() returns jqXHR object that you can listen too
$.when( c.fetch() )
.done(successFn)
.fail(failFn)
.always(alwaysFn);
Background:
Backbone model provides an option to register a fallback error handler that will be called each time a call to the server fails and no specific handler is provided.
MyModel = new Backbone.Model.extend({
initialize: function(option) {
this.on("error", this.fallbackErrorHandler);
},
fallbackErrorHandler: function() {
// default behavior in case of an error
},
foo: function() {
this.fetch(); // fallbackErrorHandler will be called
this.fetch({ // the inline error handler will be called
error: function() {
// some specific code for this error
}
});
}
});
What I want to do:
I would like all the backbone models in my application to have a fallback error handler.
But - I don't want to explicitly add the handler for each model.
I would like to define and register the handler once and have it applied to each backbone model in my application.
In addition, I don't want to change existing code and I don't want to change the way developers define Models in my application.
What I already tried:
I added an initializer which registers a handler on the Backbone.Model.prototype object.
App.Marionette.addInitializer(function() {
Backbone.Model.prototype.on("error",function(){
// default behavior in case of an error
});
});
This code worked and my fallback error handler was called.
However, the side affect of this code was that all error handlers registered in any backbone models in the application were registered on the Backbone.Model.prototype instead of on the Backbone.Model. So obviously this is not a good solution.
I also thought about defining MyModel which extends the Backbone.Model and registers the default handler. All other models in my application will then extend MyModel instead of directly extending Backbone.Model.
However, I'm trying to make this seamlessly without having to change existing code and then instructing all the developer to extend MyModel instead of Backbone.Model.
Does anyone have a solution for this problem?
Backbone uses jQuery's AJAX method calls by default. You can hook in to this directly, instead of using Backbone:
http://api.jquery.com/ajaxError/
If that doesn't give you what you want, you can override the "fetch" method in Backbone's model:
var originalFetch = Backbone.Model.prototype.fetch;
Backbone.Model.prototype.fetch = function(options){
var originalError = options.error;
options.error = function(model, error){
if (originalError){ originalError(model, error); }
// call your global error handler here.
App.myGlobalErrorHandler(model, error);
}
originalFetch.apply(this, arguments);
};
I am attempting to override a models save method and set an error callback. I am using a mixture of localStorage and server side data so in the event that the app can't connect to the server, I want to save the model to local storage. Here is my model code:
var Project = Backbone.Model.extend({
urlRoot: Settings.urls.projects.project,
save: function(attributes, options){
options || (options = {});
this.set("last_updated", new Date().toISOString(), {silent: true});
options.error = function(){
console.log("Error callback");
}
return this.constructor.__super__.save.apply(this, arguments);
},
As you can see, I am attempting to set options.error within the save method and then call the super method to actually action the save. For some reason it is ignoring the function and the console log statement is not getting called. Anyone have any ideas?
Check this reference:
http://backbonejs.org/#Model-extend
You need to do something like this:
return Backbone.Model.prototype.save.call(this, attributes, options);
So, within one of my views, I've got this function:
delete_model: function() {
var answer = confirm("Are you sure you want to delete this element?");
if (answer) {
this.model.destroy({
success: function() {
console.log("delete was a success");
}
});
}
});
When I ping that, the Ajax call goes out, the backend properly deletes the model and returns a 200 header with "OK" as the body...but the success event never fires. Am I missing something? What should I have the backend serve to fire that event?
I just had the same problem. The solution that worked for me was to make sure I return a json model from the server that corresponds to the deleted one.
edit: returning an empty json response will suffice.
Did not work:
delete(model) {
// deleted model from db
return "Model was deleted";
}
This did work:
delete(model) {
// deleted model from db
return model;
}
or:
delete(id) {
// deleted model from db with this id
return new Model {id: id};
}
Had the same problem using Backbone 1.5.3 with Rails. I tried rudena's solution, and it works!
Here's what my controller's delete function looked like initially:
def destroy
#cell = current_user.cells.find(params[:id])
#cell.destroy
render :json => "success"
end
And here's what worked:
def destroy
#cell = current_user.cells.find(params[:id])
#cell.destroy
render :json => #cell
end
That looks good to me, exactly what I have everywhere (except I have function(model) but that shouldn't matter at all) I do know that older versions of backbone didn't use the destroy(options) but instead had destroy(success, failure). Can you make sure you have the latest version.
Had this problem come up with my UI as well. Upon DELETE, the API came back with an empty 200 response.
What's happening is, jQuery expects a json response body, but when the response comes back empty, json parsing fails and the error callback is triggered.
My solution was to override the Model's sync method:
var MyModel = Backbone.Model.extend({
// Fix for empty DELETE response
sync: function(method, model, options) {
if (method === 'delete') {
options.dataType = 'html';
}
Backbone.sync.call(this, method, model, options);
}
});
This works because options is passed to jQuery's ajax call and we're instructing jQuery not to expect json.