// ===================================================================
// Author: Matt Kruse <matt@ajaxtoolbox.com>
// WWW: http://www.AjaxToolbox.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download.
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================
/**
* The AjaxRequest class is a wrapper for the XMLHttpRequest objects which
* are available in most modern browsers. It simplifies the interfaces for
* making Ajax requests, adds commonly-used convenience methods, and makes
* the process of handling state changes more intuitive.
* An object may be instantiated and used, or the Class methods may be used
* which internally create an AjaxRequest object.
*/
function AjaxRequest() {
var req = new Object();
// -------------------
// Instance properties
// -------------------
/**
* Timeout period (in ms) until an async request will be aborted, and
* the onTimeout function will be called
*/
req.timeout = null;
/**
* Since some browsers cache GET requests via XMLHttpRequest, an
* additional parameter called AjaxRequestUniqueId will be added to
* the request URI with a unique numeric value appended so that the requested
* URL will not be cached.
*/
req.generateUniqueUrl = true;
/**
* The url that the request will be made to, which defaults to the current
* url of the window
*/
req.url = window.location.href;
/**
* The method of the request, either GET (default), POST, or HEAD
*/
req.method = "GET";
/**
* Whether or not the request will be asynchronous. In general, synchronous
* requests should not be used so this should rarely be changed from true
*/
req.async = true;
/**
* The username used to access the URL
*/
req.username = null;
/**
* The password used to access the URL
*/
req.password = null;
/**
* The parameters is an object holding name/value pairs which will be
* added to the url for a GET request or the request content for a POST request
*/
req.parameters = new Object();
/**
* The sequential index number of this request, updated internally
*/
req.requestIndex = AjaxRequest.numAjaxRequests++;
/**
* Indicates whether a response has been received yet from the server
*/
req.responseReceived = false;
/**
* The name of the group that this request belongs to, for activity
* monitoring purposes
*/
req.groupName = null;
/**
* The query string to be added to the end of a GET request, in proper
* URIEncoded format
*/
req.queryString = "";
/**
* After a response has been received, this will hold the text contents of
* the response - even in case of error
*/
req.responseText = null;
/**
* After a response has been received, this will hold the XML content
*/
req.responseXML = null;
/**
* After a response has been received, this will hold the status code of
* the response as returned by the server.
*/
req.status = null;
/**
* After a response has been received, this will hold the text description
* of the response code
*/
req.statusText = null;
/**
* An internal flag to indicate whether the request has been aborted
*/
req.aborted = false;
/**
* The XMLHttpRequest object used internally
*/
req.xmlHttpRequest = null;
// --------------
// Event handlers
// --------------
/**
* If a timeout period is set, and it is reached before a response is
* received, a function reference assigned to onTimeout will be called
*/
req.onTimeout = null;
/**
* A function reference assigned will be called when readyState=1
*/
req.onLoading = null;
/**
* A function reference assigned will be called when readyState=2
*/
req.onLoaded = null;
/**
* A function reference assigned will be called when readyState=3
*/
req.onInteractive = null;
/**
* A function reference assigned will be called when readyState=4
*/
req.onComplete = null;
/**
* A function reference assigned will be called after onComplete, if
* the statusCode=200
*/
req.onSuccess = null;
/**
* A function reference assigned will be called after onComplete, if
* the statusCode != 200
*/
req.onError = null;
/**
* If this request has a group name, this function reference will be called
* and passed the group name if this is the first request in the group to
* become active
*/
req.onGroupBegin = null;
/**
* If this request has a group name, and this request is the last request
* in the group to complete, this function reference will be called
*/
req.onGroupEnd = null;
// Get the XMLHttpRequest object itself
req.xmlHttpRequest = AjaxRequest.getXmlHttpRequest();
if (req.xmlHttpRequest==null) { return null; }
// -------------------------------------------------------
// Attach the event handlers for the XMLHttpRequest object
// -------------------------------------------------------
req.xmlHttpRequest.onreadystatechange =
function() {
if (req==null || req.xmlHttpRequest==null) { return; }
if (req.xmlHttpRequest.readyState==1) { req.onLoadingInternal(req); }
if (req.xmlHttpRequest.readyState==2) { req.onLoadedInternal(req); }
if (req.xmlHttpRequest.readyState==3) { req.onInteractiveInternal(req); }
if (req.xmlHttpRequest.readyState==4) { req.onCompleteInternal(req); }
};
// ---------------------------------------------------------------------------
// Internal event handlers that fire, and in turn fire the user event handlers
// ---------------------------------------------------------------------------
// Flags to keep track if each event has been handled, in case of
// multiple calls (some browsers may call the onreadystatechange
// multiple times for the same state)
req.onLoadingInternalHandled = false;
req.onLoadedInternalHandled = false;
req.onInteractiveInternalHandled = false;
req.onCompleteInternalHandled = false;
req.onLoadingInternal =
function() {
if (req.onLoadingInternalHandled) { return; }
AjaxRequest.numActiveAjaxRequests++;
if (AjaxRequest.numActiveAjaxRequests==1 && typeof(window['AjaxRequestBegin'])=="function") {
AjaxRequestBegin();
}
if (req.groupName!=null) {
if (typeof(AjaxRequest.numActiveAjaxGroupRequests[req.groupName])=="undefined") {
AjaxRequest.numActiveAjaxGroupRequests[req.groupName] = 0;
}
AjaxRequest.numActiveAjaxGroupRequests[req.groupName]++;
if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==1 && typeof(req.onGroupBegin)=="function") {
req.onGroupBegin(req.groupName);
}
}
if (typeof(req.onLoading)=="function") {
req.onLoading(req);
}
req.onLoadingInternalHandled = true;
};
req.onLoadedInternal =
function() {
if (req.onLoadedInternalHandled) { return; }
if (typeof(req.onLoaded)=="function") {
req.onLoaded(req);
}
req.onLoadedInternalHandled = true;
};
req.onInteractiveInternal =
function() {
if (req.onInteractiveInternalHandled) { return; }
if (typeof(req.onInteractive)=="function") {
req.onInteractive(req);
}
req.onInteractiveInternalHandled = true;
};
req.onCompleteInternal =
function() {
if (req.onCompleteInternalHandled || req.aborted) { return; }
req.onCompleteInternalHandled = true;
AjaxRequest.numActiveAjaxRequests--;
if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
AjaxRequestEnd(req.groupName);
}
if (req.groupName!=null) {
AjaxRequest.numActiveAjaxGroupRequests[req.groupName]--;
if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==0 && typeof(req.onGroupEnd)=="function") {
req.onGroupEnd(req.groupName);
}
}
req.responseReceived = true;
req.status = req.xmlHttpRequest.status;
req.statusText = req.xmlHttpRequest.statusText;
req.responseText = req.xmlHttpRequest.responseText;
req.responseXML = req.xmlHttpRequest.responseXML;
if (typeof(req.onComplete)=="function") {
req.onComplete(req);
}
if (req.xmlHttpRequest.status==200 && typeof(req.onSuccess)=="function") {
req.onSuccess(req);
}
else if (typeof(req.onError)=="function") {
req.onError(req);
}
// Clean up so IE doesn't leak memory
delete req.xmlHttpRequest['onreadystatechange'];
req.xmlHttpRequest = null;
};
req.onTimeoutInternal =
function() {
if (req!=null && req.xmlHttpRequest!=null && !req.onCompleteInternalHandled) {
req.aborted = true;
req.xmlHttpRequest.abort();
AjaxRequest.numActiveAjaxRequests--;
if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
AjaxRequestEnd(req.groupName);
}
if (req.groupName!=null) {
AjaxRequest.numActiveAjaxGroupRequests[req.groupName]--;
if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==0 && typeof(req.onGroupEnd)=="function") {
req.onGroupEnd(req.groupName);
}
}
if (typeof(req.onTimeout)=="function") {
req.onTimeout(req);
}
// Opera won't fire onreadystatechange after abort, but other browsers do.
// So we can't rely on the onreadystate function getting called. Clean up here!
delete req.xmlHttpRequest['onreadystatechange'];
req.xmlHttpRequest = null;
}
};
// ----------------
// Instance methods
// ----------------
/**
* The process method is called to actually make the request. It builds the
* querystring for GET requests (the content for POST requests), sets the
* appropriate headers if necessary, and calls the
* XMLHttpRequest.send() method
*/
req.process =
function() {
if (req.xmlHttpRequest!=null) {
// Some logic to get the real request URL
if (req.generateUniqueUrl && req.method=="GET") {
req.parameters["AjaxRequestUniqueId"] = new Date().getTime() + "" + req.requestIndex;
}
var content = null; // For POST requests, to hold query string
for (var i in req.parameters) {
if (req.queryString.length>0) { req.queryString += "&"; }
req.queryString += encodeURIComponent(i) + "=" + encodeURIComponent(req.parameters[i]);
}
if (req.method=="GET") {
if (req.queryString.length>0) {
req.url += ((req.url.indexOf("?")>-1)?"&":"?") + req.queryString;
}
}
req.xmlHttpRequest.open(req.method,req.url,req.async,req.username,req.password);
if (req.method=="POST") {
if (typeof(req.xmlHttpRequest.setRequestHeader)!="undefined") {
req.xmlHttpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
}
content = req.queryString;
}
if (req.timeout>0) {
setTimeout(req.onTimeoutInternal,req.timeout);
}
req.xmlHttpRequest.send(content);
}
};
/**
* An internal function to handle an Object argument, which may contain
* either AjaxRequest field values or parameter name/values
*/
req.handleArguments =
function(args) {
for (var i in args) {
// If the AjaxRequest object doesn't have a property which was passed, treat it as a url parameter
if (typeof(req[i])=="undefined") {
req.parameters[i] = args[i];
}
else {
req[i] = args[i];
}
}
};
/**
* Returns the results of XMLHttpRequest.getAllResponseHeaders().
* Only available after a response has been returned
*/
req.getAllResponseHeaders =
function() {
if (req.xmlHttpRequest!=null) {
if (req.responseReceived) {
return req.xmlHttpRequest.getAllResponseHeaders();
}
alert("Cannot getAllResponseHeaders because a response has not yet been received");
}
};
/**
* Returns the the value of a response header as returned by
* XMLHttpRequest,getResponseHeader().
* Only available after a response has been returned
*/
req.getResponseHeader =
function(headerName) {
if (req.xmlHttpRequest!=null) {
if (req.responseReceived) {
return req.xmlHttpRequest.getResponseHeader(headerName);
}
alert("Cannot getResponseHeader because a response has not yet been received");
}
};
return req;
}
// ---------------------------------------
// Static methods of the AjaxRequest class
// ---------------------------------------
/**
* Returns an XMLHttpRequest object, either as a core object or an ActiveX
* implementation. If an object cannot be instantiated, it will return null;
*/
AjaxRequest.getXmlHttpRequest = function() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
}
else if (window.ActiveXObject) {
// Based on http://jibbering.com/2002/4/httprequest.html
/*@cc_on @*/
/*@if (@_jscript_version >= 5)
try {
return new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
return new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
return null;
}
}
@end @*/
}
else {
return null;
}
};
/**
* See if any request is active in the background
*/
AjaxRequest.isActive = function() {
return (AjaxRequest.numActiveAjaxRequests>0);
};
/**
* Make a GET request. Pass an object containing parameters and arguments as
* the second argument.
* These areguments may be either AjaxRequest properties to set on the request
* object or name/values to set in the request querystring.
*/
AjaxRequest.get = function(args) {
AjaxRequest.doRequest("GET",args);
};
/**
* Make a POST request. Pass an object containing parameters and arguments as
* the second argument.
* These areguments may be either AjaxRequest properties to set on the request
* object or name/values to set in the request querystring.
*/
AjaxRequest.post = function(args) {
AjaxRequest.doRequest("POST",args);
};
/**
* The internal method used by the .get() and .post() methods
*/
AjaxRequest.doRequest = function(method,args) {
if (typeof(args)!="undefined" && args!=null) {
var myRequest = new AjaxRequest();
myRequest.method = method;
myRequest.handleArguments(args);
myRequest.process();
}
} ;
/**
* Submit a form. The requested URL will be the form's ACTION, and the request
* method will be the form's METHOD.
* Returns true if the submittal was handled successfully, else false so it
* can easily be used with an onSubmit event for a form, and fallback to
* submitting the form normally.
*/
AjaxRequest.submit = function(theform, args) {
var myRequest = new AjaxRequest();
if (myRequest==null) { return false; }
var serializedForm = AjaxRequest.serializeForm(theform);
myRequest.method = theform.method.toUpperCase();
myRequest.url = theform.action;
myRequest.handleArguments(args);
myRequest.queryString = serializedForm;
myRequest.process();
return true;
};
/**
* Serialize a form into a format which can be sent as a GET string or a POST
* content.It correctly ignores disabled fields, maintains order of the fields
* as in the elements[] array. The 'file' input type is not supported, as
* its content is not available to javascript. This method is used internally
* by the submit class method.
*/
AjaxRequest.serializeForm = function(theform) {
var els = theform.elements;
var len = els.length;
var queryString = "";
this.addField =
function(name,value) {
if (queryString.length>0) {
queryString += "&";
}
queryString += encodeURIComponent(name) + "=" + encodeURIComponent(value);
};
for (var i=0; i<len; i++) {
var el = els[i];
if (!el.disabled) {
switch(el.type) {
case 'text': case 'password': case 'hidden': case 'textarea':
this.addField(el.name,el.value);
break;
case 'select-one':
if (el.selectedIndex>=0) {
this.addField(el.name,el.options[el.selectedIndex].value);
}
break;
case 'select-multiple':
for (var j=0; j<el.options.length; j++) {
if (el.options[j].selected) {
this.addField(el.name,el.options[j].value);
}
}
break;
case 'checkbox': case 'radio':
if (el.checked) {
this.addField(el.name,el.value);
}
break;
}
}
}
return queryString;
};
// -----------------------
// Static Class variables
// -----------------------
/**
* The number of total AjaxRequest objects currently active and running
*/
AjaxRequest.numActiveAjaxRequests = 0;
/**
* An object holding the number of active requests for each group
*/
AjaxRequest.numActiveAjaxGroupRequests = new Object();
/**
* The total number of AjaxRequest objects instantiated
*/
AjaxRequest.numAjaxRequests = 0;