Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning JavaScript With DOM Scripting And Ajax - From Novice To Professional (2006)

.pdf
Скачиваний:
81
Добавлен:
17.08.2013
Размер:
17.27 Mб
Скачать

366 C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

asteriskForm.js (excerpt)

init:function() { hfv.mandatory = []; [... code snipped ...]

hfv.f = document.getElementsByTagName( 'form' )[0]; var msgs = hfv.f.getElementsByTagName( 'span' ); for( var i = 0; i < msgs.length; i++) {

if( DOMhelp.cssjs( 'check', msgs[i], 'mandatory' ) ) { hfv.mandatory.push( msgs[i].parentNode.htmlFor );

}

}

[... code snipped ...]

},

The CSS Classes Method

Another approach is to give the fields that are mandatory appropriate CSS classes and test for these.

exampleClassesForm.html (excerpts)

<p>

<input type="text" id="Name" name="Name" class="mandatory" /> </p>

<p>

<input type="text" id="email" name="email" class="mandatory" /> </p>

In the script, you’ll have to loop through all form fields and check whether they have the right class. You could also use the DOM-2 methods, but then you need several loops, checking for INPUT, SELECT, and TEXTAREA elements.

classesForm.js (excerpt)

hfv.f = document.getElementsByTagName( 'form' )[0]; var msgs = hfv.f.elements;

for( var i = 0; i < msgs.length; i++) {

if( DOMhelp.cssjs( 'check', msgs[i], 'mandatory' ) ) { hfv.mandatory.push( msgs[i].id );

}

}

The Custom Attribute Method

The very experimental custom attribute method was explained by Peter-Paul Koch (http://www.quirksmode.org) in his “JavaScript Triggers” article on A List Apart (http://

C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

367

www.alistapart.com/articles/scripttriggers/) in February 2005. We won’t go into an example here, but what it means is that you come up with nonstandard attributes for elements like

<input type="text" name="name" id="name" required="true" />

You can then use getAttribute() in your script to check for these attributes and act accordingly. You can even extend this idea to allow for maintenance of the validation rules in HTML:

<input type="text" name="name" id="name" required="true" error="Please enter a Name at least 5 characters long"

pattern="/.{5}/" />

The main issue of this method is that you reinvent HTML, and you will have to come up with a custom DTD for it. David Eisenberg explains how to do that in “Validating a Custom DTD” (http://www.alistapart.com/articles/customdtd/).

Failures of These Methods

All of these methods have the same problem: you keep validation rules in HTML and JavaScript and in the back-end script separately, which means double maintenance. Furthermore, all but the “hidden field” method make the HTML cater to JavaScript exclusively, as the back-end script cannot determine which fields are mandatory by means of a class or a SPAN.

Sharing Validation Rules

A much cleaner approach is to keep all the validation rules and feedback messages in one spot. You could use XML to store the information and use XHR to retrieve it, but this is rather errorprone. An easier solution is to use JSON:

validationRules : { 'Name' : {

'error' : 'Please enter a name', 'pattern' : /.{10}/i

},

'subject' : {

'error' : 'Please choose a subject', 'pattern' : /.{5}/i

},

'Message' : {

'error' : 'Please enter a message at least 20 characters long', 'pattern' : /.{20}/i

},

'email' : {

'error' : 'Please enter a valid email',

'pattern' : /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/i

}

}

368

C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

You can retrieve all the mandatory elements by looping through the JSON object:

for( i in validationRules ) {

// i will be Name, subject, Message, email

if( !document.getElementById( i ) ) { continue; } toplist.checkValue( i );

}

Retrieving the patterns and error messages is as easy; an alert( validationRules ['email']['error'] ) would result in an alert stating “Please enter a valid email”.

You can also generate the JSON string on the back end. For example, in PHP:

validationRules.php

<?php

$validationRules = array ( 'Name' => array (

'error' => 'Please enter a name', 'pattern' => '/.{10}/i'

),

'subject' => array (

'error' => 'Please choose a subject', 'pattern' => '/.{10}/i'

),

'Message' => array (

'error' => 'Please enter a message at least 20 characters long', 'pattern' => '/.{20}/i'

),

'email' => array (

'error' => 'Please enter a valid email',

'pattern' => '/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/i'

)

);

if( isset( $_GET['json'] ) ) {

header( 'Content-type:text/javascript' ); echo 'validationRules = {';

foreach( array_keys( $validationRules ) as $a ) { echo '\''.$a.'\' : {'."\n";

foreach( array_keys ( $validationRules[$a] ) as $b ) { if( $b == 'pattern' ) {

echo '\'' . $b . '\' : ' . $validationRules[$a][$b].

',' . "\n";

C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

369

}else {

echo '\'' . $b . '\' : \'' . $validationRules[$a][$b].

'\',' . "\n";

}

}

echo '},' . "\n";

}

echo '};';

}

?>

Don’t worry if the syntax is confusing; what is important to know is that if a different PHP script includes this one, it’ll get the validation rules as a PHP array; but if you include the PHP script with a json parameter as a JavaScript, you’ll have the validationRules object at your disposal. This is what the part in bold does by sending a text/javascript header to the browser and writing out the array data in the right format. Without the text/javascript header, the browser would not recognize the string returned from the PHP as JavaScript, so you can see it is a very important aspect of writing out JavaScript using server-side languages.

Using this functionality is as easy as including the PHP script as a JavaScript in the head of the document:

<script type="text/javascript" src="validationRules.php?json=1"></script>

We’ll use this in a second in the first example on how to give users validation feedback

Giving Users Validation Feedback

We’ve already covered some methods of giving feedback in Chapter 4 and in Chapter 7, and we will now go through one form validation example in detail and list the necessary changes to the script in the other options.

We will not repeat the most obvious and probably oldest method—JavaScript alerts—as they’ve been covered, and you should know by now how to use them. Instead, let’s take a look at some prettier examples.

Showing a List of Erroneous Fields

In this method, you show the user a list of fields that contain errors. This is a traditional feedback of back-end scripts, which show the list above the form after the page has loaded. As you don’t reload the page when you validate with JavaScript, it might be a better idea to show the list where the user is—at the bottom of the form above the Submit button. Figure 9-1 shows how that might look. You can see the example for yourself by opening exampleTopList.html on your localhost—which must be a server running PHP, like XAMPP or Apache, as explained earlier in Chapter 8.

370

C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

Figure 9-1. Showing a list of erroneous fields above the Submit button

You start by embedding the rule set in the head of the HTML:

exampleTopList.html (excerpt)

<script type="text/javascript" src="validationRules.php?json=1"></script>

You should plan your script starting with the skeleton of properties and methods:

topList.js (skeleton)

toplist = { error:[],

errorMessage : null, errorClass : 'error',

errorTitle : 'Please fix the following problems:', sendButtonID : 'send',

init:function(){}, send:function( e ){}, flushErrors:function(){}, checkValue:function( o ){ }

}

You’ll need an error array that will store all the errors that occurred in the form. You predefine an errorMessage as null, which will become the message above the Submit button. The errorTitle and the errorClass will be shown above the list of errors and the class applied to define the look and feel. The sendButtonID is necessary to find the Submit button.

The methods are pretty standard for a form submission script: an init() method to apply the event handler when the form is submitted, an event listener send() method, a method to

C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

371

remove older error messages (as the user will not reload the page but may enter data several times because the script stops the page from reloading when there is an error), and a tool method to check the value of each mandatory form field.

I’ll now go through the script step by step and explain what the different methods do.

toplist.js

toplist = { error:[],

errorMessage : null, errorClass : 'error',

errorTitle : 'Please fix the following problems:', sendButtonID : 'send',

init:function() {

toplist.sendButton = document.getElementById( toplist. sendButtonID );

if( !toplist.sendButton ) { return; }

toplist.f = document.getElementsByTagName( 'form' )[0]; DOMhelp.addEvent( toplist.f, 'submit', toplist.send, false );

},

There are no surprises in the init() method; you check whether the Submit button exists, retrieve the first form in the document, and apply a submit handler pointing to send.

toplist.js (continued)

send:function( e ) { toplist.flushErrors();

for( var i in validationRules ) {

if( !document.getElementById( i ) ) { continue; } toplist.checkValue( i );

}

Call the flushErrors() method to remove any error messages that might already be visible and loop through the validationRules JSON object. Determine whether each of the mandatory elements exists and call the checkValue() method with the element’s ID as a parameter.

toplist.js (continued)

if( toplist.error.length > 0 ) {

toplist.errorMessage = document.createElement( 'div' ); toplist.errorMessage.className = toplist.errorClass; var errorTitle = document.createElement( 'h2' ); errorTitle.appendChild( document.

createTextNode( toplist.errorTitle ) ); toplist.errorMessage.appendChild( errorTitle ); entry = document.createElement( 'ul' ); toplist.errorMessage.appendChild( entry ); toplist.errorList = entry;

372

C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

The utility method checkValue() adds new elements to the error array if there are problems with some of the fields, which is why you check whether its length is larger than 0 before you start assembling the error message.

Next, create a DIV, store it in the errorMessage property, and apply the error class to it to make it style-able. Create an H2 and add the text stored in the errorTitle property as the text content of this heading. Add the heading to the errorMessage DIV, create a new UL element, and add the UL to errorMessage. Then store the newly created UL in the errorList property.

toplist.js (continued)

for( i = 0; i < toplist.error.length; i++ ) { entry = document.createElement( 'li' ); entry.appendChild( document. createTextNode( toplist.error[i] ) ); toplist.errorList.appendChild( entry );

}

Loop through the error array and create a new list item containing the text of each error before appending it as a child node to the errorList UL.

toplist.js (continued)

var sendPara = toplist.sendButton.parentNode; sendPara.parentNode.insertBefore( toplist.errorMessage, sendPara ); DOMhelp.cancelClick( e );

}

},

Now, retrieve the parent element of the form’s Send button and insert the error message as a new sibling element in front of it. Since there was an error, cancel the form submission by calling cancelClick().

toplist.js (continued)

flushErrors : function() { toplist.error = [];

if( toplist.errorMessage ) { toplist.errorMessage.parentNode.removeChild( toplist.errorMessage );

toplist.errorMessage = null;

}

},

The flushErrors() method redefines the error array as an empty array and tests whether there is already an existing errorMessage. If there is, it removes the document node and sets the object to null.

Note

C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

373

toplist.js (continued)

checkValue : function( o ) {

var elm = document.getElementById( o ); switch( elm.type ) {

case 'text' :

if( !validationRules[o][ 'pattern' ].test( elm.value ) ) { toplist.error.push( validationRules[o][ 'error' ] );

}

break;

case 'textarea' :

if( !validationRules[o][ 'pattern' ].test( elm.value ) ) { toplist.error.push( validationRules[o][ 'error' ] );

}

break;

The checkValue() method does the real validation tasks, and starts by retrieving the element with the ID that was sent as a parameter. There is no need to test whether the element exists, as you’ve already done that in the send method. You retrieve the element type and start a switch statement that tests for the different form elements accordingly.

In the cases of text and textarea as the type, you take the element’s value attribute and test it against the pattern stored in the validationRules object. If the pattern does not match, you add a new item to the error array with the error message associated with the element ID as the value.

The parameter o is the ID of the element, which means if you use validationRules['Name'] ['pattern'], for example, you get /.{10}/i as the result, which is a regular expression pattern that tests whether the name is at least ten characters long. If you use validationRules['Name']['error'], you get 'Please enter a name', which is the error message you put into the new item of the error array.

toplist.js (continued)

case 'select-one' :

var curelm = elm.options[ elm.selectedIndex ].value; if( elm.selectedIndex == 5 ) {

curelm = document.getElementById( 'otherSubject' ).value;

}

if( !validationRules[o][ 'pattern' ].test( curelm ) ) { toplist.error.push( validationRules[o][ 'error' ] );

}

break;

}

}

}

DOMhelp.addEvent( window, 'load', toplist.init, false );

374

C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

In the case of the select box—which in this example is a single select box, therefore the type is select-one—you need to retrieve the value of the selected option via the options array and the selectedIndex property. As the form offers an “other” option, you need to check whether it was chosen and take the value of the text field otherSubject instead.

Both the select value and the other subject gets tested with the pattern stored in the validationRules object and added as an error if it doesn’t match. Add an event handler to the window executing the init() method when the window has finished loading, and voilà, you’ve created a form that is validated using JavaScript and shows a list of errors until the user has entered the correct sort of data in all mandatory fields.

Replacing the Main Form with a Clickable Error Message

A different approach is to hide the form when the validation brings up errors and show a warning message with a link back to the form, as illustrated in Figure 9-2. You can see the example for yourself by opening exampleHideForm.html on your localhost.

Figure 9-2. Hiding the form and offering a link back to it

The differences are not that many:

hideform.js used in exampleHideForm.html

toplist = { error:[],

errorMessage : null, errorClass : 'error',

errorTitle : 'Please fix the following problems:', errorLink : 'Back to form',

errorLinkClass : 'errorlink', init:function() {

[... code snipped ...] },

You need two new properties: one to store the link text and another to define a CSS class to style the link to close the message.

C H A P T E R 9 D A T A V A L I D A T I O N T E C H N I Q U E S

375

hideform.js (continued)

send:function( e ) { toplist.flushErrors();

for( var i in toplist.validationRules ) { [... code snipped ...]

}

if( toplist.error.length > 0 ) { [... code snipped ...]

for( i = 0; i < toplist.error.length; i++ ) { [... code snipped ...]

}

entry = document.createElement( 'li' ); var closeLink = DOMhelp.createLink( '#', toplist.errorLink );

DOMhelp.addEvent( closeLink, 'click', toplist.flushErrors, false ); closeLink.onclick = DOMhelp.safariClickFix; entry.appendChild( closeLink ); entry.className = toplist.errorLinkClass; toplist.errorList.appendChild( entry ); toplist.f.style.display = 'none';

toplist.f.parentNode.insertBefore( toplist.errorMessage, toplist.f ); DOMhelp.cancelClick( e );

}

},

In the send() method, you need to add an additional list item and create a link with the errorLink property as text. You apply a click handler pointing to the flushErrors() method, add the CSS class, and add the new list item to the errorList. You hide the main form by setting its style display property to none (alternatively, you could have added a dynamic CSS class to hide the form).

hideform.js (continued)

flushErrors : function() { toplist.error = [];

if( toplist.errorMessage ) { toplist.errorMessage.parentNode. removeChild( toplist.errorMessage ); toplist.errorMessage = null;

}

toplist.f.style.display = 'block';

},

[... code snipped ...]

DOMhelp.addEvent( window, 'load', toplist.init, false );