Tuesday, March 17, 2009

Ajax Custom Form Submission Mechanism

Consider the following basic html code snippet:

<form name="detailform" method="post"
action="../someURL?Action=ProcessSubmit">
<input type="submit" value="Start">
</form>
Clicking on the "Start" button will cause the browser to collect all the form controls <name,value> pairs between the form opening and closing tags surrounding the <input...> submit button tag and post them to the server at the URL indicated in the action property of the opening form tag. So far, so good. The only thing to notice here is that the data to submit collection mechanism is completely transparent for us and that this approach of submitting data inevitably reloads the window's content in the browser, replacing whatever it currently holds with the new html response from the server.

This, of course, and especially the reloading part, can be pretty much annoying for those of us wishing to ajaxify form submissions. The reason is that we generally don't want our pages to reload in the first place, which means that the submit kind of button is certainly not an option. We have got to use a simple kind of button instead, an <input type="button"...> thus :

<form name="detailform" method="post"
action="../someURL?Action=ProcessSubmit">
<input type="submit" value="Start"
onclick="ajaxSubmit(this, 'DefaultAction')">
</form>
This in turn means that we wont be able to benefit from the automatic data to submit collection mechanism mentionned above. We will have to come up with our own parser code to build the final URL to post. In the example above clicking the "Start" button wont basically do anything else other than jumping into the ajaxSubmit() function, the rest of the work being left up to you:

function ajaxSubmit(obj, urlAction) {
if(formSubmitValidation()) {
//some working variables -- cosmetics
var baseURL;
var formNode = getFormObj(obj);
var idx1Trim = formNode.action.indexOf("..");
var idx2Trim = formNode.action.indexOf("ProcessSubmit");

if (idx1Trim!=-1) { //IE7
baseURL = formNode.action.substring(2, idx2Trim) + urlAction;
} else { //FF
baseURL = formNode.action.substring(0, idx2Trim) + urlAction;
}
//adding custom calculated parameters: timestamp, ajax tag
params = (new Date()).getTime() + "&KindRequest=Ajax";
//the actual form parsing
baseURL = baseURL + "&" + parseDOMSubTree(formNode) + params
//posting the form
ajaxRequest(baseURL);
} else {
alert("Validation Problem");
}
}

It is always a good idea to first validate the fields submited before actually submitting them. Nevertheless, this validation problem falls out of this post's scope and will therefore be left aside. The second interesting point, beside the browser related cosmetics, is the form parsing itself. This is done by the recursive parseDOMSubTree(formNode) method which takes the form-to-submit element as argument:

function parseDOMSubTree(obj) {
var url = "";
for ( var i = 0; i < obj.childNodes.length; i++) {
// we know the root has a child. does it have a tagName?
if (obj.childNodes[i].tagName != null) {
// if no - never mind, just loop further. otherwise check it
switch (obj.childNodes[i].tagName.toUpperCase()) {
/*for form elements other than the ones below, you will have to
write the url append instruction youself :) */
case "TEXTAREA":
url = url + obj.childNodes[i].name + "="
+ escape(obj.childNodes[i].value) + "&";
break;
case "INPUT":
switch (obj.childNodes[i].type.toUpperCase()) {
case "HIDDEN":
case "TEXT":
url = url + obj.childNodes[i].name + "="
+ escape(obj.childNodes[i].value) + "&";
break;
default:
}
break;
default:
// we are checking some other kind of tag. just dive into it
url = url + parseDOMSubTree(obj.childNodes[i]);
}
}
}
return url;
}

And of course, last but not least, the ajax request call responsible for managing the client-server connection. Let's take a look at the code:

function ajaxRequest(url) {

var html = "";
/*
* important flag. we are loosing the asyn aspect but still are keeping the
* neat refresh part of the ajax tehcnology
*/
var asyn = false;
// Ignore cross-browser issues for the moment being
xmlhttp = false;
if ((window.XMLHttpRequest) && !(window.ActiveXObject)) {
// asyn = true;
xmlhttp = new XMLHttpRequest();
if (xmlhttp.overrideMimeType) {
xmlhttp.overrideMimeType('text/xml');
}
} else if (window.ActiveXObject) { // IE
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
}
}
}

if (!xmlhttp) {
alert('Cannot create XMLHTTP instance');
return false;
}

xmlhttp.open("GET", url, asyn);
// Set the callback function -- needed in case of asynchronic work :-)
// xmlhttp.onreadystatechange = function() {callbackAjaxRequest(xmlhttp); };
xmlhttp.send(null);
/*
* we are operating in synchrounous mode if this ever changes - just comment
* this line and uncomment the one above
*/
callbackAjaxRequest(xmlhttp);
}


The callback method looks like this:

function callbackAjaxRequest(xmlhttp) {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200) {
var xmldoc = xmlhttp.responseXML;
//from here on you can manage the resulting XML as you wish

var rootNode = xmldoc.getElementsByTagName('ajax.updates')
.item(0);
var responseStatus = rootNode.getAttribute("status");
var id;

if (responseStatus == "OK") {
for (i = 0; i <>
if (rootNode.childNodes.item(i).nodeName == "item") {
id = rootNode.childNodes.item(i).getAttribute("id");
document.getElementById(id).innerHTML = rootNode.childNodes
.item(i).firstChild.nodeValue;
}
}
} else {
// for FF/IE compatibility reasons -- the objects re
for (i = 0; i <>
if (rootNode.childNodes.item(i).nodeName == "item") {
html = rootNode.childNodes.item(i).firstChild.nodeValue;
}
}
/*this document.write doesn't work in IE if asyn mode above*/
document.write(html);

}

} else {
alert("Ajax Request Problem");
}

}
}


Read more on this article...