Ajax Hacks: Tips & Tools for Creating Responsive Web Sites

Change static unordered lists based on content derived from a server.

One of the most common tags found on web pages is the unordered list ( ul) tag, which browsers usually render as a list of bullets accompanied by labels. This hack allows the web page user to change an unordered list by adding items to it. The content for the items derives from a server program. This hack is very similar to the previous one, in which the user was able to add items to two lists of checkboxes. The main difference is that this hack deals with unordered lists, which are designed to display information rather than to provide a selection widget (such as a list of checkboxes).

Go ahead and skip this hack if you are not interested in playing with unordered lists because the code is a revised version of the previous hack.

Figure 2-16 shows this hack's web page before the user chooses to expand either of two lists. The lists involve team sports and individual sports. When the user chooses an expansion option from the pop-up list at the top of the page, the indicated list grows by a few items without anything else on the page changing. As in the last hack, each list can be restored to its original contents by choosing the appropriate option from the second pop-up list. The speed with which the lists grow and shrink is quite impressive, particularly considering that the "growth" content comes from a server.

Figure 2-16. Watch the list grow and shrink

Figure 2-17 shows the web page after the user has expanded the team sports category.

Figure 2-17. Expanding the menu of team sports

Here's the code for the web page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <script type="text/javascript" src="/books/4/254/1/html/2/js/http_request.js"></script> <script type="text/javascript" src="/books/4/254/1/html/2/js/hacks2_9.js"></script> <title>View our sports offerings</title> </head> <body> <h3>Expand Your Sports Categories</h3> <h4>Expand Sport Choices</h4> <form action="javascript:void%200"> <div > Expand the choices for: <select name="_expand" > <option value="individual">Individual</option> <option value="team">Team</option> </select> </div> </form> <h4>Restore Original Sport Choices</h4> <form action="javascript:void%200"> <div > Restore the choices for: <select name="_restore" > <option value="individual">Individual</option> <option value="team">Team</option> </select> </div> </form> <h4>Team sport</h4> <ul > <li>Baseball</li> <li>Soccer</li> <li>Football</li> <li>Basketball</li> <li>Lacrosse</li> <li>Hockey</li> <li>Tennis </li> </ul> <h4>Individual sport</h4> <ul > <li>Cycling</li> <li>Running</li> <li>Swimming</li> <li>Nordic Skiing</li> <li>Inline Skating</li> <li>Triathlon</li> <li>Track</li> </ul> </body> </html>

The key to this code is giving the ul tags their own id values. The JavaScript code can then access the tags, as shown in the following example:

var ul = document.getElementById(sportTyp+"_u");

The ul elements contain the list items; therefore, the code increases the number of items in each list by appending child nodes or elements to the ul elements and restores the lists to their original states by removing those child elements. Of course, in this hack, the content for the new list items derives from a server. As a result, the code first must use the request object to fetch the new values.

The web page imports two JavaScript files, http_request.js and hacks2_9.js. The first file creates and sets up the XMLHttpRequest object. (See "Use Your Own Library for XMLHttpRequest" [Hack #3] for a description of a JavaScript file that manages the request object.) hacks2_9.js contains the code that grows and restores the unordered lists:

var sportTyp = ""; var itemsArray = null; //define Object for caching li items //this is a workaround for IE 6, which //doesn't save the li element's text node //or label when you cache it function CachedLiItem(liElement,liLabel){ //an li element object this.liElement=liElement; //a string representing the li text node or label this.liLabel=liLabel; } window.onload=function( ){ var sel = document.getElementById("expand"); //bind onclick event handler to a function if(sel != null){ sel.onclick=function( ){ getMoreChoices(this)}; } var selr = document.getElementById("restore"); //bind onclick event handler to a function if(selr != null){ selr.onclick=function( ){ restore(this)}; } //place all existing bullet items in two arrays //for restoring later itemsArray = new Object( ); itemsArray.team = new Array( ); itemsArray.individual = new Array( ); var bulletArr = document.getElementsByTagName("li"); populateArray(bulletArr,"team"); populateArray(bulletArr,"individual"); } //create Arrays of CachedLiItem objects for //restoring the unordered lists later function populateArray(arr,typ) { var inc = 0; var el = null; var liObj=null; for(var i = 0; i < arr.length; i++){ el = arr[i].parentNode; if(el.id.indexOf(typ) != -1) { liObj=new CachedLiItem(arr[i],arr[i].childNodes[0].nodeValue); itemsArray[typ][inc] = liObj; inc++; } } } //return the number of li elements contained //by a ul element function getULoptionsLength(_sportTyp){ var ul = document.getElementById(_sportTyp+"_u"); var len=0; for(var i =0; i < ul.childNodes.length; i++){ if(ul.childNodes[i].nodeName == "LI" || ul.childNodes[i].nodeName == "li" ){ len++; } } return len; } function getMoreChoices(obj){ if (obj == null ) { return; } var url = ""; var optsArray = obj.options; var val = ""; for(var i=0; i < optsArray.length; i++){ if(optsArray[i].selected) { val=optsArray[i].value; break; } } sportTyp=val; //determine whether the bullets have already been expanded if(itemsArray[sportTyp].length < getULoptionsLength(sportTyp)) { return; } url = "http://www.parkerriver.com/s/expand?expType="+val; httpRequest("GET",url,true,handleResponse); } function addToBullets(obj){ //ul element that contains the bullet items var ul = document.getElementById(sportTyp+"_u"); var el = null; //now add the new items derived from the server for(var h = 0; h < obj.length; h++){ el = document.createElement("li"); el.appendChild(document.createTextNode(obj[h])); ul.appendChild(el); } } function restore(_sel) { var val; var opts = _sel.options; for (var i = 0; i < opts.length; i++){ if(opts[i].selected) { val=opts[i].value; break;} } sportTyp=val; //only restore the lists if the bullets have //already been expanded if(itemsArray[sportTyp].length < getULoptionsLength(sportTyp)) { var ul = document.getElementById(val+"_u"); if(ul != null) { //rebuild the list of original bullets ul.innerHTML=""; var tmpArr = itemsArray[val]; var tmpLiElement = null; for(var j = 0; j < tmpArr.length; j++){ tmpLiElement=tmpArr[j].liElement; //workaround for IE6 if(tmpLiElement.hasChildNodes( )){tmpLiElement. removeChild(tmpLiElement.firstChild);} tmpLiElement.appendChild(document. createTextNode(tmpArr[j].liLabel)) ul.appendChild(tmpLiElement); } } } } //event handler for XMLHttpRequest function handleResponse( ){ try{ if(request.readyState == 4){ if(request.status == 200){ var resp = request.responseText; if(resp != null){ //return value is an array addToBullets(eval(resp)); } } else { //snipped for the sake of brevity } }//end outer if } catch (err) { alert("It does not appear that the server "+ "is available for this application. Please"+ " try again very soon. \\nError: "+err.message); } }

The populateArray( ) and getMoreChoices( ) functions are almost exactly the same as in the previous hack's code, so I won't take up space here explaining them in detail. The former function caches the original unordered list in an array, so that it can be restored later. getMoreChoices( ) hits the server for more sport types using the request object, but only if the unordered list has not yet been expanded.

Next, the code gets the server's return value so that the code can grow either the team sport list or the individual sport list:

var resp = request.responseText; if(resp != null){ //return value is an array addToBullets(eval(resp)); }

The return value is a string in array syntax, as in ["Field Hockey","Rugby"]. The code uses the eval( ) global function to convert the string to a JavaScript array. It then passes this array to addToBullets( ):

function addToBullets(obj){ //ul element that contains the bullet items var ul = document.getElementById(sportTyp+"_u"); var el = null; //now add the new items derived from the server for(var h = 0; h < obj.length; h++){ el = document.createElement("li"); el.appendChild(document.createTextNode(obj[h])); ul.appendChild(el); } }

This function initiates some DOM programming to create new list items and append them as children of a ul tag. The existing ul tag has an id like team_u. The code uses document.getElementById(sportTyp+"_u") to get a reference to the ul tag, then appends a new li element to the ul for each value in the array.

restore( ) comes into play if the user wants to restore the original lists:

//only restore the lists if the bullets have //already been expanded if(itemsArray[sportTyp].length < getULoptionsLength(sportTyp)) { var ul = document.getElementById(val+"_u"); if(ul != null) { //rebuild the list of original bullets ul.innerHTML=""; var tmpArr = itemsArray[val]; var tmpLiElement = null; for(var j = 0; j < tmpArr.length; j++){ tmpLiElement=tmpArr[j].liElement; //workaround for IE6 if(tmpLiElement.hasChildNodes( )){tmpLiElement. removeChild(tmpLiElement.firstChild);} tmpLiElement.appendChild(document. createTextNode(tmpArr[j].liLabel)) ul.appendChild(tmpLiElement); }

This code uses a cache of original list items to rebuild the restored unordered list. When the web page loads, the code uses a simple JavaScript object to represent each li element:

function CachedLiItem(liElement,liLabel){ //an li element object this.liElement=liElement; //a string representing the li text node or label this.liLabel=liLabel; }

The object has two properties: the li element itself, and the string that specifies its label (the text that you see next to the bullet). When you cache an li element in an array, for instance, Internet Explorer 6 will not save the li element's internal text node, so we use this workaround object. The code empties the ul element first by setting its innerHTML property to the empty string. Then the code uses appendChild( ) from the DOM API to embed the original list items within this ul parent element.

Parting Shots

Your application never has to hit the network if it has a well-defined list of items that can just be hard-coded into the client-side JavaScript as arrays. But if the task calls for expanding web lists from server databases, and this persistent information changes often, this hack's approach can come through for the developers.

Категории