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

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

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

376

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 flushErrors() message now needs to set the display property of the form back to block, and indicate that all the changes are necessary and the rest of the script stays as it was.

Highlighting Erroneous Fields Individually

Especially with larger forms, it makes a lot more sense to display error messages directly next to the fields that have an error. Figure 9-3 shows what that looks like, and you can see it yourself by opening exampleErrorFields.html on your localhost.

Figure 9-3. Highlighting individual error fields

The script is slightly different; the send, flushErrors(), and checkValue() methods have to change, and you need a new method called addErrorMessage() that adds the error message where the error occurred.

errorFields.js

ef = { error:[],

errorMessage : null, errorClass : 'error',

errorTitle : 'Please fix the marked issues', init : function() {

ef.sendButton = document.getElementById( 'send' ); if( !ef.sendButton ){ return; }

ef.f = document.getElementsByTagName( 'form' )[0]; DOMhelp.addEvent( ef.f, 'submit', ef.send, false ); ef.f.onsubmit = function(){ return false; }

},

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

377

The properties and the init() method stay the same as in the toplist example.

errorFields.js (continued)

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

for( var i in validationRules ) {

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

}

if( ef.error.length > 0 ) {

ef.errorMessage = document.createElement( 'div' ); ef.errorMessage.className = ef.errorClass;

var errorTitle = document.createElement( 'h2' ); errorTitle.appendChild( document. createTextNode( ef.errorTitle ) ); ef.errorMessage.appendChild( errorTitle );

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

}

},

The send method is a lot shorter, as you don’t need to create the error list; just the main errorMessage element with the heading in it.

errorFields.js (continued)

checkValue : function( o ) {

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

case 'text ' :

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

}

break;

case 'textarea' :

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

}

break;

case 'select-one' :

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

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

}

378

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

if( !validationRules[o]['pattern'].test( curelm ) ) { ef.error.push( validationRules[o][ 'error' ] ); ef.addErrorMsg( elm, validationRules[o]['error'] );

}

break;

}

},

The changes in the checkValue() method are that in addition to adding the error to the error array, you also call the new addErrorMsg() method with the element and the error text as parameters.

errorFields.js (continued)

addErrorMsg : function( o, msg ) {

var errorMsg = document.createElement( 'span' ); errorMsg.className = ef.errorClass; errorMsg.appendChild( document.createTextNode( msg ) ); o.parentNode.insertBefore( errorMsg, o );

},

The addErrorMsg method creates a new SPAN element, adds the error class to make it styleable, and adds the error text as a text node. It then inserts the new SPAN before the element with the error.

errorFields.js (continued)

flushErrors:function() { var elm;

ef.error = [];

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

}

for( var i in validationRules ) { elm = document.getElementById( i ); if( !elm ) { continue; }

if( elm.previousSibling && elm.previousSibling.nodeName.toLowerCase() == 'span' && elm.previousSibling.className == ef.errorClass ) { elm.parentNode.removeChild( elm.previousSibling );

}

}

}

}

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

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

379

These changes mean that there is no single new element showing the errors but one at each erroneous field. This means that the flushErrors() method needs to loop through all mandatory fields and check whether there is a previous sibling node that is a SPAN and has the right class applied to it. If all of this is true, it removes that node.

Instant Validation Feedback

All of the preceding examples validate when the form is submitted, which is—as mentioned in earlier chapters—the most accessible way of validating forms. However, it does not hurt to make forms a bit more interactive if you still validate on submission. If the user realizes her errors and remedies them immediately, the submission validation will not trigger any errors anyways. The demo exampleDynamicFields.html validates fields immediately after you change them, and you can test it by opening it on your localhost.

The script needs a new method called sendField() and some minor changes in the init() method:

dynamicFields.js

df = {

error : [], errorMessage : null, errorClass : 'error',

errorTitle : 'Please fix the marked issues', init : function() {

var elm;

df.sendButton = document.getElementById( 'send' ); if( !df.sendButton ) { return; }

df.f = document.getElementsByTagName( 'form' )[0]; DOMhelp.addEvent( df.f, 'submit', df.send,false ); df.f.onsubmit = function() { return false; }

for( var i in validationRules ) { elm = document.getElementById( i ); if( !elm ){ continue; }

DOMhelp.addEvent( elm, 'blur', df.sendField, false );

}

},

In addition to the submit handler on the form, you need to loop through all the mandatory fields and add a blur handler to the field pointing to sendField() as the event listener. If you only want feedback when the user changes the field rather than when he leaves it, you could also use a change handler.

380 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

dynamicFields.js (continued)

sendField : function( e ) {

var t = DOMhelp.getTarget( e ); if( t.previousSibling &&

t.previousSibling.nodeName.toLowerCase() == 'span' && t.previousSibling.className == df.errorClass ) {

t.parentNode.removeChild( t.previousSibling );

}

df.checkValue( t.id );

},

The sendField() method needs to retrieve the current element via getTarget(). It then removes any error message that might already be visible the same way flushErrors() does it and calls checkValue() with the ID of the element as a parameter. That is all there is to it.

dynamicFields.js (continued)

send : function( e ) { [... code snipped ...]

},

flushErrors : function() { [... code snipped ...]

},

checkValue : function( o ) { [... code snipped ...]

},

addErrorMsg : function( o, msg ) { [... code snipped ...]

}

}

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

The rest of the script remains unchanged.

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

381

Other Dynamic Validation Methods

It is very tempting to make every field validate immediately when the user changes it, and you can do a lot with Ajax and the proper back-end datasets or functionality. One great example is to include suggestions of what data is valid while you enter a form.

Google Suggest (http://www.google.com/webhp?complete=1) was one of the first examples of web forms to do that. It offers you searches other users have already done with the number of possible results as shown in Figure 9-4.

Figure 9-4. Google Suggest showing possible results while you type

Offering suggestions this way is not hard to do, and you can use the knowledge from the previous chapters to create a suggestion form element. The demo exampleContactSuggest.html does just that. It uses Ajax to read an XML file containing contact names and compares them with what the user enters while she types. Figure 9-5 shows one possible outcome.

Figure 9-5. Offering data dynamically from an XML dataset

382

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 XML data is very simple:

contacts.xml

<?xml version="1.0" encoding="utf-8"?> <contacts>

<name>Bill Gates</name> <name>Linus Torvalds</name> <name>Douglas Coupland</name> <name>Ridley Scott</name> <name>George Lucas</name> <name>Dan Akroyd</name> <name>Sigourney Weaver</name> <name>Tim Burton</name> <name>Katie Jane Garside</name> <name>Winona Ryder</name>

</contacts>

In a real-life example you might want to add a contact element around each name and add other data, like e-mail, extension, and department.

The script uses the XHR you’ve seen in the last chapter mixed with some regular expressions:

contactSuggest.js

cs = {

init : function() {

if( !document.getElementById || !document.createTextNode ) { return;

}

cs.f = document.getElementById( 'contact' ); if( !cs.f ) { return; }

cs.output = document.createElement( 'span' ); cs.f.parentNode.insertBefore( cs.output, cs.f.nextSibling ) DOMhelp.addEvent( cs.f, 'keyup', cs.check, false );

},

You test for DOM support and whether there is a field with the ID contact. If both are a given, you create a new SPAN element and add it after the field with the ID contact. You add an event handler on keyup that triggers the check() event listener method.

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

383

contactSuggest.js (continued)

check : function( e ) { if( window.event ) {

var key = window.event.keyCode;

}else if( e ) {

var key = e.keyCode;

}

if( key == 8 ) { return; } if( key == 40 ) {

cs.f.value = cs.output.innerHTML.replace( /\s\(.*\)$/, '' ); cs.output.innerHTML = '';

return;

}

cs.doxhr( 'contacts.xml' );

},

This method finds out which key was pressed, and returns when it is the Backspace (8). If the user hit the down arrow key (40), it removes anything starting with a space and ending in a parenthesis from the content of the SPAN and copies it into the form field. It then deletes the content of the SPAN and returns. If the user pressed any other key, the check() method invokes the doxhr() method with contacts.xml as the URI to load.

contactSuggest.js (continued)

doxhr : function( url ) { var request;

try{

request = new XMLHttpRequest();

}catch ( error ){ try{

request = new ActiveXObject("Microsoft.XMLHTTP"); } catch ( error ) {

return true;

}

}

request.open( 'get', url, true ); request.onreadystatechange = function() {

if( request.readyState == 4) {

if (request.status && /200|304/.test( request.status ) ) { cs.retrieved( request );

384

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

}else {

cs.failed( request );

}

}

}

request.setRequestHeader( 'If-Modified-Since',

'Wed, 05 Apr 2006 00:00:00 GMT' );

request.send( null ); return false;

},

There is no change to the xhr() method; you create an XMLHttpRequest and load the URI sent as a parameter.

contactSuggest.js (continued)

retrieved : function( requester ) { var v;

var pattern = new RegExp( '^'+cs.f.value, 'i' ); var data = requester.responseXML;

var names = data.getElementsByTagName( 'name' ); for( var i = 0; i < names.length; i++ ) {

v = names[i].firstChild.nodeValue; if( pattern.test( v ) ) {

cs.output.innerHTML = v + ' (cursor down to copy)'; break;

}

}

},

The retrieved method creates a regular expression that tests whether something starts with the value of the contact field. It reads the content of contacts.xml from responseXML, retrieves all the names using getElementsByTagName(), and loops through them. If any of the names matches the pattern, it writes the text content of the name followed by the message “(cursor down to copy)” into the SPAN and stops the loop.

contactSuggest.js (continued) failed : function( requester ) {

alert( 'The XMLHttpRequest failed. Status: ' + requester.status ); return true;

}

}

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

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

385

If there is an error, the user is informed via an alert. When the window has finished loading the document, the method cs.init() is called.

For a small number of contacts, using XHR is probably not worth the effort, and you’d be better off importing the data in JSON format and spare yourself the server round-trips every time the user hits a key. However, this example could also use a database to retrieve the right contact.

Summary

I hope that you feel quite confident to write your own form validation scripts and regular expressions after this chapter. It is not that hard to do, but as said before, it is too easy to take validation lightly or assume too many givens.

Form validation is as much a usability problem as it is a technical issue. You have to make sure that your validation methods make sense in the technical environment you are dealing with and that the solution fits the regulations of the product. Many online forms these days have to adhere to accessibility guidelines or sometimes even bespoke regulations (for example, when you work on local government sites). If your validation mechanism requires the user to see the form or use a mouse, then you will not be able to fulfill these guidelines.

It is very tempting to use the most modern approach and make everything dynamic in web sites these days. Be aware of restrictions and usage patterns of the audience you want to reach, and ensure that everything works without JavaScript, and you’ll make both your clients and yourself happy.

In the next chapter, we’ll take on a larger project and create a dynamic image gallery powered by the back end and spiced up with CSS, JavaScript, and Ajax.