I’ve been implementing some AJAX goodness in Mayday and other FastFrame apps lately. In my reading of the various pitfalls of AJAX one that popped up repeatedly was how to handle gracefully a network outage or the webserver going down. For example, Gmail’s chat handles it nicely. When I turn off my airport connection Gmail chat lets me know that it can’t contact the server and maybe my connection is down. Much better than an endlessly spinning hour glass, or worse, not letting the user know their action was never completed.
Anyhow, after some research I found an excellent post over at the AJAX blog on a general solution to the problem of aborting an AJAX request after a specified amount of time. After a bit of munging I got it to work nicely with prototype, the javascript library of my choice (You may want to check out this Prototype reference if you don’t know how to use it). Here’s the code, commented to explain what’s going on:
function callInProgress (xmlhttp) {
switch (xmlhttp.readyState) {
case 1: case 2: case 3:
return true;
break;
// Case 4 and 0
default:
return false;
break;
}
}
function showFailureMessage() {
alert('uh oh, it looks like the network is down. Try again shortly');
}
// Register global responders that will occur on all AJAX requests
Ajax.Responders.register({
onCreate: function(request) {
request['timeoutId'] = window.setTimeout(
function() {
// If we have hit the timeout and the AJAX request is active, abort it and let the user know
if (callInProgress(request.transport)) {
request.transport.abort();
showFailureMessage();
// Run the onFailure method if we set one up when creating the AJAX object
if (request.options['onFailure']) {
request.options['onFailure'](request.transport, request.json);
}
}
},
5000 // Five seconds
);
},
onComplete: function(request) {
// Clear the timeout, the request completed ok
window.clearTimeout(request['timeoutId']);
}
});
April 4th, 2006 at 8:00 pm
I’m trying to make a package of functions which will allow me to simply pass in the parameters of the path to XSL, XML, some transformation parameters, and the div to populate… then call the XML via AJAX, preform the XSLT transformation, and then populate the div.
I have been able to get this to work, but it’s not perfect.
So, I was trying to implment your code here, but I’m not getting it to work… this is probably because I had to create an object and do the entire AJAX call as a part of that object, so I could maintain the parameters from before the AJAX request.
Any help would be appriciated
[code]
/* =============================================================================
=== xform Functions ==
| these functions REQUIRE “prototype” and|or “Sarissa” to function.
| the most common 2 functions are:
- | function xform(xslPath, xmlPath, div, xformparameters)
| NOTE: this function creates an object… and then calls ajax2xform…
| example | alert(ajax2xform(’/path/xform.xsl’,'/path/xform.xml’,'return’,'display=All’));
- | function ajax2xform(xslPath, xmlPath, div, xformparameters)
| NOTE: this function must be called from an object… like:
| example | var x = new ajax2xform(’/path/xform.xsl’,'/path/xform.xml’,'updateDivID’,'divID=updateDivID&display=All’);
- | function xform_Sarissa(xslPath,xmlPath,Parameters,returnMethod)
| NOTE: if the “xmlPath” parameter is a string, it’s a path to the XML file
if the “xmlPath” parameter is an array, xmlPath[0] is used as the XML
if the “xmlPath” parameter is a object, xmlPath.xml or xmlPath.responseText are used as the XML
the “xformparameters” parameter can be an array like [["key","value"],["key2","value2"]] or can be a query string like in the blow example
| example | var x = new xform_Sarissa(’/path/xform.xsl’,'/path/xform.xml’,'divID=updateDivID&display=All’,'return’)
============================================================================= */
if (showDebug) {
if (showDebug==true) {} else { var showDebug=false;}
} else { var showDebug=false;}
function xform(xslPath, xmlPath, div, xformparameters) {
if (showDebug==true) { $(div).innerHTML=(”xform started => “+dumpthis([xslPath, xmlPath, div, xformparameters],’html’)); }
var xformObj = new ajax2xform(xslPath,xmlPath,div,xformparameters);
}
function ajax2xform(xslPath, xmlPath, div, xformparameters) {
if (showDebug==true) { $(div).innerHTML=(”xform started => “+dumpthis([xslPath, xmlPath, div, xformparameters],’html’)); }
if (typeof(xslPath)!=”string”) { xslPath=”/Custom_Components/XSL/ML_lmenu.xsl”; }
if (typeof(xmlPath)!=”string”) { xmlPath=”/Custom_Components/XML/example_ll_export.menu.xml”; }
if (xmlPath.indexOf(”?”)>-1) {
var xtemp = xmlPath.split(”?”);
xmlPath=xtemp[0];
queryString=xtemp[1];
} else {
queryString=”";
}
div=$(div);
// ensure div isn’t empty
if (div.innerHTML.length “+dumpthis([xmlPath, queryString],’html’)); }
this.AjaxRequest = new Ajax.Request(
xmlPath,
{
method: ‘get’,
parameters: queryString,
onComplete:this.update.bind(this),
onFailure: this.failure.bind(this)
});
}
ajax2xform.prototype.update = function(req) {
if (showDebug==true || this.showDebug==true) { $(this.div).innerHTML=(”ajax2xform.prototype.update started => “+dumpthis([req],’html’)); }
if (typeof(req.responseText)==”string”) {
if (showDebug==true) {
alert(”AJAX returned status = “+req.status);
alert(”AJAX returned text = “+req.responseText);
}
if (this.xslPath==”" || this.xslPath==”none”) {
// return untransformed
this.div.innerHTML = req.responseText;
} else {
// transform
xform_Sarissa(this.xslPath,req,this.xformparameters,this.div);
}
} else {
alert(”AJAX error: returned status = “+req.status);
}
}
ajax2xform.prototype.failure = function(req) {
alert(”AJAX error…”);
if (showDebug==true || this.showDebug==true) { $(this.div).innerHTML=(”ajax2xform.prototype.failure started => “+dumpthis([req],’html’)); }
if (typeof(req.responseText)==”string”) {
//alert(”AJAX returned status = “+req.status);
//alert(”AJAX returned text = “+req.responseText);
if (this.xslPath==”" || this.xslPath==”none”) {
// return untransformed
this.div.innerHTML = req.responseText;
} else {
// transform
xform_Sarissa(this.xslPath,req,this.xformparameters,this.div);
}
} else {
alert(”AJAX error: returned status = “+req.status);
}
}
function xform_Sarissa(xslPath,xmlPath,xformparameters,returnMethod) {
if (showDebug==true || this.showDebug==true) { $(returnMethod).innerHTML=(”xform_Sarissa started => “+dumpthis([xslPath,xmlPath,xformparameters,returnMethod],’html’)); }
// init XML object
var xmldoc = Sarissa.getDomDocument();
// init XSL object
var xsldoc = Sarissa.getDomDocument();
// determine XML input
if (typeof(xmlPath)==”object”) {
if (typeof(xmlPath[0])==”string”) {
xmlString = xmlPath[0];
} else if (typeof(xmlPath.xml)==”string”) {
xmlString = xmlPath.xml;
} else if (typeof(xmlPath.responseText)==”string”) {
xmlString = xmlPath.responseText;
}
xmldoc = (new DOMParser()).parseFromString(xmlString, “text/xml”);
} else if (typeof(xmlPath)==”string”) {
// load data (XML)
xmldoc.async = false;
xmldoc.load( xmlPath );
if( xmldoc.parseError != 0) { alert(”xml format error”);alert(Sarissa.getParseErrorText(xmldoc)); }
}
// load data (XSL)
xsldoc.async = false;
xsldoc.load( xslPath );
if( xsldoc.parseError != 0) { alert(”xsl format error”);alert(Sarissa.getParseErrorText(xsldoc)); }
// init processor
var p = new XSLTProcessor();
p.importStylesheet(xsldoc);
// inport parameters
if (typeof(xformparameters)==”object”) {
for(A=0;A-1) {
var Param_array = xformparameters.split(”&”);
for(A=0;A “+dumpthis([xslPath,xmlPath,xformparameters,returnMethod],’html’)); }
}
[/code]
April 5th, 2006 at 9:32 am
Alan, your code looks fine, however I don’t see where you are implementing the timeouts? When you include the code that I talked about in this post do you get any javascript errors?
April 14th, 2006 at 12:51 pm
I can’t even get my code to really, work let alone the timeout controls.
I think the first step is asking if you are familiar with a similar /
better option for ajax’ed clientside transformations.
April 19th, 2006 at 5:44 am
I was trying out your code, with no success at first, but after a bit of debugging I discovered that Firefox was showing the javascript error ‘callInProgress is not defined’. I changed the “callInProgress: function(xmlhttp)” to “function callInProgress(xmlhttp)” and it worked. This seems like a bit of a hack to me, so if you can tell me what else I could have done to fix this that would be great. - Peer
April 19th, 2006 at 8:57 am
Peer, thanks for catching that. I had copied that callInProgress() function from another script where it was part of an object, so it was written in object notation, which obviously won’t work when the function isn’t part of an object! I’ve updated the post to reflect the change.
April 22nd, 2006 at 8:23 am
Can you give an simple example of how to implement your code? I’m very new with js, I have the latest prototype.js included and don’t know where to put your code…is it inside prototype.js?
April 23rd, 2006 at 11:49 pm
Thanx for the functionality you added, I tried the code once and it worked.this is the fuctionality the ajax - prototype has been missing.
April 24th, 2006 at 10:20 am
Victor, you can simply copy the code above into a javascript file that you include on all your AJAX pages. It will ensure that if your server is down or their network connection is down for some reason that the user is alerted to that fact.
May 2nd, 2006 at 3:36 pm
Very nice. One of my pet peeves is when people use animated GIFs to indicate “busy”: obviously if there is an error or a timeout then the animated GIF will keep on keepin’ on, and the user will have no idea what happened. Your code at least handles the case of a timeout.
May 3rd, 2006 at 9:19 am
I, like Victor, am having trouble figuring out how to implement all of this (including Prototype). I have been looking through the Prototype documentation trying to figure out how to do an asynchronous request, but I can’t. How can I do a simple request, plus include your “timeout if server down” code? Thanks.
May 3rd, 2006 at 9:24 am
Jules, my code can just be included on every page in which you have an ajax request. It will take care of making sure that if the request times out the user is alerted to that fact. To actually make an AJAX request with prototype take a look at this reference: http://www.sergiopereira.com/articles/prototype.js.html#UsingAjaxRequest
June 7th, 2006 at 9:26 am
This is a nice & powerful bit of code.
It worked for me and my prototype Ajax.Request but strangely when the
request is aborted: request.transport.abort() , it triggers my onSuccess method which then blows up because there is no request.responseText.
My onFailure method gets called as well, but I’m a bit confused about the relationship between your params specified here:
request.options['onFailure'](request.transport, request.json);
and my own onFailure method which has no params…
Any insights appreciated, time to do some more Googling.
June 7th, 2006 at 9:40 am
Doug, I’m not sure why your onSuccess method would also get called, that’s very strange. As for the arguments passed to the onFailure method. Those two object (the transport and json objects) are what Prototype passes to your onFailure method if it calls them. So, I also pass them for consistency, but you don’t have to use them.
June 7th, 2006 at 1:01 pm
Thanks Jason.
Possibly a stupid question but: In a similar way is it possible to predefine/preregister handlers for the Ajax.Request object?
i.e.
Ajax.Request.register ({
on302: function(request) { someFunction() },
on404: function(request) { someFunction() },
onException: function(request) { someFunction() }
onFailure: function(request) { someFunction() }
});
So that you’d only need to specify an onSuccess handler for each new Ajax.Request?
June 7th, 2006 at 3:26 pm
Actually, Doug, onFailure will be triggered on any response code >= 200 and <= 400. So you can just use onFailure to catch them all.
June 9th, 2006 at 12:42 pm
This code is working nicely for me now. What I learned:
1) Pass the ‘response’ object (not the response.responseText) to the onComplete handler for your Ajax.Request and check for the existence of ‘response.responseText’ —don’t assume it exists, because request.transport.abort() seems to trigger both onComplete and onFailure in IE6 in certain cases (302, for example).
2) always clear your browser cache when testing javascript changes… doh!
July 4th, 2006 at 4:35 am
[...] Wer mit AJAX arbeite wird früher oder später auf “timeout”. Hier ein Lösungsansatz für Prototype. http://codejanitor.com/wp/2006/03/23/ajax-timeouts-with-prototype [...]