Scrolling Table Control ala AJAX

aka Rico innerHTML Tutorial

© 2005 Yonah Russ & Mirimar Networks

Disclaimer:

The Experiment

My goal in this experiment was to create an interface similar to that of many mail clients where the upper section is a brief description of the messages waiting and the lower section is a preview of the message currently highlighted.

The Results

Mail browser without mouseover
In this demo, you can select messages and scroll the table using your up and down arrow keys. To implement page-up and page-down is not very complicated. See the demo.
Mail browser with mouseover
In this demo, you can select messages and scroll the table using your up and down arrow keys. Additionally, you can select a message by hovering the mouse over it. You cannot scroll the list via the mouse although implementing a scrollbar for the mouse should not be too complicated. See the demo.

The Explanation

In approaching the upper section, I considered three options:

  1. A table
  2. A select box
  3. A list of lists

A table seemed the most natural and appropriate way to format the data- giving the added bonus that a page reader would be able to connect the headers to the appropriate columns.

A select box would have solved the scrolling issue (leaving very little AJAX in the page) but I did not know how I would go about formatting the columns nicely.

A list of lists made sense as the list of messages is an ordered list while each message row is an unordered list of information about the message. The downside to the list was that it would be less accessible than the table.

I decided to use a table in the end. Unfortunately this caused problems with IE and I quote:

The property is read/write for all objects except the following, for which it is read-only: COL, COLGROUP, FRAMESET, HTML, STYLE, TABLE, TBODY, TFOOT, THEAD, TITLE, TR. The property has no default value...

To change the contents of the table, tFoot, tHead, and tr elements, use the table object model described in How to Build Tables Dynamically. For example, use the rowIndex property or the rows collection to retrieve a reference to a specific table row. You can add or delete rows using the insertRow and deleteRow methods. To retrieve a reference to a specific cell, use the cellIndex property or the cells collection. You can add or delete rows using the insertCell and deleteCell methods. To change the content of a particular cell, use the innerHTML property.

MSDN

Well I got the effect working in Mozilla before I learned this so IE is just going to feel left out this time. It might be possible to get the effect working correctly in IE if I used the list method.

Handling the AJAX and Rico

I chose to use the Rico library to handle the AJAX requests because every blog on the internet has been quoted as saying that Rico provides simplified Ajax handling...

Unfortunately, I wasted precious hours trying to decipher what and how Rico handles the AJAX. There is little to no documentation on their site save a few demos with some hints as to how things work.

At my wits end, I wrote to one of the listed developers, however, and he was able to point me in the right direction at once.

Here are some tips I've picked up:

  1. AJAX is about XML that means you need to return an XML mime type (ala "Content-type: text/xml\n\n")
  2. Rico expects an <ajax-response> element at the root of your xml response.
  3. Do not registerAjaxInfo before the body loads

If you are looking for an xml template to use as a response for Rico, try this: Rico_XML_AJAX_Response_Template.xml

That template should be good for replacing innerHTML. Other Rico requests may require a slightly different format of response.

One other tidbit I've picked up is that you can have multiple <response> elements inside one <ajax-response> element. Presumably that lets you replace the innerHTML of several elements in one AJAX transaction.

Populating the Table

When the page loads it contains a table with a header and one dummy row as follows:


  <table border="0" width="100%">
    <thead>
    <tr  align="left">
      <th width="10%">ID</th><th>Subject</th><th width="20%">Sender</th><th width="20%">Date</th>
    </tr>
    </thead>
    <tbody id="mailHeaders">
    <tr><td> </td><td> </td><td> </td><td> </td></tr>
    </tbody>   
   </table>

I give the TBODY tag the id mailHeaders. Rico is going to use this id to replace the html inside the TBODY tag with the results from our AJAX request.

How do we set Rico to handle this? The answer is with the following JavaScript:

First we add this inside our HEAD tags:


<script type="text/javascript" src="prototype.js">
</script>
<script type="text/javascript" src="rico.js">
</script>

Then in an external .JS file or inside another SCRIPT tag, define the following function:

function registerAjaxInfo() {
   ajaxEngine.registerRequest( 'getMailHeaders', 'getMailHeaders.pl' );
   ajaxEngine.registerAjaxElement( 'mailHeaders' );
   ajaxEngine.registerRequest( 'getMailPreview', 'getMailPreview.pl' );
   ajaxEngine.registerAjaxElement( 'mailPreview' );
}

The first line of the function defines a request. The request is called getMailHeaders and when activated, the request will query the URL getMailHeaders.pl.

To make that clear, a GET request will be sent for the URL getMailHeaders.pl. Rico expects that this request will return an XML response as discussed above. The request may pass some parameters when it is sent. We'll see that soon.

The second line defines an element which can "receive" a response. The ajaxEngine will now expect XML responses for that element ID. When it receives a response with a matching element ID, the ajaxEngine will attempt to replace the innerHTML of that element with the contents of the response.

The second set of lines does the same, except that we are setting up the preview DIV

Next, for convenience sake, we set up some functions to actually send the requests:


function getMailHeaders(headersStart,headersLimit) {
   ajaxEngine.sendRequest( 'getMailHeaders',
                           "start=" + headersStart,
                           "limit=" + headersLimit );
}
function getMailPreview(messageID) {
   ajaxEngine.sendRequest( 'getMailPreview',
                           "current=" + messageID );
                           
                          

These functions help us pass the arguments we want to the sendRequest function. In the first function we send the request getMailHeaders which we just registered. With that request we send two argument pairs which are passed on to our serverside query handler. In this case, we are passing the indexes of the first and last row we want to be returned in the response.

In the second function, we do something similar, but we pass the index of the currently highlighted message for retrieval. In reality, the demo pages return a random message each time regardless of the messageID passed, but in theory, the serverside handler could fetch that specific message and replace the contents of the DIV just as easily

Finally, when the body loads, we register everything and send our first requests:

function magic(){
    registerAjaxInfo()
    getMailHeaders(start,limit);
    getMailPreview(currentMessage);
    highlightOn(currentOnScreen);
}
...
<body onLoad="magic()"...  
 

Basically we've populated the table and the preview with their first real content.

The only new function here is the highlightOn function. This is really very simple JavaScript so I won't go into it but as you can probably guess it is complemented by another function, highlightOff and together, they turn the highlighting on and off.

What I will mention here is a slight bug which I have not worked out. The highlighting does not work for some reason when called after a getMailHeaders request.

Scrolling the Table

Now we add the scrolling behavior. To do this we catch the up and down keys from the user using the following function:


function keys(key) {
  if (!key) {
    key = event;
    key.which = key.keyCode;
  }
  switch (key.which) 
  {
    case 40: // downkey
      if(currentMessage<=maxMessages)
      {
        highlightOff(currentOnScreen);
        currentMessage++;
        if(currentMessage>limit){
          start++;
          limit++;
          getMailHeaders(start,limit);
        }
        else
        {
          currentOnScreen++;
        }
        getMailPreview(currentMessage);
        highlightOn(currentOnScreen);
      }
      break;
...

Here we caught the down arrow key. The first thing we do is make sure we have not tried to pass the maximum number of messages. In the demo this is set at an arbitrary number but in reality it should be the number of messages total. In the event that we have reached the maximum, pressing the down arrow key does nothing.

Next, assuming we have not reached the maximum number of messages, we turn off the highlighting and advance the counter currentMessage which is the index of the message to be highlighted next.

Now we check to see if we need to scroll the table. If the new currentMessage is greater than the index of the last message visible on the screen (represented by limit), then we advance the start and limit variables and send an AJAX request for new table contents. The new table contents will start and finish one message later. This produces the scrolling effect.

After the request is sent, if need be, the next message is highlighted(see bug above) and the preview of the next message is retrieved by another AJAX request.

The code below handles scrolling up in a similar fashion:

   case 38: // upkey
      if(currentMessage>1)
      {
        highlightOff(currentOnScreen);
        currentMessage--;
        if(currentMessage<start){
          start--;
          limit--;
          getMailHeaders(start,limit);
        }
        else
        {
          currentOnScreen--;
        }
        highlightOn(currentOnScreen);
        getMailPreview(currentMessage);
      }
      break;
  }
}

Conclusions

In conclusion, it is disappointing that this project is not as simple in IE. It seems that for some ideological reason, MS has chosen that table elements must be dealt with differently than other HTML elements and in this case, doing so forces the programmer to either maintain separate code for each browser or to sacrifice accessibility

AJAX is a wonderful tool. In our case we've demonstrated a much more friendly mail browsing interface when compared to the likes of today's webmail systems. Never more will we have to click through pages of new messages. Instead we can scroll.

Back to the top | Back to Mirimar Networks Case Studies

Valid HTML 4.01!