|
Introduction
Sometimes developers forget that not everyone can code in HTML. Fewer still can code in JavaScript; server-side scripting in ASP, Perl, and Cold Fusion is rarer still. However, there are occasions where one may want to be able to hand over a project to a nondeveloper and know that they have the option to update it themselves.
With regard to most content, this is easy. Database-driven Web sites and Intranets have been around for quite a while, with page text, tables, and even Java scrollers being updated on a regular basis by others, while you continue on with something more interesting. However, when faced with complex forms that must be updated on a regular basis, including validation (server and/or client side), things can start going pear shaped.
Examples
An example of this might be a site that sets a questionnaire every week to the public. This is simple enough to update if the form is basic in structure, but what happens if the number of fields or rules of validation vary?
In this example, we can use a database to define and store information about our form. We can even use the same database that will eventually hold the information that is submitted to the form. However, returning to our form definition, we will need two tables; one for the form field definition and a second to hold subelements to each field, such as list options.
Our definitions table should hold the following information:
- FieldName – the variable name we are assigning to the form field
- Label – the display label that will be attached to the field for the user’s benefit
- Type – a one-character code defining what type of form element will be displayed and what type of data is expected. In our example here, I’ve limited it to the following:
- (t) A simple text box, i.e. <INPUT TYPE=”TEXT”>
- (n) A simple text box, as above, but designed to take in numeric values
- (m) A “memo” field in a text area, for comments or other entries where a simple text box would not suffice
- (b) A Boolean field. In this case, I represent this as a check box with the word “Yes” next to it. Checked, it returns an “on” value; unchecked, it returns nothing.
- (r) A series of radio buttons
- (l) A drop-down list
- Min – This is only used in the case of numeric fields, and it is the minimum numeric value. In the example, where I’ve defined “Age” as a numeric field, this is set as 1.
- Max – The nature of this is dependent on the field type. In the case of a numeric value, it is the maximum numeric value allowed. For example, in the case of our “Age” field, this is 100. For simple text boxes, it is the maximum length of the text entry, in characters, and for memo fields, it’s the number of visible rows is the text area.
- Required – A Boolean value that defines whether the field is mandatory or not. If not filled out, such fields would be trapped by the validation process and would also be marked by an asterisk on the form, which would point to a footnote that advises the user to their mandatory nature.
For the example form I’ve used, which is a simple ASP developer’s survey, I’ve defined the following fields:
| FieldName | Label | Type | Min | Max | Required |
| Name | Name | Text (t) | n/a | 50 | No |
| Age | Age | Numeric (n) | 1 | 100 | No |
| Sex | Sex | Radio (r) | n/a | n/a | Yes |
| E-mail | E-mail Address | Text (t) | n/a | 50 | Yes |
| Language | What language do use to code your | Drop-down List | n/a | n/a | No |
Our second table, “Lists,” is home to the subelements that populate the options given to the user in the “Sex” and ”Languages” fields. The table is simple enough, containing only three fields:
- FieldName – The form element to which this item belongs
- Value – The value of the option
- Label – The text label that the user sees
For “Sex,” we only have two items ? “Male” and “Female.” For “Language,” the list is a little longer, allowing for a number of the languages that are used with ASP, such as VBScript, JavaScript, C, Perl, and the infamous “other.”
Our third table, “Records,” where the user submissions will eventually be recorded, also holds three fields:
- Record – A memo field that will store the entire submission in a querystring format
- Created – The date/time of the submission
- RemoteIP – The remote address of the user who made the submission
One could grab additional information from the user, but for this simple example, I’ve limited this extra information to creation date and IP address.
Now that our database is constructed and our form defined, we can turn our attention to the script that will create our form, validate it, and finally handle it, recording users’ submissions.
Whether we are constructing the form or handling it, there are three things that we will always be doing. The first is defining the type of validation, which we pass as a querystring when constructing the form or as a posted hidden field when handling it. This allows us to use the form more efficiently, perhaps by validating it server side only if JavaScript is disabled. Nonetheless we have four options: no validation, client-side JavaScript, server-side ASP, and both (which will be our default - if no or an invalid querystring is sent).
iValType = Request.QueryString("val")
If IsNumeric(iValType) = False Then iValType = 3
If iValType > 3 Or iValType < 0 Then iValType = 3
We also want to create our Open DataBase Connectivity (ODBC) connection and two recordset objects: RS, which will be our main recordset object (initially from the Definitions table), and RSList that we'll use when cross-referencing related tables later. (Note that in the example script I've included the code for either a Data Source Name [DSN] or DSN-less ODBC connection.)
Finally we want to create the HTML headers and footers to the page body at the top and bottom of the script and destroy our ODBC objects before terminating the script. Once all this is done we can get to the middle, more intelligent part of the application.
The script itself represents two pages ? the form and a “form-submitted” end page, which also records the submission to the database. The simplest means of deciding which is called is by checking whether FORM data has been POSTed. If so, we handle the form; if not, we construct it.
We construct both the HTML for the form and the JavaScript to handle it one piece at a time, record by record, element by element. With the HTML, we begin by creating a label for the element:
sHTML = sHTML & vbTab & "<TR>" & vbCrLf & vbTab & vbTab
sHTML = sHTML & "<TD VALIGN=" & Chr(34) & "TOP" & Chr(34)
sHTML = sHTML & ">" & vbCrLf & vbTab & vbTab & vbTab
sHTML = sHTML & "<B>" & RS.Fields("Label")
Next, we check if the field in question is required. If so we add an asterisk to the label which will point to a footnote that informs the user that the field is required. If the field is required, we also have to create the JavaScript code to validate it. In the case of either radio buttons or a drop-down list, we have to send it to further, fixed, functions that would check to see that something is selected. With all other types we only have to check that the value is not null.
We close the label cell and write the element to the page, based on the type and attributes given to it in the Definitions table. Now the JavaScript has to be appended, depending upon the type of field type. With regard to this example only numeric fields need to be further checked to see that they contain numeric values, and that these are within the allowed maximum and minimum values.
Finally, we close our table row and carry on through our recordset, at the end of which we add the forms submit and reset buttons. In essence what we're doing is treating each field as a new table row that is two cells long, the first displaying the field label and the second containing the field element itself.
Both the code for the form’s HTML and JavaScript are accumulated by this process into two variables: sHTML and sJavaScript. Just as we are about to begin writing them to the page, we check if JavaScript validation is required, and clear sJavaScript if it's not:
If iValType = 0 Or iValType = 2 Then sJavaScript = ""
After the body tag, we begin writing our JavaScript functions:
<SCRIPT LANGUAGE="JavaScript">
<!--
function validate(TheForm)
{
<%=sJavaScript%>
return true;
}
function CheckRadio(objRadio)
{
for(var n = 0; n < objRadio.length; n++)
{
if(objRadio[n].checked)
{
return true;
}
}
return false;
}
function CheckList(objList)
{
for(var n = 1; n < objList.length; n++)
{
if(objList.options[n].selected)
{
return true;
}
}
return false;
}
//-->
</Script>
The “validate” function is left to return a simple ”return true” if we don't require JavaScript validation. The latter two static functions, CheckRadio, and CheckList, are called from ”validate” when checking that a radio or drop-down list field has a valid selection.
Now we write our form to the page, beginning with:
<FORM ACTION="./dform.asp" METHOD="POST" NAME="MyForm" onSubmit="return validate(this)">
which submits to our script only if the “validate” function returns “True.” Thus if JavaScript validation is switched off and the client is nonetheless JavaScript-enabled the validate function will automatically return ”True.”
Next, we add the hidden field for the validation type:
<INPUT TYPE="HIDDEN" NAME="val" VALUE="<%=iValType%>">
so that upon processing, the script knows whether server-side validation is required.
We begin by adding a title label to our form ? sTitleLabel ? which we defined as a constant at the top of our script.
<TABLE BORDER="0">
<TR>
<TD COLSPAN="2" ALIGN="CENTER">
<H2><%=sTitleLabel%></H2>
</TD>
</TR>
Note that an extra field in the Definitions, Lists, and Records tables called FormID could be used to separate different forms that are stored in the same database. This allows the Webmaster to run numerous forms from the same script. A table, Forms, could also be used to store global form data, such as sTitleLabel.
We now add our HTML form code and submit/reset buttons, and finally we check to see if an asterisk is present in sHTML, denoting the presence of a mandatory field, and prompting the script to add a footnote to our form informing the user of their existence:
<%=sHTML%>
<TR>
<TD COLSPAN="2" ALIGN="CENTER">
<INPUT TYPE="SUBMIT" VALUE="Submit">
<INPUT TYPE="RESET" VALUE="Reset">
</TD>
<%
If InStr(sHTML,"*") Then
%>
</TR>
<TD COLSPAN="2" ALIGN="CENTER">
<FONT SIZE="2">
<B>Please Note: </B> The fields marked with an asterisk (*) are mandatory.</FONT>
</TD>
</TR>
<%
End If
%>
</TABLE>
</FORM>
Our form and the first half of our script is now complete.
The latter half our form is the server-side handling - validation, recording the data to the database, and any submission accepted/submission error messages that are needed. We check the validation of our form using the string sBadForm, to which we write our error message. If it's empty by the end of our validation processes, then the submission is valid; otherwise we reject the submission and write sBadForm to the page.
Regardless of the validation type (passed is the hidden form field “val,” above) it is good practice to check the POSTs referrer. Such a practice prevents your script from being hijacked, particularly important if your script involves E-mailing form results. Checking that the POST has come from a page or script on your own site is simply done by comparing the necessary server variables thus:
If InStr(Request.ServerVariables("HTTP_REFERER"), _
Request.ServerVariables("HTTP_HOST")) = 0 Then
sBadForm = "<LI>Form was submitted from an invalid location" & vbCrlf
End If
Checking that server-side validation is requested, we run through our form definition recordset in much the same way as when we constructed our form. (The form definition recordset is opened at the top of our script, since it's used in both constructing and validating our form.) This time, however, we're simply validating our form and adding to our sBadForm string as we find invalid data.
Finally, we check that sBadForm is empty. If not, we reject the form submission, writing sBadForm to the page.I If so, we add a new record to our Records table and add the submitted data (parsing out the unnecessary “val” field, which is always passed as the first element):
If Len(sBadForm) = 0 Then
RS.Open "Records", DB, 3, 2, &H0002
RS.AddNew
RS.Fields("Record") = Mid(Request.Form, InStr(Request.Form, "&") + 1)
RS.Fields("Created") = Now()
RS.Fields("RemoteIP") = Request.ServerVariables("REMOTE_ADDR")
RS.Update
Response.Write("<H1>Thank you for your Submission</H1>")
RS.Close
Else
Response.Write("<H1>Your Submission was not processed</H1>")
Response.Write(vbCrLf & sBadForm)
End If
And with that our script is finished, assuming we've encapsulated the two halves of this script correctly in the main “If-Then-Else-End If” conditional, based on the existence of POSTed data, we've already written our HTML headers/footers, and destroyed our ODBC objects.
This script is only an example, so I've omitted many features. Allowing multiple forms, governed by a global definitions table, would be one such feature. Such a table could also tell the form if it should record the data or E-mail it, using the Collaboration Data Objects for Windows NT Server (CDONTS) objects library.
Another glaring omission is an update center that would allow a nondeveloper to add, delete, and amend the form or access the collected data. This is another application in itself and a more orthodox one that an ASP developer would be far more familiar with.
Field types are also limited in this example. Image or hidden fields are not catered for, and customized field types, such as text fields that are specifically designed to take in and validate E-mail addresses or credit card numbers, are not included, but could be added.
However, when faced with a site that requires a new validated form to be designed on a regular and frequent basis, this will take the long-term drudgery out of the work and give the client, whom we assume is nontechnical, direct control over the site.
Download
You can download the complete source for the sample contained in this article:
http://15seconds.com/files/000224.zip
About the Author
Gaddo F. Benedetti is an Internet and software developer in Dublin, Ireland. He specializes in server-side applications and tools for the Wireless Application Protocol (WAP), Web spiders, and Case Base Reasoning (CBR) search agents. He has a fondness for both Guinness and pasta, but not at the same time.
|