Skip to page content or Skip to Accesskey List.

Work

Main Page Content

Writing Smart Web Based Forms

Rated 4.47 (Ratings: 34)

Want more?

  • More articles in Code
 
Picture of mwarden

Matt Warden

Member info

User since: 18 May 1999

Articles written: 7

Forms are used frequently in dynamic web applications. As a consequence,

almost all web programmers have to deal with form data validation at some

point in their careers. The technique described in this article will help

you help the user submit correct information.

So, what's the technique? Building smart forms... well, smart validation,

actually. You are validating your forms on the server side, aren't

you? You don't want to target="_new" title="an example of client-side validation only">validate

only on the client side
.

I can't tell you how many web sites I've visited that don't handle input errors

correctly. They either have forms that simply refresh with a generic "Your input

has errors" message or they target="_new">only report the first input error the validation script

encounters (you'll have to turn off client-side scripting to view this gem).

An href="http://www.powerasp.com/content/hintstips/server-side-form-validation.asp"

target="_new">example on PowerASP.com
has the beginnings of TheRightIdea™

and is worth a once-over. However, they miss the logical way of form validation and

processing. The article details how to process forms and report one or more input

errors using a form handler script and a redirect back to the form if there are

input errors. Other than the fact that this method isn't the most logical way of

doing things, there is also a loss of all of the users input unless they are also

passed along in the querystring. Wasn't this supposed to be easy?

Writing smart forms can be easy if you want it to be. I'm going to use ASP

in the actual examples, but the general

concept can be applied using any language —

except ColdFusion; you just can't do ANYTHING good with that (direct flames to

flames@mattwarden.com).

The Basic Concept

There are a number of benefits gained by displaying and handling a form in the same

file. First, we have all of the code related to that form in a single location (with

the exception of code in include files). Second, we have available to our script every

submitted form variable. The general flow of our form processing is as follows:

Form Processing Flow

As you can see, we are validating the form before redirecting to any other page. In other

words, the form is submitting to itself. Within this script, there is logic which determines

whether it should display the form or process submitted form information. We are only moving

from our script if the submitted form passes our validation. If the information does not pass

validation, we re-display the form. We also pre-fill the form fields with the information already

submitted and mark the fields that didn't pass our validation. This makes it easier for the user

to supply the correct information.

So, let's say Joe User fills out the following form:

Form

We don't like some of his input and our validation methods rejected it. Joe didn't even fill in his last name.

He didn't even try with his email address. And that zip code is not the zip code for Evoltageddon, NH.

How do we present these issues to Joe User? Should we just tell him he screwed up and have him

doing the work trying to figure out what is wrong (even though we've already figured out what's

wrong)? No. We indicate to him that there is a problem with his submission with some generic

text and highlight the fields that need to be corrected. The important part here is that both

his correct and incorrect data is preseved so he doesn't have to re-type the correct information

and he can also see what was wrong with his other information. So, we show Joe something like this:

Form With Input Errors

You could take it a step further and display what was wrong with each field, but that is overkill

for most situations. In this example, the only field that could warrant an explanation is the zip code

field. However, the marked fields should do fine and there is only a very small chance that Joe will

get confused.

What does this mean? Well, when you're building forms (and form handlers) you should:

  • Make the form self-submitting
  • Add logic to either show or process the form
  • Validate submitted information on the server-side
  • Redirect to the next page if information validates
  • If the submission does not validate, re-display the form pre-filled with submitted info and problem fields highlighted

The Basic Code

How can we determine whether to display or process the form?

Some people like to pick a form field and test it for a value. For instance:

if isEmpty(trim(request.form("firstname"))) then

' display the form

else

' process the form

end if

This method isn't the best way to do things. If I fill in every field but the first name field,

the form is simply re-displayed, with no error notification (because the code doesn't know there's

an error).

Another method is to pick the name of the submit button as the field to test:

if isEmpty(trim(request.form("submitButton"))) then

Call displayForm()

else

Call processForm()

end if

The above code simply checks if request.form("submitButton") has a value. If it doesn't, it calls

a subroutine called displayForm(). This subroutine simply writes the HTML for the form. If it

does have a value, it calls processForm(), a subroutine that validates the form input. You could

also skip the subroutines (but, it's so much cleaner looking, eh?) and slop all the code for each

condition within the if test itself. However, when you scroll halfway down your script

and see an else and you don't know what expression the corresponding if tested,

you'll understand why I did it this way ;-)

Still, I don't like this method much. If the value attribute of the submit button tag is omitted,

isEmpty(trim(request.form("submitButton"))) will always be true. This is an easy thing to catch, but why

bother when there are easier ways of doing things?

The method I prefer uses the server variable REQUEST_METHOD. Most likely, when Joe User first

visits your form page, his browser will use the GET method. When the form is submitted, the browser

will use the POST method (you are using method="POST" when writing forms,

aren't you?). So, an easy way to check to see if the form has been submitted it to check the

REQUEST_METHOD:

if ucase(trim(request.ServerVariables("REQUEST_METHOD")))="GET" then

Call displayForm()

elseif ucase(trim(request.ServerVariables("REQUEST_METHOD")))="POST" then

Call processForm()

else

' are you supporting any other request methods?

end if

What is an easy way to make a form self-submitting?

Simple. Another server variable available is SCRIPT_NAME. This holds the root-relative path

as well as the filename of the current script.

<!-- expanded tag for readability -->

<form

method="POST"

name="userdetails"

action="<%Response.Write request.ServerVariables("SCRIPT_NAME")%>"

>

How can I pre-fill fields with submitted data after an input error has been found?

Also fairly simple. Because the form is self-submitting, you have all the submitted form information

at your disposal.

<input type="text" name="firstname" value="<%Response.Write request.form("firstname")%>"><br>

<input type="text" name="lastname" value="<%Response.Write request.form("lastname")%>"><br>

Note that if you already have these form values stored in variables (which you should if you've

already run the submitted values through validation), you should use those values rather than

accessing them from the request object. More

It gets a little complicated if the form's purpose is to edit saved data in the database. The form

will already have data in it before the user inputs anything. So, if they change something and there's

an error with what they've changed, do you show the data from the database and let them re-edit that, or

do you display what they've just entered? Truthfully, it's a mix. Here, we're going to give importance to

the form data just submitted:

<%

function getValue(byval sImportant, byval sPassive)

' notice that we aren't checking for

' a valid value, just *any* value

if not isEmpty(sImportant) then

getValue = sImportant

else

getValue = sPassive

end if

end function

%>

<!-- tags expanded for readability -->

<input type="text" name="firstname"

value="<%Response.Write getValue(request.form("firstname"),Recordset("firstname"))%>"

><br>

<input type="text" name="lastname"

value="<%Response.Write getValue(request.form("lastname"),Recordset("lastname"))%>"

><br>

The function getValue(...) above simply takes two values. The first value

is the value that should be returned if it has a value. If it does not have a value, we use

the second value. If the second value is simply an emptry string, then an empty string will be

returned. Since our first value is from the submitted form (if there is a submitted

form), then we are using the form input if it exists. Otherwise, we're using the data from the

database.

So, how do I do this validation?

Well, it obviously depends on the information you're validating. However, in my experience,

there are a few things that makes everything go a lot easier.

We need to know two things. First, we need to know if all the data validated. We also need to

know which bits of data validated and which did not.

<%

' general true/false for

' validation of entire submission

Dim bGoodPost

' specific true/false for

' validatin of each bit of data

Dim bGoodFname, bGoodLname, bGoodEmail

' data vars

Dim sFname, sLname, sEmail, sReferringFriend

sFname = trim(request.form("firstname"))

sLname = trim(request.form("lastname"))

sEmail = trim(request.form("emailaddr"))

sReferringFriend = trim(request.form("referrer"))

if isEmpty(sFname) then bGoodFname=false else bGoodFname=true

if isEmpty(sLname) then bGoodLname=false else bGoodLname=true

if not isValidEmail(sEmail) then bGoodEmail=false else bGoodEmail=true

bGoodPost = (bGoodFname and bGoodLname and bGoodEmail)

if bGoodPost then

' validates!!!

else

' re-display the form with values pre-filled

' and problem fields highlighted

end if

%>

Here we have four variables. The first, bGoodPost stores whether

the entire form submission passes validation. This value is determined by the

required fields in the submitted form. In this example, firstname, lastname,

emailaddr are all required. referrer is not required and therefore we don't

care whether it has a value (though you may still want to validate

it if it does have a value, depending on the situation). So, we need to validate

these required fields. bGoodFname, bGoodLname,

bGoodEmail will hold whether each of these values are valid. For

sFname and sLname, all we do is check for a value.

For sEmail, we run it through our own email-validating function

(not shown here, use your imagination ;-). Like I said in the beginning,

bGoodPost is true only if all of the required bits of information

pass our validation. An easy way to check for this is to use the line:

bGoodPost = (bGoodFname and bGoodLname and bGoodEmail)

Here, bGoodPost will only be true if bGoodFname,

bGoodLname, and bGoodEmail are all true. This is

exactly what we want.

Also note that if bGoodPost then is the same as

if bGoodPost=true then.

After I've validated, how do I mark problem fields?

The easiest way to do this is to use the same subroutine you used to display

the form in the first place. In this example, it was called displayForm().

Now we have to add the highlighting functionality so that the user knows

which fields had errors. Ok, at this point (assuming the form has already

been submitted), we know whether the first name, last name, and email address

are valid. The variables bGoodFname, bGoodLname,

bGoodEmail tell us which values are valid and which are not. So,

depending on how you want to go, you can use one of the following methods to

highlight the fields with errors:

<!-- method 1 -->

...

<tr>

<td><font

face="arial"

<%if not bGoodFname then response.write " color=""#ff0000""%>

>First Name</font>

<input

type="text"

name="firstname"

value="<%Response.Write request.form("firstname")%>"

></td>

<!-- method 2, the cleaner method -->

<td<%if not bGoodFname then response.write " class=""error""%>>

First Name

<input

type="text"

name="firstname"

value="<%Response.Write request.form("firstname")%>"

></td>

Now, we run into a problem when the form is first viewed (before submission).

All of those variables are going to be false. So, up where we're doing validation,

we need to account for this case:

function isValidEmail(byval sEmail)

' very simple email validation function.

returnVal = false

if not isEmpty(sEmail) then

if instr(sEmail, "@") > 0 and instr(sEmail, ".") > 0 then

returnVal = true

end if

end if

isValidEmail = returnVal

end function

if isEmpty(sFname) then bGoodFname=false else bGoodFname=true

if isEmpty(sLname) then bGoodLname=false else bGoodLname=true

if not isValidEmail(sEmail) then bGoodEmail=false else bGoodEmail=true

bGoodPost = (bGoodFname and bGoodLname and bGoodEmail)

if ucase(trim(request.ServerVariables("REQUEST_METHOD")))="GET" then

bGoodFname=true

bGoodLname=true

bGoodEmail=true

end if

And I'm sure you can think up some nice ways of getting around checking

the REQUEST_METHOD twice in the same script ;-)

Cool?

 

A Caveat

Sending a response.redirect (or equivalent in your language of choice) generates

a HTTP response code of 302 (the requested file has been found but it is in a

different location). Rather than it executing the redirection by requesting

the new location, in very old browsers an "Object Moved" page might be

displayed. This is obviously not something we want. In ASP, there is a method of

the Server object called Transfer().

Server.Transfer() is a server-side redirect, rather than a client-side

redirect. If this is available to you (ASP3/IIS5 and probably in other languages),

you will want to consider using that rather than sending a 302 header.

 

--

mattwarden

mattwarden.com

Matt Warden spends his spare time writing up author bios for his accounts on various websites... er... you know what? It's all here somewhere anyways. No use repeating myself...

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

evolt.org Evolt.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.