How to add a default error handler to all Backbone models? - backbone.js

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

Related

Detect when Backbone has finished a model fetch

I can't seem to figure out which event to listen to when fetching data for a model. Usually when I'm doing it for a collection, I listen to the sync event. However, it seems like that doesn't work for models.
So, how do I know when my model is done fetching? Which event does it trigger?
Edit: Here's the beginning part of my view that is using the model:
var HomeContent = BaseView.extend({
initialize: function(options) {
self = this;
this.academyID = this.options.parent.academyID;
this.model = new AcademyModel({academyID: this.academyID});
this.model.on('sync', function() {
console.log('sync');
});
this.model.fetch();
}
fetch returns a jQuery promise. Just use something like:
this.model.fetch().done(function() {
...
}
Another solution is in the docs:
Accepts success and error callbacks in the options hash, which are both passed (model,response, options) as arguments.

detect when a backbone collection has been fetched (Backbone 1.0.0)

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

Backbone view Render method default parameter

I have a backbone view which is associated with model. View is listening for change method and it calls render if model is changed.
this.listenTo(this.model, 'change', this.render);
I'm having a problem that my backbone view's render method is getting called multiple times. I'm trying to debug this problem. For this purpose I added console.log statement in the render method:
render: function(data) {
if(this.model){
console.log("Render Method:",data," For model:",this.model.cid);
}
}
Now this data value is getting printed as undefined sometimes or something like model. Does anyone know what is the argument passed to a model change listener?
Note that : I'm not passing anything to render method.
and backbone documentation mentions nothing about this: http://documentcloud.github.io/backbone/#View-render
The change event passes the model and a hash of options
In backbone sources:
this.trigger('change', this, options);
And so in the documentation as mu is too short has commented:
"change" (model, options) — when a model's attributes have changed.
As far as I am aware the render function is not supposed to have any parameters pass to it
Event.Listen=Backbone.View.extend({
model: 'something'
initialize:function(){
this.listenTo(this.model,'change',this.render);
}
render:function(){
//is called when the listen to event is triggered
//if(this.model) does not make a lot of sense?? Does it need to be
//true ornull
//call another object
new Event.DoSomething();
}
});
From the Backbone site "Tell an object to listen to a particular event on an "other" object"
Looks like externally to the View there are other calls to render(additional to the event listener).
if you try this(listen to other method instead of render):
this.listenTo(this.model, 'change', this.customMethod);
And then declare this in the view below render:
customMethod: function() {
console.log(arguments, '<==== is there arguments?');
if(this.model){
console.log("Custom Render Method For model:",this.model.cid);
}
}
So please review in your code, where additional to the model.change listener, you are calling the render method outside the view.
The event callback gets called multiple times if the binding happens multiple times i.e.
this.listenTo(this.model, 'change', this.render);
is being executed multiple times.
It can also happen if the change is triggered multiple times. eg. you setting each of the attributes of a model in a for loop rather than at once.
Any call back receives an event as argument.
In your case
render: function(data) {
if(this.model){
console.log("Render Method:",data," For model:",this.model.cid);
}
}
data will be logged as undefined when view.render() is called.
data will be an event object when triggered by a change event

backbone.js + global event dispatcher + require.js: how-to?

I have a number of backbone models, organized in collections and connected to their corresponding views and/or collections of views. Some of these models that do not belong to the same collection need to trigger an event which is of interest to another model (and maybe more than one).
The recommended way to deal with this is, I think, the "global event dispatcher/aggregator" as described here and other places.
However, I also happen to be using require.js, which seems to go against the idea of attaching the dispatcher/aggregator to the application's namespace object -- or am I wrong here?
So my question is: using require.js how can I have a number of different backbone models trigger an event that will be handled by another model?
A similar solution to what #Andreas proposed but without Backbone.Marionette (heavily inspired nonetheless, see the article linked in the question).
All you have to do is to define a module that creates a singleton of an event listener and require this object in the modules where you want to trigger an event or listen to this event.
Let's say you have app/channel.js defining your channel
define(['backbone', 'underscore'], function (Backbone, _) {
var channel = _.extend({}, Backbone.Events);
return channel;
});
You can then use it as a listener via
require(['app/channel'], function (channel) {
channel.on('app.event', function () {
console.log('app.event');
});
});
and you can trigger an event on this channel via
require(['app/channel'], function (channel) {
channel.trigger('app.event');
});
We using Marionettes app.vent (which is the global event transmitter for our application), allong with require js and it works really well.
app
define(, function(){
return new Backbone.Marionette.Application();
})
Model1
define(['app'], function(app){
return Backbone.Marionette.Model.extend({
initialize: function(){
this.bindTo('app.vent', 'create:model2', this.toSomething, this);
}
})
})
Model2
define(['app'], function(app){
return Backbone.Marionette.Model.extend({
initialize: function(){
app.vent.trigger('create:model2', this);
}
})
})

Render a view for a model on first load, not on every change

I would like to render a view for a model when the model is first fetched but not on every change.
My setup is as follows:
var m = new $.model.Customer({id: customer});
var v = new $.view.GeneralEditView({el: $("#general"), model: m});
m.fetch();
Then in the view initialize I bind the change event to the render method to render when the model is loaded:
this.model.bind('change', this.render);
The problem is that the view then renders on every change. I'd like to only render after the fetch. Unfortunately I'm not aware of any event that's fired after a fetch for a model other than change.
Is there something like 'reset' for collections that I can bind to?
EDIT:
Perhaps to put it more succinctly, for Backbone models is there a way to distinguish when a model is loaded from the server versus changed locally?
There are a bunch of different ways to approach this (these all assume var view = this; somewhere in your view code):
Call .fetch() with a one-time success callback:
m.fetch({
success: function() {
view.render();
}
});
Bind to change but unbind in the handler:
function handle() {
view.render();
view.model.off('change', handle);
}
this.model.bind('change', handle);
Use _.once to limit handler calls:
this.model.bind('change', _.once(function() {
view.render();
}));
Use a .ready() pattern for your models - example here. I like this option in cases where multiple views need to know when the model is loaded, and when you need to want to be able to write the same code without worrying about whether your model is loaded yet. The downside of this is that it requires you to add a model method like .isFullyLoaded() to test every time; the upside is that using a test function, rather than setting a flag, allows models to be loaded in bulk as part of a collection without having to change your code.
Models
You can make the change event specific to a certain key changing, such as the uniqueId (if you have one):
this.model.bind('change:id', this.render, this);
By default, fetch does not fire any event directly, but indirectly fires the change event once new data is loaded using set
If that is not an option, you can always trigger an event in your fetch function:
initialize: function () {
this.model.bind("fetch", this.update, this);
}
fetch: function () {
// do stuff
this.model.trigger("fetch", this);
}
update: function () {
// your refresh stuff here
}
I may have a general solution from https://github.com/documentcloud/backbone/pull/1468#issuecomment-6766096. I overwrote the sync method on Backbone as follows:
Backbone.Model.prototype.sync = function(method, model, options) {
var succ = options.success;
var customSuccess = function(resp, status, xhr) {
//call original
succ(resp, status, xhr);
model.trigger('synced', model, options);
}
options.success = customSuccess;
Backbone.sync(method, model, options);
}
To save the original success method as I don't want to mess that unless I need to, pass the custom success method. When the custom success method is invoked the custom event is triggered as suggested by #Austin and then the original success method in invoked.

Resources