Edit Backbone Ajax Success Method to Add in Custom Error Handling - javascript

I have my own custom error code inside of a backbone ajax success method in case the server returns an error. The problem is that this code is repeated throughout my app and I wanted to edit the success function in one place so I don't have to constantly repeat this error handler in every ajax success. I want to edit the success function to include this error check wrapper. Do you know how to do that?
Here is an example of my success method in one of my views:
"success" : function success(model, data)
{
if(data['error'] !== undefined && data['error'].length === 0)
{
message('error', 'Whoops! System Error. Please refresh your page.');
}
else if(data['error'] !== undefined)
{
message('error', data['error']);
}
else
{
//add templates and do stuff here
}
},
Ideally I'd like to set that in a config somewhere and then I'd just be able to use:
"success" : function success(model, data)
{
// add templates and do stuff here
}
Is this possible? I tried using ajaxSetup but that didn't seem to work for me.
UPDATED CODE STILL NOT WORKING:
That does get me a little further along but the error handler isn't functioning as a wrapper. The data is not being passed into my ajax calls. In fact, my success methods on my ajax calls aren't running at all anymore. I tried console.log("some text") in my ajax calls but nothing is being output. Do you know what is wrong with this?
// Save the original Sync method
defaultSync = Backbone.sync;
//Over ride Backbone async
Backbone.sync = function(method,
model,
options)
{
success = options.success
options.success = function(data)
{
if(data['error'] !== undefined && data['error'].length === 0)
{
message('error', 'Whoops! System Error. Please refresh your page.');
}
else if(data['error'] !== undefined)
{
message('error', data['error']);
}
else
{
success(model,
data);
}
}
return defaultSync(method,
model,
options)
}

There are two ways to solve this:
Inheriting Backbone Model
You could create your own custom model which inherits from Backbone Model. In it you could override the save method. Read Backbone docs on how to extend their model
In your custom save method, you will call the save method of super, check the responseText, if it's success then you'll call the success callback. (please do read backbone docs on how to call a method of your parent model in Javascript)
Override Backbone.Sync
Backbone has a Sync module which basically by default makes all ajax requests, parses the response and then calls the success/error callbacks you specified when calling save on your model. It's pretty simple. Take a look at this doc . Again you could override this, do exactly what Backbone is doing by default but only call the success/error callbacks based on responseText you received.
UPDATE: Sample Code (warning code not tested)
//Over ride Backbone async
defaultSync = Backbone.Sync // Save the original Sync method. We'll be needing that.
Backbone.Sync = function(method, model, options) {
success = options.success
error = options.error
options.success = function(model, data, options) {
if (/% all your custom checks are true */) {
success (model, data, options);
}
else {
error(model,data,options);
}
}
return defaultSync(method, model, options);
}
Please make sure with this strategy the Sync method will be overriden for ALL your Backbone sync. If you don't want that then use Model#save override.
Take a look at this code where I am overriding Backbone.Sync to make it work with Parse.com API.

Related

Global ajaxSuccess event before local success event

I have an ASP MVC application that lets a user add multiple partial views to a div with jQuery ajax calls.
jQuery("#AddNew").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) {
jQuery("#DivId").append(html);
}
});
return false;
});
The problem is that since a user must be authorized for the action returning the partial view, when a user's session has timed out, it is rendering the login page instead of the partial view. I have multiple places that are using similar ajax calls, so I added the following as a global ajaxSuccess event:
jQuery(document).ajaxSuccess(function (event, request, settings) {
var isLogin = jQuery(request.responseText).find('.login-form').length;
if (isLogin > 0) {
var url = document.URL;
var rootPath = '#Request.Url.GetLeftPart(UriPartial.Authority)';
var path = url.replace(rootPath, '');
var pathEncoded = encodeURIComponent(path);
var loginURL = rootPath + "/Account/Login?returnUrl=" + pathEncoded;
location.href = loginURL;
}
});
This works, as it will redirect the user to the login page when an unauthorized ajax request is made. However, it is still adding the html to the div, which is visible for a short time before the redirect.
Is there a way to get the global event to trigger before the local one? The jQuery API shows that the local success event is triggered before the global ajaxSuccess event, so I tried changing the ajax call to use complete, rather than success. This does work, but it seems like if for some reasons I needed to add code in the future that only executes on success, that I'll run into the same problem. Is there a better way to handle this?
I might advise creating your own API wrapping the ajax method which ensures the functionality you desire (in particular, the order of operations). Here's a very over-simplified example:
var async = function(props) {
var finished = $.Deferred();
$.ajax(props)
.done(function(response) {
// detect auth timeout, handle consisently
if(response.isAuthTimeout) {
// render login ui
finished.reject('auth-timeout');
} else {
finished.resolve.apply(finished, arguments);
}
})
.fail(function() {
finished.reject.apply(finished, arguments);
})
return finished;
};
Then, in practice you'll make calls to async (or whatever you decide to call your wrapper) rather than the native $.ajax API.
Make sense?

'error' event fires on my Backbone model no matter the http status code

I am trying to create a base Backbone model that handles all my http errors. Here is the code for the model:
define(function(require) {
'use strict';
var Backbone = require('backbone');
var BaseModel = Backbone.Model.extend({
initialize: function(attributes, options) {
options || (options = {});
this.bind("error", this.defaultErrorHandler);
this.init && this.init(attributes, options);
},
defaultErrorHandler: function(model, error) {
if(error.status === 401 || error.status === 403 || error.status === 500) {
console.log(error.status);
}
}
});
return BaseModel;
});
Apparently the error event fires no matter what happens with the http call. Even if I get a 200 back from the server I still hit my defaultErrorHandler. Could someone please explain what the error event that I have attached to really does? Also, is there some kind of event that I can subscribe to that will only give me true errors?
Backbone's save method will fail if: it does not get a 200 response from the server; the model has a method named validate that returns false. Although the docs don't say this explicitly, if you're saving a new record, I think it expects to get back a hash of attributes including an "id" attribute, which should be an integer. If it's missing, Backbone may assume that the record could not be saved server-side.

Backbone fetch collection lifecycle, set property from response

Question:
What is the lifecycle of the standard fetch method for a collection in Backbone? i.e. what events/methods are fired and in what order?
Context:
The JSON response I receive from the server for my collection has an array of models and a property:
{
results: [model1, model2],
aProperty: "example"
}
I would like to read this property from the JSON response and set it as a property on the Collection. I am currently overriding the parse function:
parse: function(response, options) {
this.aProperty = response.aProperty;
return response.results;
}
This feels like the wrong place to set properties in the collection - the parse function has a specific job and happens before the model array has been verified.
I have also tried:
initialize: function() {
this.on('sync', function(collection, resp) {
collection.aProperty = resp.aProperty;
});
}
However, 'sync' is called after the success callback for a fetch (I need to set the properties as part of fetch, before the success callback).
After reading the source code some, I think what you want to do is capture it on the request event. It is triggered on the model in Backbone.sync:
model.trigger('request', model, xhr, options);
Here you can override the callback sent as part of the request, wrapping it with the change you want to make. I didn't test this, but maybe this can give you an idea:
this.on('request', function(model, xhr, options) {
var success;
success = options.success;
options.success = function(resp) {
model.aProperty = resp.aProperty;
success();
}
});
Checkout out the annotated backbone source documentation. In particular the bit on Backbone.sync will help.

Call a function everytime and AJAX request is processed and returned from the server

I have a application where there are numerous number of ajax calls to the server.
Now I want to audit the response that comes from the server (This requirement poped up after the ajax code was laid).
So I have a function that would audit the response data, only problem is how can I get the data to be sent to the function which now sits separately.
I don't want to do the laborious work of adding the line of code for calling the function in each ajax call.
Is there easier and general way out. Somehow I could detect when a response come back and then process the response.
Using both traditional javascript method as well as jquery ajax calls in the system. (The app has been getting changes from a long time and has changed hands a lot so the new things get added and the older ones never get removed)
Wrap your ajax calls with a helper function and use it throughout your code.
An (untested) example:
MyApp = MyApp || {
logRequest: function _logRequest(settings, response) {
// Log your response
},
ajax: function _ajax (settings) {
var that = this;
// Log attempt request here?
// Example of logging the success callback (do similar for error or complete)
if (settings.success) {
// A success handler is already specified
settings.success = function (data) {
that.logRequest(settings, data); // Log the response
settings.success(data); // Call the original complete handler
};
} else {
// No success handler is specified
settings.success = function (data) {
that.logRequest(settings, data);
};
}
return jQuery.ajax(settings);
}
};
I favour this mechanism for lots situations where I want to reduce boilerplate. I only have to modify the state of the MyApp object which is my own (named appropriately for the application), so it is sort of an interface that allows you to intercept function calls without modifying other global objects. You can also swap this functionality out with something else very easily without having to update your references everywhere, which could be useful in a lot of other situations as well.
Using .ajaxComplete() should be enough to catch the onComplete event for all AJAX requests made through jQuery. Isn´t that what you´re asking for?
$('.ajaxRequest').click(function(event) {
event.preventDefault();
$.getJSON(
'/echo/json/',
this.id,
function(data, textStatus, jqXHR) {
console.log(data, textStatus, jqXHR);
}
);
});
// Listen to all ajax requests
$("#log").ajaxComplete(function(event, request, settings) {
console.log(event, request, settings);
});​
View demo.

jQuery - set Ajax handler priority

actually the question is as simple as the topic says. Is there any way to give different ajax handlers a higher/lower priority (which means here, that they will fire earlier) ?
What do I mean? Well, I have to deal with a fairly huge web-app. Tons of Ajax requests are fired in different modules. Now, my goal is to implement a simple session-timeout mechanism. Each request sends the current session-id as parameter, if the session-id is not valid anymore, my backend script returns the request with a custom response-header set (value is a uri).
So I'm basically going like this
window.jQuery && jQuery( document ).ajaxComplete(function( event, xhr, settings ) {
var redirectto = xhr.getResponseHeader( 'new_ajax_location' );
if( redirectto ) {
location.href = redirectto;
}
});
This does work like a charm of course, but my problem is that this global ajax event actually needs to get fired first 100% of the time, which is not the case. Some of those original ajax-requests handlers will throw an error because of missing or unexpected data, in that case, the global handler never gets executed.
Now, I'd be kinda happy if I wouldn't need to go through every single request handler and make it failsafe for invalid session data responses. I'd much more prefer to do the job at one particular place. But how can I make sure my global handler will get executed first ?
If I understand you correctly, you want to add your own callback to json requests everywhere. You could use the ajaxPrefilter hook.
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
options.complete = function (jqXHR2, settings) {
var redirectto = jqXHR2.getResponseHeader( 'new_ajax_location' );
if( redirectto ) {
location.href = redirectto;
}
}
});
You can also change the options there to add the session-id to the request params.
jQuery allows you to "patch" it's methods, such as .post/.ajax.
You might be able to patch the appropriate method(s) so your special AJAX event is always triggered before the one your code wants to call.
This is some "pseudo-jQuery-code" which should help you get started. I doubt it works as is, but it demonstrates the basic concept and it should get you started.
(function( $ ){
var originalPost = $.post;
var calledSpecialOne = false;
$.post = function(url, data, success, dataType) {
if (!calledSpecialOne) {
originalPost( ... your special AJAX query ... ).then( function() {
originalPost(url, data, success, dataType);
}
calledSpecialOne = true;
} else {
originalPost(url, data, success, dataType);
}
}
})( jQuery );
The above is based on some other, unrelated, code I have tested, which makes $.each() work for undefined/null arrays):
(function($){
var originalEach = $.each;
$.each = function(collection, callback) {
return collection? originalEach(collection, callback) : [];
}
})(jQuery);
Note that many exposed functions are called internally by jQuery too, so be VERY carefull using this trick; you might break a different part of jQuery or cause other trouble.
In case of the AJAX functions, you should probably patch the innermost function only, to prevent infinite recursion.
(FWIW, I haven't found any side-effects for the $.each() patch so far).
I'm not sure if this is even what you meant, but I figured I might need it as some point anyway.
It patches jQuery's ajax function to accept an object with the property priority. If there isn't a priority set, its priority becomes 1 (highest priority 0). If there is a priority, it does one of two things. If the priority is something like 5, it checks to see if the ajax call with the previous priority (in this case 4) was called. If not, it adds it to an array of ajax calls with that priority (in this case 5). If the previous priority has been called, it calls the request normally. When a request is called with a priority, it calls any outgoing requests with the next highest priority.
In other words, if there's no priority, it waits for priority 0, if there is a priority, it waits for the priority below it to get called. If you want calls with no priority to be sent immediately, comment out the line with the comment I put saying default action?
(function( $ ){
var oldAjax=$.ajax;
var outgoing=[];
var sent=[];
$.ajax=function(url, settings) {
var pr = (typeof url==='object'?url:settings).priority;
pr = pr===undefined ? 1 : pr; // default action?
if(pr!==undefined){
if(pr==0 || sent[pr-1]!==undefined){
oldAjax(url,settings);
sent[pr]=true;
if(outgoing[pr]){
var rq=outgoing[pr].splice(0,1)[0];
$.ajax(rq[0],rq[1]);
}
if(outgoing[pr+1]){
var rq=outgoing[pr+1].splice(0,1)[0];
$.ajax(rq[0],rq[1]);
}
}
else{
if(outgoing[pr]!==undefined){
outgoing[pr].push([url,settings]);
}
else{
outgoing[pr]=[[url,settings]];
}
}
}
else{
oldAjax(url, settings);
}
};
})( jQuery );
Patches for get and post. You will have to include those extra arguments, or change this yourself to get the priority working with post and get requests. This is straight from jQuery 1.7, except for the extra argument, and the two lines I added comments to.
jQuery.each( [ "get", "post" ], function( i, method ) {
jQuery[ method ] = function( url, data, callback, type, priority ) {
// shift arguments if data argument was omitted
if ( jQuery.isFunction( data ) ) {
priority = type; // my change
type = type || callback;
callback = data;
data = undefined;
}
return jQuery.ajax({
type: method,
url: url,
data: data,
success: callback,
dataType: type,
priority: priority // my change
});
};
});
I setup an example here: http://jsfiddle.net/tk39z/
I actually created some ajax requests targeting google, so you can check the network panel in Chrome to see the requests.
I recently implemented something very similar to the functionality that I believe you're looking for.
I set up a filter on the server side to test for a valid session and, if not, return a 401 error with custom status text for a more specific error detail.
The ajax calls I made to these pages were written in the normal convention using the 'success' attribute.
For situations where there was an error, I added a global callback to the error function that would test for the specific error and redirect accordingly. such as:
$( document ).ajaxError(function(e, xhr) {
var redirectto = xhr.getResponseHeader( 'new_ajax_location' );
if( redirectto ) {
location.href = redirectto;
}
});

Categories