Generic error handler for Backbone - backbone.js

I am trying to put in place a generic error handler for my models and collection. I did something like this:
var originalFetchModel = Backbone.Model.prototype.fetch;
Backbone.Model.prototype.fetch = function(options){
var originalError = options.error;
options.error = function(model, error){
if (originalError){ originalError(model, error, options); }
//my logic here
}
originalFetchModel.apply(this, arguments);
};
This is working pretty fine for fetch but I cannot make it work for save.
When I do somethink like this:
model.save(modelDetails, {
success: function (model) {
//logic
}
});
I am under the impression that the error callback is overriden.
Does someone have an idea how to do this?

You could override Backbone.sync and use the promise returned by jQuery to globally handle errors. Something like
(function() {
var orgsync = Backbone.sync;
Backbone.sync = function(method, model, options) {
var xhr = orgsync.call(this, method, model, options);
xhr.fail(function() {
console.log('global error handler', method);
});
return xhr;
}
})();
And a demo http://jsfiddle.net/nikoshr/qm6z4sqy/

Related

Success callback function is not called

I`m building a simple backbone application, and have a problem with success callback function in my View.
Here is a code
var EditUser = Backbone.View.extend({
el: '.page',
render: function(option){
var that = this;
if(option.id){
that.user = new User({id : option.id});
that.user.fetch({
success:function(user){
var template = _.template($("#edit-user-template").html());
that.$el.html(template({user: user}));
}
});
}else{
var template = _.template($('#edit-user-template').html());
that.$el.html(template({user: null}));
}
},
events:{
'submit .edit-user-form': 'saveUser',
'click .delete': 'deleteUser'
},
saveUser: function(ev){
var userDetails = $(ev.currentTarget).serializeObject();
var user = new User();
user.save(userDetails,{success: function(){
router.navigate('',{trigger:true});
},
error: function(e){console.log(e);}
});
return false;
},
deleteUser:function(ev){
this.user.destroy({
success: function(){
router.navigate('',{trigger:true});
}
})
return false;
},
wait:true
});
On the SaveUser function,query send to the server correct, but after this, success callback function is not called, for navigating to the app home page.
The same problem appear with deleteUser method.
Any ideas what is the problem? Thanks!
It could be related to the response type from your server, the expected response is a JSON object that will be set on your attributes, but if the response is different as "text" for example, the parse fails.
Here is a fiddle for demo using Mock request
https://jsfiddle.net/gvazq82/rdLmz2L2/1/:
$.mockjax({
url: "hello.php",
responseTime: 0,
//responseText: 'A text response from mock ajax'
responseText: '{"a": "a"}'
});
In this example, the error function is been called that is not happening in your case, Is it possible your app defines some default behavior for "Ajax" calls?.
I need more information to be able to determinate this issue, but hope this give you some guidance with your problem.

How to dynamically update a Marionette CollectionView when the underlying model changes

Seems like this should be obvious, but there seem to be so many different examples out there, most of which cause errors for me, making me think they are out of date. The basic situation is that I have a MessageModel linked to a MessageView which extends ItemView, MessageCollection linked to a MessageCollectionView (itemView: MessageView). I have a slightly unusual scenario in that the MessageCollection is populated asynchronously, so when the page first renders, it is empty and a "Loading" icon would be displayed. Maybe I have things structured incorrectly (see here for the history), but right now, I've encapsulated the code that makes the initial request to the server and receives the initial list of messages in the MessageCollection object such that it updates itself. However, I'm not clear, given this, how to trigger displaying the view. Obviously, the model shouldn't tell the view to render, but none of my attempts to instantiate a view and have it listen for modelChange events and call "render" have worked.
I have tried:
No loading element, just display the CollectionView with no elements on load, but then it doesn't refresh after the underlying Collection is refreshed.
Adding modelEvents { 'change': 'render' } to the view --> Uncaught TypeError: Object function () { return parent.apply(this, arguments); } has no method 'on'
I also tried this.bindTo(this.collection..) but "this" did not nave a bindTo method
Finally, I tried, in the view.initialize: _.bindAll(this); this.model.on('change': this.render); --> Uncaught TypeError: Object function () { [native code] } has no method 'on'
Here is the code
Entities.MessageCollection = Backbone.Collection.extend({
defaults: {
questionId: null
},
model: Entities.Message,
initialize: function (models, options) {
options || (options = {});
if (options.title) {
this.title = options.title;
}
if (options.id) {
this.questionId = options.id;
}
},
subscribe: function () {
var self = this; //needed for proper scope
QaApp.Lightstreamer.Do('subscribeUpdate', {
adapterName: 'QaAdapter',
parameterValue: this.questionId,
otherStuff: 'otherstuff',
onUpdate: function (data, options) {
console.log("calling sync");
var obj = JSON.parse(data.jsonString);
self.set(obj.Messages, options);
self.trigger('sync', self, obj.Messages, options);
}
});
},
});
Views.MessageCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: Views.MessageView,
tagName: 'ul',
// modelEvents: {
// 'change': 'render'
// },
onAfterItemAdded: function (itemView) {
this.$el.append(itemView.el);
}
});
var Api = {
subscribe: function (id) {
var question = new QaApp.Entities.Question(null, { id: id });
question.subscribe();
var questionView = new QaApp.Views.QuestionView(question);
QaApp.page.show(questionView);
}
};
I am very grateful for all the help I've received already and thanks in advance for looking.
Try this:
var questionView = new QaApp.Views.QuestionView({
collection: question
});

Can't get waiting for function to return (with promises?) working in angular controller

I'm trying to get the following findTimelineEntries function inside an Angular controller executing after saveInterview finishes:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId}, function() {
$scope.findTimelineEntries();
});
};
The save action adds or edits data that also is part of the timeline entries and therefore I want the updated timeline entries to be shown.
First I tried changing it to this:
$scope.saveInterview = function() {
var functionReturned = $scope.interviewForm.$save({employeeId: $scope.employeeId});
if (functionReturned) {
$scope.findTimelineEntries();
}
};
Later to this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
$scope.saveInterview.done(function(result) {
$scope.findTimelineEntries();
});
And finaly I found some info about promises so I tried this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
var promise = $scope.saveInterview();
promise.done(function() {
$scope.findTimelineEntries();
});
But somehow the fact that it does work this way according to http://nurkiewicz.blogspot.nl/2013/03/promises-and-deferred-objects-in-jquery.html, doesn't mean that I can use the same method on those $scope.someFuntcion = function() functions :-S
Here is a sample using promises. First you'll need to include $q to your controller.
$scope.saveInterview = function() {
var d = $q.defer();
// do something that probably has a callback.
$scope.interviewForm.$save({employeeId: $scope.employeeId}).then(function(data) {
d.resolve(data); // assuming data is something you want to return. It could be true or anything you want.
});
return d.promise;
}

Collection fetch and model parse

I override function parse() in my model, when name or surname from the database is empty I'm asking Facebook API for that:
var Friend = Backbone.Model.extend({
parse : function(response) {
var self = response,
that = this;
if(!response.first_name) {
FB.api('/'+response.fbid, function(response) {
self.first_name = response.first_name;
self.surname = response.last_name;
});
}
return self;
}
});
My problem is that during fetch-ing in collection this values (first_name and surname) are still empty (though console.log in the model shows it properly). How could I resolve it?
Javascript call to FB.api is async, so basically there is no delay between FB.api and return self. Since your console.log(model) is probably immediately after fetch there is no returned data from FB.api since the request is not over.
What you could do is set try to put some callback when your model is updated and listen to it, and if you change the model trigger update method something like ...
Friend.fetch( { success: function(model, response) {
if ( !model.get('first_name') ) {
FB.api('/'+model.get('fbid'), function(fb_response) {
model.set('first_name', fb_response.first_name);
model.set('last_name', fb_response.last_name);
console.log('model updated with facbook info', model);
});
}
}});
Try running ( in your current code ) console.log('updated'); in your FB.api callback to see the delay I'm talking about.

Backbone model.destroy(): Is explicit removal from collection necessary?

I have a simple question. I am looking at a function with 2 lines of code:
deleteTask: function() {
this.parent.collection.remove(this.model);
this.model.destroy();
}
If I comment out the first line, which is supposed to remove the model from its collection, things seem to work as intended (as in, the model is removed automatically). From Backbone's website, this is the relevant discription for a model's "destroy" function:
Triggers a "destroy" event on the model, which will bubble up through any collections that contain it.
Am I safe to assume that the removal of this.parent.collection.remove(this.model); will not affect the functionality of the code in any way? This is what I think, but I wanted to make sure of it.
Thank you!
If you destroy a model, it is removed from any collections that was containing it. You can see that in the backbone source
//Internal method called every time a model in the set fires an event.
_onModelEvent: function(event, model, collection, options) {
...
if (event === 'destroy') this.remove(model, options);
So yes, I wouldn't think you would need to remove the model from your collection explicitly.
But don't trust me, test for yourself :)
deleteTask: function() {
that = this;
this.model.destroy({
success: function() {
console.log(that.parent.collection);
}
});
}
Check the console for yourself to see whether the model was removed from the collection.
The solution is to override the Backbone model destroy function. I made this on an abstract model with success and callback strategy:
Parameter "data" corresponds to the original parameter "resp".
destroy: function(successCallback, errorCallback)
{
var options = { wait: true };
var model = this;
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
var destroy = function()
{
model.trigger('destroy', model, model.collection, options);
};
options.success = function(data)
{
if ('SUCCESS' === data.responseCode)
{
if (options.wait || model.isNew())
destroy();
successCallback(data);
if (!model.isNew())
model.trigger('sync', model, data, options);
}
else
{
errorCallback(data);
}
};
if (this.isNew())
{
options.success();
return false;
}
var xhr = this.sync('delete', this, options);
if (!options.wait)
destroy();
return xhr;
}

Resources