In Chapter 14, you saw how to add Ajax to an XHTML form to asynchronously send user data between the client and the server. Somewhere in the application, that data should be checked—or validated—to determine whether it is the type of data that the program expected. This chapter will look at ways that validation can happen within an Ajax application, and where the validation should take place. Then we can see what benefits Ajax can bring to form validation to make your web application more robust.
Any developer who doubts the importance of data validation should think again. In fact, I would call such a developer crazy. The old paradigm “garbage in, garbage out” is extremely dangerous in any environment where the developer cannot control the users of an application. Crashes, hacks, and undesirable results can occur when the user is left to his own devices regarding the information he sends to the server or any other part of the client application. We’ll discuss several scenarios that demonstrate the danger of collecting data from a user without checking what that user entered before letting the program have at it.
First, imagine you have built a form that collects emergency contact information from a user and stores it in a database. In several places in this scenario, it would be important to have some validation around the form:
Is there a valid-looking phone number?
Was a name entered?
Was a relationship selected?
All of these would be important fields to validate; after all, a name is necessary, a phone number with at least the correct syntax is required, and it would be good to have the relationship of the contact.
Here’s a second scenario: imagine you built a form that allowed a user to log in to a site where security is a requirement. This is a potentially dangerous case where a malicious user could try to access a system she does not have a right to access. In this type of attack, called a SQL injection attack, the user attempts to fool the system by placing SQL code in the form field. Most likely, the JavaScript on the client side is just checking to make sure something was entered in the field and not that the field looks like a password.
The code on the server is responsible for filtering out bad data to prevent attacks of this nature. To give you a better idea of the scenario, consider the following code used to change a password:
SELECT id FROM users WHERE username = '$username' AND password = '$password';
Now pretend that the user enters the following password and the system has no safeguards against this sort of thing:
secret' OR 'x' = 'x
You can see how a clever individual could enter a SQL script such as this and gain access to things she should not. In this case, the SQL injection would allow the user to log in to the system without actually knowing the password. The SQL that would be passed to the server would look like this:
SELECT id FROM users WHERE username = 'Anthony' AND password = 'secret' OR 'x'='x';
To prevent this sort of scenario, many languages provide ways to
strip out potential problem code before it becomes a problem. In PHP’s
case, it provides the mysql_real_escape_string( )
function, which
you can use like this:
<?php /* Protect the query from a SQL Injection Attack */ $SQL = sprintf("SELECT id FROM users WHERE username='%s' AND password='%s'", mysql_real_escape_string($username), mysql_real_escape_string($password) ); ?>
Frameworks such as Zend provide a wrapper for this functionality; for Zend it is the following:
<?php /* Protect the query from a SQL Injection Attack - the Zend way */ $sql = $db->quoteInto('SELECT id FROM users WHERE username = ?', $username); $sql .= $db->quoteInto(' AND password = ?', $password); ?>
As you can see from the two example scenarios, validating the data passed from a form is important for both the client and the server. They do not necessarily check for the same things, but they both have their own duties. Table 15-1 summarizes these duties.
Table 15-1. Validation duties of the client and server
Duty | Client | Server |
---|---|---|
Check for null and blank values. | X | X |
Check for syntax errors. | X | |
Check for type errors. | X | X |
Check for potential hacks. | X |
The contents of Table 15-1 basically say that anything that could harm the server if client validation were to fail in some way should be validated on the server, while the server should check for specialized attacks, and the client should check for reasons not to send the form to the server in the first place.
JavaScript’s main job in terms of its role in validation is to keep forms from being sent to the client when something is obviously wrong with them. Things are obviously wrong with a form when required fields have not been filled in, but there are other issues to check for as well. One is that the value in the field is an expected type—there should not be characters in a field where a number is expected, for example. Finally, there is the obvious consideration of whether the syntax of a given field is in a format that is expected—a phone number missing an area code, for instance.
The point of this kind of validation is to reduce the load on a server that has the potential for a lot of traffic, especially if the page in question is part of a web application that many people use. Whenever possible, checking should be done on the client, at the cost of the client CPU rather than the server CPU.
An easy form of client-side validation using JavaScript is to check fields for values. In these situations, you know what you are or are not looking for, and all you need to do is simply check the field. First, on any form field (especially those that the form requires), you need to make sure there is a value in the field. It does not do a whole lot of good to try to check for field types, syntaxes, and so on when there is nothing to check.
For example, I find out whether the field is null
or blank in some way. Some methods
for doing this include checking for null
, seeing whether the field holds an
empty string, and checking whether the field length is 0
. Here is an example of this sort of
function:
/** * This function, isNull, checks to see if the passed parameter /p_id/ has a * value that is null or not. It also checks for empty strings, as form values * cannot really be null. * * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input is a valid number * or not. * @type Boolean */ function isNull(p_id) { try { p_id = $F(p_id); return (p_id == null || p_id == ''), } catch (ex) { return (true); } }
Another easy test is to ensure that the value needed is being entered. This involves testing whether the values are equal, as in the following code:
/** * This function, testValue, checks to see if the passed /p_id/ has a value that * is equal to the passed /p_value/ in both value and type. * * @param {String} p_id The name of the input field to get the value from. * @param {Number | String | Boolean | Object | Array | null | etc.} p_value The * value to test against. * @return Returns a value indicating whether the passed inputs are equal to one * another. * @type Boolean */ function testValue(p_id, p_value) { try { /* Check both value and type */ return($F(p_id) === p_value); } catch (ex) { return (false); } }
You will notice in this example that I am using the inclusive
===
operator to test for
equality. This means I want to make sure the two values are the same
in type and value. If your needs differ and value is all you need to
check for, change this to use the ==
operator instead.
The test I have not yet discussed is that of field type. You
can use two different approaches in this case. The first is to use
JavaScript’s built-in functions and operators. For example, you
could use any of the following functions: parseInt( ), parseFloat( ), isFinite( )
,
or isNaN( )
. For user-defined
types, however, these functions do not do the trick and you need
something else. This is where you can turn to regular
expressions.
When you need to check for more complex data formats, syntax, or values, your best solution is to use regular expressions. Oftentimes, developers either forget all about regular expressions, or are afraid of them. I admit they are rather daunting until you become more familiar with them, but once you do, you will see how useful they can be. Just pretend they are not a part of the subject of theoretical computer science!
Regular expressions can parse a string much more efficiently
and effectively than writing the code to do the parsing yourself.
They work by comparing patterns with strings to find matches. For
example, to match the strings “color” and “colour,” you can use the
pattern colou?r
. Example 15-1 gives some basic examples of
some common regular expressions.
Example 15-1. Common regular expressions
/[A-Z][A-Z]/ // State abbreviation /^(.| ){0,20}$/ // Limit the size of a string /[1-9]d{4}-d{4}/ // US Zip code /* IP4 address */ /(([01]?d?d|2[0-4]d|25[0-5]).){3}([01]?d?d|2[0-4]d|25[0-5])/ /* US dates */ /^[0,1]?d{1}/(([0-2]?d{1})| ([3][0,1]{1}))/(([1]{1}[9]{1}[9]{1}d{1})|([2-9]{1}d{3}))$/
Regular expressions are well outside the scope of this book, although they are a fascinating subject. For more information on them, check out Mastering Regular Expressions by Jeffrey E. F. Friedl (O’Reilly).
Taking what we now know about regular expressions, we can apply them to more specific type checks that give us much greater flexibility in what the client-side validation checks. Now, user-defined types such as phone number, email address, and credit card number can be checked (at least for syntax) before being passed along to the server.
Phone numbers are fields found in many forms, and although we have the means to check whether any number was entered into the field, we are more limited in what else we can check. Sure, a developer could test the string length, and if it was within an accepted range of values, the field could pass a test. But what about testing to make sure that it is in a format that our backend system can handle before even giving the server the number to parse? Here is where using a regular expression can improve a phone number check, as the following example demonstrates:
/** * This function, isPhoneNumber, checks the syntax of the passed /p_id/ and * returns whether it is a valid US phone number in one of the following * formats: * - (000) 000-0000 * - (000)000-0000 * - 000-000-0000 * - 000 000 0000 * - 0000000000 * * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input has a valid US * phone number format. * @type Boolean */ function isPhoneNumber(p_id) { try { return (/^(?[2-9]d{2}[)-]?s?d{3}[s-]?d{4}$/.test($F(p_id))); } catch (ex) { return (false); } }
Breaking down the regular expression a bit, the first part,
(?[2-9]d{2}[)-]?
, gives the
option of an opening parenthesis, a three-digit area code that
starts with a number between 2 and 9, and an optional closing
parenthesis followed by an optional dash. Following this is the
second part, s?d{3}[s-]?
,
which gives a possible space, and then a three-digit prefix
followed by an optional space or dash. The last part, d{4}
, checks for the four-digit suffix.
It is not perfect by any means, but it is a lot better than the
alternatives.
Checking for a valid email address would also result in pretty poor validation without regular expressions. Most developers need to do more than just check to see whether the email address contains an at character (@). The following is an example of a pretty robust email check:
/** * This function, isValidEmail, indicates whether the passed variable has a * valid email format. * * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input has a valid * email format. * @type Boolean */ function isValidEmail(p_id) { try { return (/^(([^<>( )[]\.,;:s@"]+(.[^<>( )[]\.,;:s@"]+)*)| (".+"))@(([(2([0-4]d|5[0-5])|1?d{1,2})(.(2([0-4]d|5[0-5])| 1?d{1,2})){3} ])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/.test( $F(p_id))); } catch (ex) { return (false); } }
This regular expression is very messy, I admit. In a nutshell, this expression checks the string for any invalid characters that would automatically invalidate it before going on to check the string. This string checks that the email address has an addressee, followed by the @ and then the domain. The domain can be an IP address or any domain name.
This regular expression checks the syntax of the domain for the email address, but it cannot check to see whether the domain is actually valid. The server is the proper place to make this check, provided that it is fast enough to do so.
Social Security numbers follow the format XXX-XX-XXXX. The first group of numbers is assigned by state, territory, and so on and is any series from 001-772 (as of this writing). The second group of numbers is assigned based on a formula (I do not know what it is), and the final group of numbers is sequential from 0001-9999. The following code demonstrates this:
/** * This function, isSSN, checks the passed /p_id/ to see whether it is a valid * Social Security number in one of the following formats: * - 000-00-0000 * - 000 00 0000 * - 000000000 * * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input has a valid * SSN format. * @type Boolean */ function isSSN(p_id) { try { if (!(/^d{3}(-|s?)d{2}1d{4}$/.test($F(p_id)))) return (false); var temp = $F(p_id) /* Strip valid characters from number */ if (temp.indexOf('-') != -1) temp = (temp.split('-')).join(''), if (temp.indexOf(' ') != -1) temp = (temp.split(' ')).join(''), return ((temp.substring(0, 3) != '000') && (temp.substring (3, 5) != '00') && (temp.substring(5, 9) != '0000')); } catch (ex) { return (false); } }
A Social Security number cannot comprise all zeros, as in 000-00-0000. A separate check is used to test for these occurrences.
An accredited company must properly validate a credit card before an online store will accept the card number, and this validation should be done on the server side of things. The client can still make sure the card number has the correct number of digits based on its type, and whether the digits make sense. It does this using the Luhn Formula, which tests digits by using a modulus 10 checksum as the last digit in the number. Table 15-2 shows some basic information available on credit cards that use this method to issue card numbers.
Table 15-2. Acceptable values for certain credit cards
Card type | Valid prefixes | Valid length |
---|---|---|
American Express | 34 or 37 | 15 |
Diners Club | 30, 36, or 38 | 14 |
Discover | 6011 | 16 |
MasterCard | 51–55 | 16 |
Visa | 4 | 16 |
The following code example uses a variation to the Luhn Formula to acquire a checksum, to account for cards with even and odd digits:
/** * This function, isValidCreditCard, checks to see if the passed * /p_cardNumberId/ is a valid credit card number based on the passed * /p_cardTypeId/ and the Luhn Formula. The following credit cards may be * tested with this method: * - Visa has a length of 16 numbers and starts with 4 (dashes are optional) * - MasterCard has a length of 16 numbers and starts with 51 through 55 * (dashes are optional) * - Discover has a length of 16 numbers and starts with 6011 (dashes are * optional) * - American Express has a length of 15 numbers and starts with 34 or 37 * - Diners Club has a length of 14 numbers and starts with 30, 36, or 38 * * @param {String} p_cardTypeId The name of the input field to get the card * type from. * @param {String} p_cardNumberId The name of the input field to get the card * number from. * @return Returns whether the card number is in the correct syntax and has a * valid checksum. * @type Boolean */ function isValidCreditCard(p_cardTypeId, p_cardNumberId) { var regExp = ''; var type = $F(p_cardTypeId); var number = $F(p_cardNumberId); /* Is the card type Visa? [length 16; prefix 4] */ if (type == "VISA") regExp = /^4d{3}-?d{4}-?d{4}-?d{4}$/; /* Is the card type MasterCard? [length 16; prefix 51 - 55] */ else if (type == "MasterCard") regExp = /^5[1-5]d{2}-?d{4}-?d{4}-?d{4}$/; /* Is the card type Discover? [length 16; prefix 6011] */ else if (type == "Discover") regExp = /^6011-?d{4}-?d{4}-?d{4}$/; /* Is the card type American Express? [length 15; prefix 34 or 37] */ else if (type == "AmericanExpress") regExp = /^3[4,7]d{13}$/; /* Is the card type Diners Club? [length 14; prefix 30, 36, or 38] */ else if (type == "Diners") regExp = /^3[0,6,8]d{12}$/; /* Does the card number have a valid syntax? */ if (!regExp.test(number)) return (false); /* Strip valid characters from number */ number = (number.split('-')).join(''), number = (number.split(' ')).join(''), var checksum = 0; /* Luhn Formula */ for (var i = (2 - (number.length % 2)), il = number.length; i <= il; i += 2) checksum += parseInt(number.charAt(i - 1)); for (var i = (number.length % 2) + 1, il = number.length; i < il; i += 2) { var digit = parseInt(number.charAt(i - 1)) * 2; checksum += ((digit < 10) ? digit : (digit - 9)); } return (!(checksum % 10) && checksum) }
To sum up all of the tests I have shown so far, it makes sense
to create an object based on Prototype’s Form
object that can handle all of our
validation needs. Example 15-2 shows what this
object looks like.
Example 15-2. validation.js: The Form.Validation object
/** * @fileoverview, This file, validation.js, encapsulates some of the basic methods * that can be used to test values, types, and syntax from within JavaScript before * the form is sent to the server for processing. * * This code requires the Prototype library. */ /** * This object, Form.Validation, is an extension of the Prototype /Form/ object and * handles all of the methods needed for validation on the client side. It consists * of the following methods: * - isNull(p_id) * - isNumber(p_id) * - isMoney(p_id) * - testValue(p_id, p_value) * - isValidDate(p_id) * - isPhoneNumber * - isValidEmail * - isSSN * - isValidCreditCard */ Form.Validation = { /** * This method, isNull, checks to see if the passed parameter /p_id/ has a * value that is null or not. It also checks for empty strings, as form values * cannot really be null. * * @member Form.Validation * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input is a valid * number or not. * @type Boolean */ isNull: function(p_id) { try { p_id = $F(p_id); return (p_id == null || p_id == ''), } catch (ex) { return (true); } }, /** * This member, isNumber, checks to see if the passed /p_id/ has a value that * is a valid number. The method can check for the following types of number: * - 5 * - -5 * - 5.235 * - 5.904E-03 * - etc., etc., etc....(you get the idea, right?) * * @member Form.Validation * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input is a valid * number or not. * @type Boolean */ isNumber: function(p_id) { try { return (/^[-+]?d*.?d+(?:[eE][-+]?d+)?$/.test($F(p_id))); } catch (ex) { return (false); } }, /** * This member, isMoney, checks to see if the passed /p_id/ has a value that * is a valid monetary value. The method can check for the following types of * number: * - 250 * - -250 * - 250.00 * - $250 * - $250.00 * * @member Form.Validation * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input is a valid * monetary value or not. * @type Boolean */ isMoney: function(p_id) { try { return (/^[-+]?$?d*.?d{2}?$/.test($F(p_id))); } catch (ex) { return (false); } }, /** * This method, testValue, checks to see if the passed /p_id/ has a value that * is equal to the passed /p_value/ in both value and type. * * @member Form.Validation * @param {String} p_id The name of the input field to get the value from. * @param {Number | String | Boolean | Object | Array | null | etc.} p_value * The value to test against. * @return Returns a value indicating whether the passed inputs are equal to * one another. * @type Boolean */ testValue: function(p_id, p_value) { try { return($F(p_id) === p_value); } catch (ex) { return (false); } }, /** * This method, isValidDate, checks to see if the passed /p_id/ has a value * that is a valid /Date/. The method can check for the following date * formats: * - mm/dd/yyyy * - mm-dd-yyyy * - mm.dd.yyyy * where /mm/ is a one- or two-digit month, /dd/ is a one- or two-digit day and * /yyyy/ is a four-digit year. * * After the format is validated, this method checks to make sure the value is * a valid date (i.e., it did or will exist.) * * @member Form.Validation * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input is a valid date * or not. * @type Boolean */ isDate: function(p_id) { try { date = $F(p_id); /* Is the value in the correct format? */ if (!/^d{1,2}(/|-|.)d{1,2}1d{4}$/.test(date)) return (false); else { /* * Find the separator for the different date parts, then split * it up. */ var ds = /^/|-|.$/; ds = date.split(ds.exec(/^/|-|.$/), 3); /* Was there something to split? */ if (ds != null) { /* Check if this date should exist */ var m = ds[0], d = ds[1], y = ds[2]; var td = new Date(y, --m, d); return (((td.getFullYear( ) == y) && (td.getMonth( ) == m) && (td.getDate( ) == d))); } else return (false); } } catch (ex) { return (false); } }, /** * This method, isPhoneNumber, checks the syntax of the passed /p_id/ and * returns whether it is a valid US phone number in one of the following * formats: * - (000) 000-0000 * - (000)000-0000 * - 000-000-0000 * - 000 000 0000 * - 0000000000 * * @member Form.Validation * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input has a valid US * phone number format. * @type Boolean */ isPhoneNumber: function(p_id) { try { return (/^(?[2-9]d{2}[)-]?s?d{3}[s-]?d{4}$/.test($F(p_id))); } catch (ex) { return (false); } }, /** * This method, isValidEmail, indicates whether the passed /p_id/ has a valid * email format. * * @member Form.Validation * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input has a valid * email format. * @type Boolean */ isValidEmail: function(p_id) { try { return (/^(([^<>( )[]\.,;:s@"]+(.[^<>( )[]\.,;:s@"]+)*)| (".+"))@(([(2([0-4]d|5[0-5])|1?d{1,2})(.(2([0-4]d|5[0-5])| 1?d{1,2})){3} ])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/.test( $F(p_id))); } catch (ex) { return (false); } }, /** * This method, isSSN, checks to see if the passed /p_id/ is in a valid format * and returns whether it is a valid Social Security number in one of the * following formats: * - 000-00-0000 * - 000 00 0000 * - 000000000 * * @member Form.Validation * @param {String} p_id The name of the input field to get the value from. * @return Returns a value indicating whether the passed input has a valid * SSN format. * @type Boolean */ isSSN: function(p_id) { try { if (!(/^d{3}(-|s?)d{2}1d{4}$/.test($F(p_id)))) return (false); var temp = $F(p_id) /* Strip valid characters from number */ if (temp.indexOf('-') != -1) temp = (temp.split('-')).join(''), if (temp.indexOf(' ') != -1) temp = (temp.split(' ')).join(''), return ((temp.substring(0, 3) != '000') && (temp.substring(3, 5) != '00') && (temp.substring(5, 9) != '0000')); } catch (ex) { return (false); } }, /** * This method, isValidCreditCard, checks to see if the passed * /p_cardNumberId/ is a valid credit card number based on the passed * /p_cardTypeId/ and the Luhn Formula. The following credit cards may be * tested with this method: * - Visa has a length of 16 numbers and starts with 4 (dashes are * optional) * - MasterCard has a length of 16 numbers and starts with 51 through 55 * (dashes are optional) * - Discover has a length of 16 numbers and starts with 6011 (dashes are * optional) * - American Express has a length of 15 numbers and starts with 34 or 37 * - Diners Club has a length of 14 numbers and starts with 30, 36, or 38 * * @member Form.Validation * @param {String} p_cardTypeId The name of the input field to get the card * type from. * @param {String} p_cardNumberId The name of the input field to get the card * number from. * @return Returns whether the card number is in the correct syntax and has a * valid checksum. * @type Boolean */ isValidCreditCard: function(p_cardTypeId, p_cardNumberId) { var regExp = ''; var type = $F(p_cardTypeId); var number = $F(p_cardNumberId); /* Is the card type Visa? [length 16; prefix 4] */ if (type == "Visa") regExp = /^4d{3}-?d{4}-?d{4}-?d{4}$/; /* Is the card type MasterCard? [length 16; prefix 51 - 55] */ else if (type == "MasterCard") regExp = /^5[1-5]d{2}-?d{4}-?d{4}-?d{4}$/; /* Is the card type Discover? [length 16; prefix 6011] */ else if (type == "Discover") regExp = /^6011-?d{4}-?d{4}-?d{4}$/; /* Is the card type American Express? [length 15; prefix 34 or 37] */ else if (type == "AmericanExpress") regExp = /^3[4,7]d{13}$/; /* Is the card type Diners Club? [length 14; prefix 30, 36, or 38] */ else if (type == "Diners") regExp = /^3[0,6,8]d{12}$/; /* Does the card number have a valid syntax? */ if (!regExp.test(number)) return (false); /* Strip valid characters from number */ number = (number.split('-')).join(''), number = (number.split(' ')).join(''), var checksum = 0; /* Luhn Formula */ for (var i = (2 - (number.length % 2)), il = number.length; i <= il; i += 2) checksum += parseInt(number.charAt(i − 1)); for (var i = (number.length % 2) + 1, il = number.length; i < il; i += 2) { var digit = parseInt(number.charAt(i - 1)) * 2; checksum += ((digit < 10) ? digit : (digit - 9)); } return (!(checksum % 10) && checksum) } };
None of the JavaScript libraries, toolkits, or frameworks has
done much in the way of validation—with the exception of the Dojo
Toolkit. Dojo does have some validation capabilities regarding form
elements, making it easier to check forms before sending them to the
server. Dojo has two objects to handle validation: dojo.validate
and dojo.validate.us
. The dojo.validate.us
object allows for
U.S.-specific validation. Table 15-3 and Table 15-4 list the methods
available for validation.
Table 15-3. Methods in the dojo.validate object
Method | Description |
---|---|
| This method checks
constraints that are passed as array arguments, returning
|
| This method checks
that the |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
Table 15-4. Methods in the dojo.validate.us object
Method | Description |
---|---|
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
| This method checks
that the passed |
The dojo.validate
and
dojo.validate.us
objects rely on
Dojo’s regular expression objects for all of the underlying
checking. For example, the isRealNumber(
)
method looks like this:
dojo.validate.isRealNumber = function(value, flags) { var re = new RegExp('^' + dojo.regexp.realNumber(flags) + '$'), return re.test(value); }
Using the validation objects is straightforward. Example 15-3 will give you an idea how to use these objects within your code.
Example 15-3. Dojo validation in action
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <title>Example 15-3. Dojo validation in action.</title> <script type="text/javascript" src="dojo.js"> </script> <script type="text/javascript"> //<![CDATA[ dojo.require('dojo.widget.validate'), //]]> </script> </head> <body> <div> <form id="myDojoForm" name="myDojoForm" method="post" action="dojoForm.php"> <label for="myCurrency">Enter amount: <input type="text" id="myCurrency" name="myCurrency" value="" class="regular" dojoType="CurrencyTextBox" trim="true" required="true" cents="true" invalidMessage="Invalid amount entered. Include dollar sign, commas, and cents." /> </label> <input type="submit" value="Submit Amount" /> </form> </div> </body> </html>
Most of the time when a validation problem has occurred somewhere on the form, a developer issues an alert telling the user there was a problem. Some developers go further and note in the alert where the problem is and then focus on that field after the user closes the alert. The problem is that sometimes it is still difficult to see where the problem is. Users need visual cues to quickly locate problems with the form so that they can be corrected. This is especially true when the form is long and there is a good chance that it will scroll on the page.
This is a good place for CSS rules to aid in visually telling the user where the problem is. You can also use CSS rules to indicate to the user where required fields are, and whether the form has everything it needs to be submitted.
The rules that you can use to indicate form errors should be
fairly simple and are meant only as an easy way for you to indicate
problem fields. One good way to do this is to change the background
color of an <input>
element
when it is of type text
or
password
. This is also a good
indicator for <textarea>
elements. Consider the following:
input.text, textarea { background-color: #fff; border: 1px inset #999; color: #000; font: .8em Arial; margin: 1px 2px 3px; padding: 1px 3px; } input.error, textarea.error { background-color: #900; color: #fff; }
All you need to do is to have a default setting for the fields
and then a setting for when there is a problem. In the preceding
code sample, the default values for the <input>
element are set first, and
then below them are the rules for the error indicator.
This is all well and good, but how do we alert the user to a
problem when the <input>
type is a radio button or checkbox? After all, as I indicated in
Chapter 14, the
developer has little to no control over the standard radio button or
checkbox <input>
elements.
An easy solution to this problem is to surround all radio
buttons and checkbox choices with a <fieldset>
element. Then you can set
the error indicator to this element instead of attempting to
manipulate the radio button or checkbox directly. The following
shows a sample of the <fieldset>
wrapper:
<fieldset> <legend>Choice 1:</legend> <input id="choice1A" type="radio" class="otherInput" name="choice1" value="A" /> <label for="choice1A">A</label> <input id="choice1B" type="radio" class="otherInput" name="choice1" value="B" /> <label for="choice1B">B</label> <input id="choice1C" type="radio" class="otherInput" name="choice1" value="C" /> <label for="choice1C">C</label> <input id="choice1D" type="radio" class="otherInput" name="choice1" value="D" /> <label for="choice1D">D</label> </fieldset> <fieldset> <legend>Choice 2:</legend> <input id="choice2A" type="radio" class="otherInput" name="choice2" value="A" /> <label for="choice2A">A</label> <input id="choice2B" type="radio" class="otherInput" name="choice2" value="B" /> <label for="choice2B">B</label> <input id="choice2C" type="radio" class="otherInput" name="choice2" value="C" /> <label for="choice2C">C</label> <input id="choice2D" type="radio" class="otherInput" name="choice2" value="D" /> <label for="choice2D">D</label> </fieldset>
Figure 15-1 shows how our error rules look in the browser. The following is the CSS for the radio buttons and checkboxes:
fieldset { background-color: #fff; border: 2px outset #999; color: #000; } fieldset.error { background-color: #900; color: #fff; } fieldset.error legend { background-color: transparent; color: #000; }
Once you have set up the rules to handle error indicators for the client, you need a mechanism to switch to CSS rules when applicable. This is simple—all you need to do is toggle the error rule for the field. Example 15-4 shows the function you can use to handle this.
Example 15-4. A simple example of CSS rule toggling
/* * Example 15-4. A simple example of CSS rule toggling. */ /** * This function, toggleRule, toggles the passed /p_ruleName/ for the passed * /p_elementId/ in the page's form using Prototype's /Element.xxxClassName/ * methods. * * @param {String} p_elementId The id of the element to toggle the rule for. * @param {String} p_ruleName The name of the CSS class that contains the rule to * be toggled. * @return Returns whether the function was a success or not. * @type Boolean * @see Element#hasClassName * @see Element#removeClassName * @see Element#addClassName */ function toggleRule(p_elementId, p_ruleName) { try { if ($(p_elementId).hasClassName(p_ruleName)) $(p_elementId).removeClassName(p_ruleName); else $(p_elementId).addClassName(p_ruleName); return (true); } catch (ex) { return (false); } }
In terms of validation, the server script’s primary job (regardless of the language involved) is to protect the application from storing or parsing anything that could be harmful to it. It must check that it got the data it was expecting to get, because a form with only part of the necessary data is not very useful. The server script must protect itself from SQL injections and other attacks by hackers, as well as make sure that the correct values are being stored. Finally, the server script is responsible for informing the client of any problems it may have had in executing its functionality.
The first thing the server needs to check is whether it even
received the data it was expecting. If the server script is
expecting six parameters of data and gets only five, it might not be
able to perform the operations it is meant to perform. For PHP, the
easiest way to check on parameters is to test the $_REQUEST
variable for the given parameter
using the isset( )
or empty( )
language construct. The following
code shows how to test for variables passed from the server in
PHP:
<?php /* Are the variables set that need to be? */ if (isset($_REQUEST['data1']) && isset($_REQUEST['data2']) && isset($_REQUEST['data3'])) { // Do something here /* Do we have this variable? */ if (isset($_REQUEST['data4'])) { // Do something else here } else { // We can live without data4 } } ?>
isset( )
returns whether
the variable is set, whereas empty(
)
checks whether the variable is empty. There is a
difference between the two, as demonstrated here:
<?php $data = 0; empty($data); // TRUE isset($data); // TRUE $data = NULL; empty($data); // TRUE isset($data); // FALSE ?>
A value of 0
passed from
the client would be considered empty, even though it is set. Be
careful what you test with.
In terms of securing the server side from malicious data being
sent from unknown sources, the $_REQUEST
variable is not the best way to
get data from the client. Instead, you should use the $_GET
and $_POST
variables depending on what the
server script is actually expecting. This way, if the server is
expecting a variable through a form POST
, an attacker sending the same
variable through a GET
would not
be able to find a hole. This is an easy way to protect yourself from
attackers.
The server must protect itself from damage because it receives all the data requests without truly knowing where the data came from. It is easy to fake the client response from Telnet, a server-side script, or another web site. Because of this, the server must assume that it cannot trust any data coming from any client without first cleansing it of any potential bad characters.
We talked about the SQL injection attack earlier in the
chapter, and we discussed how PHP can protect itself when you’re
using MySQL with the mysql_real_escape_string( )
function. This
is not the only use for this function. It also will encode
characters that MySQL may have a conflict with when executing a
statement.
Other languages may not have a function readily available to use against these issues. When this is the case, it is up to the developer to write the code to escape all potentially dangerous characters to the database so that nothing unexpected will happen when a statement is executed on the SQL server.
Besides protecting against database attacks, the server must also check the actual data coming from a client. Multiple layers of checking provide better security, and although the client will check the values with regular expressions and other means, you never know where the data is coming from. The server should never assume that the data came from the client, and should do its own value validation even if it was already done on the client.
The server should check the lengths of all values as well as their types, and apply regular expression validation against them to ensure that they are in the proper formats. The server has other responsibilities as well. Here is where it ensures that a Social Security number or credit card number actually exists, using services that provide this capability.
Only after the value has been checked by whatever means the server deems necessary should any data from the client be sent to a database or parsed by a server-side script. This will minimize any potential damage done to the server or the application and make the application on the whole more stable and secure.
Whenever an application requires user input as part of its
functionality, problems can occur with this data. In such
situations, the server must alert the client of the problem so that
it can be dealt with and communicated back to the user. What is
actually returned need not be complicated as long as the client
understands what it is getting. It can be as simple as returning
0
or false
, and it can be the client’s
responsibility to do more with the error once it is received. I
showed you the code required for returning errors in this way
already. Nothing more than this should be required of the
server.
Ajax provides the ability to check a user’s inputs in a more real-time manner. This can take some of the burden off the client, as it would no longer need to check every field value at once on a form submission. Instead, it checks fields as the user enters them, and it has the potential of speeding up the submission process, especially when the forms are longer. This capability will also lead to another feature that we will discuss in Chapter 16: search hiding and suggestions. First things first, though.
Checking form fields on the fly is something that Windows
applications can do, but it was not plausible on the Web until the
advent of Ajax. Here’s how it works. Once the focus of a field
blurs, a function is called, or a method in an object, that makes an
asynchronous call to the server, thereby allowing the user to
continue to work in the client while the validation takes place. The
easiest way to do this is with the onblur(
)
event on the <input>
element, like this:
<input type="text" id="myElement" name="myElement" value="" onblur=return Form.Validation.ajaxCheck(this, 'phone')," />
The simplest way to add this is to create some additional
functionality in the Form.Validation
object from Example 15-2. Our new method
must validate on the client that it is not empty when the element
blurs, and unless you wish to develop an extremely complicated
method of parsing, it should also be sent the type of validation
needed so that the Form.Validation
knows what to use before
sending it off to the server. Example 15-5 shows the new
function.
Example 15-5. Added functionality for Ajax in the Form.Validation object
/* * Example 15-5. Added functionality for Ajax in the Form.Validation object. */ /** * This method, ajaxCheck, provides on-the-fly validation on the client and * server by using Ajax to allow the server to validate the passed /p_element/'s * value based on the passed /p_validationType/ and optional /p_options/ * parameters. * * @member Form.Validation * @param {Object} p_element The element to validate. * @param {String} p_validationType The type of validation the client and * server should provide. * @param {string} p_options Optional string to provide when validating credit * cards to provide the card type. * @return Returns true so that the blur will happen on the passed element as * it should. * @type Boolean * @see #isNumber * @see #isMoney * @see #isDate * @see #isPhoneNumber * @see #isEmail * @see #isSSN * @see #isValidCreditCard * @see #reportError * @see #reportSuccess * @see Ajax#Request */ ajaxCheck: function(p_element, p_validationType, p_options) { var validated = false; /* Is the validation type for validating a number? */ if (p_validationType == 'number') validated = this.isNumber(p_element); /* Is the validation type for validating a monetary value? */ else if (p_validationType == 'money') validated = this.isMoney(p_element); /* Is the validation type for validating a date? */ else if (p_validationType == 'date') validated = this.isDate(p_element); /* Is the validation type for validating a phone number? */ else if (p_validationType == 'phone') validation = this.isPhoneNumber(p_element); /* Is the validation type for validating an email address? */ else if (p_validationType == 'isValidEmail') validation = this.isEmail(p_element); /* Is the validation type for validating a Social Security number? */ else if (p_validationType == 'ssn') validation = this.isSSN(p_element); /* Is the validation type for validating a credit card? */ else if (p_validationType == 'cc') validation = this.isValidCreditCard(p_options, p_element); /* Did client-side validation succeed? */ if (validation) { new Ajax.Request('ajaxCheck.php', { method: 'get', parameters: { value: $F(p_element), type: p_validationType, options: p_options }, onSuccess: function(xhrResponse) { /* Did the value not validate on the server? */ if (xhrResponse.responseText == '0') this.reportError(p_element.id, p_validationType); else this.reportSuccess(p_element.id); } }); } else this.reportError(p_element.id, p_validationType); return (true); }, /** * This method, reportError, creates an element to put next to the form * field that did not validate correctly with a supplied message alerting the * user that there is a problem. * * @member Form.Validation * @param {String} p_element The element id to place the new element next to. * @param {String} p_validationType The type of validation the client and * server provided. * @see #ajaxCheck * @see Element#addClassName */ reportError: function(p_id, p_validationType) { var message = ''; /* Is the validation type for validating a number? */ if (p_validationType == 'number') message = 'This field expects a number. Example: 31'; /* Is the validation type for validating a monetary value? */ else if (p_validationType == 'money') message = 'This field expects a monetary value. Example: $31.00'; /* Is the validation type for validating a date? */ else if (p_validationType == 'date') message = 'This field expects a date. Example: 01/01/2007'; /* Is the validation type for validating a phone number? */ else if (p_validationType == 'phone') message = 'This field expects a phone number. Example (800) 555-5555'; /* Is the validation type for validating an email address? */ else if (p_validationType == 'isValidEmail') message = 'This field expects a valid email account. Example: ' + '[email protected]'; /* Is the validation type for validating a Social Security number? */ else if (p_validationType == 'ssn') message = 'This field expects a valid Social Security number. Example: ' + '234-56-7890'; /* Is the validation type for validating a credit card? */ else if (p_validationType == 'cc') message = 'This field expects a valid credit card number. Example: ' + '4123 4567 8901 2349'; /* There was an unknown validation type */ else message = 'The input in this field is invalid.'; var span = document.createElement('span'), span.appendChild(document.createTextNode(message)); span.id = 'span' + p_id; Element.addClassName(span, 'validationError'), $(p_id).parentNode.appendChild(span); }, /** * This method, reportSuccess, checks to see if the passed /p_id/ has a * sibling element. If it does and that element is a <span> element with a class * name of /validationError/ then remove it. * * @param {String} p_element The element id to check for another element next to it. * @see #ajaxCheck * @see Element#hasClassName */ reportSuccess: function(p_id) { var elem = $(p_id); /* Does the element have another element next to it? */ if (elem.nextSibling) /* * Is the other element a <span> element with a class name of * /validationError/? */ if (elem.nextSibling.nodeName == 'SPAN' && Element.hasClassName(elem.nextSibling, 'validationError')) $(p_id).parentNode.removeChild(elem.nextSibling); }
This method of validation could also handle form submissions on the fly, but the developer would have to have a very specific need to do this.
Once the server comes back with a response, the method must alert the user if there is a problem without interrupting what she is doing. The easiest way to do this is to create an element and place it next to the input that has the problem, as Example 15-5 showed.
By using Ajax to aid in validation, a developer can have the power of client and server validation on a field before a form is ever submitted to the server. This can be a powerful tool, but it is not a replacement for server validation on a complete form submission. This is because there is still no way to be sure where a form submission came from, and the server should not take any unnecessary risks. The point of using Ajax in this manner is to speed up the application by trying to ensure that the data is good before the server ever tries to do anything with it. Of course, the server takes a stab at all of the data in the first place, because it must assume that no data submitted by a client has already been validated.
This can come in handy in several places, as you will see in the next chapter. Keep in mind that there is always a price when using Ajax, and the developer must weigh whether any given form really needs such advanced validation functionality.
44.200.94.150