AJAX and Mrs. King

November 14th, 2011

Scarecrow and Mrs. King was a TV show from way way back. For some reason, it came to mind right when I was typing the title of this post. Scarecrow was the code name for a spy. Mrs. King was a housewife. They spied on people and helped others. I don’t recall the details but Scarecrow was played by Bruce Boxleitner who was recently seen in Tron: Legacy but not much else that I know of. Mrs. King was played by the late Kate Jackson from the original Charlie’s Angels.

On to Ajax

The text messages “webapp” I was writing a while back was designed to show a log of incoming and outgoing messages that are being handled by an automated server program. The program would simply check a mailbox and create automated responses for incoming emails. Each incoming and outgoing message is logged as well as some other status information. Certain incoming messages would cause actions like broadcast messages to a known group of email addresses.

The last work I did on the app was on the server side and involved changes to how the HTML was generated. The HTML that included a table for each log entry with graphics filling the various cells to form speech bubbles was simply too complex for mobile Safari. When large numbers of messages are in the log, scrolling becomes jerky. I changed the HTML to use a single div for each message and then rely on the browser shading and corner rounding to get a nice look. Unfortunately, it’s not really a nice look.

I don’t have picture because the server it offline right now.

The AJAX code involves some JavaScript on the client and a PHP file on the server. The client code keeps track of the size of the log file on the server and passes the size to the server as part of the URL used for the AJAX code. AJAX is accomplished using XMLHttpRequest().

function AddToMessages( SomeStuff, ScrollQuick )
{
    var CanScrollToBottom = false;
    // At or near bottom? OK to auto-scroll to show new content.
    if( MessageScroll.maxScrollY >= 0 
        || MessageScroll.y == MessageScroll.maxScrollY 
        || Math.abs( MessageScroll.maxScrollY - MessageScroll.y ) < 10 )
        CanScrollToBottom = true;
                
    var TheScroller = document.getElementById( "scroller" );
    if( TheScroller == undefined || TheScroller == null )
        return;
    var NewContent = document.createElement( "div" );
    NewContent.innerHTML = SomeStuff;

    //FilterMessages( NewContent.firstChild );

    var NewNodeCount = 0;
    while( NewContent.firstChild )
    {
        if( NewContent.firstChild.nodeType == 8 )
        {
            MessageListFileSize = NewContent.firstChild.data;
            NewContent.firstChild.parentNode.removeChild( NewContent.firstChild );
        }
        else
        {
            ++NewNodeCount;
            TheScroller.appendChild( NewContent.firstChild ); // Will MOVE it from here to there.
        }
    }
            
    MessageScroll.refresh();
    if( CanScrollToBottom && NewNodeCount > 0 )
        ScrollMessageToBottom( ScrollQuick );
}

function FillMessageList()
{
    var Container = document.getElementById( "scrollercontainer" );
    if( Container == undefined || Container == null )
        return;
    var CleanupWaitAnimation = false;
    if( MessageListFileSize == 0 )
        Container.style.background = "white url('./wait.gif') no-repeat center center";
			
    Request = new XMLHttpRequest();
    if( Request == undefined || Request == null )
        return;

    Request.onreadystatechange = function()
    {
        if( Request.readyState == 4 && Request.status == 200 )
        {
            HasData = 0;
            if( Request.responseText.indexOf( "<!--No Data" ) < 0 )
                HasData = 1;
            AddToMessages( Request.responseText, HasData );

            var Container = document.getElementById( "scrollercontainer" );
            if( Container != undefined && Container != null )
            {
                Container.style.backgroundImage = "none";
                Container.style.backgroundColor = "#dbe2ed";
            }
            if( HasData )
                setTimeout( function () { FillMessageList(); }, 150 );
            else
                setTimeout( function () { FillMessageList(); }, 5000 );
           }
        }

    Request.open( "GET", "getdata.php?data=messages&last=" + MessageListFileSize + "&t=" + Math.random(), true );
    Request.send();
}
FillMessageList() Function

The FillMessageFunction() shown above will change the scrollcontainer element so that it shows an animation while the request is being made. This only shows up the first time the list is filled because the contents always cover it up after the first set of data is retrieved.

The onreadystatechange function is set to be a new inline, or whatever they call it, function that will get called when the AJAX request changes state. The finished state is the only one we care about here. When the AJAX request if finished, the code changes the background to remove the animation and to show the background in the correct color. The AddToMessages() function creates a temporary DOM element and sets it to contain whatever HTML comes back from the server. The server response is used as text and is not expected to be valid XML. The content is then appended to the data already available.

It is up to the server to create the HTML for the list.

And now the most important part of this operation. The code checks for an HTML comment node in the data coming from the server. This is parsed to get the current file size of the log file on the server. Each time a request is made, the file size is passed to the server in the request URL and the server PHP file compares this to the current size. If there is a difference then the server code creates HTML data for only the part of the log file that the client has not seen.

This would work even if multiple clients are running. It minimizes the amount of data that is passed to the client for any one request. In addition to this, the server can control how much data is passed back and can make the client use multiple requests to get the entire log file. This keeps the client responsive if the log file is huge.

I use iScroll for the list. It is a minimal framework for creating scrolling regions within a page so that the page can be made to look like a native IOS app. Without this, the header on this list would scroll up out of view when the entire page is scrolled.

My older post SMS-Style Text Display Using HTML shows how this looked on an iPhone simulator when I had the pretty speech bubbles.

I have other AJAX code to update a list of registered users. It creates a new list each time the request is fulfilled. That one uses a timestamp on the registered user file to keep track of when the AJAX request needs to return a new set of data. Only when valid HTML beyond the HTML comment is returned from the server is that list updated.

I am still new at writing JavaScript so I used integers to keep track of things that should be boolean values. Forgive my ignorance.

Leave a Reply

You must be logged in to post a comment.