Hi there!
In this article, I provide an explanation of Ajax with a historical introduction WITHOUT using any frameworks such as jQuery. If you are eager to start seeing the code, please scroll down.
What is Ajax?
Historical approach for calling back-end apps
Throughout web-development history, the very first way used to achieve back-end processing followed by front-end display was to create a form and set the back-end script as the action to that form. Upon submission, the form fields would be sent to the back-end script as a series of special environment variables, which would then be handled by the programmed logic. The back-end output would be displayed on the screen (either the same page/frame or a different page/frame, depending on the target attribute of the form).
The catch 22 of this approach is that if you want to present another form after processing, that form must be produced by the back-end script, which leads to maintenance mayhem – to add or remove a field, you have to do so in both the original HTML and in the HTML of the back-end script.
Hidden IFRAMES and Javascript to the rescue
The natural evolution of the regular form submission was to avoid having to generate HTML from the back-end script. A technique that I eventually came up with (had never seen it before, but it was probably already out there) prior to adhering to Ajax was using a hidden IFRAME as target to a simple form containing an action_cd field and a data field. There would not be a regular submit button, but a set of input elements with onclick or onchange events and a regular (non-submit) button at the end.
In this approach, Javascript did all the work – when the user clicked the pseudo-submit button, an onclick event would read all the input fields and join them like a regular HTTP query (field1=val1&field2=val2
…). The action_cd value would be dynamically evaluated and the back-end script would receive the data string and action code and process it. Javascript would perform the submission when, say, a value of a drop-down field was selected, and the output would be a series of dynamically generated Javascript commands inside the IFRAME. Those commands would manipulate the user’s form (e.g. Auto-populate a “state” drop-down after selecting a “country” value).
Although this approach successfully handled the no-longer-need to submit and reload a form, it still had 2 main downsides: generating and maintaining Javascript code is not a simple task (especially due to having to escape quotes), and the whole procedure was still synchronous – you could see the hourglass, browser process bars, clicking sounds (IE), etc. It’s much better user experience, but still not the real deal, seamless/transparent back-end processing.
The XmlHttpRequest object
Javascript back-end calls are made possible thanks to the XmlHttpRequest object that most modern browsers now support. For IE 6 and older, it is done through an ActiveXObject.
Javascript code to initialize Ajax object
var xmlHttp; // global variable. It’s important for later on function GetXmlHttpObject(){ if (window.XMLHttpRequest){ // code for IE7+, Firefox, Chrome, Opera, Safari return new XMLHttpRequest(); } if (window.ActiveXObject){ // code for IE6, IE5 return new ActiveXObject("Microsoft.XMLHTTP"); } return null; }
It’s important to have a global variable in which to assign the XMLHttpRequest object, since this technique has its shortcomings when dealing with passing variables around. We’ll understand that better shortly. The function checks for the existence of window.XMLHttpRequest which is used in IE7 (finally, it complies to standards!), Firefox, and other non IE browsers, and checks for window.ActiveXObject existence for IE6 and 5. I’m not sure if this works on IE4 – it probably does not, but hopefully IE4 is already in extinction. If neither checks work, it returns null, which will be handled in the function calling the XMLHttpObject.
Making Synchronous Ajax calls (GET method)
Although the hype of Ajax is due to the “Asynchronosity” of the technique, it is also capable of making synchronous calls. Synchronous calls are important in cases where you don’t want to allow the user to do something while the back-end is checking a previous action, but also don’t want to have to submit the whole form.
Ajax calls can be done using GET or POST methods. I recommend GET methods for small queries, POST for large ones. There used to be a limit of 255 characters on URL fields – not sure if a) this is still true; b) this could apply to these Ajax techniques.
The call to the back-end is done by 2 commands: xmlHttp.open(Method,url,async), and the xmlHttp.send(null) calls. For POST method, this is a little different, so we’ll stick to GET calls for the time being.
Handling back-end reply
function synch_call() { // initialize Ajax object var xmlHttp = GetXmlHttpObject(); // Set the URL to your backend script with query vars var url = “/url/to/your/backend/script?with=var1&and=var2”; // Synchronous call xmlHttp.open("GET",url,false); // “false” means asynchronous = false xmlHttp.send(null); // the code above does the back-end call. Now we handle the response try { alert("Ajax Response: n"+xmlHttp.responseText); // Just pop-up the reply return false; } catch(e) { alert(e); // if error, show it as a pop-up return false; } }
// Just the try/catch block changed try { data = xmlHttp.responseXML.documentElement; } catch(e) { alert(e); return false; }
data
variable now holds a DOM object of the XML structure. To access its elements, we use DOM functions.
Example XML structure:
<OUT> <STATUS>Success</STATUS> <MSG type="some_type">This is a test message</MSG> </OUT>Accessing Example XML through DOM:
// gets the Success text var msg_status = data.getElementsByTagName(‘STATUS’)[0].childNodes[0].nodeValue; // Assign MSG object to msg variable var msg = data.getElementsByTagName(‘MSG’)[0]; // gets the attribute “type” var msg_type = msg.getAttribute(‘type’); // gets the text inside the MSG node var msg_text = msg.childNodes[0].nodeValue;
Putting it all together
function synch_call() { // initialize Ajax object var xmlHttp = GetXmlHttpObject(); // Set the URL to your backend script with query vars var url = “/url/to/your/backend/script?with=var1&and=var2”; // Synchronous call xmlHttp.open("GET",url,false); // "false" means asynchronous = false xmlHttp.send(null); // the code above does the back-end call. Now we handle the response try { data = xmlHttp.responseXML.documentElement; } catch(e) { alert(e); return false; } // gets the Success text var msg_status = data.getElementsByTagName(‘STATUS’)[0].childNodes[0].nodeValue; // Assign MSG object to msg variable var msg = data.getElementsByTagName(‘MSG’)[0]; // gets the attribute “type” var msg_type = msg.getAttribute(‘type’); // gets the text inside the MSG node var msg_text = msg.childNodes[0].nodeValue; alert("Got the following backend reply: " + msg_status + "n" + msg + "n" + msg_type + "n" + msg_text); return true; }
Making Asynchronous Ajax calls (GET method)
open()
command. However, we also need changes in the way we handle the reply. We do that by setting onreadystatechange property on the xmlHttp object.
OnReadyStateChange should be a function that will check the readyState attribute of the xmlHttp object. Understanding readyState
Value | State |
0 | Uninitialized |
1 | Loading |
2 | Loaded |
3 | Interactive |
4 | Complete |
Setting onreadystatechange
onreadystatechange
can be assigned a anonymous function() { }
block, or a named function. Just be careful with function parameters – to this day, I have not been able to pass any parameters to the functions set to onreadystatechange
. Hence the need for some global variables in play.
Let’s copy over our sync_call()
function and make it Async_call()
function Asynch_call() { // initialize Ajax object var xmlHttp = GetXmlHttpObject(); // Set the URL to your backend script with query vars var url = “/url/to/your/backend/script?with=var1&and=var2”; // onreadystatechange must be set BEFORE making the call // it takes the response handling code that we had in the sync_call() previously xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) { // Complete // Now we handle the response within the onreadystatechange try { data = xmlHttp.responseXML.documentElement; } catch(e) { alert(e); return false; } // gets the Success text var msg_status = data.getElementsByTagName(‘STATUS’)[0].childNodes[0].nodeValue; // Assign MSG object to msg variable var msg = data.getElementsByTagName(‘MSG’)[0]; // gets the attribute "type" var msg_type = msg.getAttribute('type'); // gets the text inside the MSG node var msg_text = msg.childNodes[0].nodeValue; alert("Got the following backend reply: " + msg_status + "n" + msg + "n" + msg_type + "n" + msg_text); return true; } }; // don’t forget the ';' at the end of the function() {} block!! // Asynchronous call xmlHttp.open("GET",url,true); // 'true' means asynchronous = true xmlHttp.send(null); }
onreadystatechange
function. function Asynch_call() { // initialize Ajax object var xmlHttp = GetXmlHttpObject(); // Set the URL to your backend script with query vars var url = “/url/to/your/backend/script?with=var1&and=var2”; // onreadystatechange must be set BEFORE making the call // it takes the response handling code that we had in the sync_call previously xmlHttp.onreadystatechange = handlerfunction; // See separate function below // Asynchronous call xmlHttp.open("GET",url,true); // “true” means asynchronous = true xmlHttp.send(null); } function handlerfunction() { if (xmlHttp.readyState == 4) { // Complete – must be global xmlHttp variable // Now we handle the response within the onreadystatechange try { data = xmlHttp.responseXML.documentElement; } catch(e) { alert(e); return false; } // gets the Success text var msg_status = data.getElementsByTagName(‘STATUS’)[0].childNodes[0].nodeValue; // Assign MSG object to msg variable var msg = data.getElementsByTagName(‘MSG’)[0]; // gets the attribute “type” var msg_type = msg.getAttribute(‘type’); // gets the text inside the MSG node var msg_text = msg.childNodes[0].nodeValue; alert("Got the following back-end reply: " + msg_status + "n" + msg + "n" + msg_type + "n" + msg_text); return true; } }
Parsing forms to create the GET/POST strings
Since we are not submitting the form with the usual submit button, we have to handle building the queries ourselves. I came up with a function that mimics the way browsers handle function calls.
function build_post_string(frm) { var str; var poststr_array = []; if (!frm.id) { // assume it's a string. get the form object frm = document.getElementById(frm); } for (i=0;i<frm.elements.length;i++){ var elem = frm.elements; if (!elem.id) { // skip any fields that don't have IDs continue; } if (elem.type == 'radio' || elem.type == 'checkbox') { // only grab radio buttons and checkboxes that are checked if (!elem.checked) { continue; } } // get select values if (elem.nodeName.match(/SELECT/i) && elem.multiple) { var sel_array = []; for (var o=0;o<elem.options.length;o++) { if (elem.options[o].selected) { sel_array[sel_array.length] = elem.id+"="+elem.options[o].value; } } var sel_str = sel_array.join('&'); // build key/value pairs for SELECTs poststr_array[poststr_array.length] = sel_str; } else if (elem.nodeName.match(/SELECT/i)) { poststr_array[poststr_array.length] = elem.id+'='+elem.options[elem.selectedIndex].value; } else { // build key/value pairs for everything else poststr_array[poststr_array.length] = elem.id+"="+elem.value; } } // build and return str str = poststr_array.join("&"); return str; }
Using POST method for Ajax calls
So far we saw that making GET calls is pretty straight-forward. POST calls are different in the way that they are not put in the HTTP Header, but rather in a separate packet. In order to do that, we need to set a few header variables:
function Asynch_call_with_post() { // initialize Ajax object var xmlHttp = GetXmlHttpObject(); // Set the URL to your backend script with query vars var post_str = build_post_string(‘my_form’); var url = “/url/to/your/backend/script?” + post_str; // onreadystatechange must be set BEFORE making the call // it takes the response handling code that we had in the sync_call previously xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) { // Complete // Now we handle the response within the onreadystatechange try { data = xmlHttp.responseXML.documentElement; } catch(e) { alert(e); return false; } // gets the Success text var msg_status = data.getElementsByTagName(‘STATUS’)[0].childNodes[0].nodeValue; // Assign MSG object to msg variable var msg = data.getElementsByTagName(‘MSG’)[0]; // gets the attribute “type” var msg_type = msg.getAttribute(‘type’); // gets the text inside the MSG node var msg_text = msg.childNodes[0].nodeValue; alert("Got the following backend reply: " + msg_status + "n" + msg + "n" + msg_type + "n" + msg_text); return true; } }; // don't forget the ';' at the end of the function() {} block // Asynchronous call xmlHttp.open('POST',url, true); // Replace "GET" with "POST" // Set headers xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlHttp.setRequestHeader("Content-length", url.length); xmlHttp.setRequestHeader("Connection", "close"); // Send the packet. Note that it’s no longer null as parameter to send xmlHttp.send(url); }
Showing/Hiding "Processing…" messages
DIV
element with initial display property set as none, or visibility set as hidden (the difference is that visibility="hidden"
makes the element still occupy its space in the page, whereas display="none"
effectively removes the element from the page and the space that it occupied).
Typically, I include code to show the element in the Ajax function, and code to hide it again in the onreadystatechange function (where readyState == 4).
document.getElementById(‘wait_image’).display = ‘block’; document.getElementById(‘wait_image’).display = ‘none’;
Third-Party AJAX Libraries
Testing the back-end script
$_REQUEST
array. Perl CGI doesn’t really care if it’s GET or POST. I don’t know how JSP can handle it. With this being said, I simply have the Ajax alert the URL variable and return false. I then copy the URL variable and paste it in another window to see the output.
I strongly recommend debugging with Firefox – it has a handy error console and also parses the output XML or complains if it’s not built right. Keeping IE from crashing with the XMLs
"Content-type: text/xml"
header in place, but it appears that IE needs some caching control and expiry headers as well.
This is what I use in PHP: header("Expires: Fri, 09 Jan 1981 05:00:00 GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", FALSE); // FALSE means add this Cache-Control line instead of replacing the previous one header("Pragma: no-cache"); header("Content-type: text/xml");
print
them before printing any content. When you’re done printing all your headers, print an empty newline to indicate where the page content will go.
I hope you’ve enjoyed this tutorial. Feel free to paste comments regarding your experience and preferences. I hope you liked the article. To see our special website/WordPress Maintenance and Care plans, click here:
https://usestrict.net/wordpress-care-services/