Sunday, September 28, 2008

AJAX and Select HTML element or how I underestimated Internet Explorer.

Working on widget for selecting recommended books I used article from w3cschools. Desired functionality was simple: user changes the category so the list of books is updated without necessity to reload the page completely. I never keep in mind all technical details, that's why I always prefer to refer to respected sources for an advice. And the advice was to use AJAX in the next way:

1) Create the instance of XMLHttpRequest handling a type of browser.
2) Initialize its onreadystatechange property: xmlHttp.onreadystatechange = callbackFunction;
3) Initialize parameters of Http request: xmlHttp.open("GET", "handler.php", true);
4) Perform the request: xmlHttp.send(null);

So far so good! It seemed reasonable and I decided to apply it. Here I throw off all insignificant details. Bluntly speaking, HTML view looked like this (file ndex.html):

<html>
<body>
<div>
<script type="text/javascript" src="/select.js"></script>
<select id="select" onchange="javascript:changed();">
<option value="1">Firstly</option>
<option value="2">Secondly</option>
<option value="3">Finally</option>
</select>
</div>
<div id="advice" />
</html>

select element
It is simple Select element with associated onchange event handler represented by JavaScript function "changed( )" defined in select.js file:

var httpObject = createXMLHttpRequest();

function changed() {
var selected = document.getElementById('select');
httpObject.onreadystatechange = handleResponse;
httpObject.open('GET', '/handler.php?option='
+ selected.options[selected.selectedIndex].value);
httpObject.send(null);
}

// Handles server responses.
function handleResponse() {
if (httpObject.readyState == 4) {
var response = httpObject.responseText;
document.getElementById('advice').innerHTML = response;
}
}

// Creates new XMLHttpRequest
function createXMLHttpRequest() {
var xmlHttp;
try {
// create an XMLHttpRequest object
xmlHttp = new XMLHttpRequest();
} catch(e) {
// assume IE6 or older
try {
xmlHttp = new ActiveXObject("Microsoft.XMLHttp");
} catch(e) {
alert("Error creating the XMLHttpRequest object.");
}
}
return xmlHttp;
}

Let's review two other functions. Firstly, createXMLHttpRequest( ) creates global instance of XMLHttpRequest depending on type of browser. Function handleResponse( ) is called every time when our server sends a response with updated status. We wait until operation is complete (httpObject.readyState is equal to 4) and immediately update the advice with message generated by the server. Here these messages are (file handler.php):

<?php
if ($_GET['option'] == '1') {
echo 'Initialize parameters of Http request';
} else if ($_GET['option'] == '2') {
echo 'Initialize onreadystatechange property';
} else if ($_GET['option'] == '3') {
echo 'Perform the request';
} else {
echo 'Exception';
}
?>

It simply takes the option set by httpObject.open(...) call and generate appropriate text by means of HTTPResponse. This text will be contained in httpObject.responseText when the response is complete.

And... Tada! I decided that my widget is ready. I've verified it in Mozilla Firefox (the best browser for software developers ;) and Opera (cool browser too). And then... Oh! I found that it doesn't work in Internet Explorer. More precisely, it works but only once. So, you can try, when user selects option 'Secondly' from the list at first time, Internet Explorer gets the response and updates the text of 'advice' HTML element. But then user may reselect different options infinitely without any result. Only F5 refreshing the page will provide one more attempt.

I was depressed. But listening to S.O.S. by ABBA brought me to life.

I couldn't leave this issue as it was because people still use IE. Nevertheless, here I should present my apologies to Internet Explorer. Actually, it was my fault that I'd been disappointed by w3cschools' article. The mistake was in order of steps of preparing and sending XMLHttpRequest. So, the fix is:

httpObject.open('GET', '/handler.php?option='
+ selected.options[selected.selectedIndex].value);
httpObject.onreadystatechange = handleResponse;
httpObject.send(null);

Here I set onreadystatechange property after initialization of HTTPRequest parameters. It is exactly what the script listed above shouts about.

Currently this widget works in all browsers I know, including IE6 and IE7. You are welcome to use it in your web projects as well!

Taking everything into consideration, listening to music is the best approach to bug fixing.