Skip to page content or skip to Accesskey List.
Search evolt.org
evolt.org login: or register

Work

Main Page Content

Form Validation: an Object-Oriented Approach

Rated 3.8 (Ratings: 3) (Add your rating)

Log in to add a comment
(5 comments so far)

Want more?

 
Picture of ben_g

Ben Gustafson

Member info | Full bio

User since: November 05, 2001

Last login: November 05, 2001

Articles written: 1

Everyone who develops a form for a website, be it a simple contact form or a more complex form for collecting order information for an e-commerce site, needs to address the issue of validating and handling the data collected from it. There are opinions both for and against validating form data. Let's assume, however, that you've designed your form with international audience in mind in order to cause no headaches to folks outside your country when it's validated. Let's also assume that your boss or client doesn't want a lot of the registrations from their lead-generation website being submitted empty or with "asdf" typed in every element of the form. Similarly, people sometimes mistakenly type their e-mail address in the line for their phone number, for example. And then there's the issue of how to collect the data from the form and process it in a methodical manner.

An object-oriented approach can lend scalability, code reusability and readability to such programming tasks. This article will describe an object-oriented methodology for form data validation and collection using ASP and JavaScript (or JScript, as the folks at Microsoft like to call their implementation in ASP). The methodology can be applied to other scripting languages that use a class or object orientation, such as VBScript, JSP or PHP.

Broadly defined, the steps followed here in processing a form are:

  1. Instantiate a form validation object;
  2. Request data from the form;
  3. Insert the data and corresponding form element names in arrays in the object;
  4. Check the data for each element against validation rules (regular expressions or functions);
  5. Flag invalid data in the object;
  6. Display the form in the Web browser again with elements containing invalid data marked;
  7. Repeat the above steps until all the data the visitor inputs is valid;
  8. Process the validated data and redirect to the confirmation page.

Form design

The method for marking form elements containing invalid entries requires that either the <P> tag containing the text label for the form element or the form element itself have an ID that matches the name of the form element (depending on which you want to be marked in the form). In the example below, the text label for the form element will be marked:

<p id="first_name">First name <INPUT TYPE="text" NAME="first_name"></p>
<p id="last_name">Last name <INPUT TYPE="text" NAME="last_name"></p>

A quirk in the Form Collection in ASP is that checkboxes and radio buttons are not included in the collection, and thus not added to the Request.Form.Count property, if they are not checked (or if one of the elements in a group of radio buttons or checkboxes is not checked). Thus, a group of radio buttons that does not have a button selected will be considered "valid." To overcome this, include a hidden form element after each group of radio buttons and checkboxes with the same name as the group and an empty value, like so:

<p id="mood">Mood</p>
<input type="radio" name="mood" value="good"> Good<BR>
<input type="radio" name="mood" value="bad"> Bad<BR>
<input type="radio" name="mood" value="indifferent"> Indifferent<BR>
<input type="hidden" name="mood" value="">

A side-effect of this is that there will be an extra element containing a space in the Request.Form array, or a trailing comma and space after the last value if it is converted to a string. I'll show you how to trim it off the string in the function that processes valid data.

So that a visitor doesn't need to fill out the whole form again if some of the data is flagged as invalid, your server-side code should fill in the value with the data that the visitor input. For a radio button or checkbox, use:

<input type="radio" name="mood" value="good" 
<% if (String(formData.str["mood"]).indexOf("good") != -1) Response.Write(" checked")%>>

And for a text element, use:

<input type="text" name="first_name" 
value="<%=formData.str["first_name"]%>">

Give your Submit button a name and value so that you can use Request.Form to flag when the form has been submitted:

<input type="submit" name="btnReg" value="Register">

The form validation object

A form validation object consists of the following properties and methods:

  • An array for storing the data;
  • An array for storing the name of the corresponding form element;
  • An array for storing a Boolean value (true or false) for the validity of the data;
  • A variable for indicating whether the object has any invalid entries;
  • A method for collecting the data;
  • A method for validating the data entries;
  • A method for flagging invalid entries;
  • A method for deciding whether to process the form or mark invalid entries in the browser;
  • A method for processing the data.

The formValidationObject function creates the form validation object with the above properties and methods.

function formValidationObject()
{
	this.str = new Array();
	this.elem = new Array();
	this.isValidEntry = new Array();
	this.hasInvalidEntry = false;
	this.getData = getData;
	this.validate = validate;
	this.flagInvalidEntries = flagInvalidEntries;
	this.processOrMarkInvalidEntries = processOrMarkInvalidEntries;
	this.processData = processData;
}

Requesting and inserting data into the form validation object

Instantiate a form validation object in the form page and call its getData method to fill the array properties with data. You should make the form page also be the action page, to keep the form validation object alive throughout the process, and to fill in the form with the user's data if some is invalid.

var formData = new formValidationObject();
if (Request.Form("btnReg") == "Register")
	formData.getData();

The getData method creates an enumerator object to count the number of items in the Form object and loops through the object, inserting the data into the str array and the form element name in the elem array. Note that str is indexed by the counter, and elem is indexed by the name of the form element. The isValidEntry array property for the validity of the data is also filled here. (It is important to assume the data is valid by default.)

After the while loop finishes, the methods for validating the data and determining whether to process the data or mark invalid entries in the browser are called.

function getData()
{
	var formItems = new Enumerator(Request.Form);
	
	var i = 0;
	while (!formItems.atEnd())
	{
		var elem = formItems.item();
		this.str[elem] = Request.Form(elem);
		this.elem[i] = elem;
		// assume data is valid:
		this.isValidEntry[i] = true;
		i++;
		formItems.moveNext();
	}
	this.validate();
	this.processOrMarkInvalidEntries();
}

Checking the data against validation rules

The validate method loops through the str and elem arrays of the formData object, and checks the data against rules in regular expressions or functions within a switch statement. This is where it becomes important and useful that the str array is indexed by the name of the form element, and the elem array is indexed numerically. The elem array is used for setting the number of repetitions of the loop, and the name of the form element in elem at each increment of the loop is used as the index for str when testing str's data in the if statements within the switch.

function validate()
{
	// validation regular expression for characters
	//  that shouldn't appear in a person's name:
	var reJunkChars = /\;|\[|\]|\:|\,|\^|\?|\{|\}|\\|\!|\@|\#|\$|\%|\&|\*|\(|\)|\+|\-$/;
	// to check if a string contains a number:
	var reNums = /\d/;
	for (var i = 0; i < this.elem.length; i++)
	{
		switch (this.elem[i])
		{
			case "first_name":
			case "last_name":
				if (this.str[this.elem[i]] == "" ||
     				 reJunkChars.test(this.str[this.elem[i]]) ||
				    reNums.test(this.str[this.elem[i]]))
				{
					this.isValidEntry[i] = false;
					this.hasInvalidEntry = true;
					break;
				}
			case "mood":
				// convert to string and use indexOf for radio button and checkbox values, 
				// since value will have a comma at the end of it from the hidden form field:
				if (this.str[this.elem[i]] == "" || this.str[this.elem[i]].indexOf("bad") != -1) // c'mon! cheer up!
				{
					this.isValidEntry[i] = false;
					this.hasInvalidEntry = true;
					break;
				}
			//' no default case, so that fields not required don't need to be filled out
		}
	}
}

Process or flag invalid data?

Now that each piece of data in the formData object has been labeled as valid or invalid, we determine whether to process it or flag any invalid entries in the browser. The processOrMarkInvalidEntries method checks the hasInvalidEntry property to see if an element in the object was flagged as invalid. If not, we call the processData method; if so, we go back to the form by calling the flagInvalidEntries method.

function processOrMarkInvalidEntries()
{
	if (this.hasInvalidEntry)
		this.flagInvalidEntries();
	else
		this.processData();
}

Marking invalid data in the Web form

If the flagInvalidEntries method is called, it will mark in the browser form elements containing invalid data. It does so by writing out a stylesheet that marks elements containing invalid data. Below, each element containing invalid data gets an ID selector that sets its color to red and font weight to bold. (This is where following the paragraph-ID-matching-the-form-element-name convention when designing your form comes into play.)

function flagInvalidEntries()
{
	Response.Write("\n<P style=\"color: red;\">Please enter or correct data in the fields marked with bold red text.</P>\n");
	Response.Write("<style type=\"text/css\">\n");
	for (var i = 0; i < this.elem.length; i++)
		if (this.isValidEntry[i] == false)
			Response.Write("P#" + this.elem[i] + "{ color: red; font-weight: bold; font-size: 14pt; }\n");
	Response.Write("</style>\n");
}

Processing data

When all the data the visitor has submitted is flagged as valid, you can now do something useful with it when the processData method is called. One way to process the data is to use a loop to write out SQL that inserts the data into a database. This assumes the database column names are the same as the form element names.

function processData()
{
	var insertSQL = "INSERT INTO registrant (";
	for (var i = 0; i < this.elem.length; i++)
		if (this.elem[i] != "btnReg") // exclude the Submit button
			// add commas between column (form element) names
			insertSQL += this.elem[i] + ", ";
	// chop off the trailing comma at end of SQL string:
	insertSQL = insertSQL.substring(0, insertSQL.lastIndexOf(","));
	insertSQL += ") VALUES (";
	
	for (var i = 0; i < this.elem.length - 1; i++)
	{
		if (this.elem[i] != "btnReg")
		{
			var inp = String(this.str[this.elem[i]]);
			// get rid of trailing comma and space in radio button and checkbox data:
			if (inp.lastIndexOf(", ") == inp.length - 2)
				insertSQL += "'" + inp.substring(0, inp.lastIndexOf(",")) + "', ";
			else
				insertSQL += "'" + inp + "', ";
		}
	}
	insertSQL = insertSQL.substring(0, insertSQL.lastIndexOf(","));
	insertSQL += ")";

	insertData = conn.Execute(insertSQL);

	conn.Close();
	conn = null;
	insertData = null;
	insertSQL = null;
	// send the visitor to the confirmation page:
	Response.Redirect("thanks.htm");
}

Conclusion

This methodology can be used for forms with one or 100 elements, by adding cases to the switch statement and rules for validating different types of data, and putting the appropriate form fill-in ASP code in your form elements. You could also have different types of data processing functions, depending on whether you wanted to insert data into a database as above, use an e-mail object to send it as a message, or use another processing method.

Well organized article

Submitted by IanO on June 14, 2002 - 15:51.

Thanks for writting your JavaScript with matching braces in the same column.

As to "An object-oriented approach can lend scalability, code reusability and readability to such programming tasks. " I see this statement often. However I don't see the generality in this example. Would it be useful to declare a field type for each field in the form and have the validation code switch statement use this field type to process the individual fields? Of course we need the field name so we can display the errors if they occur.

I first attempted this type of solution in the early 1980's using pascal on the p-system. Suppose we have three forms on the page. How do we use this code without changing the code for each form?

login or register to post comments

Re: Well organized article

Submitted by ben_g on June 16, 2002 - 17:16.

Hi IanO,

Thanks for the compliment on my article. I think the model I present of using arrays to collect, organize and sort through form data and different functions to divide up the work, all related as properties and methods of an object, is quite scable. If you keep in mind that (at least in the case of "classic" ASP) the only thing the Form collection in the action page knows about the items passed to it are the names of the form elements, the number of elements passed and the data associated with each form element, you can do quite a lot with this model. For example, you can add different attributes to the form name, parse those attributes out and put them into different arrays, thus giving additional characteristics on which to validate. For example, you might follow a form naming convention where the first three characters are the form element type (such as "chk" for a checkbox, "rad" for a radio button, etc.). You might also use a similar naming convention to indicate the type of data the form should accept, such as "int" for integers or "str" for strings. Then, within the formValidateObject getData functions, add other arrays, such as this.formType[i] for the form element type, and this.dataType[i] for the acceptable type of data. Finally, add additional switch statements within the validate function, and cases where data should be declared invalid.

As for having more than one form on a page, I'm not quite sure how that would require more work than having all the elements in one form, given that each form element have a unique name and the forms' action page is the same. The action page only knows about the names of the elements submitted to it and their accompanying data, not which form submitted it. Perhaps you could eleborate on your supposition?

login or register to post comments

maintainability

Submitted by IanO on June 17, 2002 - 14:33.

I had no disagrements with the arrays aproach. My question was related to the statement
"object-oriented approach can lend scalability, code reusability and readability to such programming tasks."

Let me put this in context: I work as a contractor; I am assigned to do a web based application. Suppose I build a "generalized object" that can be used for every form validation. I get it to work and then there is a change in the requiements. My code has been stored in the source code library. Now I have to check out the application and the code for the object because the nature of the requirements is such that the validation code must be enhanced. At some point I am going to be asked if the additional file is really helping or causing more work.

Please understand that I am not being critical of your article, it is just that Pascal p-system experience reminds me that a real useful generalized anything is very hard to do.
Now I have done some generalized applications but there were clear limitations in the specification. The example I am thinking about is a generalized Table Editor for any SQLServer or Oracle Table. One of the requirements was a single field primary key. If the primary key for the table was constructed from multiple fields, this Table Editor could not accomodate it.

Now back to the implementation:Once I instantiate the validation object, do I call methods that pass the form data to the object for validation. Does it mark up the arrays so that the calling code continues the processing or re-writes the page with the form augmented with error indications? Next issue is cross field checks. I strongly suspect that this would not be done by a generalized validation object. Do you have a demo site?

Let me finish with this:object design reminds me somewhat of Integral calculus in that you have to have a problem that fits. I had read a good bit on object design and found a lot of it wanting. However when I saw MS Access 2, I saw an application that made sense as objects. It validated all that I had read about object design. Could you give me other object solutions that you find are good object examples?

login or register to post comments

Not a good idea

Submitted by jweissig on December 23, 2003 - 15:14.

It is NOT a good idea to store the raw SQL statement on the client side. What happens when X user comes along and saves the page to the harddrive. Removes all your Javascript and rewriters your SQL statement and insert data into a differient table, maybe the password table.. this is extremely poor security.

login or register to post comments

Re: Not a good idea

Submitted by ben_g on December 23, 2003 - 17:50.

"It is NOT a good idea to store the raw SQL statement on the client side."
True. Who's talking about storing SQL on the client side?

login or register to post comments

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.orgEvolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.