Friday, 27 June 2008

Graceful Lists

In a project I was working on recently I needed to return a list. I can't remember what the list consisted of (okay not that recent), but each item in the list was going to require some processor power to generate the item's associated content. I didn't want to take the standard server side approach of building the list prior to rendering the page. While the list size was fixed, the processing time for each item varied, making timeouts a factor. The age of the data was also important, preventing the possibility of server side caching over any length of time.

The proposition I came up with is not an uncommon one. The idea was to create a page which loaded each item on the list incrementally using XHR. But what about browsers without JavaScript or with JavaScript switch off? Initially, I considered drastically reducing the amount of results returned by the page using paging. My main concern here was that while you can generally tell whether the user's browser is JavaScript enabled from the User Agent, it's impossible (as far as I'm aware) to detect whether the user has opted to disable JavaScript, on the first request to the server. Also, paging drives me nuts, I prefer to scroll!

I had assumed browsers broadcasted the status of scripting abilities and alike in the header of each request. I have no reason for assuming this, other than it sounds like quite a good idea to me.

Without being able to reliably detect a browser's scripting ability, the paging solution risks serving an empty page to browsers with JavaScript disabled. While investigating this problem I came across 123-reg.co.uk. 123-reg.co.uk uses a similar pattern to the one I've suggested, for displaying domain availability. User's with JavaScript enabled browsers see the list built in front of them using Ajax. Everyone else is served a pre-rendered list. What is clever (in its simplicity) is the way in which 123-reg.co.uk detects JavaScript. On the page where the user enters their search terms, a script modifies the action URL of the form to include the parameter "ajax_enabled=true". The results page is rendered based on the value of that parameter.

The example provided by 123-reg.co.uk is a great way of detecting script. The problem with this approach is the reliance on the search page (to modify the URL), my list exists on a page that is a possible entry point to the application. I'm not making it easy for myself. As a result of the previous example I also toyed with the idea of a page that assumes JavaScript is switched on, but with a META Refresh tag in the header. If JavaScript is enabled, remove the META tag. If JavaScript is disabled, the page redirects (via the META tag) to a pre-rendered page. I was put off by the idea of effectively breaking the Back button for users without JavaScript.

My eventual implementation was a pre-rendered list that assumed JavaScript was switched off. This list doesn't contain any of the extended content, so no heavy lifting that originally had me concerned. Each item on the list instead provides anchors to a second page that generates the extended content. Once the page is loaded, JavaScript enabled browsers move though each of the items, dynamically adding the extend content. This solution works extremely well for script enabled browsers, everything on one page with visual feedback for the processing going on server side. Unscripted browsers have the burden of an extra click to and from the extended content, but the initial page load dramatically reduced when compared to the server side approach described at the beginning of this post.

This particular problem made me think a lot about my audience and the merits of graceful degradation. Who am I making this application for? And more importantly, who am I willing to exclude? Within a corporate Intranet, these decisions are sometimes made by company guidelines and policies, a lot of the time by time constraints. In the open web, (I believe) accessibility is extremely important. If you are building something like Facebook, where you a targeting a market defined by age, gender or interest, your application should be accessible to text readers and main stream browsers alike. The experience has also showed me that I'm not so bothered about catering for users that have purposefully restricted the capability of their own browser (switching off script or CSS). I guess there might be a valid reason for doing this, but I'm unable to come up with one.

Saturday, 21 June 2008

Bridging ASP.NET & jQuery

Until recently I was inclined to use the ASP.NET AJAX framework when working with JavaScript within Visual Studio, if I used a library at all. I hadn't really been sold on the idea of JavaScript frameworks or libraries from the get go. I realise this sounds a bit ridiculous coming from an ASP.NET developer, but I enjoy writing JavaScript and saw these libraries as taking some of the fun out of it.

I eventually saw the benefits of a library when I started playing around with the wicked cool JavaScript intellisence and debugging support in Visual Studio 2008. Still, it was a mixed bag. I enjoyed the short cuts and the object descriptor’s provided through the script manager but was wary of the amount of JavaScript being generated in the background. I was also very conscious that, at least in my opinion, this library was built for developers who don't enjoy working with the client, in fact providing a level of abstraction such that the developer doesn't need to touch the client at all. For that last concern I found jQuery. jQuery isn’t just about creating short cuts around well trodden problems, jQuery enables you to work in an entirely more intuitive way. Thank you John Resig.

So now I have two libraries, I was torn. If I wanted to make use of ASP.NET great JSON web services I had to use the Microsoft library. If I wanted to do anything else I was using jQuery. It’s all sounding a bit bloated.

That was until I read a fantastic article @ Encosia called 3 mistakes to avoid when using jQuery with ASP.NET AJAX. In the article, Dave Ward explains the connection between the ASP.NET JSON Page Method and the client side AJAX library. What had never occurred to me and what Dave pointed out was that client and the server were not inseparable. In fact you can talk quite happily with your JSON enabled Page Methods and Web Services with jQuery alone.

"Great", I thought. "This is it!" I said. I started writing Ajax calls in the way Dave described, thrilled by the possibilities that laid before me. FireFox loved it, FireBug reported all's well. IE was not having it. "Aw", I thought. "Aw dear." I said. You can read up on the trials and tribulations of talking to an ASP.NET JSON Page Method on the Encosia blog, needless to say I experienced these and more. Once I had Page Methods working I had more problems with Web Services.

I should mention at this point that most of these problems arose from false assumptions on my part about certain aspects of the code, specifically the need to stringify the data parameter.

My conclusion was that the requirements of these calls were a bit too specific for jQuery's ajax function. So to the point of my post, I have written a very simple function that works much like jQuery's ajax method but is designed solely to work with ASP.NET JSON Page Methods and Web Services. I've namespace'd it to avoid collisions with other frameworks and I believe the scripts only external dependency is on that of json2.js from json.org.

/// <reference path="jquery-1.2.6.min.js">
/// <reference path="json2.js">

var k3r = new Object();
k3r.ajax = function() {}
k3r.ajax.xhr = function() {
 try { return new XMLHttpRequest(); } catch(e) {}
 try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
 try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
 return null;
}

k3r.ajax.request = function(obj) {
 var xhr = k3r.ajax.xhr();
 var loc = obj.location;
 var data = '';

 if (obj.type == 'GET') {
  for (var i in obj.pack) {
   if (data != '')
    data += '&';
    data += i + '=' + obj.pack[i];
  }
  loc += '?' + data;
  data = null;
 }
 else if (obj.type == 'POST') {
  data = JSON.stringify(obj.pack);
 }

 xhr.open(obj.type, loc, true); //true = async
 xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
   var response;
   try {
    response = JSON.parse(xhr.responseText);
   }
   catch (err) {
    response = err;
   }

   if (xhr.status == 200 && typeof obj.success != 'undefined')
    obj.success(response, xhr.status, xhr.statusText);
   else if (typeof obj.other != 'undefined')
    obj.other(response, xhr.status, xhr.statusText);
  }
 }

 xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
 xhr.send(data);
}

The method k3r.ajax.request takes an object containing 5 parameters; 'type', 'pack', 'location', 'success' and 'other'.

  • type: The verb associated with the request. Only GET & POST are currently catered for.
  • pack: A JSON object containing the input parameters.
  • location: The URL of the web service method.
  • success: Callback function if request is successful. JSON returned.
  • other: Catch-all callback function for everything else. Error message returned.

An example request would look like this:

k3r.ajax.request({
 type: 'GET',
 pack: {id: 1},
 location: 'Example.aspx/GetExample',
 success: function(obj) {
  console.info(obj); //FireBug required;
 },
 other: function() {
 }
});

The benefits of k3r.ajax.request over the jQuery ajax function, when looking at ASP.NET JSON Web Services specifically, are as follows:

  • You can pass the input parameters as a JSON object, no need to stringify.
  • The IE Content-Type strangeness is taken care of behind the scenes.
  • k3r.ajax.request is not dependant on jQuery, it should work with any library.
  • Should work with JSON enabled ASP.NET Page Methods and Web Services straight out of the box.

What comes next? When I have the time I'd like to explore the possibility of adding this and other ASP.NET helpers into a jQuery plugin. Also, I'm certain there is some room for improvement with my error handling.

Twitter Updates