Accessible Forms using Javascript and AJAX
Nearly a year ago, I rebuilt a business website. On this website, there is a “Contest” page that offers each month a gift to the randomly selected winner.
However, this contest is supposed to be available only for Belgian.
First Solution
A few years ago, when I first built the website and that we encountered this problem (having French, German and event Lebanese people taking the contest), we thought of a simple solution: when someone fills in his post code in the participation form, a simple JavaScript code (using AJAX) would then populate a Combo box with all the cities that matched the given 4 digit Belgian post code.
After making up a post code table in the database, I wrote up a php page that would return a list of cities associated with the given post code.
Here is an example of the JSON response from that page, when called with 5300 as post code:
{ cities : ['Andenne','Bonneville','Coutisse','Landenne','Maizeret','Namêche','Sclayn','Seilles','Thon','Vezin'] }
I then wrote my first AJAX script (early 2006), that would call this page with a XMLHTTPRequest
. As soon as there were 4 digits in the postal code fields, the request was launched. Once the response received, the combobox was filled with options made using the received list.
Using this technique, we enforced participants to enter a valid Belgian post code. Actually, that didn't enforce anything as participant could leave blank fields, but we could easily sort relevant contest participants in the database.
Accessibility Issue
Last year, I rebuild the website from ground and realized that there was an accessibility issue. What would happen to the poor people which don't run JavaScript? They would all be rejected even is they were Belgian!
I had to find a way to make sure that people that didn't have JavaScript would still be able to fill the form in.
Second Solution
I thought of a simple way to avoid that. On the initial page, I replaced the select
tag (Combo box) by an input
tag (single line text input).
I then coded a event that would replace that input
tag by a select
tag.
I used the excellent MooTools JavaScript framework for this code.
Here is the Javascript code:
window.addEvent('domready', function() {
//Replaces the input field by a select field
oldEdtCity = $('edtCity');
newEdtCity = $(document.createElement('select'));
newEdtCity.setAttribute('id', 'edtCity');
newEdtCity.setAttribute('name', 'city');
newEdtCity.options = new Option('- Enter your postal code -', '- Enter your postal code -');
oldEdtCity.parentNode.replaceChild(newEdtCity, oldEdtCity);
//Adding event watcher on the postcode field
$('edtPostcode').onkeyup = function(e) {
var target = new Event(e).target;
if (target.getValue().length 4) {
new Ajax('_ajax.php?', Object.toQueryString({'task': 'postcode', 'cp': $('edtPostcode').getValue()}), {
method: 'get',
onComplete: function(responseText) {
//Function that will populate the select with cities returned by the ajax request
var response = Json.evaluate(responseText);
var cities = $('edtCity').options;
cities.length = 0;
if (response.cities == '') {
cities = new Option('- No corresponding city -', '- No corresponding city -');
}
else {
cities = new Option('- Choose -', '- Choose -');
response.cities.each(function(city) {
cities[cities.length] = new Option(city, city);
});
}
}
}).request();
}
};
});
So, when the page loads, it contains a simple input
tag, and once it has loaded, the Javascript replaces that input
tag by a select
tag.
Error Handling and Timeout
This is far from being perfect. What if the AJAX request fails or timeouts?
Even if the the errors are different, the resolution is the same for both: simply turn back the select
tag into a input
tag. So in this case, one function will be called, no matter which error rises:
function requestFailure() {
//Replaces the select field by an input field
oldEdtCity = $('edtCity');
newEdtCity = $(document.createElement('input'));
newEdtCity.setAttribute('id', 'edtCity');
newEdtCity.setAttribute('name', 'city');
newEdtCity.setAttribute('type', 'text');
oldEdtCity.parentNode.replaceChild(newEdtCity, oldEdtCity);
}
Error Handling
As is, the MooTools framework didn't support error handling in Ajax object. However, one of this framework developer, Aaron Newton, wrote an Ajax extension that is to be used with the framework in order to specify a function that will be called if the request fails.
As said before, when I wrote that code the MooTools framework didn't support error and timeout handling. As of now, the latest version of MooTools supports both error and timeout handling. I think I will soon rewrite a new version of this code using the latest MooTools version, as it is more elegant.
Once this extension added, we simply need to give a onFailure
parameter to Ajax object call in the previous code.
Here is what the new Ajax object (abbreviated) call looks like :
new Ajax('_ajax.php?' + Object.toQueryString({'task': 'postcode', 'cp': $('edtPostcode').getValue()}), {
method: 'get',
onComplete: function(responseText) { [...] },
onFailure: requestFailure }
).request();
That's it. If the request fails, requestFailure
function is called.
Timeout Handling
Again, nothing for timeout handling in MooTools at the time I wrote that code.
I used Jeremy Keith excellent technique described in his book Bulletproof Ajax.
The technique is simple: after starting the request, simply launch a timer that cancels the request after a certain time. So when the requests takes too long, it is canceled and the requestFailure
function is called:
var timer = setTimeout(function() {
ajx.cancel();
requestFailure();
}, 5000);
If the request success, the timer
must be stopped:
clearTimeout(timer);
So, adding all this code to the original function gives us the final code:
window.addEvent('domready', function() {
[...]
$('edtPostcode').onkeyup = function(e) {
var target = new Event(e).target;
var ajx, timer;
if (target.getValue().length == 4) {
ajx = new Ajax('_ajax.php?' + Object.toQueryString({'task': 'postcode', 'cp': $('edtPostcode').getValue()}), {
method: 'get',
onComplete: function(responseText) {
[..]
},
onFailure: requestFailure
});
timer = setTimeout(function() {
ajx.cancel();
requestFailure();
}, 5000);
ajx.request();
}
};
});
I used a 5 seconds timers as this has to be fast. The user will not wait for a long time, especially as the select
field directly follows the input
field that launches the request.
Conclusion
The main point here was to show how to build a form that uses Ajax while making sure that users that don't use JavaScript will still be able to fill data in.
So, what do you think? Is it usefull for you?
blog comments powered by Disqus