AJAX = Asyncronous (but not always), JavaScript, XML (Text or JSON). These technologies have been around almost as long as the Internet itself, but have only recently been used together to change the way that web applications are built.
I’ve messed with many of the frameworks available online, and while they are very good, I’ve routinely found issues or features that they lack. Over time I’ve cobbled together my own framework, I fully expect this to change over time, but publish it here for your consideration!
NOTE: The example response program is written in PHP, but should be easily ported to any other language.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>AJAX Example</title>
<script type=”text/javascript”>
var xbusy = true;
var otherHost = ‘http://www.giantgeek.com’; // DO NOT USE the ending slash!
var xhr = null;
/**
* Check for existance on DOM browsers (Mozilla, etc.)
* @return xhr
*/
function ajaxCheckDOM(){
var myxhr = null;
if(window.XMLHttpRequest) {
try {
myxhr = new XMLHttpRequest();
}
catch(e) {}
}
return myxhr;
}
/**
* Check for existance on Windows/MSIE (prior to MSIE7 which is now DOM)
* Evaluate using – new ActiveXObject(“Microsoft.XMLDOM”);
* @return xhr
*/
function ajaxCheckActiveX(){
var myxhr = null;
//if(window.ActiveXObject){
try {
myxhr = new ActiveXObject(“Msxml2.XMLHTTP”);
}
catch(e) {
try {
myxhr = new ActiveXObject(“Microsoft.XMLHTTP”);
}
catch(e) {}
}
//}
return myxhr;
}
/**
* This is a default response hook for AJAX, you SHOULD create your own as it’s only for DEMO
* @param obj – the clicked item
* @param customObj – user defined
*/
function ajaxResponseHook(obj,customObj){
var txt = xhr.responseText;// NOTE: xhr.responseXML is also valid
popStatus(‘AJAX Response value [‘ + txt + ‘|’ + customObj +’]’,”);
}
/**
* This is a common Response handler AJAX, you SHOULD NOT need to modify, use a ‘custom’ ajaxhook function to process the responses.
* @param obj – the clicked item
* @param ajaxhook Function (default will be used if undefined)
* @param customObj (optional – left for developer implementation, passed to the ajaxhook)
*/
function ajaxResponse(obj,ajaxhook,customObj){
var status=”;
if(xhr!=undefined){
var state=xhr.readyState;
if(state!=undefined){
if(state==4){
var code = xhr.status;
status = xhr.statusText;
if(code==200){
popStatus(‘AJAX Response Received.’,”);
if(ajaxhook==undefined){
ajaxhook = function(){ ajaxResponseHook(obj,customObj); }
}
ajaxhook(obj,customObj);
ajaxBusy(obj,false);
} else {
popStatus(‘AJAX Error ‘ + code + ‘ ‘ + status + ‘.’,”);
}
ajaxBusy(obj,false);
xhr=null; /* MSIE6 leak fix */
}
}
}
}
/**
* NOTE: MSIE6-7 supports XSS url’s (be careful!)
* @param obj – the clicked item
* @param url – the GET url params (FQDN)
* @param async (true or false) – false will LOCK browser until response – use cautiously!
* @param callback Function – allows for customized response handling
*/
function ajaxObj(obj,url,async,callback){
if(xbusy == true){
popStatus(‘AJAX BUSY, Please Retry.’,”);
} else {
if(callback==undefined){
callback = function(){ ajaxResponse(obj,hook,”); }
}
ajaxInit(obj,url,async,callback);
}
}
/**
* This is a Non-Caching implementation of ajaxObj() to show how you can avoid the MSIE caching issue.
* NOTE: You can use similar approaches to cache per page or session.
* @param obj – the clicked item
* @param url – the GET url params (FQDN)
* @param async (true or false) – false will LOCK browser until response – use cautiously!
* @param callback Function – allows for customized response handling
*/
function ajaxObjNoCache(obj,url,async,callback){
var cacheBuster=uniqueUrl(url);// damn MSIE!
ajaxObj(obj,cacheBuster,async,callback)
}
/**
* Initializes the AJAX operation
* @param obj – item clicked
* @param url – FQDN
* @param async (true/false) – for locking
* @param callback Function – provided for customization.
*/
function ajaxInit(obj,url,async,callback){
xhr=ajaxCheckDOM();
if(!xhr){
xhr=ajaxCheckActiveX();
}
if(xhr){
ajaxBusy(obj,true);
popStatus(‘AJAX Start.’,”);
xhr.onreadystatechange = callback; // method call
try {
xhr.open(‘GET’,url,async); /* POST */
//xhr.setRequestHeader(‘Content-Type’,’application/x-www-form-urlencoded’);
xhr.send(”); /* null */
}
catch(e)
{
if(xhr){
var code = xhr.status;
var status = xhr.statusText;
popStatus(‘AJAX Client SECURITY ‘ + navigator.appName +’ ‘+ navigator.appVersion + ‘ ‘ + code + ‘ ‘ + status + ‘.’,”);
}
}
ajaxBusy(obj,false);
} else {
popStatus(‘AJAX Client ERROR.’,”);
}
}
function ajaxBusy(obj,yn){
if(yn){
swapStyleObj(obj,’idle’,’busy’);
} else {
swapStyleObj(obj,’busy’,’idle’);
}
xbusyInd(yn,”);
}
function xmillis(){
return new Date().getTime();
}
function swapStyleObj(obj,oldCSS,newCSS){
if(obj!=undefined) {
var current=obj.className;
if(current!=undefined){
var txtOld = current.replace(newCSS,’ ‘);//no doubles
var txtMid = txtOld.replace(oldCSS, ‘ ‘);
var txtNew = (txtMid + ‘ ‘ + newCSS);
obj.className = txtNew;
} else {
obj.className = newCSS;
}
}
}
function xbusyInd(valnew, msg){
xbusy = valnew;
if(xbusy==true){
document.body.style.cursor=’wait’;
if(msg != ”){
top.window.defaultStatus=msg;
popStatus(msg,”);
}
showDiv(‘throbber’);
//blockScreen();
} else {
document.body.style.cursor=’default’;
hideDiv(‘throbber’);
//unBlockScreen();
}
}
function showDiv(id) { //show a div
var obj=xgetHelper(id);
if(obj!=null){ obj.style.display=”block”;}
//xrestart();
}
function hideDiv(id) { //hide a div
var obj=xgetHelper(id);
if(obj!=null){ obj.style.display=”none”;}
//xrestart();
}
function popStatus(txt,title){
popit(‘statusdyn’,’statusarrow’,’statusul’,’statusdiv’,txt,false,title,”);
}
function popit(dynId,arrowId,ulId,divId,txt,expand,title,cssCls){
popText(ulId,txt,title,cssCls);
showDiv(divId);
if(expand == true){
var arrowObj=xgetHelper(arrowId); if(arrowObj!=null){ arrowObj.style.backgroundPosition=’2px -106px’; }
showDiv(dynId);
}
}
function popText(id,txt,title,cssCls){
var obj = xgetHelper(id);
if(obj != null){
var oldHTML = obj.innerHTML;
var cls=”; if(cssCls!=”){ cls=’ class=”‘ + cls +'”‘; }
var htm = ‘<‘+’li’+ cls +’>’ + txt + ‘<‘+’/’+’li’+’>’ + oldHTML;
obj.innerHTML = htm;
}
}
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert(“Error:” + z);
}
return obj;
}
function arrowTog(objectID,arrow) {
var obj = xgetHelper(objectID);
if(obj!=null){
if (obj.style.display ==’block’) {
arrow.style.backgroundPosition = ‘2px -21px’;}
else {arrow.style.backgroundPosition = ‘2px -106px’;}
objTog(objectID);
}
return false;
}
function objTog(objectID) {
var obj = xgetHelper(objectID);
if(obj!=null){
if (obj.style.display ==’block’) obj.style.display=’none’;
else {obj.style.display=’block’;}
}
return false;
}
function headTog(objectID,arrow) {
var obj = xgetHelper(objectID);
if(obj!=null){
if (obj.style.display ==’block’) {
arrow.style.borderBottomWidth = ‘1px’;}
else {arrow.style.borderBottomWidth = ‘0px’;}
arrowTog(objectID,arrow);
}
return false;
}
/*
* adds timestamp to URLs to make them unique
* @param URL String
*/
function uniqueUrl(x){
return urlAppender(x,’.cache’,xmillis());
}
/*
* helps to add parms to the url
* @param URL String
* @param aname String
* @param avalue String
*/
function urlAppender(x,aname,avalue){
var delim = “?”;
if(x.indexOf(“?”) >=0) { delim = “&”; }
return x + delim + aname + ‘=’ + avalue;
}
function xload(){
hideDiv(‘throbber’);
xbusy=false;
}
function testAjax(obj){
var callback=function(){ ajaxResponse(obj,null,’testAjax’); }
var x = ajaxObjNoCache(obj,’/ajax.php’,true,callback);
}
function testAjaxParms(obj){
var callback=function(){ ajaxResponse(obj,null,’testAjaxParms’); }
var x = ajaxObjNoCache(obj,’/ajax.php?testing=Y’,true,callback);
}
function testAjaxXSS(obj){
var callback=function(){ ajaxResponse(obj,null,’testAjaxXSS’); }
var x = ajaxObjNoCache(obj,otherHost+’/ajax.php’,true,callback,otherHost);
}
function testAjaxHook(obj){
var hook=function(){ customAjaxHook(obj,’ajaxhookTestMessageObect’); }
var callback=function(){ ajaxResponse(obj,hook,’testAjaxHook’); }
var x = ajaxObjNoCache(obj,’/ajax.php’,true,callback);
}
function testAjaxHookXSS(obj){
var hook=function(){ customAjaxHook(obj,’ajaxhookXSSMessageObect’); }
var callback=function(){ ajaxResponse(obj,hook,’testAjaxHookXSS’); }
var x = ajaxObjNoCache(obj,otherHost+’/ajax.php’,true,callback);
}
/**
* This is a custom implemenation, the customObj COULD be used for anything (perhaps delay measurement!)
* @param obj – the clicked item
* @param customObj – user defined
*/
function customAjaxHook(obj,customObj){
var xml = xhr.responseXML;// NOTE: xhr.responseText is also valid
var tmp = ‘DEMO customAjaxHook [‘ + customObj + ‘]\n’ + xml;
alert(tmp);
}
</script>
<style type=”text/css”>
.busy { color:red; }
.idle { color:black; }
</style>
</head>
<body onload=”xload();”><!– onbeforeunload=”alert(‘before’);” onunload=”alert(‘after’);” –>
<div id=”throbber”>WORKING!</div>
<div id=”statusdiv” class=”dyn” style=”display:none;”>
<h3><a id=”statusarrow” onclick=”headTog(‘statusdyn’,this);” href=”javascript:void(0);”>Status</a></h3>
<fieldset id=”statusdyn” style=”display:block;background-color:#ffffcc;”>
<div id=”statusBox” class=”box”>
<ul id=”statusul” class=”error”>
<li id=”ajaxStatus” style=”list-style:none;display:none;”>FILLER</li>
</ul>
</div>
</fieldset>
</div>
<h3><a href=”javascript:void(0);” onclick=”testAjax(this);”>TEST</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxParms(this);”>TESTPARMS</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxXSS(this);”>TESTXSS</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxHook(this);”>TESTHOOK</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxHookXSS(this);”>TESTHOOKXSS</a></h3>
<p><a href=”index.php”>RELOAD</a></p>
</body>
</html>
What are the MSIE Bugs you might ask… these are all in the ActiveX implementation of XmlHttpRequest, they are: