Cates Associates Web Services
SPRY Forms Validation

Dreamweaver CS3 Spry Forms + CSS + Validate

CSS Forms

Aug. 2007

I ran into problems building a simple email form for a client site. I wanted to use CSS, no tables (tableless), validation and avoid a page reload for a Web 2 feel using SPRY (ver 1.5).

 

The Adobe example on the Labs site works but uses size to work around some of problems handling error messages. A large form was used to provide space for the error messages i.e., Required field missing, wrong format, etc. I wanted a more compact form.

This is how I built the form and some of the problems I think the demo does not cover.

 

CSS Layout - No Tables - Tableless

There are lots of solutions to CSS form layout. You can Google for numerous examples. I like the look of Jeff Howden's forms.

Form Fiels Accessibilty Dialog

 

Most CSS form layouts are based on a style for the <label> tag. If you are going to use Spry, skip using WRAPPED labels (label tags with input tags nested within the label tag). Use labels with a 'for' attribute for accessibility. In DW if you have accessibility turned on, the dialogs for these items come up automatically when you insert a form field.

To turn Accessibility on click: Edit > Preferences and select the item Accessibility. Check the boxes.

To insert the Spry INPUT fields use the Spry Widgets under the Spry tab. The dialog will add both the label and input tags. But, hold off on that, we are not quite ready to start inserting fields.

Spry Widgets

Note: When you plan to work with Spry, use ID's. Using only NAME attributes can create some odd errors that are hard to track down. To be on the safe side, use them both.

Since I was going for a Web 2.0 look and feel, I needed a DIV to hold the returned HTML fragment, the submit result. So, I placed a DIV and gave it an ID of 'emilForm'. I set a style to place it where I wanted and give it a size.

#emailform {
   display: block;
   border-top-width: 1px;
   border-right-width: 1px;
   border-bottom-width: 1px;
   border-left-width: 1px;
   border-top-style: outset;
   border-right-style: outset;
   border-bottom-style: outset;
   border-left-style: outset;
   width: 500px;
   background-color: #DDDDDD;
   margin: auto;
   padding: 10px;
   text-align: left;
}

   

I started inserting my 'form' fields inside this DIV. For accessibility fill out all the fields in the Input Field Accessibility dialog. Add the fields one after the other. You do not need to add <br /> tags.

I used <fieldset> tags to organize the form.

Note: A key to making a Web2 page change to a success or fail message is to place all of the form within an outer DIV and format it to hold the form and provide placement and appearance. It has to be a DIV as SPAN will not html validate. I use this outer DIV ID in my CSS style definitions to allow me to have different label, input, etc. styles in different forms.

To get the labels to line up and right justify, you need to set the label style to block, define a width and float it left with right justified text.

#emailform form label {
   font-size: 11px;
   display: block;
   text-align: right;
   float: left;
   margin: 0px;
   width: 75px;
   padding-top: 4px;
   padding-right: 0px;
   padding-bottom: 3px;
   padding-left: 0px;
}


Spry Form Input Error Messages

A problem one runs into when using Spry Validation is positioning the error messages. The CSS needs to either set the <input> fields to block and float left or the error messages to a class that is block, float right and may be justify right. You are basically going for a 3 column layout.

So, the layout is not that difficult once you get these float ideas down. Changing your design style to use Spry Validation is just a matter of using Spry Input Fields in place of regular Form Input Fields.

Spry CSS

Spry will handle the validation messages formatting. To modify it use the DW Spry CSS that is added when you start adding Spry validation. The class names pretty much tell you which style handles what. (Spry CSS shown left)

When you build your forum using Spry for validation, DW will add most of the code you need.

You will see DW render the fields with a Spry label when selected..

Spry Field Label

If you click the Input Field you will see the standard properties in the inspector. When you click on the Spry Label you get a Spry property inspector.

The error messages are also added automatically and are just text that can be edited. You will find the the DW Spry generated error messages are long. Select a Spry Label (shown above) and Spry Properties provides a Preview States selection drop down that will reveal the text for each message and show it's positioning. You can also edit the text in code view, which I think is faster. I shortened all the messages. Format the text using the Spry CSS styles.

There are some limitations to Spry validation. For instance, there is not much info available on writing custom validation formats and the ones they currently have are limited. For instance you can validate a 5-digit zip or a 9-digit zip code. But there seems to be no way to allow both. One has to go to custom formatting to get both. I have yet to get that figured out. I'm using 5-digit to make it easier on the user.

Once the form is built you need a page to handle the incoming data, that is next.

Submitting & Validating

I used an ASP page to receive the form submittal. But DW Spry Validation has no 'widget' to submit the page without suffering a page refresh. That requires another widget. I left that part of the design until after I had everything else working, a submittal that actually sent a email with the data formatted as I wanted and returned a simple on screen message of success or failure. If one does not mind the page refresh, this is all one needs.

To avoid the page refresh the processing page needs to return an HTML fragment, no head, body, html, etc. Just the html you want to appear within the outer DIV (in my case #emailForm) of the form.

Adobe explains how to change Spry to allow an Ajax type form submittal and avoid the page refresh. (Reference) So, you can change the default browser submit to 'an Ajax-enabled submit' by setting the onSubmit handler to Spry.Utils.submitForm(form, callback);

<form name="form1" method="post" action="SubmitChecker.asp" onsubmit="return Spry.Utils.submitForm(this, updateResponseDiv);">
... // where the success callback:

function updateResponseDiv(req) { // do something with the response from the server }

There are more options in the Spry Submit. Visit the Adobe page or examine the Spry function. But this function will normally do whatever one needs done with an Ajax submit. With one minor problem, it cancels validation. We will get to that problem. We know validation and submission were working because we tested that earlier. To build the Web2 effect and get it working we will combine the the two widgets.

There is more sample code here: http://labs.adobe.com/technologies/spry/samples/

DW does not always add the needed libraries so you will need to check your page links to these Spry libraries;

<script type="text/javascript" src="SpryAssets/xpath.js"></script>
<script type="text/javascript" src="SpryAssets/SpryData.js"></script>
<script type="text/javascript" src="SpryAssets/SpryUtils.js"></script>

You will find a 'Spry.Utils.loadURL' function in Spry. This is the basic Ajax process you find in most books and tutorials. You use either this one or the Spry.Utils.submitForm to submit the form. I suggest Spry.Utils.submitForm.

For use with a form using Spry.Utils.submitForm one needs to supply a function to handle the response from the server. Remember. The form target/response page should generate an HTML fragment of just the HTML you want to display. So the ACTION page you submit to must generate the fragment. You can use IE7 to test the fragment, IE7 renders the malformed pages pretty well.

The handler code looks like this:

<script type="text/javascript">
<!--
//
// Callback function that will update the response_form1 div with the response that comes from the server
//
function updateResponseDiv(req)
{
Spry.Utils.setInnerHTML('response_form1', req.xhRequest.responseText);
}

//-->
</script>

The handler code must be present and you can change the 'response_form1' to suit your page (emailForm in my case).

Once you have returning code replacing the form, you are ready to combine the replacement and validation.

The Problem - SPRY Submit & SPRY Validation

Now the problem. To get the page to submit, one needs to replace the onSubmit function built into browsers. Spry Validation does that and adds one for you, which is how it works.When you add Spry.Utils.submitForm you effectively replace it and validation stops working. So, we change the <form> tag to use our replacement something like this:

<form action="processForm.asp" method="post" id="contact" name="contact" onSubmit="return SendForm('contact');" >

I thought I could just use the Spry.Utils.submitForm but that does not work. As I explained when you place your onSubmit in the form you replace Spry Validation's onSubmit. So your page submits but there is no validation on submit. So a user can just click submit with a completely blank form and it will submit. Oops.

The work-around is to build a function and call both Spry.Widget.Form.validate and Spry.Utils.submitForm.

Part of the problem Adobe does not cover is that while Spry.Utils.submitForm will take a form handle (getElementbyID) or a form ID, the validate widget won't. It needs a handle. In the code below that is done.

<script type="text/javascript">
<!--
// Callback function that will update the response_form1 div with the response that comes from the server
function updateResponseDiv(req) {
    Spry.Utils.setInnerHTML('emailform', req.xhRequest.responseText);
}
// handle form submit & validate
function SendForm(form) {
   var theForm = typeof form != 'object' ? document.getElementById(form): form;
   var ret = Spry.Widget.Form.validate(theForm);
   if (ret) {
      Spry.Utils.submitForm(theForm, updateResponseDiv);
   }
   return false;
};
//-->
</script>

Replace the onSubmit in the <form> tag with a call to this new function SendForm(form). The tag should look like this:

<form action="validate.asp" method="post" id="contact" name="contact" onSubmit="return SendForm('contact');" >


Now if you have everything correct, you have a working form. It validates and updates with success or failure without loading the target page.

 

Debugging

I ran into some problems figuring out what Spry was doing and how some of the CSS Adobe provided was working. There are some debugging tools provided. To see the markup Spry does you can set Spry.Data.Region.debug = true in one of the header scripts. More info is available at Adobe in the SAMPLES section (ver 1.5).

<script language="JavaScript" type="text/javascript">
<!--
Spry.Data.Region.debug = true;
var dsStates = new Spry.Data.XMLDataSet("../../data/states/states.xml", "/states/state");
-->
</script>

Additionally an Adobe writer recommends "IE7pro", an add on for IE7. The add on allows one to see Spry generated code in IE7. Firefox and Firebug have tools to let you see resulting page code too.

For forms one can add a TRY block inside the onSubmit function.

<form action="processForm.asp" method="post" id="contact" name="contact" onSubmit="try{return SendForm('contact');}catch e{alert('Error' + (e.message?e.message:e));}" >

Firebug (I love it) for Firefox is a great tool for debugging. Does a nice step through with variable values showing.

You also may want to check out: Bruce Phillips' page.

Actual HTML Form Markup

<div id="emailform">
   Email Form
   <form action="validate.asp" method="post" id="contact" name="contact" onSubmit="return SendForm('contact');" >
   <fieldset>
      <legend>Contact Information</legend>
         <div id="sprytextfield1">
            <label for="name">Name:</label>
            <input name="name" type="text" id="name" accesskey="n" tabindex="1" size="40" maxlength="50">
             <div class="textfieldRequiredMsg">Required.</div>
             <div class="textfieldMinCharsMsg">Minimum characters not met.</div>
             <div class="textfieldMaxCharsMsg">Exceeded max characters.</div>
           </div>
           <div id="sprytextfield2">
              <label for="phone">Phone:</label>
              <input name="phone" type="text" id="phone" accesskey="p" tabindex="2" size="15" maxlength="15">
           </div>
           <div id="spryEmail">
              <label for="email">Email:</label>
              <input name="email" type="text" id="email" accesskey="e" tabindex="3" size="45">
              <div class="textfieldRequiredMsg">Required.</div>
              <div class="textfieldInvalidFormatMsg">Invalid format.</div>
              <div class="textfieldMaxCharsMsg">Exceeded max characters.</div>
           </div>
           <div id="sprytextfield4">
              <label for="zip">Zip:</label>
              <input type="text" name="zip" id="zip" accesskey="z" tabindex="4">
              <div class="textfieldRequiredMsg">Required.</div>
              <div class="textfieldInvalidFormatMsg">Invalid format (5 digit only).</div>
           </div>
        </fieldset>


        <fieldset><legend>Message</legend>
           <div id="sprytextfield5">
              <label for="subject">Subject:</label>
              <input name="subject" type="text" id="subject" accesskey="s" tabindex="5" size="30" maxlength="50">
              <div class="textfieldRequiredMsg">Required.</div>
              <div class="textfieldMinCharsMsg">Minimum number of characters not met.</div>
           </div>
           <div id="sprytextarea1">
              <label for="comments">Comments:</label>
              <textarea name="comments" id="comments" cols="35" rows="5" accesskey="c" tabindex="6"></textarea>
              <div class="textareaRequiredMsg">Required.</div>
              <div class="textareaMaxCharsMsg">Exceeded max characters.</div>
              <br>
              <span class="spacer">&nbsp;</span>
              <span id="countsprytextarea1" class="smallText">&nbsp;</span>
              <span class="smallText">Characters Remaining</span>
           </div>
        </fieldset>


        <fieldset><legend>Submit</legend>
        <div align="center">
        <input name="Reset" type="reset" tabindex="8">
        <input type="submit" name="button" id="button" value="Submit" tabindex="7">
        </div>
      </fieldset>
   </form>

</div>