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.

<select id="edtCity" name="city">
    <option>- Enter your postal code -</option>
</select>

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).

<input type="text" id="edtCity" name="city" />

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?

A WSDL Endpoint for Yahoo! REST Api

As I am currently learning .NET and taking interest in SOA, I was searching for free web services using WSDL as their access point.

I headed for Google website.

Google Search Api

I quickly reached Google developer page only to find out that the Search API was deprecated and replaced by the Google AJAX Search API. The search web service is still available, but Google don’t issue key to use it anymore, making it unusable for new users like me. I found many blog posts criticizing this decision, but this is not the point here.

I headed to my second choice search engine: Yahoo!

Yahoo! REST Api

As you might know, Yahoo! provides a REST Api to use their web search service. The call is made using a simple URI with GET parameters, and the response uses a custom XML format.

In my opinion, it is a shame that their don’t offer a WSDL entry point, as it is a W3C open standard. It also forces each developer to write custom XML parsing code in order to process the answer, while there are many Api to call and process responses from WSDL format services without even seeing any XML.

Again, I stumbled upon may blog posts criticizing that decision, but again, it is not the point here. I had to go for my third search engine choice: Live Search (formerly MSN Search).

Live Search

On Windows Live Dev page, I crawled to find information on the search API. I found that the site is mess, as they recently changed name from msn to live. However, I managed to find what I was looking for: a WSDL file.

After a fight with Live account setting page, I finally found a way to obtain a Application ID that is needed to get a response from their web service.

I was now able to start coding my test application, at last!

First Test Application

Inspecting Live Search response format, I quickly wrote up two structs to hold search results.

The first one was designed to hold a result from a search:

public struct SearchResult
{
    public string Title;
    public string Summary;
    public string Url;
    public string DisplayUrl;
    public string Date;
}

The second one was designed to hold a set of SearchResult and an eventual error message:

public struct SearchResponse
{
    public List ResultSet;
    public string Error;
}

I added the WSDL file URI as a web reference to my project, and wrote up a function in order to call the Live Search web service:

public SearchResponse LiveSearch(string query)
{
    SearchResponse rs = new SearchResponse();
    rs.ResultSet = new List();
    SearchResult sr;
    try
    {
        com.msn.search.soap.SearchRequest req = new com.msn.search.soap.SearchRequest();
        com.msn.search.soap.SourceRequest[] srcreq = new com.msn.search.soap.SourceRequest();
        srcreq = new com.msn.search.soap.SourceRequest();
        srcreq.Source = com.msn.search.soap.SourceType.Web;
        req.Requests = srcreq;
        req.AppID = LiveApiID;
        req.Query = query;
        req.CultureInfo = "en-US";
        foreach (com.msn.search.soap.SourceResponse response in new com.msn.search.soap.MSNSearchService().Search(req).Responses)
        {
            foreach (com.msn.search.soap.Result result in response.Results)
            {
                sr = new SearchResult();
                sr.Title = result.Title;
                sr.Summary = result.Summary;
                sr.Url = result.Url;
                sr.DisplayUrl = result.DisplayUrl;
                if (result.DateTime != null) sr.Date = result.DateTime.ToString();
                rs.ResultSet.Add(sr);
            }
        }
    }
    catch (SoapException soapex)
    {
        rs.ResultSet.Clear();
        rs.Error = soapex.Detail.InnerText;
    }
    catch (Exception ex)
    {
        rs.ResultSet.Clear();
        rs.Error = ex.Message;
    }
    return rs;
}

A few exceptions later, my Windows Form test application was working fine. But I wanted more.

Second Test Application

During my search for a Yahoo! search Api, I stumbled upon this article , that gave me an idea: why not usie .NET to build a webservice that would do the search and return results using Yahoo! REST Api?

Looking at wat was returned by Yahoo! REST Api, I slightly modified my initial SearchResult struct:

public struct SearchResult
{
    public string Title;
    public string Summary;
    public string Url;
    public string ClickUrl;
    public string DisplayUrl;
    public string ModificationDate;
    public string MimeType;
    public string Source;
}

I then created a new ASP.NET project to hold my web service, copied the two struct into the new class and wrote a function to call Yahoo! RESP Api, parse it using XPath and fill the responses into a SearchResponse:

[WebMethod]
public SearchResponse YahooSearch(string query, int maxResult)
{
    SearchResponse rs = new SearchResponse();
    rs.ResultSet = new List();
    SearchResult sr;
    try
    {
        HttpWebRequest req = WebRequest.Create("http://api.search.yahoo.com/WebSearchService/V1/webSearch?appid=" + YahooApiID + "&#38;query=" + query + "&#38;results=" + maxResult.ToString()) as HttpWebRequest;
        using (HttpWebResponse res = req.GetResponse() as HttpWebResponse)
        {
            XmlDocument doc = new XmlDocument();
            XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
            ns.AddNamespace("yns", "urn:yahoo:srch");
            doc.Load(new StreamReader(res.GetResponseStream()));
            foreach (XmlNode node in doc.SelectNodes("/yns:ResultSet/yns:Result", ns))
            {
                sr = new SearchResult();
                sr.Title = node.SelectSingleNode("yns:Title/text()", ns).Value;
                sr.Summary = node.SelectSingleNode("yns:Summary/text()", ns).Value;
                sr.Url = node.SelectSingleNode("yns:Url/text()", ns).Value;
                sr.ClickUrl = node.SelectSingleNode("yns:ClickUrl/text()", ns).Value;
                sr.DisplayUrl = node.SelectSingleNode("yns:DisplayUrl/text()", ns).Value;
                sr.ModificationDate = node.SelectSingleNode("yns:ModificationDate/text()", ns).Value;
                sr.MimeType = node.SelectSingleNode("yns:MimeType/text()", ns).Value;
                rs.ResultSet.Add(sr);
            }
        }
    }
    catch (SoapException soapex)
    {
        rs.ResultSet.Clear();
        rs.Error = soapex.Detail.InnerText;
    }
    catch (Exception ex)
    {
        rs.ResultSet.Clear();
        rs.Error = ex.Message;
    }
    return rs;
}

I am not new to XML, but still, I had trouble with namespaces on this one. I know, I know, shame on me…

Anyway, that was working! I then copied Liave Search function into the ASP.NET class in order to provide access to both services trough WSDL, and wrote up a small Windows Form application to do some testing.

Conclusion

I don’t think there is much to conclude from this experiment. To most experienced .NET developers, this will look like a piece of cake, but as I said this is all new to me. I learned some things along the way, so I think it was worth it.

If you want to let me know what you think, that you found mistakes or you want to enlighten me with your knowledge, please drop a comment.