JavaScript EditorFreeware JavaScript Editor     Ajax Tutorials 



Main Page

Previous Page
Next Page

Communication Control Patterns

You already know, from Chapter 2, how to communicate with the server from JavaScript. The real question is: What is the best way to initiate and continue to make requests back to the server? In some cases, it may be best to preload information from the server so that it is available immediately upon some user action. In other cases, you may want to send data to, or receive data from, the server in varying intervals. Perhaps everything shouldn't be downloaded at once, and instead should be downloaded in a particular sequence. Ajax affords you fine granularity in controlling the communication between client and server to achieve your desired behavior.

Predictive Fetch

In a traditional web solution, the application has no idea what is to come next. A page is presented with any number of links, each one leading to a different part of the site. This may be termed "fetch on demand," where the user, through his or her actions, tells the server exactly what data should be retrieved. While this paradigm has defined the Web since its inception, it has the unfortunate side effect of forcing the start-and-stop model of user interaction upon the user. With the help of Ajax, however, this is beginning to change.

The Predictive Fetch pattern is a relatively simple idea that can be somewhat difficult to implement: the Ajax application guesses what the user is going to do next and retrieves the appropriate data. In a perfect world, it would be wonderful to always know what the user is going to do and make sure that the next data is readily available when needed. In reality, however, determining future user action is just a guessing game depending on your intentions.

There are simple use cases where predicting user actions is somewhat easier. Suppose you are reading an online article that is separated into three pages. It is logical to assume that if you are interested in reading the first page, you're also interested in reading the second and third page. So if the first page has been loaded for a few seconds (which can easily be determined by using a timeout), it is probably safe to download the second page in the background. Likewise, if the second page has been loaded for a few seconds, it is logical to assume that the reader will continue on to the third page. As this extra data is being loaded and cached on the client, the reader continues to read and barely even notices that the next page comes up almost instantaneously after clicking the Next Page link.

Another simple use case happens during the writing of an e-mail. Most of the time, you'll be writing an e-mail to someone you know, so it's logical to assume that the person is already in your address book. To help you out, it may be wise to preload your address book in the background and offer suggestions. This approach is taken by many web-based e-mail systems, including Gmail and AOL Webmail. The key, once again, is the "logical-to-assume" criterion. By anticipating and pre-loading information related to the user's most likely next steps, you can make your application feel lighter and more responsive; by using Ajax to fetch information related to any possible next step, you can quickly overload your server and make the browser bog down with extra processing. As a rule of thumb, only pre-fetch information when you believe it's logical to assume that information will be requisite to completing the user's next request.

Page Preloading Example

As mentioned previously, one of the simplest and most logical uses of the Predictive Fetch pattern is in the preloading of pages in an online article. With the advent of weblogs, or blogs for short, everyone seems to have been bitten by the publishing bug, writing their own articles on their own web sites. Reading long articles online is very difficult on the eyes, so many sites split them into multiple pages. This is better for reading, but takes longer to load because each new page brings with it all of the formatting, menus, and ads that were on the original page. Predictive Fetch eases the load on both the client and server by loading only the text for the next page while the reader is still reading the first page.

To begin, you'll need a page that handles the server-side logic for page preloading. The file ArticleExample.php contains code for displaying an article online:

<?php
    $page = 1;]
    $dataOnly = false;
    if (isset($_GET["page"])) {
        $page = (int) $_GET["page"];
    }

    if (isset($_GET["data"]) && $_GET["dataonly"] == "true") {
        $dataOnly = true;
    }

    if (!$dataOnly) {
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang=" en" lang=" en">
    <head>
        <title>Article Example</title>
        <script type=" text/javascript" src=" zxml.js"></script>
        <script type=" text/javascript" src=" Article.js"></script>
        <link rel=" stylesheet" type=" text/css" href=" Article.css" />
    </head>
    <body>
        <h1>Article Title</h1>
        <div id=" divLoadArea" style=" display:none"></div>
<?php
        $output = "<p>Page ";

        for ($i=1; $i < 4; $i++) {
            $output .= "<a href=\" ArticleExample.php?page=$i\" id=\"aPage$i\"";
            if ($i==$page) {
                $output .= "class=\" current\"";
            }
            $output .= ">$i</a> ";
        }
        echo $output;
    }

    if ($page==1) {
        echo $page1Text;
    } else if ($page == 2) {
        echo $page2Text;
    } else if ($page == 3) {
        echo $page3Text;
    }

    if (!$dataOnly) {
?>
    </body>
</html>
<?php
    }
?>

By default, this file displays the first page of text for the article. If the page query string parameter is specified, such as page=2, then it shows the given page of the article. When the query string contains dataonly=true, the page outputs only a <div/> element containing the article text for the given page. Combining this with the page parameter enables you to retrieve just any page of the article that you need.

The HTML in this page has a space for the article title as well as a <div/> element used for loading extra pages. This <div/> element has its display property set to none to ensure that its contents are not displayed accidentally. The PHP code immediately following contains logic to output a list of pages available for the article. In this example, there will be three pages of content, so there are three links output at the top (see Figure 3-1).

Image from book
Figure 3-1

The current page is assigned a CSS class of current so that the user knows which page he or she is viewing. This class is defined in Article.css as:

a.current {
    color: black;
    font-weight: bold;
    text-decoration: none;
}

When the reader is viewing a particular page, the link for that page becomes black, bold, and is no longer underlined, providing a clear indication of the page that he or she is reading. By default, these links simply load the same page and change the page parameter of the query string; this is the way that most web sites handle multipage articles. Using Predictive Fetch, however, will improve the user's experience and the speed with which the data is available.

Several global JavaScript variables are required to implement Predictive Fetch for this example:

var oXmlHttp = null;        //The XMLHttp object
var iPageCount = 3;         //The number of pages
var iCurPage = -1;          //The currently displayed page
var iWaitBeforeLoad = 5000; //The time (in ms) before loading new page
var iNextPageToLoad = -1;   //The next page to load

The first variable is a global XMLHttp object that is used to make all requests for more information. The second, iPageCount, is the number of pages used in this article. (This is hard coded here, but in actual practice this would have to be generated.) The iCurPage variable stores the page number currently being displayed to the user. The next two variables deal directly with the preloading of data: iWaitBeforeLoad is the number of milliseconds to wait before loading the next page, and iNextPageToLoad contains the page number that should be loaded once the specified amount of time has passed. For this example, a new page is loaded behind the scenes every 5 seconds (5000 milliseconds), which should be long enough for someone to read the first few sentences of an article to determine if it's worth reading the rest. If the reader leaves before 5 seconds are up, chances are they have no intention of reading the rest of the article.

To begin the process, you'll need a function to determine the URL for retrieving a particular page. This function, getURLForPage(), accepts a single argument that specifies the page number you want to retrieve. Then, the current URL is extracted and the page parameter is appended to the end:

function getURLForPage(iPage) {
    var sNewUrl = location.href;
    if (location.search.length > 0) {
        sNewUrl = sNewUrl.substring(0, sNewUrl.indexOf("?"))
    }
    sNewUrl += "?page=" + iPage;
    return sNewUrl;
}

This function begins by extracting the URL from location.href, which gives the complete URL for the page, including the query string. Then, the URL is tested to see if there is a query string specified by determining if the length of location.search is greater than 0 (location.search returns just the query string, including the question mark, if there is one specified). If there is a query string, it is stripped off using the substring() method. The page parameter is then appended to the URL and returned. This function will come in handy in a number of different places.

The next function is called showPage(), and as you may have guessed, it is responsible for displaying the next page of the article:

function showPage(sPage) {

    var divPage = document.getElementById("divPage" + sPage);

    if (divPage) {
        for (var i=0; i < iPageCount; i++) {
            var iPageNum = i+1;
            var divOtherPage = document.getElementById("divPage" + iPageNum);
            var aOtherLink = document.getElementById("aPage" + iPageNum);

            if (divOtherPage && sPage != iPageNum) {
                divOtherPage.style.display = "none";
                aOtherLink.className = "";
            }
        }
        divPage.style.display = "block";
        document.getElementById("aPage" + sPage).className = "current";
    } else {
        location.href = getURLForPage(parseInt(sPage));
    }
}

This function first checks to see whether the given page has a <div/> element already loaded. The <div/> element would be named divPage plus the page number (for example, divPage1 for the first page, divPage2 for the second, and so on). If this <div/> element exists, the page has been prefetched already, so you can just switch the currently visible page. This is done by iterating through the pages and hiding all pages except the one indicated by the argument sPage. At the same time, the links for each page are given an empty string for their CSS class. Then, the <div/> element for the current page has its display property set to block in order to show it, and the link for the page has its CSS class set to current.

If, on the other hand, the <div/> element doesn't exist, the page navigates to the next page in the article the old-fashioned way, by getting the URL (using the getURLForPage() function defined previously) and assigning it to location.href. This is a fallback functionality so that if the user clicks a page link before 5 seconds are up, the experience falls back to the traditional web paradigm.

The loadNextPage() function is used to load each new page behind the scenes. This function is responsible for ensuring that requests are made only for valid pages and that pages are retrieved in order and in the specified intervals:

function loadNextPage() {

    if (iNextPageToLoad <= iPageCount) {

        if (!oXmlHttp) {
            oXmlHttp = zXmlHttp.createRequest();
        } else if (oXmlHttp.readyState != 0) {
            oXmlHttp.abort();
        }

        oXmlHttp.open("get", getURLForPage(iNextPageToLoad)
                                           + "&dataonly=true", true);
        oXmlHttp.onreadystatechange = function () {

            //more code here
        };
        oXmlHttp.send(null);
    }
}

The function begins by ensuring that the page number stored in iNextPageToLoad is valid by comparing it to iPageCount. Passing this test, the next step is to see if the global XMLHttp object has been created yet. If not, it is created using the zXml library's createRequest() method. If it has already been instantiated, the readyState property is checked to ensure that it's 0. If readyState is not 0, the abort() method must be called to reset the XMLHttp object.

Next, the open() method is called, specifying that the request will get an asynchronous GET request. The URL is retrieved by using the getURLForPage() function and then appending the string "&dataonly=true" to ensure that only the page text is returned. With all of that set, it's time to move on to the onreadystatechange event handler.

In this case, the onreadystatechange event handler is responsible for retrieving the article text as well as creating the appropriate DOM structure to represent it:

function loadNextPage() {

    if (iNextPageToLoad <= iPageCount) {

        if (!oXmlHttp) {
            oXmlHttp = zXmlHttp.createRequest();
        } else if (oXmlHttp.readyState != 0) {
            oXmlHttp.abort();
        }

        oXmlHttp.open("get", getURLForPage(iNextPageToLoad)
                                           + "&dataonly=true", true);
        oXmlHttp.onreadystatechange = function () {

            if (oXmlHttp.readyState == 4) {
                if (oXmlHttp.status == 200) {
                    var divLoadArea = document.getElementById("divLoadArea");
                    divLoadArea.innerHTML = oXmlHttp.responseText;
                    var divNewPage = document.getElementById("divPage"
                                                              + iNextPageToLoad);
                    divNewPage.style.display = "none";
                    document.body.appendChild(divNewPage);
                    divLoadArea.innerHTML = "";
                    iNextPageToLoad++;
                    setTimeout(loadNextPage, iWaitBeforeLoad);
                }


            }
        };
        oXmlHttp.send(null);
    }
}

As discussed in the previous chapter, the readyState property is checked to see when it is equal to 4, and the status property is checked to make sure there was no error. Once you've passed those two conditions, the real processing begins. First, a reference to the load area <div/> element is retrieved and stored in divLoadArea. Then, the responseText from the request is assigned to the load area's innerHTML property. Since the text coming back is an HTML snippet, it will be parsed and the appropriate DOM objects will be created. Next, a reference to the <div/> element that contains the next page is retrieved (you know the ID will be divPage plus iNextPageToLoad) and its display property is set to none to ensure it remains invisible when it is moved outside of the load area. The next line appends divNewPage to the document's body, putting it into the regular viewing area for usage. Then the load area's innerHTML property is set to an empty string to prepare for another page to be loaded. After that, the iNextPageToLoad variable is incremented and a new timeout is set to call this function again after the specified period of time. This function will continue to be called every 5 seconds until all pages have been loaded.

Because this page should be functional without JavaScript, all this code is attached at runtime after determining if the browser is capable of using XMLHttp. Fortunately, the zXmlHttp object in the zXml library has a function, isSupported(), that can be used to determine this:

window.onload = function () {
    if (zXmlHttp.isSupported()) {
        //begin Ajax code here
    }
};

Inside this code block is where all the Predictive Fetch code will go, ensuring that browsers without XMLHttp support will not have their usability adversely affected by half-functioning code.

The first step in the process of setting up Predictive Fetch for the article is to determine which page the user is currently viewing. To do so, you must look into the URL's query string to see if the page parameter is specified. If it is, you can extract the page number from there; otherwise, you can assume that the page number is 1 (the default):

window.onload = function () {
    if (zXmlHttp.isSupported()) {

        if (location.href.indexOf("page=") > -1) {
          var sQueryString = location.search.substring(1);
          iCurPage = parseInt(sQueryString.substring(sQueryString.indexOf("=")+1));
        } else {
          iCurPage = 1;
        }

        iNextPageToLoad = iCurPage+1;

        //more code here
    }
};

In this section of code, the page's URL (accessible through location.href) is tested to see if page= has been specified. If so, the query string is retrieved by using location.search (which returns only the query string, including the question mark, that the call to substring(1) strips out). The next line retrieves just the part of the query string after the equals sign (which should be the page number), converts it to an integer using parseInt(), and stores the result in iCurPage. If, on the other hand, the page parameter isn't specified in the query string, the page is assumed to be the first one, and 1 is assigned to iCurPage. The last line in this section sets the iNextPageToLoad variable to the current page plus one, ensuring that you don't end up reloading data that is already available.

The next step is to override the functionality of the page links. Remember, by default, these links reload the same page with a different query string to specify which page should be displayed. If XMLHttp is supported, you need to override this behavior and replace it with function calls to the Ajax functionality:

window.onload = function () {
    if (zXmlHttp.isSupported()) {

        if (location.href.indexOf("page=") > -1) {
          var sQueryString = location.search.substring(1);
          iCurPage = parseInt(sQueryString.substring(sQueryString.indexOf("=")+1));
        } else {
           iCurPage = 1;
        }

        iNextPageToLoad = iCurPage+1;

        var colLinks = document.getElementsByTagName("a");
        for (var i=0; i < colLinks.length; i++) {
            if (colLinks[i].id.indexOf("aPage") == 0) {
                colLinks[i].onclick = function (oEvent) {
                    var sPage = this.id.substring(5);
                    showPage(sPage);

                    if (oEvent) {
                        oEvent.preventDefault();
                    } else {
                        window.event.returnValue = false;
                    }
                }
            }
        }

        setTimeout(loadNextPage, iWaitBeforeLoad);

    }
};

Here, a collection of links (<a/> elements) is retrieved using getElementsByTagName(). If the link has an ID beginning with aPage, it is a page link and needs to be addressed; this is determined by using indexOf() and checking for a value of 0, which indicates that aPage is the first part of the string. Next, an onclick event handler is assigned to the link. Within this event handler, the page number is extracted by using the ID of the link (accessible through this.id) and using substring() to return everything after aPage. Then, this value is passed into the showPage() function defined earlier in this section, which displays the appropriate page. After that point, you need only worry about canceling the default behavior of the link, which is to navigate to a new page. Because of differences in the Internet Explorer (IE) and DOM event models, an if statement is necessary to determine the appropriate course of action. If the event object was passed in to the function (the argument oEvent), then this is a DOM-compliant browser and the preventDefault() method is called to block the default behavior. If, however, oEvent is null, that means it's IE and so the event object is accessible as window.event. The returnValue property is then set to false, which is the way IE cancels default event actions.

After the links have been properly handled, a timeout is created for the initial call to loadNextPage(). This first call will take place after 5 seconds and will automatically load the second page at that point.

When you test this functionality yourself, try clicking the page links at different points in time. If you click it before 5 seconds have passed, you will see the page navigate to a new URL with the query string changed. The next time, wait about ten seconds and click a page link. You should see that the text changes while the URL does not (it is also noticeably faster than navigating to a URL).

Submission Throttling

Predictive Fetch is one pattern for retrieving data from the server; the other side of an Ajax solution is the sending of data to the server. Since you want to avoid page refreshes, the question of when to send user data is important. In a traditional web site or web application, each click makes a request back to the server so that the server is always aware of what the client is doing. In the Ajax model, the user interacts with the site or application without additional requests being generated for each click.

One solution would be to send data back to the server every time a user action occurs, similar to that of a traditional web solution. Thus, when the user types a letter, that letter is sent to the server immediately. The process is then repeated for each letter typed. The problem with this approach is that it has the possibility to create a large number of requests in a short amount of time, which may not only cause problems for the server but may cause the user interface to slow down as each request is being made and processed. The Submission Throttling design pattern is an alternative approach to this problematic issue.

Using Submission Throttling, you buffer the data to be sent to the server on the client and then send the data at predetermined times. The venerable Google Suggest feature does this brilliantly. It doesn't send a request after each character is typed. Instead, it waits for a certain amount of time and sends all the text currently in the text box. The delay from typing to sending has been fine-tuned to the point that it doesn't seem like much of a delay at all. Submission Throttling, in part, gives Google Suggest its speed.

Submission Throttling typically begins either when the web site or application first loads or because of a specific user action. Then, a client-side function is called to begin the buffering of data. Every so often, the user's status is checked to see if he or she is idle (doing so prevents any interference with the user interface). If the user is still active, data continues to be collected. When the user is idle, which is to say he or she is not performing an action, it's time to decide whether to send the data. This determination varies depending on your use case; you may want to send data only when it reaches a certain size, or you may want to send it every time the user is idle. After the data is sent, the application typically continues to gather data until either a server response or some user action signals to stop the data collection. Figure 3-2 outlines this process.

Image from book
Figure 3-2
Important

The Submission Throttling pattern should never be used for mission-critical data. If information must be posted to the server within a specific amount of time, you are better off using a traditional form to ensure the correct and timely delivery of the information.

Incremental Form Validation Example

As mentioned previously, Submission Throttling can be achieved through various user interactions. When using forms, it's sometimes useful to upload data incrementally as the form is being filled out. The most common usage is to validate data as the user is filling in the form instead of waiting until the end to determine any errors. In this case, you would most likely use the onchange event handler of each form element to determine when to upload the data.

The change event fires for a <select/> element whenever a different option is selected; it fires for other controls when its value has changed and it has lost focus. For example, if you typed a couple of letters into a text box and then clicked elsewhere on the screen (causing the text box to lose focus), the change event fires and the onchange event handler is called. If you click in the text box again, and then click elsewhere (or press the Tab key), the text box will lose focus but the change event will not fire because no changes have been made. Using this event handler for Submission Throttling can prevent extraneous requests.

Normally, the form validation is simply a precursor to submission. The form's submit button starts out disabled, becoming enabled only when all fields in the form have been validated by the server. For example, suppose you are running a web site where users must sign up to gain access to certain features. This may be a shopping site that requires sign-in to purchase items or a site that requires membership to access the message board. The items you'll want to be sure of when creating this new account are:

  • The user name must not be taken.

  • The e-mail address must be valid.

  • The birthday must be a valid date.

Of course, the type of data required will differ depending on your usage, but these items provide a good starting point for most applications.

The first step in creating such interaction is to define the HTML form that will collect the data. This form should stand alone so that it can be used even if Ajax calls aren't possible:

<form method=" post" action=" Success.php">
    <table>
        <tr>
            <td><label for="txtFirstName">First Name</label></td>
            <td><input type="text" id="txtFirstName" name=" txtFirstName" /></td>
        </tr>
        <tr>
            <td><label for="txtLastName">Last Name</label></td>
            <td><input type="text" id="txtLastName" name=" txtLastName" /></td>
        </tr>

         <tr>
             <td><label for="txtEmail">E-mail</label></td>
             <td><input type="text" id="txtEmail" name="txtEmail" /><img
src=" error.gif" alt="Error" id="imgEmailError" style="display:none" /></td>
         </tr>
         <tr>
             <td><label for=" txtUsername">Username</label></td>
             <td><input type=" text" id="txtUsername" name=" txtUsername" /><img
src=" error.gif" alt="Error" id="imgUsernameError" style=" display:none" /></td>
         </tr>
         <tr>
             <td><label for="txtBirthday">Birthday</label></td>
             <td><input type="text" id="txtBirthday" name=" txtBirthday" /><img
src=" error.gif" alt="Error" id="imgBirthdayError" style=" display:none" />
(m/d/yyyy)</td>
        </tr>
        <tr>
             <td><label for="selGender">Gender</label></td>
             <td><select id="selGender"
name="selGender"><option>Male</option></option>Female</option></select></td>
        </tr>
    </table>
    <input type="submit" id="btnSignUp" value="Sign Up!" />
</form>

You should note a few things about this form. First, not all fields will be validated using Ajax calls. The fields for first and last name as well as gender (represented by a combo box) don't require validation. The other fields — for e-mail, user name, and birthday — will make use of Ajax validation. Second, you'll note that these fields have a hidden image after the text box. This image is used only in the event that there is a validation error. Initially the images are hidden, because those browsers without Ajax capabilities should never see them. There is absolutely no JavaScript on this form; all the appropriate functions and event handlers are defined in a separate file.

A single function called validateField() is used to validate each form field. This is possible because each field uses the same validation technique (call the server and wait for a response). The only differences are the types of data being validated and which image to show if validation is unsuccessful.

The server-side functionality is stored in a file named ValidateForm.php. This file expects a name-value pair to be passed in the query string. The name should be the name of the control whose value is being checked, and the value should be the value of that control. Depending on the name of the control, this page runs the appropriate validation tests on the value. Then, it outputs a simple string in the following format:

<true|false>||<error message>

The first part of this string indicates whether the value is valid (true if it is; false if not). The second part, after the double pipes (||), is an error message that is provided only when the value is invalid. Here are a couple of examples of what the returned string might look like:

true||
false||Invalid date.

The first line represents a valid value; the second represents an invalid date.

Important

This is a plain-text message, although later in the book you will learn about using other data formats, such as XML and JSON for this same purpose.

The code that does the validation is as follows:

<?php
    $valid = "false";
    $message = "An unknown error occurred.";

    if (isset($_GET["txtUsername"])) {

        //load array of usernames
        $usernames = array();
        $usernames[] = "SuperBlue";
        $usernames[] = "Ninja123";
        $usernames[] = "Daisy1724";
        $usernames[] = "NatPack";

        //check usernames
        if (in_array($_GET["txtUsername"], $usernames)) {
            $message = "This username already exists. Please choose another.";
        } else if (strlen($_GET["txtUsername"]) < 8) {
            $message = "Username must be at least 8 characters long.";
        } else {
            $valid = "true";
            $message = "";
        }

    } else if (isset($_GET["txtBirthday"])) {

        $date = strtotime($_GET["txtBirthday"]);
        if ($date < 0) {
            $message = "This is not a valid date.";
        } else {
            $valid = "true";
            $message = "";
        }

    } else if (isset($_GET["txtEmail"])) {

        if(!eregi(
           "^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$",
           $_GET["txtEmail"])) {
            $message = "This e-mail address is not valid";
        } else {
            $valid = "true";
            $message = "";
        }
    }

echo "$valid||$message"; ?>

In this file, the first step is to determine which field to validate. This is done using the isset() function to test the $_GET array for a value. If there is a value for a particular field, then the validation commences. For the user name, the value is checked to see if it already exists in an array of user names and then checked to ensure that it is at least eight characters long. The birthday is passed directly into PHP's built-in strto time() function, which converts a date string in any number of U.S. formats into a UNIX timestamp (the number of seconds since January 1, 1970). If there is an error, this function returns –1, indicating that the string passed in was not a valid date. The e-mail address is checked against a regular expression to ensure that it is in the correct format. This regular expression was devised by John Coggeshall in his article, "E-mail validation with PHP 4," available online at www.zend.com/zend/spotlight/ev12apr.php.

Important

Note that the user names in this example are stored in a simple array and hard-coded into the page. In an actual implementation, the user names should be stored in a database and the database should be queried to determine whether the user name already exists.

The $valid and $message variables are initialized to false and An unknown error occurred. This ensures that if the file is used incorrectly (passing in an unrecognized field name, for example), a negative validation will always be returned. When a positive validation occurs, however, this requires that both variables be reset to appropriate values (true for $valid, an empty string for $message). In the case of a negative validation, only the $message variable has to be set since $valid is already false. The very last step in this page is to output the response string in the format mentioned previously.

Next, the JavaScript to perform the validation must be created. A single function, validateField(), can be used to validate each field so long as it knows which field it should be validating. This takes a little bit of work to counteract cross-browser compatibility issues:

function validateField(oEvent) {
    oEvent = oEvent || window.event;
    var txtField = oEvent.target || oEvent.srcElement;

    //more code to come
}

The first two lines of code inside this function equalize the differences between event models in IE and DOM-compliant browsers (such as Mozilla Firefox, Opera, and Safari). DOM-compliant browsers pass in an event object to each event handler; the control that caused the event is stored in the event object's target property. In IE, the event object is a property of window; therefore, the first line inside the function assigns the correct value to the oEvent variable. Logical OR (||) returns a non-null value when used with an object and a null object. If you are using IE, oEvent will be null; thus, the value of window.event is assigned to oEvent. If you are using a DOM-compliant browser, oEvent will be reassigned to itself. The second line does the same operation for the control that caused the event, which is stored in the srcElement property in IE. At the end of these two lines, the control that caused the event is stored in the txtField variable. The next step is to create the HTTP request using XMLHttp:

function validateField(oEvent) {
    oEvent = oEvent || window.event;
    var txtField = oEvent.target || oEvent.srcElement;

    var oXmlHttp = zXmlHttp.createRequest();
    oXmlHttp.open("get", "ValidateForm.php?" + txtField.name + "="
                                       +encodeURIComponent(txtField.value), true);
    oXmlHttp.onreadystatechange = function () {
        //more code to come
    };
    oXmlHttp.send(null);
}

As in Chapter 2, you are using the zXml library for cross-browser XMLHttp support. The XMLHttp object is created and stored in oXmlHttp. Next, the connection is initialized to a GET request using open(). Note that the query string for ValidateForm.php is created by combining the name of the field, an equals sign, and the value of the field (which is URL encoded using encodeURIComponent()). Also note that this is an asynchronous request. This is extremely important for this use case, because you don't want to interfere with the user filling out the rest of the form while you are checking the validity of a single field; remember that synchronous requests made using XMLHttp objects freeze most aspects of the user interface (including typing and clicking) during their execution The last part of this function is to handle the response from the server:

function validateField(oEvent) {
    oEvent = oEvent || window.event;
    var txtField = oEvent.target || oEvent.srcElement;
    var oXmlHttp = zXmlHttp.createRequest();
    oXmlHttp.open("get", "ValidateForm.php?" + txtField.name + "="
                                       + encodeURIComponent(txtField.value), true);
    oXmlHttp.onreadystatechange = function () {
        if (oXmlHttp.readyState == 4) {
            if (oXmlHttp.status == 200) {
                var arrInfo = oXmlHttp.responseText.split("||");
                var imgError = document.getElementById("img"
                                             + txtField.id.substring(3) + "Error");
                var btnSignUp = document.getElementById("btnSignUp");

                if (!eval(arrInfo[0])) {
                    imgError.title = arrInfo[1];
                    imgError.style.display = "";
                    txtField.valid = false;
                } else {
                    imgError.style.display = "none";
                    txtField.valid = true;
                }

                btnSignUp.disabled = !isFormValid();
            } else {
                alert("An error occurred while trying to contact the server.");
            }
        }
    };
    oXmlHttp.send(null);
}

After checking for the correct readyState and status, the responseText is split into an array of strings (arrInfo) using the JavaScript split() method. The value in the first slot of arrInfo will be the value of the PHP variable $valid; the value in the second slot will be the value of the PHP variable $message. Also, a reference to the appropriate error image and the Sign Up button is returned. The error image is gained by dissecting the field name, removing the "txt" from the front (using substring()), prepending "img" and appending "Error" to the end (so for the field "txtBirthday", the error image name is constructed as "imgBirthdayError").

The value in arrInfo[0] must be passed into eval() in order to get a true Boolean value out of it. (Remember, it's a string: either true or false.) If this value is false, the error image's title property is assigned the error message from arrInfo[1], the image is displayed, and the custom valid property of the text box is set to false (this will come in handy later). When a value is invalid, the error image appears, and when the user moves the mouse over it, the error message appears (see Figure 3-3). If the value is valid, however, the error image is hidden and the custom valid property is set to true.

Image from book
Figure 3-3

You'll also notice that the Sign Up button is used in this function. The Sign Up button should be disabled if there is any invalid data in the form. To accomplish this, a function called isFormValid() is called. If this function returns false, the Sign Up button's disabled property is set to true, disabling it. The isFormValid() function simply iterates through the form fields and checks the valid property:

function isFormValid() {
    var frmMain = document.forms[0];
    var blnValid = true;

    for (var i=0; i < frmMain.elements.length; i++) {
        if (typeof frmMain.elements[i].valid == "boolean") {
            blnValid = blnValid && frmMain.elements[i].valid;
        }
    }

    return blnValid;
}

For each element in the form, the valid property is first checked to see if it exists. This is done by using the typeof operator, which will return boolean if the property exists and has been given a Boolean value. Because there are fields that aren't being validated (and thus won't have the custom valid property), this check ensures that only validated fields are considered.

The last part of the script is to set up the event handlers for the text boxes. This should be done when the form has finished loading, but only if XMLHttp is supported (because that is how the Ajax validation is being performed here):

//if Ajax is enabled, disable the submit button and assign event handlers
window.onload = function () {
    if (zXmlHttp.isSupported()) {
        var btnSignUp = document.getElementById("btnSignUp");
        var txtUsername = document.getElementById("txtUsername");
        var txtBirthday = document.getElementById("txtBirthday");
        var txtEmail = document.getElementById("txtEmail");

        btnSignUp.disabled = true;
        txtUsername.onchange = validateField;
        txtBirthday.onchange = validateField;
        txtEmail.onchange = validateField;
        txtUsername.valid = false;
        txtBirthday.valid = false;
        txtEmail.valid = false;

    }
};

This onload event handler assigns the onchange event handlers for each text box as well as initializes the custom valid property to false. Additionally, the Sign Up button is disabled from the start to prevent invalid data from being submitted. Note, however, that the button will be disabled only if XMLHttp is supported; otherwise, the form will behave as a normal web form and the validation will have to be done when the entire form is submitted.

When you load this example, each of the three validated text fields will make a request to the server for validation whenever their values change and you move on to another field. The user experience is seamless using the Submission Throttling pattern, but the form remains functional even if JavaScript is turned off or XMLHttp is not supported.

Important

Even when using this type of validation, it is essential that all the data be validated again once the entire form is submitted. Remember, if the user turns off JavaScript, you still need to be sure the data is valid before performing operations using it.

Incremental Field Validation Example

Whereas the previous example validated each field when its value changed, the other popular form of the Submission Throttling design pattern involves submitting a single field periodically as changes are made. This is the version of Submission Throttling used for both Bitflux LiveSearch and Google Suggest, where data is repeatedly sent to the server as the user types. In both of these cases, the submission activates a search on the server; however, the same method can be used to validate a single field repeatedly as the user types.

Suppose that instead of asking you to fill in a whole form, sign-up for a given site requires you first to select a user name (maybe as step 1 of a multistep sign-up process). In this case, you'd want to ensure that only a non-existent user name be used. Instead of waiting for the form to be submitted, you can periodically upload the data to the server for validation, making sure that the data can't be submitted until a valid user name is entered.

Important

Note that this example is for demonstration purposes. If you were to use the technique described in a production environment, you would have to protect against spam bots that may use this feature to harvest user names and passwords.

The form for this example is much simpler, made up of a single text box and a Next button:

<form method="post" action="Success.php">
    <table>
        <tr>
            <td><label for="txtUsername">Username</label></td>
            <td><input type="text" id="txtUsername" name="txtUsername" />
                <img src="error.gif" alt="Error" id="imgUsernameError"
                     style="display:none" /></td>
        </tr>
    </table>
    <input type="submit" id="btnNext" value="Next" />
</form>

Note that the same basic format of the previous example has been kept, including the hidden error image. Next, the validateField() function from the previous example is used, with a few changes:

var oXmlHttp = null;
var iTimeoutId = null;

function validateField(oEvent) {
    oEvent = oEvent || window.event;
    var txtField = oEvent.target || oEvent.srcElement;

    var btnNext = document.getElementById("btnNext");
    btnNext.disabled = true;

    if (iTimeoutId != null) {
        clearTimeout(iTimeoutId);
        iTimeoutId = null;
    }

    if (!oXmlHttp) {
        oXmlHttp = zXmlHttp.createRequest();
    } else if (oXmlHttp.readyState != 0) {
        oXmlHttp.abort();
    }

    oXmlHttp.open("get", "ValidateForm.php?" + txtField.name + "="

                                       + encodeURIComponent(txtField.value), true);
    oXmlHttp.onreadystatechange = function () {

        if (oXmlHttp.readyState == 4) {
            if (oXmlHttp.status == 200) {
                var arrInfo = oXmlHttp.responseText.split("||");
                var imgError = document.getElementById("img"
                                             + txtField.id.substring(3) + "Error");

                if (!eval(arrInfo[0])) {
                    imgError.title = arrInfo[1];
                    imgError.style.display = "";
                    txtField.valid = false;
                } else {
                    imgError.style.display = "none";
                    txtField.valid = true;
                }

                btnNext.disabled = !txtField.valid;
            } else {
                alert("An error occurred while trying to contact the server.");
            }
        }
    };

    iTimeoutId = setTimeout(function () {
        oXmlHttp.send(null);
    }, 500);
};

The first thing to note about this updated function is the inclusion of two global variables: oXmlHttp and iTimeoutId. The first, oXmlHttp, holds a global reference to an XMLHttp object that is used repeatedly (as opposed to being used just once in the previous example); the second, iTimeoutId, holds a timeout identifier used to delay sending a request. Inside the function, the first new part sets the Next button to be disabled right away. This is important because a request may not be sent out immediately following a call to this function. The next block after that clears the timeout identifier if it's not null, which prevents the sending of too many requests in succession. (If there is a pending request, this cancels it.)

Next, the global oXmlHttp object is tested to see if it is null. If so, a new XMLHttpobject is created and assigned to it. If an XMLHttp object already exists, its readyState is checked to see if it's ready for a request. As mentioned in the previous chapter, the readyState changes from 0 to 1 when the open() method is called; therefore, any readyState other than 0 indicates that a request has already been started, so the abort() method must be called before attempting to send a new request. Note that the same ValidateForm.php page is used for validation purposes.

Inside of the onreadystatechange event handler, the only new line is one that changes the Next button's disabled state based on the validity of user name. Toward the end of the function, the setTimeout() function is called to delay the sending of the request by half a second (500 milliseconds). The identifier from this call is saved in iTimeoutId, so it is possible to cancel the request the next time the function is called. By using the timeout functionality of JavaScript in this way, you are ensuring that the user hasn't typed anything for at least half a second. If the user types something quickly, the timeout will repeatedly be cleared and the request aborted. It's only when there is a pause that the request will finally be sent.

The only part left now is to set up the event handler. Since this method uploads information as the user types, you can't rely on the onchange event handler alone (although it is still needed). In this case, you need to use the onkeyup event handler, which is called every time a key is pressed and then released:

window.onload = function () {
    if (zXmlHttp.isSupported()) {
        var btnNext = document.getElementById("btnNext");
        var txtUsername = document.getElementById("txtUsername");

        btnNext.disabled = true;
        txtUsername.onkeyup = validateField;
        txtUsername.onchange = validateField;
        txtUsername.valid = false;
    }
};

Once again, this is very similar to the previous example. The only changes are the name of the button (which is now btnNext) and the assignment of validateField() to the onkeyup event handler. As the user types, the user name will be checked for validity. Every time a valid user name is entered, the Next button becomes enabled. Whenever a request is being made, the button is first disabled to accommodate a specific situation. It is possible that the user will continue typing even after a valid user name has been entered. As a side effect, the extra characters may cause the user name to become invalid, and you don't want to allow invalid data to be submitted.

Important

Although a nice feature, incremental field validation should be used sparingly because it creates a high volume of requests. Unless your server configuration is set up specifically to handle an increased amount of requests, it is best to forego this approach.

Periodic Refresh

The Periodic Refresh design pattern describes the process of checking for new server information in specific intervals. This approach, also called polling, requires the browser to keep track of when another request to the server should take place.

This pattern is used in a variety of different ways on the Web:

  • ESPN uses Periodic Refresh to update its online scoreboards automatically. For example, the NFL Scoreboard, located at http://sports.espn.go.com/nfl/scoreboard, shows up-to-the-minute scores and drive charts for every NFL game being played at the time. Using XMLHttp objects and a little bit of Flash, the page repeatedly updates itself with new information.

  • Gmail (http://gmail.google.com) uses Periodic Refresh to notify users when new mail has been received. As you are reading an e-mail or performing other operations, Gmail repeatedly checks the server to see if new mail has arrived. This is done without notification unless there is new mail, at which point the number of new e-mails received is displayed in parentheses next to the Inbox menu item.

  • XHTML Live Chat (www.plasticshore.com/projects/chat/) uses Periodic Refresh to implement a chat room using simple web technologies. The chat room text is updated automatically every few seconds by checking the server for new information. If there is a new message, the page is updated to reflect it, thus creating a traditional chat room experience.

  • The Magnetic Ajax demo (www.broken-notebook.com/magnetic/) re-creates online the experience of magnetic poetry (using single word magnets that can be rearranged to make sentences). The full version polls the server for new arrangements every few seconds, so if you and someone else are rearranging words at the same time, you will see the movement.

Clearly, there are many different ways that Period Refresh can increase user experience, but the basic purpose remains the same: to notify users of updated information.

New Comment Notifier Example

A feature that has been creeping into blogs across the Web since the beginning of 2005 is a New Comment Notifier. A New Comment Notifier does exactly what it says it does: it alerts the user when a new comment has been added. This can take the form of a simple text message displayed on the page or an animated message that slides in from out of view, but the basic idea is the same. In this example, Periodic Refresh is used to check a database table containing comments to see which is the newest.

Suppose you have a simple MySQL table, defined as follows:

CREATE TABLE `BlogComments` (
`CommentId` INT NOT NULL AUTO_INCREMENT ,
`BlogEntryId` INT NOT NULL ,
`Name` VARCHAR(100) NOT NULL ,
`Message` VARCHAR(255) NOT NULL ,
`Date` DATETIME NOT NULL ,
PRIMARY KEY (`CommentId`)
) COMMENT = 'Blog Comments';

The SQL query to run this is:

select CommentId,Name,LEFT(Message, 50)
from BlogComments order by Date desc
limit 0,1

This query returns the comment ID (which is autogenerated), the name of the person who left the comment, and the first 50 characters of the message text (using the LEFT() function) for the most recent comment. The 50 characters are used as a preview of the actual comment (you probably don't want to get the entire message because it could be long).

The page that runs this query is called CheckComments.php, and it outputs a string in the following format:

<comment ID>||<name>||<message>

This format allows the JavaScript Array.split() method to be used in order to extract the individual pieces of information with little effort. If there are no comments or there is an error, the comment ID will be –1 and the other parts of the string will be blank. Here is the complete code listing for CheckComments.php:

<?php
    header("Cache-control: No-Cache");
    header("Pragma: No-Cache");

    //database information
    $sDBServer = "your.database.server";
    $sDBName = "your_db_name";
    $sDBUsername = "your_db_username";
    $sDBPassword = "your_db_password";

    //create the SQL query string
    $sSQL = "select CommentId,Name,LEFT(Message, 50) as ShortMessage from
BlogComments order by Date desc limit 0,1";

    $oLink = mysql_connect($sDBServer,$sDBUsername,$sDBPassword);
    @mysql_select_db($sDBName) or die("-1|| || ");

    if($oResult = mysql_query($sSQL) and mysql_num_rows($oResult) > 0) {
        $aValues = mysql_fetch_array($oResult,MYSQL_ASSOC);
        echo $aValues['CommentId']."||".$aValues['Name']."||".
             $aValues['ShortMessage'];
    } else {
        echo "-1|| || ";
    }

    mysql_free_result($oResult);
    mysql_close($oLink);
?>

Perhaps the most important parts of this file are the two headers included at the top. By setting Cache-control and Pragma to No-Cache, you are telling the browser to always retrieve this file from the server and not from the client cache. Without this, some browsers would return the same information repeatedly, effectively nullifying this functionality altogether. The rest of this file should look very familiar, as it uses essentially the same algorithm as previous examples that make use of MySQL database calls.

Note

You can also avoid caching problems by changing the query string every time a request is made to this file. This is often done by assigning a timestamp into the query string to trick the browser into getting a fresh copy from the server.

Next comes the JavaScript that calls this file. To start, you'll need a few global variables once again:

var oXmlHttp = null;          //The XMLHttp object
var iInterval = 1000;         //The interval to check (in milliseconds)
var iLastCommentId = -1;      //The ID of the last comment received
var divNotification = null;   //The layer to display the notification

As usual, the first global variable is an XMLHttp object called oXmlHttp, which will be used for all requests. The second variable, iInterval, specifies the number of milliseconds that should occur between each check for new comments. In this case it is set to 1000 milliseconds, or 1 second, although this can and should be customized based on your needs. Next, the iLastCommentId variable is used to store the last comment ID in the database. It is by comparing this value to the most recently retrieved comment ID that you can determine whether a new comment has been added. The last variable, divNotification, holds a reference to the <div/> element that is used to display a notification to the user about new comments.

When a new comment is detected, divNotification is filled with information about the new comment, including the name of the person making the comment, a summary of the message, and a link to view the entire comment. If the <div/> element hasn't yet been created, it must be created and assigned the appropriate style information:

function showNotification(sName, sMessage) {
    if (!divNotification) {
        divNotification = document.createElement("div");
        divNotification.className = "notification";
        document.body.appendChild(divNotification);
    }

    divNotification.innerHTML = "<strong>New Comment</strong><br />" + sName
              + "says: "+ sMessage + "...<br /><a href=\" ViewComment.php?id="
              + iLastCommentId + "\">View</a>";
    divNotification.style.top = document.body.scrollTop + "px";
    divNotification.style.left = document.body.scrollLeft + "px";
    divNotification.style.display = "block";
    setTimeout(function () {
        divNotification.style.display = "none";
    }, 5000);
}

As you can see, the showNotification() function accepts two arguments: a name and a message. However, before this information is used, you must ensure that divNotification is not null. If necessary, a new <div/> element is created and its CSS class set to notification before being added to the document's body. After that, the innerHTML property is used to set the notification HTML, which says "New Comment" in bold, followed by the name, the message, and the link to view the comment. The link points to ViewComment.php and assigns a query string parameter id the value of iLastCommentId, which indicates the comment to view. Then, the position of the notification is set by using the scrollTop and scrollLeft properties of document.body. This ensures that the notification is always visible at the upper-left corner of the page regardless of the scroll position (if you have scrolled down or right). Following that, the display property is set to block to make the notification visible.

The last part of this function is a timeout that hides the notification after 5 seconds (5000 milliseconds). It's not a good idea to leave the notification up unless you have a spot specifically designated for such a purpose in your design; otherwise, you could be covering up important information.

In this example, the notification CSS class is defined as follows:

div.notification {
    border: 1px solid red;
    padding: 10px;
    background-color: white;
    position: absolute;
    display: none;
    top: 0px;
    left: 0px;
}

This creates a white box with a red border around it. Of course, you'll want to style this in a manner that's appropriate for the site or application in which it is used. The important parts for this example are that position is set to absolute and display is set to none. Setting both properties ensures that when the <div/> element is added to the page, it won't interrupt the normal page flow or move any elements around. The result is a notification area, as displayed in Figure 3-4.

Image from book
Figure 3-4

Back to the JavaScript. The function that does the most work is checkComments(), which is responsible for checking the server for updates. The code is very similar to the previous examples:

function checkComments() {
    if (!oXmlHttp) {
        oXmlHttp = zXmlHttp.createRequest();
    } else if (oXmlHttp.readyState != 0) {
        oXmlHttp.abort();
    }

    oXmlHttp.open("get", "CheckComments.php", true);
    oXmlHttp.onreadystatechange = function () {

        if (oXmlHttp.readyState == 4) {
            if (oXmlHttp.status == 200) {

                var aData = oXmlHttp.responseText.split("||");
                if (aData[0] != iLastCommentId) {

                    if (iLastCommentId != -1) {

                        showNotification(aData[1], aData[2]);
                    }
                    iLastCommentId = aData[0];
                }

                setTimeout(checkComments, iInterval);

            }
        }
    };

    oXmlHttp.send(null);

}

This function creates an XMLHttp object and calls CheckComments.php asynchronously. The important part of this code is highlighted (the rest is almost exactly the same as the previous examples). In this section, the responseText is split into an array using the split() method. The first value in the array, aData[0], is the comment ID that was added last. If it isn't equal to the last comment ID stored, then a notification may be needed. Next, if the last comment ID is –1, no comment IDs have been retrieved and thus a notification should not be shown. If the last comment ID is not –1, at least one comment ID has been retrieved and since it's different from the one just received from the server, the notification should be displayed. After that, the new ID is assigned to iLastCommentId for future use. The very last step in the event handler is to set another timeout for checkComments(), to continue checking for more comments.

The final step in the process is to initiate a call to checkComments() once the page has loaded. This will retrieve the most recent comment ID in the database but won't display a notification (because iLastCommentId will be equal to –1 initially). When the next call is made to checkComments(), the ID retrieved from the database can be checked against the one stored in iLastCommentId to determine if a notification must be displayed. As usual, this functionality should be initiated only if the browser supports XMLHttp:

window.onload = function () {
    if (zXmlHttp.isSupported()) {
        checkComments();
    }
};

That's all it takes to create this Periodic Refresh solution. You need only remember to include the necessary JavaScript and CSS files in any page that you would like this functionality on.

Important

The files for this example are available for download at www.wrox.com. Along with those files are other pages you can use to add and view comments for the purpose of testing.

Multi-Stage Download

One of the lasting problems on the Web has been the speed at which pages download. When everyone was using 56 Kbps modems, web designers were much more aware of how much their pages "weighed" (the size of the page in total bytes). With the popularity of residential broadband Internet solutions, many sites have upgraded, including multimedia, more pictures, and more content. This approach, while giving the user more information, also leads to slower download times as everything is loaded in seemingly random order. Fortunately, there is an Ajax solution for this problem.

Multi-Stage Download is an Ajax pattern wherein only the most basic functionality is loaded into a page initially. Upon completion, the page then begins to download other components that should appear on the page. If the user should leave the page before all of the components are downloaded, it's of no consequence. If, however, the user stays on the page for an extended period of time (perhaps reading an article), the extra functionality is loaded in the background and available when the user is ready. The major advantage here is that you, as the developer, get to decide what is downloaded and at what point in time.

This is a fairly new Ajax pattern and has been popularized by Microsoft's start.com. When you first visit start.com, it is a very simple page with a search box in the middle. Behind the scenes, however, a series of requests is being fired off to fill in more content on the page. Within a few seconds, the page jumps to life as content from several different locations is pulled in and displayed.

Although nice, Multi-Stage Download does have a downside: the page must work in its simplest form for browsers that don't support Ajax technologies. This means that all the basic functionality must work without any additional downloads. The typical way of dealing with this problem is to provide graceful degradation, meaning that those browsers that support Ajax technologies will get the more extensive interface while other browsers get a simple, bare-bones interface. This is especially important if you are expecting search engines to crawl your site; since these bots don't support JavaScript, they rely solely on the HTML in the page to determine your site's value.

Additional Information Links Example

When reading through an article online, frequently there are Additional Information links included for further reading on the topic. The key question here is this: What is the main content? Clearly the article text is the main content on the page, so it should be downloaded when the page is initially loaded. The additional links aren't as important, so they can be loaded later. This example walks you through the creation of such a solution.

First, you'll need to lay out a page to hold the article. For this example, it's a very simple layout:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang=" en" lang=" en">
    <head>
        <title>Article Example</title>
        <script type="text/javascript" src="zxml.js"></script>
        <script type="ext/javascript" src="Article.js"></script>
        <link rel="stylesheet"type="text/css" href="Article.css" />
    </head>
    <body>
        <h1>Article Title</h1>
        <div id=" divAdditionalLinks"></div>
        <div id=" divPage1">
            <!--  article content here -->
        </div>
    </body>
</html>

The important part of the HTML is the <div/> with the ID of divAdditionalLinks. This is the container for the additional links that will be downloaded for the article. By default, it is styled to be right aligned and invisible:

#divAdditionalLinks {
    float: right;
    padding: 10px;
    border: 1px solid navy;
    background-color: #cccccc;
    display: none;
}

It's very important that the CSS display property be set to none so that the empty <div/> element doesn't take up any space in the page layout. Without this, you would see a small empty box to the right of the article.

Unlike the previous examples, the content to download is just plain text contained in a text file containing links and a header. This file, AdditionalLinks.txt, contains some simple HTML code:

<h4>Additional Information</h4>
<ul>
    <li><a href="http://www.wrox.com">Wrox</a></li>
    <li><a href="http://www.nczonline.net">NCZOnline</a></li>
    <li><a href="http://www.wdonline.com">XWeb</a></li>
</ul>

This file could just as well be created dynamically using server-side logic, but for the purposes of this example, static content works just as well.

The JavaScript that makes this work is very simple and quite similar to all the previous examples in this chapter:

function downloadLinks() {
    var oXmlHttp = zXmlHttp.createRequest();

    oXmlHttp.open("get", "AdditionalLinks.txt", true);
    oXmlHttp.onreadystatechange = function () {
        if (oXmlHttp.readyState == 4) {
            if (oXmlHttp.status == 200) {
                var divAdditionalLinks =
                                     document.getElementById("divAdditionalLinks");
                divAdditionalLinks.innerHTML = oXmlHttp.responseText;
                divAdditionalLinks.style.display = "block";
            }
        }
    }
    oXmlHttp.send(null);
}

window.onload = function () {
    if (zXmlHttp.isSupported()) {
        downloadLinks();
    }
};

The function that does the work is downloadLinks(), which is called only if the browser supports XMLHttp and only once the page is completely loaded. The code inside of downloadLinks() is the standard XMLHttp algorithm that you've used before. After the content from AdditionalLinks.txt has been retrieved, it is set into the placeholder <div/> using the innerHTML property. The last step in the process is to set the <div/> element's display property to block so it can be seen. The end result is displayed in Figure 3-5.

Image from book
Figure 3-5

If XMLHttp isn't supported in the browser, the block containing the additional links will never appear and so the first paragraph will stretch all the way across the top.

This technique can be done numerous times for any number of sections of a page; you certainly aren't restricted to having only one section that is loaded after the initial page is complete. You can create new XMLHttp objects for each request and then send them off one after the other, or you can do it sequentially, waiting until a response has been received before sending off the next request. The choice is completely up to you and your desired functionality.


Previous Page
Next Page

R7


JavaScript EditorAjax Editor     Ajax Validator


©