Ajax Hacks: Tips & Tools for Creating Responsive Web Sites

Dynamically add widgets to an existing group of checkboxes.

This hack deals with another type of adaptive web form, where a group of widgets can change based on the preferences of the user that accesses the web page. In "Submit Checkbox Values to the Server Without a Round Trip" [Hack #17], the code submitted a clicked checkbox value right away to a server program. This hack allows users to add new choices to the same bunch of checkboxes before they choose among those widgets. The web page has a select list including the choices Team or Individual. It shows two groups of checkboxes representing team sports and individual sports. Choosing either Team or Individual from the first pop-up menu or select list expands the existing checkboxes for the selected group by getting new content from a server. Choosing one value from the second pop-up menu restores the original checkboxes for either the Team or Individual list.

Figure 2-14 shows the web page before the user makes a choice.

Figure 2-14. Expand the offerings

Figure 2-15 depicts the same page after the user chooses Team from the pop-up menu at the top of the page, thus expanding the choices of team sports.

Figure 2-15. Team offerings expanded

How Does It Work?

We are assuming that the content for the new checkboxes must come from the server, because it changes often and/or derives from the organization's database. Otherwise, an application like this can just include a JavaScript array of new content and never touch the server program. When the user makes a choice from the first pop-up menu or select list, this action sends the choice of Team or Individual to a server program. The code uses the request object to connect with the server the Ajax way.

The server replies with an array of titles for new checkboxes. Flipping the choices in the select list launches an onclick event handler in the JavaScript code, which the upcoming code sample shows.

I won't take up space with the HTML code, because the page is almost exactly the same as the one in "Submit Checkbox Values to the Server Without a Round Trip" [Hack #17]. The page uses a script tag to import all its Ajax-related JavaScript in a file named hacks2_8.js. You can read through the code comments right now to get a feel for what the code does.

Generally, a single-line JavaScript comment begins with // while a multiline comment is bracketed by /* */. You can, of course, have lots of consecutive //-style comments if you prefer to comment that way.

Here is the relevant JavaScript code:

var sportTyp = ""; var checksArray = null; var request=null; window.onload=function( ){ //the 'expanding checkboxes' select pop-up var sel = document.getElementById("expand"); //bind onclick event handler to a function if(sel != null){ sel.onclick=function( ){ getMoreChoices(this)}; } //the 'restoring checkboxes' select pop-up var selr = document.getElementById("restore"); //bind onclick event handler to the function if(selr != null){ selr.onclick=function( ){ restore(this)}; } //Place all existing checkbox elements in two arrays //for restoring the original checkbox lists checksArray = new Object( ); checksArray.team = new Array( ); checksArray.individual = new Array( ); var ckArr = document.getElementsByTagName("input"); populateArray(ckArr,"team"); populateArray(ckArr,"individual"); } function populateArray(arr,typ) { var inc = 0; for(var i = 0; i < arr.length; i++){ if(arr[i].type == "checkbox") { if(arr[i].name.indexOf(typ) != -1) { checksArray[typ][inc] = arr[i]; inc++; } } } } //Return the number of input checkbox elements contained //by a div element function getCheckboxesLength(_sportTyp){ var div = document.getElementById(_sportTyp+"_d"); var len=0; for(var i =0; i < div.childNodes.length; i++){ if(div.childNodes[i].nodeName == "INPUT" || div.childNodes[i].nodeName == "input" ){ len++; } } return len; } /* Use the request object to fetch an array of titles for new checkboxes. The obj parameter represents a select element; get the value of this element, then hit the server with this value to request the new titles, but only if the checkbox hasn't already been expanded */ 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 checkboxes have already been expanded if(checksArray[sportTyp].length < getCheckboxesLength(sportTyp)) { return; } url = "http://www.parkerriver.com/s/expand?expType="+val; httpRequest("GET",url,true); } /* Add new checkboxes to either of the original checkbox lists. Only add the new checkboxes if the list hasn't been expanded yet. Just return from this function and don't hit the network if the list has already been expanded. Parameter: obj: An array of new titles, like ["Field Hockey","Rugby"] */ function addToChecks(obj){ //div element that contains the checkboxes var div = document.getElementById(sportTyp+"_d"); var el = null; //now add the new checkboxes derived from the server for(var h = 0; h < obj.length; h++){ el = document.createElement("input"); el.type="checkbox"; el.name=sportTyp+"_sports"; el.value=obj[h]; div.appendChild(el); div.appendChild(document.createTextNode(obj[h])); div.appendChild(document.createElement("br")); } } //restore the original list of checkboxes, using //the checksArray object containing the //original checkboxes 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;} } //only restore if the checkboxes have //already been expanded if(checksArray[sportTyp].length < getCheckboxesLength(sportTyp)) { var _div = document.getElementById(val+"_d"); if(_div != null) { //rebuild the list of original checkboxes _div.innerHTML=""; var tmpArr = checksArray[val]; for(var j = 0; j < tmpArr.length; j++){ _div.appendChild(tmpArr[j]); _div.appendChild(document.createTextNode(tmpArr[j].value)); _div.appendChild(document.createElement("br")); } } } } //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 addToChecks(eval(resp)); } } else { /* Create and initialize a request object; not included. See Hack #1 or #2. */

Most of the code is involved with capturing and restoring the checkboxes's original state, and the comments contained in the latter code sample should make it clear what this code is accomplishing. This code is included to prevent the same set of new checkboxes from being appended to the list multiple times if the user chooses the same value repeatedly from the pop-up list.

The code checks whether the list has already been expanded, by comparing the number of checkboxes in the cached array with number in the existing checkbox group. If the existing group has more checkboxes than the original group, the list has already been expanded. If the user tries to expand the list twice, the second click is ignored, thus sparing the network from a needless hit.

Ajax Requests

getMoreChoices( ) makes a server request using the request object to acquire titles for new checkboxes. (See "Detect Browser Compatibility with the Request Object" [Hack #1] or "Submit Text Field or textarea Values to the Server Without a Browser Refresh" [Hack #12] if you have not been introduced to the request object.) The first select list's onclick event handler, which is set up when the browser window first loads the web page (window.onload), launches this function, passing in a reference to the select element.

The select element in our page can only have the values Team or Individual. The code appends the value (Team or Individual) onto the end of the URL reflecting the server program. Finally, the httpRequest( ) function sets up and launches the request:

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 checkboxes have already been expanded if(checksArray[sportTyp].length < getCheckboxesLength(sportTyp)) { return; } url = "http://www.parkerriver.com/s/expand?expType="+val; httpRequest("GET",url,true); }

Here Comes an Array

The server sends back the HTTP response as a string that can be converted to a JavaScript array:

if(request.readyState == 4){ if(request.status == 200){ var resp = request.responseText; if(resp != null){ //return value is an array addToChecks(eval(resp)); } } else {...}

The return value represented by the variable resp is a string, such as ["Field Hockey","Rugby"]. The code passes this string to the eval( ) global function, which returns a JavaScript array. The addToChecks( ) function then creates new checkboxes from this array:

function addToChecks(obj){ //div element that contains the checkboxes var div = document.getElementById(sportTyp+"_d"); var el = null; //now add the new checkboxes derived from the server for(var h = 0; h < obj.length; h++){ el = document.createElement("input"); el.type="checkbox"; el.name=sportTyp+"_sports"; el.value=obj[h]; div.appendChild(el); div.appendChild(document.createTextNode(obj[h])); div.appendChild(document.createElement("br")); } }

This function uses the DOM API to create new input elements and add them to the end of the div element containing the checkboxes. The user sees the checkbox list grow, but nothing else changes on the page. Nifty!

You may want to take a look at the restore( ) function, which takes an expanded checkbox list and restores it to its original content, without any network hits.

Категории