This is my offering to the world relating to asynchronous web programming, or generically, AJAX. I copy this code to nearly every web project that I write anymore, and I'm always glad I have it. I've become accustomed to using JSON from my web services as it is very easy to read and write, so this code has primary support for that. Feel free to use it in any way you see fit. Please credit me if you do, I enjoy having my name everywhere. I am not opposed to receiving large sums of money either.
sendAjax is the return value of a self-invoking function, so everything is initialized when the code is loaded (you don't have to remember to do it in some 'main' function). The benefit of this is that sendAjax becomes a callable function with all the supporting information cached. And you call it like so...
sendAjax("your_server_script.php", function(result) {
if (result) {
//do whatever with the result...
} //otherwise an error occurred.
}, ["id", "data"], [idvalue, datavalue]);Explanation is in the comments as well as information on configuration and options. Enjoy!
var sendAjax = function() {
//DEFAULTS...
var defPath = ""; //the 'command' parameter will be appended to defPath to form the complete URL. Override defPath by specifying the path in the 'command' parameter. If defPath is blank, the full URL must be specified in the 'command' parameter. Example: var defPath = "http://www.yoursite.com/services/";
var defMethod = "GET"; //defMethod should be "GET" or "POST", but may be overridden.
var defAsync = true; //defAsync should be true or false, but may be overridden.
var defErrorHandler = function(txt) { throw new Error(txt); }; //defErrorHandler should be a function taking the text of the error as a parameter. Assign null to defErrorHandler to send to the receiver no matter what.
var defExpectJSON = true; //defExpectJSON should be true or false. If false, the result is treated as plain text.
//Get the AJAX object that transmits and receives the data. Modern browsers all use XMLHttpRequest these days, but we can still allow the hold-outs to play too.
function getAjaxObj() {
var ajaxobj = null;
try {
ajaxobj = new XMLHttpRequest(); //accepted standard
} catch (e) {
try {
ajaxobj = new ActiveXObject("Msxml2.XMLHTTP"); //old ie
} catch (e) {
try {
ajaxobj = new ActiveXObject("Microsoft.XMLHTTP"); //really old ie
} catch (e) {
ajaxobj = null;
}
}
}
return ajaxobj;
}
//If 'resulttext' is JSON, it is trimmed, cleaned up and returned ready to be exec'ed into an object. Otherwise, the plain text is just sent back.
function extractJSON(resulttext) {
var startpos = resulttext.indexOf("{");
var endpos = resulttext.lastIndexOf("}");
if (startpos >= 0 && endpos >= 0) var datavalue = resulttext.substring(startpos, endpos + 1);
else datavalue = resulttext;
datavalue = datavalue.replace(/\&/g, "&"); //in case it was wrapped in xml, as is often the case with .NET web services
return datavalue;
}
//This is the function that is actually called when you use sendAjax().
//
//REQUIRED PARAMETERS...
//command {string}: the path to the page or web service that you are calling. This will be appended to defPath UNLESS it contains a path itself, in which case it will be used as-is.
//handler {function}: the function that is called with the result when it has returned. The parameters that will be sent are <result>, <command>, <names>, <values>. 'command', 'names' and 'values' are given just as they were provided. If an error occurs and no error handler is provided, the error text will be sent as a fifth parameter (and result will be null).
//names {array}: an array of strings that will be paired with 'values' to form the data content.
//values {array}: an array of strings that will be paired with 'names' to form the data content. 'names' and 'values' must have the same number of elements.
//
//OPTIONAL PARAMETERS. May be specified in any order after 'values'...
//method {string}: may be "GET" or "POST" to override defMethod.
//async {boolean}: may be true or false to override defAsync.
//error handler {function}: may be provided to override defErrorHandler. The parameters are the same as for 'handler' except the error message will be send instead of the result.
return function(command, handler, names, values) {
if (!command || typeof(command) != "string") throw new Error("sendAjax: first argument must be a string");
if (!handler || typeof(handler) != "function") throw new Error("sendAjax: second argument must be a function");
//parse the parameters...
var path = defPath;
if (command.indexOf("/") >= 0) path = ""; //if a path is specified, it takes the place of defPath
var method = defMethod;
var async = defAsync;
var errorhandler = defErrorHandler;
for (var i = 4; i < arguments.length; i++) {
if (typeof(arguments[i]) == "string" && (arguments[i].toUpperCase() == "GET" || arguments[i].toUpperCase() == "POST")) method = arguments[i].toUpperCase();
else if (typeof(arguments[i]) == "boolean") async = arguments[i];
else if (typeof(arguments[i]) == "function" || arguments[i] == null) errorhandler = arguments[i];
}
//create an AJAX object. Each call to sendAjax() will maintain its own AJAX object so multiple calls will not interfere with each other.
var ajaxobj = getAjaxObj();
if (!ajaxobj) throw new Error("sendAjax: could not create XMLHttpRequest.");
//the localhandler will take care of the interpretation of the AJAX result. It is either called explicitly for synchronous mode or assigned to XMLHttpRequest.onreadystatechange for asynchronous mode.
var localhandler = function() {
try {
if (ajaxobj.readyState == 4) { //4 = finished.
if (ajaxobj.status != "200") { //200 = OK, nothing broke on the server end.
if (errorhandler) errorhandler("error encountered (" + ajaxobj.status + "): " + ajaxobj.responseText, command, names, values);
else handler(null, command, names, values, ajaxobj.responseText);
} else {
if (defExpectJSON) {
//try to make a JSON object out of the result. If it turns out not to be JSON, use the result as plain text.
var dataobject = null;
var datavalue = extractJSON(ajaxobj.responseText);
if (datavalue.length > 0) {
if (datavalue.indexOf("{") == 0) {
dataobject = eval("(" + datavalue + ")");
dataobject.sourceJSON = datavalue;
} else dataobject = datavalue;
}
} else {
var dataobject = ajaxobj.responseText;
}
//call your handler with the result.
handler(dataobject, command, names, values);
}
}
} catch (ex) {
//most likely there was either a javascript error in 'handler' or there was a problem parsing the result.
if (errorhandler) errorhandler("error encountered: " + ex.message, command, names, values);
else throw new Error("sendAjax: ajax response received but an error was thrown: " + ex.message);
}
}
ajaxobj.onreadystatechange = localhandler;
//prepare the URI content string with the names and values to be sent.
var content = "";
if (names && values && names.length > 0 && values.length > 0) {
for (var i = 0; i < Math.min(names.length, values.length); i++) {
if (content.length > 0) content += "&";
content += names[i] + "=" + encodeURIComponent(values[i] != null ? values[i] : "");
}
}
//finally, make the call. If synchronous mode was specified, call localhandler after the AJAX call returns.
if (method == "GET") {
ajaxobj.open("GET", path + command + (content.length > 0 ? "?" + content : ""), async);
ajaxobj.send(null);
if (!async) localhandler();
} else if (method == "POST") {
ajaxobj.open("POST", path + command, async);
ajaxobj.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
ajaxobj.setRequestHeader("Content-length", content.length);
ajaxobj.setRequestHeader("Connection", "close");
ajaxobj.send(content);
if (!async) localhandler();
} else throw new Error("sendAjax: method must be either GET or POST, not " + method);
//sendAjax() returns a function that can be used to cancel an asynchronous request.
return function() {
ajaxobj.abort();
};
}
}();