Since the release of jQuery 1.4, I have been very eager to integrate jQuery into QCubed. Integrating a solid, cross browser javascript engine to QCubed could leverage the power, stability and growth of QCubed.
In these series, I'm taking a first step on integrating jQuery into QCubed. By writing these articles, my goal is to provide everyone with a (hopefully clear) view on the changes that are needed, have been-in order to become a full PHP jQuery framework, a possible way to integrate these changes and of course (and most important!) to receive feedback on the code changes, as well as a Q/A platform for me and the other developers on how to continue the integration.
To start, in this first part, we will replace the ajax post with the ajax post through jQuery. Sounds easy enough to begin with, doesn't it :)
Note: Baselius has already doen a lot of work on integrating jQuery (1.3.2), and has commited his work into the experimental branch of QCubed. During this series of articles, we will try understand the code that Baselius has commited, take the good bits and pieces from his work and integrate them where we can. Thanks a lot already to him!
Anyway, let's get started.
Including jQuery
I will not invest much time in this for now: I will simply include jQuery into the header of my scripts. Later on, we will probably patch configuration.inc.php to contain some constants of the location of jQuery, and include them automatically on each call of QCubed.
This goes in the template file:
<script src="<?php echo __SUBDIRECTORY__; ?>/assets/js/jquery-1.4.js"></script>postAjax
Ajax posts are handled through the postAjax function in post.js. This function will
a) add the form variables into array called "ajaxQueue"
b) call dequeueAjaxQueue() to post the to QCubed
dequeueAjaxQueue
This is where the actual posting of the form takes place.
a) construct form variables in such a way they can be read by QCubed on the server side
b) post the form to QCubed
c) repeat until there is nothing in the queue
The interesting part is the following:
var strUri = objForm.action;
var objRequest;
if (window.XMLHttpRequest) {
objRequest = new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined") {
objRequest = new ActiveXObject("Microsoft.XMLHTTP");
};
if (objRequest) {
objRequest.open("POST", strUri, true);
objRequest.setRequestHeader("Method", "POST " + strUri + " HTTP/1.1");
objRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
objRequest.onreadystatechange = function() {
if (objRequest.readyState == 4) {
if (!qcodo.beforeUnloadFlag) {
try {
var objXmlDoc = objRequest.responseXML;
// qcodo.logMessage(objRequest.responseText, true);
// alert('AJAX Response Received');
if (!objXmlDoc) {
alert("An error occurred during AJAX Response parsing.\r\n\r\nThe error response will appear in a new popup.");
var objErrorWindow = window.open('about:blank', 'qcodo_error','menubar=no,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes,width=1000,height=700,left=50,top=50');
objErrorWindow.focus();
objErrorWindow.document.write(objRequest.responseText);
return;
} else {
var intLength = 0;
// Go through Controls
var objXmlControls = objXmlDoc.getElementsByTagName('control');
intLength = objXmlControls.length;
for (var intIndex = 0; intIndex < intLength; intIndex++) {
var strControlId = objXmlControls[intIndex].attributes.getNamedItem('id').nodeValue;
var strControlHtml = "";
if (objXmlControls[intIndex].textContent)
strControlHtml = objXmlControls[intIndex].textContent;
else if (objXmlControls[intIndex].firstChild)
strControlHtml = objXmlControls[intIndex].firstChild.nodeValue;
// Perform Callback Responsibility
if (strControlId == "Qform__FormState") {
var objFormState = document.getElementById(strControlId);
objFormState.value = strControlHtml;
} else {
var objSpan = document.getElementById(strControlId + "_ctl");
if (objSpan)
objSpan.innerHTML = strControlHtml;
};
};
// Go through Commands
var objXmlCommands = objXmlDoc.getElementsByTagName('command');
intLength = objXmlCommands.length;
for (var intIndex = 0; intIndex < intLength; intIndex++) {
if (objXmlCommands[intIndex] && objXmlCommands[intIndex].firstChild) {
var strCommand = "";
intChildLength = objXmlCommands[intIndex].childNodes.length;
for (var intChildIndex = 0; intChildIndex < intChildLength; intChildIndex++)
strCommand += objXmlCommands[intIndex].childNodes[intChildIndex].nodeValue;
eval(strCommand);
};
};
};
} catch (objExc) {
alert(objExc.message + "\r\non line number " + objExc.lineNumber + "\r\nin file " + objExc.fileName);
alert("An error occurred during AJAX Response handling.\r\n\r\nThe error response will appear in a new popup.");
var objErrorWindow = window.open('about:blank', 'qcodo_error','menubar=no,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes,width=1000,height=700,left=50,top=50');
objErrorWindow.focus();
objErrorWindow.document.write(objRequest.responseText);
return;
};
};
// Perform the Dequeue
qcodo.ajaxQueue.shift();
// Hid the WaitIcon (if applicable)
if (qcodo.objAjaxWaitIcon)
qcodo.objAjaxWaitIcon.style.display = 'none';
// If there are still AjaxEvents in the queue, go ahead and process/dequeue them
if (qcodo.ajaxQueue.length > 0)
qcodo.dequeueAjaxQueue();
};
};
objRequest.send(strPostData);
};Compatibility mode
First thing I learned from the code of baselius is to use jQuery in compatibility mode. This is important, as we do not want to tie the developer to jQuery only. So call:
// Make sure we set $j.noConflict() to $j
var $j = jQuery.noConflict();At the beginning of the file.
Replacing Post Code
Ajax calls in jQuery are handled through .ajax (original name, no!?). See http://api.jquery.com/jQuery.ajax/ for more details.
So we need:
a) the url: this is
var strUri = objForm.action;in the code above
b) the type, we will use POST
c) the data to be posted: this the constructed strPostData
d) an error handler
e) the succes handler
POC:
$j.ajax({
type: "POST",
url: strUri,
data: strPostData,
success: function(xml){
// do something useful
},
error: function(XMLHttpRequest, textStatus, errorThrown){
// do something useful
}
}); Error handling
For the error, we will use the same error as originally code in QCubed:
alert("An error occurred during AJAX Response parsing.\r\n\r\nThe error response will appear in a new popup.");
var objErrorWindow = window.open('about:blank', 'qcodo_error','menubar=no,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes,width=1000,height=700,left=50,top=50');
objErrorWindow.focus();
objErrorWindow.document.write(XMLHttpRequest.responseText);
return;Success handling
For succes handling, we can also copy/paste the code from QCubed. We just need to put the xml response in the variable (objXmlDoc) the original javascript can use:
var objXmlDoc = xml;complete POC code
This is what we have so far:
$j.ajax({
type: "POST",
url: strUri,
data: strPostData,
success: function(xml){
var objXmlDoc = xml;
var intLength = 0;
// Go through Controls
var objXmlControls = objXmlDoc.getElementsByTagName('control');
intLength = objXmlControls.length;
for (var intIndex = 0; intIndex < intLength; intIndex++) {
var strControlId = objXmlControls[intIndex].attributes.getNamedItem('id').nodeValue;
var strControlHtml = "";
if (objXmlControls[intIndex].textContent)
strControlHtml = objXmlControls[intIndex].textContent;
else if (objXmlControls[intIndex].firstChild)
strControlHtml = objXmlControls[intIndex].firstChild.nodeValue;
// Perform Callback Responsibility
if (strControlId == "Qform__FormState") {
var objFormState = document.getElementById(strControlId);
objFormState.value = strControlHtml;
} else {
var objSpan = document.getElementById(strControlId + "_ctl");
if (objSpan)
objSpan.innerHTML = strControlHtml;
};
};
// Go through Commands
var objXmlCommands = objXmlDoc.getElementsByTagName('command');
intLength = objXmlCommands.length;
for (var intIndex = 0; intIndex < intLength; intIndex++) {
if (objXmlCommands[intIndex] && objXmlCommands[intIndex].firstChild) {
var strCommand = "";
intChildLength = objXmlCommands[intIndex].childNodes.length;
for (var intChildIndex = 0; intChildIndex < intChildLength; intChildIndex++)
strCommand += objXmlCommands[intIndex].childNodes[intChildIndex].nodeValue;
eval(strCommand);
};
};
// Perform the Dequeue
qcodo.ajaxQueue.shift();
// Hid the WaitIcon (if applicable)
if (qcodo.objAjaxWaitIcon)
qcodo.objAjaxWaitIcon.style.display = 'none';
// If there are still AjaxEvents in the queue, go ahead and process/dequeue them
if (qcodo.ajaxQueue.length > 0)
qcodo.dequeueAjaxQueue();
},
error: function(XMLHttpRequest, textStatus, errorThrown){
alert("An error occurred during AJAX Response parsing.\r\n\r\nThe error response will appear in a new popup.");
var objErrorWindow = window.open('about:blank', 'qcodo_error','menubar=no,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes,width=1000,height=700,left=50,top=50');
objErrorWindow.focus();
objErrorWindow.document.write(XMLHttpRequest.responseText);
return;
}
}); Cleanup original QCubed Code
From reading the code of Baselius, I noticed the smoothness of the success code:
$j(xml).find('control').each(function() {
var strControlId = '#' + $j(this).attr("id");
var strControlHtml = $j(this).text();
if (strControlId == "#Qform__FormState") {
$j(strControlId).val(strControlHtml);
} else {
$j(strControlId + "_ctl").html(strControlHtml);
}
});
var strCommand = '';
$j(xml).find('command').each(function() {
strCommand += $j(this).text();
});
eval(strCommand); Woow! Compare that to the original code!
Final post code
$j.ajax({
type: "POST",
url: strUri,
data: strPostData,
success: function(xml){
$j(xml).find('control').each(function() {
var strControlId = '#' + $j(this).attr("id");
var strControlHtml = $j(this).text();
if (strControlId == "#Qform__FormState") {
$j(strControlId).val(strControlHtml);
} else {
$j(strControlId + "_ctl").html(strControlHtml);
}
});
var strCommand = '';
$j(xml).find('command').each(function() {
strCommand += $j(this).text();
});
eval(strCommand);
// Perform the Dequeue
qcodo.ajaxQueue.shift();
// Hid the WaitIcon (if applicable)
if (qcodo.objAjaxWaitIcon)
qcodo.objAjaxWaitIcon.style.display = 'none';
// If there are still AjaxEvents in the queue, go ahead and process/dequeue them
if (qcodo.ajaxQueue.length > 0)
qcodo.dequeueAjaxQueue();
},
error: function(XMLHttpRequest, textStatus, errorThrown){
alert("An error occurred during AJAX Response parsing.\r\n\r\nThe error response will appear in a new popup.");
var objErrorWindow = window.open('about:blank', 'qcodo_error','menubar=no,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes,width=1000,height=700,left=50,top=50');
objErrorWindow.focus();
objErrorWindow.document.write(XMLHttpRequest.responseText);
return;
}
});Conclusions
My knowledge of jQuery javascript is still quit limited compared to what Baselius and probably a whole bunch of the other developers know about. With these articles, my knowledge of jQuery will also get a boost. And with the help of everyone, I'm sure we can pull it off to integrate jQuery into QCubed!
Q/A and Remarks
- the second parameter in the error handler can have the following values: "timeout", "error", "notmodified", "parsererror".
The least we can do is to show this message to the user, but it also implies we can perform different actions based on the return value. So my basic question: should we handle timeouts, and if so, how, or should we just show the error information to the user?
- baselius also improved the way the queue worked, we will have a look at this later on.
- does anybody have any tools/methods on how to measure performance of javascript? It would be nice to know if there are any improvements in terms of speed when replacing QCubed javascript with jQuery javascript
Looking forward to your comments!

Overall this looks very cool, but I wonder if we can jQueryize it even more.
For example, can't
qcodo.objAjaxWaitIcon.style.display = 'none';become:
qcodo.objAjaxWaitIcon.hide();The error handling also looks like it could be modified to be more jQuery based.
The hope here is that by making everything use jQuery, we're avoiding any potential cross-browser issues that could come up. Like the current ajax error code doesn't work in IE, which makes me worry that this reworked function still won't display ajax errors in IE. If it was all in jQuery syntax, I'd be much more comfortable expecting it to be fixed, rather than porting old bugs over to 2.0.
LOL, in short, I'm against copy/pasting any old code. Let's rewrite it all from scratch using jQuery.
I sign my name under every word Vexed wrote above.
Suggested approach:
1) Slowly delete every bit of old JS. Replace it with TODO comments describing exactly what it is that bit of code did.
2) Go through each of those TODOs and make them work using jQuery code.
If for some reason, the TODO wasn't explicit enough, you can look at the old js code, but don't copy it, just clarify the TODO. And don't put too much stock in its exact approach either (for loops, etc may not be necessary with jQuery).
3) As each TODO point is addressed, remove the TODO tag, and leave the comment there as documentation.
Very interesting article! :)