In this section, we introduce the JavaScript scripting language as a client-side method for validation and other simple tasks. JavaScript isn't a full-fledged programming language like PHP: it can't connect to databases, it offers only limited interaction with certain system resources, and it can't do most tasks a web database application requires. However, JavaScript is good for interacting with a form and for controlling the display of data to the user. Client-side validation with JavaScript is optional but has benefits, including faster response to the user than server-side validation, a reduction in web server load, and a reduction in network traffic. Also, unlike server-side validation, it can be implemented as interactive validation where errors are checked as they occur and field-by-field reporting where error messages are shown individually. However, validation in the client tier is unreliable: the user can bypass the validation through design, error, or misconfiguration of their web browser. For that reason, client-side validation should be used only to improve speed, reduce load, and add features, and never to replace server-side validation.
Besides validation, there are many other common uses of JavaScript in web database applications including:
Most of these techniques are oriented around events. An event is an action that can be trapped through JavaScript code, such as a mouse passing over an object, a window opening, or a user clicking on a button. The next section introduces JavaScript through a simple example. After that, we show you the basics of JavaScript by contrasting and comparing it with PHP, and then we show you several more examples including a case study. However, this section isn't comprehensive and isn't aimed as a replacement for many of the excellent resources that are available; selected resources are listed in Appendix G. 9.3.1 Introducing JavaScriptConsider the short JavaScript validation example in Example 9-5. Example 9-5. A simple JavaScript example to check if a form field is empty<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Simple JavaScript Example</title> <script type="text/javascript"> <!-- Hide the script from old browsers function containsblanks(s) { for(var i = 0; i < s.value.length; i++) { var c = s.value.charAt(i); if ((c == ' ') || (c == '\n') || (c == '\t')) { alert('The field must not contain whitespace'); return false; } } return true; } // end hiding --> </script> </head> <body> <h1>Username Form</h1> <form onSubmit="return(containsblanks(document.userform.username));" method="POST" name="userform" action="test.php"> <input type="text" name="username" size=10> <input type="submit"> </form> </body> </html> This example is designed to check if an optional username field contains whitespace and, if so, to show a dialog box containing an error message to the user. The dialog box is shown in Figure 9-1. The example contains a mixture of HTML and JavaScript, and almost all the JavaScript is encapsulated between the <script> and </script> tags in the <head> tag of the document. Figure 9-1. The dialog box produced when whitespace is entered in the username fieldThe JavaScript function containsBlanks( ) is called when the user submits the form. The function call is part of the form element: <form onSubmit="return(containsblanks(document.userform.username));" method="post" name="userform" action="test.php"> When the submission event occurs (when the user presses the Submit button or presses the Enter key while the cursor is in the text widget) the onSubmit event is triggered. In this case, the result is that the function containsblanks( ) is called with one parameter, document.userform.username. The object document refers to the document loaded in the browser window, the userform is the name of the form itself, and username is the name of the input widget within the form. The function call itself is wrapped in a return( ) expression. The overall result of executing containsblanks( ) is that if the function returns false, the form isn't submitted to the server; if the function returns true, the HTTP request proceeds as usual. The function containsblanks( ) works as follows:
HTML comment tags are included inside the <script> tags and surround the JavaScript script. This is good practice, because if JavaScript is disabled or the user has an old browser that knows nothing about scripts, the comments hide the script from a potentially confused browser. An old browser happily displays the HTML page as usual. In addition, an old browser or one that has JavaScript turned off will ignore the onSubmit event handler in the form element. 9.3.2 JavaScript and PHPThe syntax of JavaScript is similar to PHP and to other languages such as C and Java. Table 9-2 compares some of the basic features of PHP and JavaScript that we used in Example 9-5 and others that are used later in this chapter. The key differences are that JavaScript variables aren't prefixed with a dollar sign, local variables must be declared in JavaScript, different open and close script tags are used, and string concatenation in JavaScript uses a plus sign and PHP uses a period. Other than that, the languages are very similar when used for basic tasks.
9.3.2.1 Generating outputIn PHP, output to the browser is generated using the print or printf statements, or by using a template and template methods as discussed in Chapter 7. In JavaScript, there are several different ways output can be produced including writing output to the browser window as a document is created, creating dialog boxes, updating values in form widgets, and creating new windows. To write output to a window, the writeln( ) method can be used: <script type="text/javascript"> document.writeln("Hello, world."); </script> The document object refers to the document that is displayed in the browser window, and writeln( ) is a method associated with that document. You can write to a document only as it's created, you can't use this method to write text to the document after it's been rendered in the browser. The basic objects and methods are discussed later in this section. In Example 9-5, a dialog box with an OK button is created with the alert( ) method. For example, you can pop up a dialog box when the user clicks on a button: <form action="test.php"> <input type="button" value="Pop a box" onclick="alert('Pop!');"> </form> The onclick attribute causes the box to appear when the user clicks on the button. It's also possible to create dialog boxes using the confirm( ) method that displays both an OK and Cancel button: <script type="text/javascript"> if (confirm("Are you sure?")) alert("Great!"); else alert("What a pity!"); </script> The confirm( ) method returns true when the user clicks Ok and false otherwise. Another approach to producing output is to write to the browser window status line. However, this isn't a very effective mechanism: the status bar may be hidden or disabled, and it's easy to overlook messages displayed at the base of the window. Yet another approach is to create a new fully-featured non-dialog browser window, which we discuss later in this chapter. The final approach is to update values in input widgets, an approach we use later in our examples. 9.3.2.2 Loops and conditionalsLoops and conditionals are almost the same in both languages. As discussed in Chapter 2, PHP has the for, while, foreach, and do...while loops, and the if and switch conditionals. JavaScript has the for, while, do...while, and for...in loops, and the if and switch statements. The continue and break statements are available in both languages. The for, while, and do...while loops are the same in PHP and JavaScript, with the exception that in JavaScript it's possible to declare a variable in a for loop with the var statement; an example is shown in Example 9-5. JavaScript also has the for...in statement which allows you to iterate through properties of objects, while PHP has the foreach statement for iterating through elements in arrays. An example with the for...in statement is presented later in this section. 9.3.2.3 FunctionsFunctions are similar in PHP and JavaScript. Consider the following JavaScript example: function bold(string) { document.writeln("<b>" + string + "</b>\n"); } When called with the function call bold("this is bold"), the function prints the string <b>this is bold</b> as part of the document. Similarly to PHP, functions are declared with the statement function, parameters are listed in brackets and separated by commas, and the function body is surrounded by curly braces. Functions can optionally return values using the return statement, which behaves identically in PHP and JavaScript. Variables that are declared within a function are local to that function. Local variables must be declared using the var statement as in the following example: function count( ) { var x=1; while (x<6) { document.writeln(x + " "); x++; } } Variables that are used or declared outside functions are global variables. Declaring globals with var is optional: as in PHP, they can be declared implicitly by assigning values to them. However, unlike PHP, global variables in JavaScript are accessible everywhere in the current document; global variables are not declared in functions using the global keyword. 9.3.2.4 Debugging JavaScriptJavaScript has two types of errors that report messages: load-time and run-time errors. Load-time errors are sometimes reported by the user agent before it runs the JavaScript, and you'll be shown a warning box that details the error, its line number, and the code fragment itself. Run-time errors occur when a code fragment is running and, again, a warning box is sometimes displayed with the line number of the code that caused the error. The inconsistent nature of error reporting can be annoying: often, you'll get no messages at all but the script won't run. However, in many browsers (including Mozilla and Netscape), you can get more detailed error information by typing javascript: in the Location box and pressing Enter. The JavaScript console that pops up lists all errors that have occurred since the browser began running. You can remove old messages by clicking on the Clear button that's shown at the top of the console window; periodically doing this is a good way to avoid confusion about which errors are applicable to what. Errors can also be annoying because they are often platform- or browser-dependent and change from one release to the next. Complex JavaScript adds a thicker client to a web database application, and this may reveal differences between browser applications, browser versions, and different platforms. If complex JavaScript is required or desired, make sure it's tested on all the popular platforms with the popular browser products and versions. However, we recommend that JavaScript be kept simple: complex tasks should be left to PHP scripts, and you should ensure that user interfaces function correctly even if JavaScript is faulty or disabled. 9.3.2.5 ObjectsObjects associated with the browser, windows, and the document are accessible in JavaScript. For example, in Example 9-5, the form object and its child (an input text widget) are accessed and used. Historically, the definition of these objects (and the events, properties, and methods described in the next sections) was part of the JavaScript standard and they were loosely known as the Navigator objects . This has now changed, and the objects are defined as part of the Document Object Model (DOM). In this section, we informally describe the objects and properties that are accessible from within JavaScript, and avoid the details of DOM. However, the complete specification is accessible at http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/. The window object is the top of the DOM hierarchy, and it contains the toolbars and menus of the browser, as well as the document and its sub-components. The hierarchy of the objects that descend from window is shown in Figure 9-2. Figure 9-2. The hierarchy of window objects in the DOMIn JavaScript code, the document object can be referenced using the same notation as in PHP's object-oriented model using window.document, or just document for short (because there is only one window when you're not using frames, which we don't in this book). Each of the objects in the hierarchy has properties and methods, and creates events. Properties are characteristics that describe the appearance of the object, while the methods are its behaviors. Events are actions that the object can act on such as mouse clicks or key presses. Events are discussed in the next section, and methods and properties in the following section. However, selected events, methods, and properties are used in examples here. Consider a document that contains a form that has a text input widget and a submit button: <form name="custform" method="GET" action="cust.php"> Surname: <input type="text" name="surname"> <br><input type="submit" name="submit"> </form> The text widget is accessible using the names associated with the objects in the hierarchy. In this example, the form widget has the attribute name="custform", and the input widget has the attribute name="surname". You can therefore reference the text widget's value as document.custform.surname.value. The value is a property of the text input widget and it contains the data the user has entered, or it can be assigned a value to modify the data that's shown in the widget. The value could be output when an onchange event is triggered in the widget itself: <input type="text" name="surname" onchange="alert('You entered ' + document.custform.surname.value);"> The browser automatically generates an onchange event when the text in the widget changes; we explain events more later. Alternatively, you could output the value when the submission process itself occurs: <form name="custform" method="GET" action="cust.php" onsubmit="alert('You entered ' + document.custform.surname.value);"> Another way to access the properties of an object is to access the DOM element array. For example, the value of the text input widget in the custform can be referenced as: document.forms[0].elements[0].value The notation forms[0] means the first form in the document, and elements[0] means the first element of that form. You can iterate through all properties in an object using the for...in statement that we introduced previously. For example, to show the names of all elements in the form, use: for (o in document.custform) string += o + ' '; alert("Here are the elements: " + string); The loop assigns each element in document.custform in turn to the variable o, and then o is appended to string. If you include this fragment in a document containing the custform, you may be surprised that it outputs not just the surname widget and the submit button, but more than 50 properties. Some of these are discussed in the next section. Some browsers are fussier than others. For example, Microsoft's Internet Explorer complains when you reference an object before it's defined. This means that you can't reference a form earlier in your HTML source than where the form is actually declared. You'll find that these kinds of issues make developing complex or portable code difficult. As we've already discussed, we recommend you use JavaScript for simple tasks and leave the complex ones to PHP on the server side. In JavaScript, there are also several other pre-defined objects. These include core objects such as Array, RegExp, Date, and Math that are discussed in detail in the Core JavaScript Guide available from http://devedge.netscape.com/library/manuals/. 9.3.2.6 EventsEvents are triggered by both the user agent (usually a web browser), and the user working with the document and browser. These events are useful triggers for JavaScript actions. For example, a function might be called as a page loads, when the user presses the submit button, when a form field changes, or when the mouse passes over a document element. Examples using many of these events are included in later examples in this chapter. The key events that can be trapped and handled by JavaScript are as follows:
We have omitted other events related to key presses and text selection, as well as other types of mouse clicks and movements. These are detailed in the HTML 4.01 documentation at http://www.w3.org/TR/html4/interact/scripts.html. and in the DOM documentation listed in the previous section. 9.3.2.7 Methods and propertiesThe window, document, form, and input element objects have properties and methods that are commonly accessed and used in validation tasks. This section lists selected methods and properties, and examples later in this chapter show many of these used in scripts. Images, tables, the document body, document styles, and frames also have their own methods and properties, but we don't discuss these here. The complete list of objects, methods, and properties can be found at http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/ecma-script-binding.html. The navigator object is outside of the window hierarchy we showed in Figure 9-2 and the standards. However, it is useful because it describes the browser environment. Its properties include:
The window object properties and methods include:
The document object properties and methods include:
The form objects in a document include the following properties and methods:
The form elements select, textarea, input, and button have common methods and properties that include:
9.3.3 JavaScript ExamplesThe short examples in this section implement simple, common, and useful JavaScript web database application features that use the techniques we have discussed so far. These include:
9.3.3.1 A password form validation functionExample 9-6 is an example of JavaScript validation that checks whether a password is the same when the user enters it twice. The validation is interactive: an onchange event is trapped for the two password widgets, formPassword1 and formPassword2, and the function thesame( ) is called whenever the user changes the data in a widget and then leaves it. The error reporting is field-by-field. Example 9-6. Using JavaScript for interactive validation of password fields<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Password Validation</title> <script type="text/javascript"> <!-- Hide the script function thesame(value1, value2) { if (((value1 != null) || (value1 != "")) && value2 != "" && value1 != value2) { alert("The passwords must be identical."); return (false); } return (true); } // end hiding --> </script> </head> <body> <h1>Username Form</h1> <form method="post" action="test.php" name="userForm"> <br>Username: <input type="text" name="userName" size=10> <br>Password: <input type="password" name="formPassword1" size=10 onchange="thesame(document.userForm.formPassword1.value, document.userForm.formPassword2.value);"> <br>Re-enter password: <input type="password" name="formPassword2" size=10 onchange="thesame(document.userForm.formPassword2.value, document.userForm.formPassword1.value);"> <br><input type="submit" value="SUBMIT"> </form> </body> </html> The function thesame( ) checks if the current widget contains data. If it does, and the other password widget also contains data, the data in the two widgets is compared. If the data in the widgets is different, an error message is shown to the user. It's necessary to test whether both widgets actually contain data in interactive validation; without this check, the function annoyingly displays an error before the user has the opportunity to enter data into both widgets. 9.3.3.2 Rollover presentation with mouseover eventsExample 9-7 shows a basic implementation of the common rollover feature used in many web applications. Example 9-7. mouseover example with JavaScript<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>MouseOver Example</title> </head> <body bgcolor="#ffffff"> <a href="add_to_cart.php" onmouseout="cart.src='/books/2/581/1/html/2/cart_off.jpg'" onmouseover="cart.src='/books/2/581/1/html/2/cart_on.jpg'"> <img src="/books/2/581/1/html/2/cart_off.jpg" border=0 name="cart" alt="cart picture"></a> </body> </html> When the page is first loaded, an un-highlighted image of a shopping cart is shown; the image is used in the front page of the winestore in Chapter 16. The image is loaded with the HTML fragment: <img src="/books/2/581/1/html/2/cart_off.jpg" border=0 name="cart"> The only difference to the usual approach of loading images is that the <img> tag has the attribute name="cart". If the mouse passes over the cart image, an onmouseover event is triggered, and the JavaScript action carried out is: onmouseover="cart.src='/books/2/581/1/html/2/cart_on.jpg'" The event handler changes the value of the src attribute of the <img> tag with the name="cart". The result is that a new highlighted image is loaded to replace the un-highlighted image. In the case of our winestore, a shopping cart with a blue foreground is shown. When the mouse leaves the image region, the onmouseout event is generated and handled with the following JavaScript fragment: onmouseout="cart.src='/books/2/581/1/html/2/cart_off.jpg'" This restores the original image. The impression to the user is that the cart element is highlighted as the user focuses on the element. Rollovers are straightforward to develop and the approach we've shown you works in all graphical browsers. You can even use the same technique to highlight menu options, and to produce pop-up and pull-down menus. 9.3.3.3 Prefilling form data with JavaScript calculationsAnother common use of JavaScript is to pre-fill a form with data from a calculation. Example 9-8 shows how data can be managed and updated in a shopping cart. When the user changes the quantity of a wine he intends to purchase, an onchange event is generated. This change event is handled by the update( ) function, which modifies the value attribute of the total widget, showing the new total cost to the user. The new value shown to the user is calculated by multiplying together the quantity.value and the unit.value. Of course, as in all web database applications, the values and mathematics should be rechecked at the server when the form is submitted to the server. Example 9-8. Using JavaScript to dynamically update values of form widgets<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Dynamic Form Update Example</title> </head> <body> <h1>Your Shopping Cart</h1> <form method="get" action="test.php"> <table border="0" width="100%" cellpadding="0" cellspacing="5"> <tr> <td>Quantity </td> <td>Wine</td> <td>Unit Price</td> <td>Total</td> </tr> <tr> <td><input type="text" name="quantity" value="1" size=3 onchange="total.value = unit.value * quantity.value;"> <td>1997 Anderson and Sons Wines Belcombe Grenache</td> <td>$<input type="text" value="17.29" name="unit" readonly></td> <td>$<input type="text" value="17.29" name="total" align="right" readonly></td> </tr> </table> <input type="submit" value="Purchase Wines"> </form> </body> </html> 9.3.3.4 Interacting with the web browserExample 9-9 shows four examples of handlers for buttons that use the methods defined for the window object. The method window.close( ) closes the focused window, window.print( ) shows the print dialog window, window.back( ) goes back one page, and window.open( ) opens a new browser window. Example 9-9. Closing and opening windows with JavaScript, printing the current page, and adding a Back button to a form<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Playing with the Browser and Windows</title> </head> <body> <h1>Playing with the Browser and Windows</h1> <form action="example.9-6.php"> <input type="button" value="Close Window" onClick="window.close( );"> <br><input type="button" value="Print Window" onClick="window.print( );"> <br><input type="button" value="Go Back" onclick="window.back( );"> <br><input type="button" value="Visit the book site" onClick="window.open('http://www.webdatabasebook.com/','BookSite', 'toolbar=yes,location=yes,menubar=yes,directories=yes,scrollbars=yes, resizable=yes');"> </form> </body> </html> Only window.open( ) is complex. The first parameter is the URL to request in the new window, the second is a title, and the third is a set of properties of the new window. Without the list of properties that are included, the default new window has no Location box, no toolbars, no scrollbars, and can't be resized. 9.3.3.5 Which browser is the user using?As discussed previously, even simple JavaScript sometimes highlights annoying differences in the way browsers support standard features. Indeed, even different versions of the same browsers support different JavaScript features from the same version of the standard. Example 9-10 shows how the browser application name and version can be detected with both JavaScript and PHP. If a JavaScript script requires customization for a particular product, if statements can carry out actions in different ways. Another common approach in JavaScript-intensive web database applications is to write two sites: one that uses Internet Explorer JavaScript (known as Jscript), and another that uses Netscape Navigator or Mozilla JavaScript. However, as we recommended earlier, complex JavaScript is often best avoided in favor of server-side scripts. Example 9-10. Which browser is the user using?<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <title>Playing with the Browser and Windows</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body> <script type="text/javascript"> <!-- Hide the script from old browsers alert("You are using " + navigator.userAgent); // end the hiding --> </script> This page should pop up a box if you have a JavaScript-capable and enabled browser. <br>But, using PHP, we can tell you that you're using the <?php print $_SERVER["HTTP_USER_AGENT"]; ?> browser. </body> </html> 9.3.3.6 Drop-down menusA common use of JavaScript is to automatically load a new page when a user selects a menu option from a drop-down list. Example 9-11 shows how to do this using a select widget and its properties. The JavaScript in the body of the document is straightforward: when the user changes their selection of menu item, an onchange event is triggered, and the loadNewPage( ) function is called. Example 9-11. Drop-down menus that load a new URL<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Menu Example</title> <script type="text/javascript"> <!-- function loadNewPage( ) { var listItem = document.menuForm.newPage.selectedIndex; var newPage = document.menuForm.newPage.options[listItem].value; location.href = newPage; } //--> </script> <body> Where do you want to go now? <br><form method="GET" action="menus.html" name="menuForm"> <select name="newPage" onchange="loadNewPage( );"> <option value="menus.html">This page <option value="http://www.webdatabasebook.com/">The book web site <option value="http://www.oreilly.com/">O'Reilly and Associates <option value="http://www.hughwilliams.com/">Hugh's homepage <option value="http://www.mds.rmit.edu.au/~dave/">Dave's homepage </select> </form> </body> </html> The form in Example 9-11 has a name attribute of menuForm and the select list has a name attribute of newPage. Therefore, the list is referenced as document.menuForm.newPage. The loadNewPage( ) function references the list to load the new page in three steps:
9.3.4 Case Study: A Generic JavaScript Validation FunctionThe example in this section shows how JavaScript can be used as a validation tool across multiple HTML pages or templates. An example of errors produced by applying the techniques described in this section to customer validation is shown in Figure 9-3. We show you the JavaScript code, the PEAR IT template, and the PHP code in this section. Figure 9-3. A dialog box showing an error produced by the JavaScript validation function9.3.4.1 The JavaScript validation scriptThe general-purpose verify( ) function for post-validation and field-by-field error reporting is shown in Example 9-12. The code is stored in the file example.9-12.js and is designed to be added to a template, such as the phonebook template developed in Chapter 8. Later in this section, we show you how to add it to a customer details template that has diverse validation needs. By storing JavaScript code in its own file, it can be reused across multiple HTML pages or templates. To do this, instead of including code between the <script> and </script> tags, you add a src attribute to the <script> element that specifies the file that contains the JavaScript code. For example, to load the code in Example 9-12 into a document or template, you use: <script type="text/javascript" src="/books/2/581/1/html/2/example.9-12.js"> </script> This approach saves cutting and pasting the code into more than one file, and avoids the need to update several pages when the script changes. It also has the additional advantage of reducing network traffic if the user has a web browser cache, because a copy of the script can be reused in multiple HTML pages without retrieving it again from the web server. Example 9-12. A general-purpose JavaScript form validation function// A utility function that returns true if a string contains only // whitespace characters. function isblank(e) { if (e.value == null || e.value == "") return true; for(var i = 0; i < e.value.length; i++) { var c = e.value.charAt(i); if ((c != ' ') && (c != '\n') && (c != '\t')) return false; } return true; } // Checks if an optional field is blank function checkblank(e) { if (isblank(e)) { alert("The field " + e.description + " must be filled in."); return false; } return true; } // Checks if a field is numeric. // If the optional min property is set, it checks it is greater than // its value // If the optional max property is set, it checks it is less than // its value function checknumber(e) { var v = parseFloat(e.value); if (isNaN(v)) { alert("The field " + e.description + " must be a number"); return false; } if ((e.minNumber != null) && (v < e.minNumber)) { alert("The field " + e.description + " must be greater than or equal to " + e.minNumber); return false; } if (e.maxNumber != null && v > e.maxNumber) { alert("The field " + e.description + " must be less than or equal to " + e.maxNumber); return false; } return true; } // Checks if a field looks like a date in the 99/99/9999 format function checkdate(e) { var slashCount = 0; if (e.value.length != 10) { alert(" The field " + e.description + " must have the format 99/99/9999" + " and be 10 characters in length"); return false; } for(var j = 0; j < e.value.length; j++) { var c = e.value.charAt(j); if ((c == '/')) slashCount++; if (c != '/' && (c < '0' || c > '9')) { alert(" The field " + e.description + " can contain only numbers and forward-slashes"); return false; } } if (slashCount != 2) { alert(" The field " + e.description + " must have the format 99/99/9999"); return false; } return true; } // Checks if a field contains any whitespace function checkwhitespace(e) { var seenAt = false; for(var j = 0; j < e.value.length; j++) { var c = e.value.charAt(j); if ((c == ' ') || (c == '\n') || (c == '\t')) { alert("The field " + e.description + " must not contain whitespace"); return false; } } return true; } // Now check for fields that are supposed to be emails. // Only checks that there's one @ symbol and no whitespace function checkemail(e) { var seenAt = false; for(var j = 0; j < e.value.length; j++) { var c = e.value.charAt(j); if ((c == ' ') || (c == '\n') || (c == '\t')) { alert("The field " + e.description + " must not contain whitespace"); return false; } if ((c == '@') && (seenAt == true)) { alert("The field " + e.description + " must contain only one @"); return false; } if ((c == '@')) seenAt = true; } if (seenAt == false) { alert("The field " + e.description + " must contain one @"); return false; } return true; } // This is the function that performs <form> validation. // It is invoked from the onSubmit( ) event handler. // The handler should return whatever value this function // returns. function verify(f) { // Loop through the elements of the form, looking for all // text and textarea elements. Report errors using a post validation, // field-by-field approach for(var i = 0; i < f.length; i++) { var e = f.elements[i]; if (((e.type == "text") || (e.type == "textarea"))) { // first check if the field is empty and shouldn't be if (!e.isOptional && !checkblank(e)) return false; // Now check for fields that are supposed to be numeric. if (!isblank(e) && e.isNumeric && !checknumber(e)) return false; // Now check for fields that are supposed to be dates if (!isblank(e) && e.isDate && !checkdate(e)) return false; // Now check for fields that are supposed to be emails if (!isblank(e) && e.isEmail && !checkemail(e)) return false; // Now check for fields that are supposed // not to have whitespace if (!isblank(e) && e.hasNospaces && !checkwhitespace(e)) return false; } // if (type is text or textarea) } // for each character in field // There were no errors if we got this far return true; } Example 9-12 contains several functions and the main function is the last one in the file, verify( ). The verify( ) function is called when a form is submitted, and it expects the form object to be passed to it as a parameter. The function iterates through the elements in the form and carries out validation checks on each field, depending on what properties you set for that field. If any check fails, the function returns false. If all checks succeed, the function returns true. We show you how to call the function and set the element properties later. The first fragment of the verify( ) function is as follows: function verify(f) { // Loop through the elements of the form, looking for all // text and textarea elements. Report errors using a post validation, // field-by-field approach for(var i = 0; i < f.length; i++) { var e = f.elements[i]; A form object f is expected as a parameter. The for loop iterates through each element object in f. The first element is numbered 0 and the total elements in the form is stored in the property f.length. As discussed previously, the element objects are stored in the elements array and so, for example, f.elements[0] is the object representation of the first element in f. For compactness in the code, with each iteration of the loop, we assign the current element object to the local variable e. The next fragment in verify( ) checks whether the current input element is of type text or textarea: if (((e.type == "text") || (e.type == "textarea"))) { We've only written validation functions for these types of element, and we leave it to you to extend this further to meet your needs. The remainder of the verify( ) function tests different properties of the current element and calls functions to validate it. For example, the following fragment tests if the element contains a value (that is, it's not blank), if the isNumeric property is set, and if the value is not a number: // Now check for fields that are supposed to be numeric. if (!isblank(e) && e.isNumeric && !checknumber(e)) return false; The result of this check is that if the element isn't blank and is supposed to be numeric and isn't a number, the function returns false. In the same way as PHP's short-circuit evaluation discussed in Chapter 2, the second and subsequent tests in the if expression are only carried out if all preceding tests are true. The isblank( ) and checknumber( ) functions are validation functions in Example 9-12, and the isNumeric property is a user defined property that we discuss later. There are several functions in Example 9-12 that each begin with the prefix check. In addition to testing if a mandatory field is blank and if a field is numeric, these check whether mandatory fields have data in them, dates are in a reasonable format, email addresses look plausible, and whether there's whitespace within a value. As an example, we discuss the checkdate( ) function next; we don't discuss the others in detail but they use the same ideas and validation steps. The checkdate( ) functions perform very simple date format checking: it tests if a date has the format 99/99/9999 where 9 is a digit. More explicitly, it checks that the value is exactly 10 characters in length, contains only forward slashes and digits, and has only two forward slashes. It doesn't check the ordering of the characters, nor the validity of the date by the calendar. It's therefore a simple first step in validation: if the check succeeds, there's more chance it'll pass the more detailed server-side validation that occurs after the form is submitted. The checkdate( ) function returns true if validation succeeds and false otherwise. The checkdate( ) function begins as follows: // Checks if a field looks like a date in the 99/99/9999 format function checkdate(e) { var slashCount = 0; if (e.value.length != 10) { alert(" The field " + e.description + " must have the format 99/99/9999" + " and be 10 characters in length"); return false; } It expects a form element e as a parameter. The local variable slashCount is used later to count the number of forward slashes. The first test checks if the value is ten characters in length and, if not, it shows an error dialog and the function returns false. The description property of the element is set before the verify( ) function is called and we show you this later. The next fragment is as follows: for(var j = 0; j < e.value.length; j++) { var c = e.value.charAt(j); The for loop iterates through each character of the value in the element; the first element is 0 and the last is determined from the property e.value.length. For compactness in the later code, we store the current character in the local variable c by retrieving is using the built-in charAt( ) method discussed previously. The body of the loop has two straightforward steps. First, if the current character is a forward slash, we increment the counter: if ((c == '/')) slashCount++; Second, if the current character isn't a forward slash and isn't a digit we pop up an error dialog and the function returns false: if (c != '/' && (c < '0' || c > '9')) { alert(" The field " + e.description + " can contain only numbers and forward-slashes"); return false; } } If the execution of the function makes it to the next fragment only digits and forward slashes have been encountered in the value. Now, we check whether there were two forward slashes: if (slashCount != 2) { alert(" The field " + e.description + " must have the format 99/99/9999"); return false; } return true; } If the check fails, we pop up an error dialog and return false. If all checks have succeeded the value looks like a date and the function returns true. 9.3.4.2 Using the JavaScript validation functionTo use the verify( ) function, you call it from the onsubmit handler of a form. For example, suppose you have authored the customer details input form that's shown in Figure 9-4. Figure 9-4. A customer form with JavaScript validationThe form requires users to provide a first name, surname, address, email address, date of birth, and salary. For this form, the onsubmit handler that's used is as follows: <form action="test.php" method="post" name="custform" onsubmit="document.custform.firstname.hasNospaces = true; document.custform.firstname.description = 'First Name'; document.custform.surname.description = 'Surname'; document.custform.address.description = 'Address'; document.custform.email.description = 'Email'; document.custform.email.isEmail = true; document.custform.dob.isDate = true; document.custform.dob.description = 'Date of Birth (99/99/9999)'; document.custform.salary.description = 'Salary'; document.custform.salary.isNumeric = true; document.custform.salary.minNumber = 1; document.custform.salary.maxNumber = 1000000; document.custform.salary.hasNospaces = true; return verify(document.custform);"> This code fragment creates and sets properties for each form element. These are properties that we've created (and not part of JavaScript itself), and each is used by some part of our verify( ) function. For example, when validation fails, the validation functions show error messages that inform the user which field contains the error. To pass this string to the validation function, we create a description property for all elements that are validated. For instance, the fragment: document.custform.email.description = 'Email'; sets the description property of the email input element to 'Email'. As shown in the previous section, this description is displayed in a dialog box when an error occurs. To control validation, you set a property that triggers a validation function in the verify( ) function. For example, if you want an element to be validated using the checkdate( ) function we described previously, you set the isDate property to be true: document.custform.dob.isDate = true; The verify( ) function inspects this property and, because it's true, it calls the checkdate( ) function to validate the dob field. The other properties we've set up can be used to trigger other types of validation. You can set are isEmail (for an email address), hasNoSpaces (if an element should not contain whitespace), isNumeric (for integers), minNumber (a minimum value for an isNumeric element), maxNumber (a maximum value for an isNumeric element), and isOptional (for an element that can be left blank). 9.3.4.3 The PHP and template componentsTo complete our JavaScript validation case study, Example 9-13 and Example 9-14 show the PHP script and template respectively that create the customer details form. Example 9-13 is a variation of Example 8-7 (which displays the phonebook form) with three differences. The first is that it displays several more elements than the phonebook script. The second is that it sets a new SUBMITACTION placeholder in the template to the JavaScript code fragment discussed in the previous section. The final difference is that it also sets the name of the form into a new placeholder, FORMNAME. Example 9-13. The PHP script to produce the customer details form<?php require 'db.inc'; require_once "HTML/Template/ITX.php"; $template = new HTML_Template_ITX("./templates"); $template->loadTemplatefile("example.9-11.tpl", true, true); $template->setVariable("MESSAGE", "Please fill in the details below to join"); $template->setVariable("SUBMITVALUE", "Join Now!"); $template->setVariable("FORMNAME", "custform"); $template->setVariable("SUBMITACTION", " document.custform.firstname.hasNospaces = true; document.custform.firstname.description = 'First Name'; document.custform.surname.description = 'Surname'; document.custform.address.description = 'Address'; document.custform.email.description = 'Email'; document.custform.email.isEmail = true; document.custform.dob.isDate = true; document.custform.dob.description = 'Date of Birth (99/99/9999)'; document.custform.salary.description = 'Salary'; document.custform.salary.isNumeric = true; document.custform.salary.minNumber = 1; document.custform.salary.maxNumber = 1000000; document.custform.salary.hasNospaces = true; return verify(document.custform);"); $template->setCurrentBlock("mandatoryinput"); $template->setVariable("MINPUTTEXT", "First name"); $template->setVariable("MINPUTNAME", "firstname"); $template->setVariable("MINPUTVALUE", ""); $template->setVariable("MINPUTSIZE", 50); $template->parseCurrentBlock("mandatoryinput"); $template->setCurrentBlock("mandatoryinput"); $template->setVariable("MINPUTTEXT", "Surname"); $template->setVariable("MINPUTNAME", "surname"); $template->setVariable("MINPUTVALUE", ""); $template->setVariable("MINPUTSIZE", 50); $template->parseCurrentBlock("mandatoryinput"); $template->setCurrentBlock("mandatoryinput"); $template->setVariable("MINPUTTEXT", "Address"); $template->setVariable("MINPUTNAME", "address"); $template->setVariable("MINPUTVALUE", ""); $template->setVariable("MINPUTSIZE", 50); $template->parseCurrentBlock("mandatoryinput"); $template->setCurrentBlock("mandatoryinput"); $template->setVariable("MINPUTTEXT", "Date of Birth (dd/mm/yyyy)"); $template->setVariable("MINPUTNAME", "dob"); $template->setVariable("MINPUTVALUE", ""); $template->setVariable("MINPUTSIZE", 50); $template->parseCurrentBlock("mandatoryinput"); $template->setCurrentBlock("mandatoryinput"); $template->setVariable("MINPUTTEXT", "Email"); $template->setVariable("MINPUTNAME", "email"); $template->setVariable("MINPUTVALUE", ""); $template->setVariable("MINPUTSIZE", 30); $template->parseCurrentBlock("mandatoryinput"); $template->setCurrentBlock("mandatoryinput"); $template->setVariable("MINPUTTEXT", "Annual salary (whole dollars)"); $template->setVariable("MINPUTNAME", "salary"); $template->setVariable("MINPUTVALUE", ""); $template->setVariable("MINPUTSIZE", 6); $template->parseCurrentBlock("mandatoryinput"); $template->parseCurrentBlock( ); $template->show( ); ?> Example 9-14 is almost identical to the template in Example 8-8. Example 9-14. The template used to produce the customer form<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <script type="text/javascript" src="/books/2/581/1/html/2/example.9-12.js"> </script> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Customer Details</title> </head> <body bgcolor="white"> <form name="{FORMNAME}" method="post" action="test.php" onsubmit="{SUBMITACTION}"> <h1>Customer Details</h1> <h2>{MESSAGE}. Fields shown in <font color="red">red</font> are mandatory.</h2> <table> <!-- BEGIN mandatoryinput --> <tr> <td><font color="red">{MINPUTTEXT}:</font></td> <td> <input type="text" name="{MINPUTNAME}" value="{MINPUTVALUE}" size={MINPUTSIZE}> </td> </tr> <!-- END mandatoryinput --> <tr> <td><input type="submit" value="{SUBMITVALUE}"></td> </tr> </table> </form> </body> </html> The differences are that the JavaScript code from Example 9-12 is included using the src attribute (as discussed at the beginning of this case study), and that the FORMNAME and SUBMITACTION placeholders have been added to the form element. The FORMNAME is the name of the form, and the SUBMITACTION supports the onsubmit function call. |