Ответ
Sanja v.2 09.03.2006 14:41
Ajax Hacks By Bruce Perry
Publisher: O'Reilly Pub Date: March 01, 2006 ISBN: 0-596-10169-4
Глава 3:
Dynamically Generate A New Checkbox Group With Server DataLet a web page's checkbox content evolve from a user's interaction with an application.
Most web forms are static, meaning the text labels and entry widgets like textareas, checkboxes, and radio buttons are hard-coded into the HTML. Lots of applications however can benefit from the ability to whip together form elements on the fly, based on a user's preferences. The content for the forms, if necessary, can even be derived from a server, such as questions for various types of quizzes and polls.
Hey, hack #14 showed how to do this with a select list widget, so why don't we auto-generate a bunch of checkboxes?
This hack gives the user a choice of "team sports" or "individual sports" in two radio buttons, then, when they click either button, grabs the sports categories from a server component and creates a new group of checkboxes.
Choose Your Activity
Figure 2-12 shows our barebones web page to begin with, before the DOM magic starts!
Let the form evolution begin
Here is the HTML for the form. The dynamic behavior for this page is all contained in the JavaScript file hacks2_7.js. The two radio buttons that the users may click to get things going are represented by the two input elements, and the newly generated checkboxes will appear within the div element with the id "checks."
<!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="js/hacks2_7.js"></script>
<title>Dynamic checkboxes</title>
</head>
<body>
<h3>Voting on Favorite Sports</h3>
<h4>Pick a sports category</h4>
<form action="javascript:void%200">
<table border="0">
<tr><td>
Team Sports:
**<input type="radio" name="_sports" value="team" />**
</td></tr>
<tr><td> Individual Sports:
**<input type="radio" name="_sports" value="individual" />**
</td></tr>
</table>
<hr />
<div id="checks"></div>
</form>
</body>
</html>
When the user clicks a checkbox, the page instantly displays either of two different sets of new checkboxes representing either individual sports or team sports. The actual lists of sports that make up the checkboxes are arrays of strings that the server returns. They obviously could be hard-coded into the JavaScript in order to prevent a network hit, but imagine that the checkbox widgets represent values that are frequently changing and/or must be derived from persistent storage on the server, such as complex multiple-choice questions in a questionnaire or product information?
Figure 2-13 shows the web page after the user has clicked a radio button. This action only submits the value associated with the checkbox that the user clicked, not the entire form.
Widgets spawning other widgets
Okay, where's the code?
Here is the JavaScript contained in the file hacks2_7.js. We have omitted the code that creates and initializes the request object, which you can review in Hack #1 and several other earlier hacks. The first thing you may notice in the code is that it assigns a function to handle the radio button's onclick event handler. These events are triggered by the user clicking either radio button.
NOTE
An "event handler" such as onclick or onchange is an attribute of an HTML element that can be assigned the code that will be executed whenever the user clicks that element, for example.
This assignment begins in the window's onload event handler. This event takes place when all of the elements in the HTML page have been loaded by the browser.
var sportType="";
window.onload=function(){
var rads = document.getElementsByTagName("input");
if(rads != null) {
for(var i = 0; i < rads.length; i++) {
if(rads[i].type=="radio"){ rads[i].onclick=function(){
getSports(this)};}
}
}
}
function getSports(obj){
if (obj == null ) { return; }
var url = "";
var val = "";
if(obj.checked) {
val=obj.value;
sportType=val;
url = "http://www.parkerriver.com/s/fav_sports"+
"?sportType="+encodeURIComponent(val)+"&col=y";
httpRequest("GET",url,true);
}
}
//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 a JSON array
var objt = eval(resp);
createChecks(objt);
}
} else {
//request.status is 503
//if the application isn't available;
//500 if the application has a bug
alert(
"A problem occurred with communicating between"+
" the XMLHttpRequest object and the server program.");
}
}//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);
}
}
function createChecks(obj){
var _div = document.getElementById("checks");
var el;
//first remove all existing checkboxes
while(_div.hasChildNodes()){
for(var i = 0; i < _div.childNodes.length; i++){
_div.removeChild(_div.firstChild);
}
}
//obj is an array of new sports names
for(var i=0; i < obj.length;i++) {
el = document.createElement("input");
el.setAttribute("type","checkbox");
el.setAttribute("name",sportType);
el.setAttribute("value",obj[i]);
_div.appendChild(el);
_div.appendChild(document.createTextNode(obj[i]));
_div.appendChild(document.createElement("br"));
}
}
/* httpRequest() and related code omitted for the sake of brevity... */
The first stage in generating the checkboxes is to send the request that fetches the values for each of these widgets. When the user clicks a radio button, the code calls getSports(). This function formats a URL based on the value it receives from the checkbox, then sends a request to a server component for a list of related sports.
Greetings JSON
The response comes back from the server in a string formatted as JavaScript Object Notation (JSON). A response might look like "["football","soccer","tennis", etc.]". We get the response from the request object's responseText property, then convert the response to a JavaScript array using the eval() global function. Phew, that was a mouthful! If that wasn't clear, then see the discussion of handling JSON server values in Hack #6.
NOTE
Ajax developers often advocate JSON as the format of the server return value, in the many cases where XML might be overkill on both the server and client side of things.
Once the code has this array of values from the server, then the code passes the array along to createChecks(). This function uses the DOM API to create the checkboxes, one checkbox for each value in the array (a checkbox for tennis, another for soccer, and so on). Here is the code for this function.
function createChecks(obj){
var _div = document.getElementById("checks");
var el;
//first remove all existing checkboxes
while(_div.hasChildNodes()){
for(var i = 0; i < _div.childNodes.length; i++){
_div.removeChild(_div.firstChild);
}
}
//obj is an array of new sports names
for(var i=0; i < obj.length;i++) {
el = document.createElement("input");
el.setAttribute("type","checkbox");
el.setAttribute("name",sportType);
el.setAttribute("value",obj[i]);
_div.appendChild(el);
_div.appendChild(document.createTextNode(obj[i]));
_div.appendChild(document.createElement("br"));
}
The function gets a reference to the div element on the HTML page that will enclose the checkboxes. Then the code removes any existing checkboxes, because of it didn't, then the user could keep checking the radio buttons and generate several duplicate checkboxes appended on the end of the web page; this is an outcome we want to avoid. Finally, the code creates a new input element for each sport, so that each of these widgets looks like:
<input type="checkbox" name=
"teams_sports" value="baseball" /> baseball

As soon as this function completes executing, the checkboxes appear on the web page without any visible refresh. Like magic!
Hacking the Hack
Naturally, you want the user to check these generated checkboxes for some purpose. Maybe to generate another sub-set of widgets or checkboxes? Or to send the values from the new checkboxes, when the user clicks them, to a server component? You can adapt the code from Submit Checkbox Values To The Server Without A Browser Refresh to accomplish the latter task, as well as create onclick event handlers for the new checkboxes (as in this hack) to give them some behavior.
======================
Сокращённый в этой главе код (из предыдущей главы):
/* Initialize a Request object that is already constructed */
function initReq(reqType,url,bool){
try{
/* Specify the function that will handle the
HTTP response */
request.onreadystatechange=handleResponse;
request.open(reqType,url,bool);
request.send(null);
} catch (errv) {
alert(
"The application cannot contact the server "+
"at the moment. "+
"Please try again in a few seconds." );
}
}
/* Wrapper function for constructing a Request object.
Parameters:
reqType: The HTTP request type such as GET or POST.
url: The URL of the server program.
asynch: Whether to send the request asynchronously or not. */
function httpRequest(reqType,url,asynch){
//Mozilla-based browsers
if(window.XMLHttpRequest){
request = new XMLHttpRequest();
initReq(reqType,url,asynch);
} else if (window.ActiveXObject){
request=new ActiveXObject("Msxml2.XMLHTTP");
if (! request){
request=new ActiveXObject("Microsoft.XMLHTTP");
}
if(request){
initReq(reqType,url,asynch);
} else {
alert("Your browser does not permit the use "+
"of all of this application's features!");}
} else {
alert("Your browser does not permit the use "+
"of all of this application's features!");}
}