SubjectThis tutorial illustrates how the Whitebeam 'forms' library can help you write and
manage complex form interactions in web browsers. The library primarily consists
of a set of XML tags that allow web-designers to define and manipulate the forms
they design. The following topics are covered:
Overview
The Whitebeam Form library simplifies the development of user interaction
with websites through HTML forms. The aim is not to do something that cannot be done
using the raw server-side and client-side capabilities of the environment, but
rather to apply a set of abstractions to simplify the implementation and speed
development.
The model encapsulates a set of form fragments and command handlers in a
single 'Presentation Page' (file). A simple example should illustrate the capabilities.
Consider a simple form sequence that does the following:
- Displays a list of users
- Allows the user to edit one of the users configurations.
- Allows a user to be 'removed' - with the safety check of a 'confirm' page.
In a traditional web design the forms would be designed in one page - possibly
static. A 'submit' button would direct the data to a server side programme - such
as a CGI programme. That page would apply the changes then go back to the main page.
This can be very clumsy. Take a simple sequence:
- Display Users
- User selects a user and pushes the 'remove' button
- Server sends a 'confirmation' page
- User a) confirms or b) cancels
- On confirm the user is removed and the user list is redisplayed
- On cancel we simply go back to the user list having made no changed.
Traditionally you'd implement the initial list, the confirm box and the delete action
in separate files. Most server side processing environments, including Whitebeam,
would allow you to keep all three elements in a single file. This con be complex and
at times confusing. The Whitebeam form library takes this common behaviour and makes
it much simpler by defining a set of XML tags to encapsulate and abstract away the
complexity.
Dependencies
The Whitebeam form library is implemented in a single file 'form.inc'. This
file contains all the JavaScript and tag implementations to support the form
abstraction. To use this mechanism in a file - simply include it at the start
of your file using:
<rb:include src="form" system="yes"/>
Formal Definitions
Formal definitions for the tags described in this tutorial can be found here.
Design
If you are following the Whitebeam recommended web design practices
you will already be familiar with this step. Forms and user interraction can be complex - and while it
is possible to implement a complex interraction by trial and error it is much easier if the system
is properly designed up front. As an example we're going to build on the proposal in the previous
section.
First of all some basics. Each user interraction is going to be broken into a number of 'commands'.
Each command is either the result of a user pressing a 'submit' button or clicking on a hotlink
within the displayed page. We're going to use the
design notation to describe parts of the user interaction, and each of the 'bubbles'
represents a page as seen by the user - not necessarily a separate Whitebeam presentation page. In
fact you'll see that most of the client pages are implemented as a single Presentation Page (file).
The following diagram illustrates the interraction we are going to implement:
In this diagram there are 8 logical 'pages' - but remember those with 'dotted' lines
are transient - they perform some action such as creating a member - then immediately
move on to another page. The initial page displayed to the browser is the 'Member List' -
this is the entry point for the sequence.
The Whitebeam form library encapsulates this entire sequence in a single presentation page.
Each of the 'pages' is represented as a 'command'. The set of commands that make up this
sequence are called the 'formset'. Each command has a name - these have to be unique within
the formset. The entry command to the formset has the name 'default'. The formset and
the commands are represented within the file using XML tags as follows:
<rbm:formset> <rbm:command name="default"> <!-- The 'default' command. This is the page displayed if the page contains --> </rbm:command> <rbm:command name="createform"> <!-- First stage in the create sequence - display a user creation form. --> </rbm:command> <rbm:command name="createmember"> <!-- Transient command to create a new user. --> </rbm:command> </rbm:formset>
In this example skeleton we have a formset with three actions - no implementation yet!
This would generally be the first stage of generating the form handler after
the diagram above. That is - take the diagram and create a formset with command
tags for each identified command.
The form library decides which command to load by looking for a form parameter with
a name stating with 'rbcmd-'. For example 'rbcmd-create'. 'rbcmd-' will be stripped
from the front of this leaving a string, in this example 'create'. The form library
looks for a command handler with this name.
If there are no parameters with the 'rbcmd-' prefix, or the resulting command cannot be
loaded, the library will attempt to load the command called 'default'.
Form Data
If you include 'form.inc' in your application we can be pretty sure you are going to
want to use form data of some kind. To simplify the use of this - the form library
itself extracts the form data and stores it in a special global variable called
rb$params. This means that if you want to check for a specific parameter, for example
'username', then you just have to look at rb$params.username, rather than have
to call rb.page.formdata().
Processing Command Handlers
The library will execute the selected command handler. Execution comprises the
following steps.
- Look for a 'test' attribute, if found evaluate the test expression.
- If the test passes (evaluates to true) or there is no test, run the command.
- If the test is present and fails - execute the specified error command instead.
- See if the command wants to execute another command, if so locate
the command handler and go to stage 1.
Command Test
There is often a pre-condition that must be tested before a command can be executed. Take
our membership example above. In this case, in order for the 'view member'
command to be correctly executed, the user must have selected a user on the
default page.
These tests can be performed manually within the body of the command, most commonly
using an <rb:test...> tag. This does bloat the code though - and makes the
resulting page more complex to understand. The form library 'test' facility is a
good alternative if the test is fairly straightforward.
The test facility uses 3 XML attributes on the <rbm:command...> tag:
attribute | value | comments |
---|
test | JS expression | Must evaluate to 'true' for the command to execute. If the command fails then
instead of running the command, run the command specied by 'error' attribute. | error | command name | Used if 'test' expression evaluates to false. Name of an alternative command
to execute. | msg | Error Message | Used if test evaluates to false. Stores the value part in the special Whitebeam
variable called rb$message before executing the error command handler. This
attribute is optional. |
This seems simple - but can make for very easy form error checking that can cover a multitude
of error recovery situations. Again - take the membership example. In this example, all the commands
except create need to have the identification of the member on which to operate (who are
we going to edit/view/delete?). We decide we're always going to pass this through as a
parameter called 'memberid'. If the member is not specified then we simply want to go back
to the member list - but also display an error message telling the user to first select
a member.
With the Whitebeam form library this is very easy. Remember from the previous section
the form data is already stored in a global variable, rb$params. To check for the 'memberid'
parameter we simply write our rbm:command tag as follows:
<rbm:command test="rb$params.memberid!=null" error="default" msg="First select a member">
[body - skipped if the test fails.]...
</rbm:command>
If the test fails the body of the command is not executed, instead the default command
is executed and the rb$message JavaScript variable is set to contain an error message. More
complex tests can be performed by defining a validation function and calling that from the
'test' expression - having the function return 'false' of validation fails.
Command SequencesAs previously described the form library will select a command handler basic on the 'rbcmd-'
parameters passed from the client. Some of these command will display information on the browser
and wait for the user to take some action - a user event. Other commands thought may take some internal action - consider the command we execute when the
user has entered new member information and pressed the 'OK' button. The handler must take the form
information presented - validate that information then either@
- If the data is valid - create the new member - then display the list of members once more.
- If there are errors in the data - such as missing mandatory fields - then show the form again,
telling the user what errors they have made.
Obviously the 'create' handler could do all this, but the member list is already handled by the 'default'
command and the form is already presented by the 'createUser' command. These need to be reused to avoid
duplication. In effect the create code wants to redirect execution to another command. Ideally the create
command will look like:
<rbm:command name="createMember">
<rb:script>
if (doValidateParams(rb$params)) {
// Its OK to try and create the member.
var aMember = new Member(rb$params);
aMember.create();
rb$command = 'default';
}
else {
// Validation failed.
rb$command = 'showCreateForm';
}
</rb:script>
</rbm:command>
Note that this is not intended to be complete - the parameters and the creation result should check to make sure it happend.
You can however see that one command handler passes control to another simply by setting the value of rb$command before
returning. In this way an entire sequence of handlers can be executed.
Note that setting thiw propertry Notes- Commands can redirect to themselves - or sequences of command could in theory get
stuck in infinte loops. To avoid this the Presentation Engine will not execute more than 5
commands before requiring a user event (basically a submit or click on a URL).
- Automatically passing control using rb$command does not cause any intervention from the
browser and the operational environment (parameters etc) are not changed.
Displaying and Manipulating the FormsSome of the commands in the form set will want to display real HTML forms -
showing data and allowing input from the user. These forms can be implemented
in exactly the same way as standard HTML forms - using HTML tags. The Whitebeam
Form Library on the other hand provides some facilities to make this a lot simpler.
This additional behaviour is provided by enhancing the standard form tags
using server-side processing before they reach the clients browser. The
following sections describe the facilities available.
Initialising Form FieldsThe following HTML creates a very simple form containing two text elements: <form action="membadmin.rhtm" method="get"> <table class="pretty"> <tr> <td>Title</td> <td><input type="text" size="10" name="title"/></td> <tr> <tr> <td>First Name</td> <td><input type="text" size="50" name="first_name"/></td> </tr> <tr> <td>Surname</td> <td><input type="text" size="50" name="surname"/></td> </tr> <tr> <td colspan="2"> <input type="submit" name="rbcmd-apply" value="OK" /> <input type="submit" name="rbcmd-cancel" value="cancel" /> </td> </tr> </table> </form>
This form displays three edit boxes along with two buttons. This basic
skeleton could be used both for a 'create member' and a 'edit member'
type form. However - in the latter, it would have to be modified to contain
the existing values. This is achieved in HTML using the 'value' attribute
to the input tag.
So to create both forms there would usually be two instances of this HTML
in the file: one for the create form and a more complex for the edit
form. The edit form would be required to load its data from some dynamic
JavaScript variable containing the members information, something like: <form action="membadmin.rhtm" method="get"> <table> <tr> <td>Title</td> <td><input type="text" size="10" name="title" rb:eval="value#aMember.title"/></td> <tr> <tr> <td>First Name</td> <td><input type="text" size="50" name="first_name" rb:eval="value#aMember.first_name"/></td> </tr> <tr> <td>First Name</td> <td><input type="text" size="50" name="surname" rb:eval="value#aMember.surname"/></td> </tr> <tr> <td colspan="2"> <input type="submit" name="rbcmd-apply" value="OK" /> <input type="submit" name="rbcmd-cancel" value="cancel" /> </td> </tr> </table> </form>
This becomes cumbersome, especially if you consider the case where some (or all)
fields may not be present. To simplify this process the forms library allows you
to specify a data source before creating the form. The library then searches this
JavaScript object for values matching the field name. If found the library automatically
create a value clause for that item. To use this facility you must take the following steps: - Set rb$data to reference something containing initialisation parameters for the form items.
- Make sure the <input...> name attribute matches the field name in rb$data.
So the simplification of the form above is as follows: <rb:script> rb$data = aMember; </rb:script>
<form action="membadmin.rhtm" method="get"> <table> <tr> <td>Title</td> <td><input type="text" size="10" name="title"/></td> <tr> <tr> <td>First Name</td> <td><input type="text" size="50" name="first_name"/></td> </tr> <tr> <td>First Name</td> <td><input type="text" size="50" name="surname"/></td> </tr> <tr> <td colspan="2"> <input type="submit" name="rbcmd-apply" value="OK" /> <input type="submit" name="rbcmd-cancel" value="cancel" /> </td> </tr> </table> </form>
Which coincidentally is the same as the format for an empty form - so including the form in an
rb:block tag and using it in both places becomes possible. For the create form, set rb$data to null
before including the block.
Highlighting Errors in Form EntryWe can now fairly simply generate a form preloaded with data and accept the submit
of that data back into the browser via the command model. The first thing the submit handler
does is to validate the information entered by the user. There remains the problem of
what to do if the validataion fails - for example because the name contains illegal characters.
Ideally we would display the form again, this time with an error message and with the incorrect field
highlighted. The form library supports the marking of individual fields in a form as it is constructed. It does
this by looking at a JavaScript variable called rb$errors for a property name matching the
field name. If it is present and the value is 'true' then this field is considered in need of highlighting
and the library will show the item with a different background colour. By default this is a light blue,
but the colour can be changed to storing the appropriate string in rb$errorColour. At the moment the colour of the text in a highlighted field is not modified so choose
a highlighting colour appropriate to the text colour!
Static DisplayIt is sometimes useful to be able to display a form as static text - i.e. use the same fields
as a defined form - but simply display the values rather than include edit boxes. The standard
method would be to use separate HTML along with server-side rb:evals to display the static data. Instead - if a form has been defined that contains the necessary definitions, then the form
library allows the form to be display as 'static' (i.e. not a form) data. To do this simply set the
rb$static JavaScript variable to 'true' before displaying the form.
ExampleThis example combines all the elements described above to implement a user create/edit/view system.
Note that in this example the code for the default command has been omitted, as have several command handlers. <rb:script> rb$data = aMember; </rb:script>
<!-- Define the form in a block so it can be reused. --> <rb:block rb:id="form"> <tr> <td>Title</td> <td><input type="text" size="10" name="title"/></td> <tr> <tr> <td>First Name</td> <td><input type="text" size="50" name="first_name"/></td> </tr> <tr> <td>First Name</td> <td><input type="text" size="50" name="surname"/></td> </tr> </rb:id>
<!-- Clear the rb$data field. --> <rb:script> // Make sure there is no data laying around. rb$data = null; </rb:script>
<!-- Collection of commands. --> <rbm:formset>
<!-- Display member details in a table.. --> <rbm:command name="view"> <form action="membadmin.rhtm" method="get"> <table> <rb:script> // Make it static. rb$static = true;
// Output the form var tags=rb.xml.tree("form"); tags.regenerate(); rb.page.write(tags.bodytext()); </rb:script> <tr> <td colspan="2"> <!-- Just a cancel button here. --> <input type="submit" name="rbcmd-cancel" value="cancel" /> </td> </tr> </table> </form> </rbm:command>
<!-- Display a table containing editable field values. --> <rbm:command name="createForm"> <form action="membadmin.rhtm" method="get"> <table> <rb:script> // Output the form var tags=rb.xml.tree("form"); tags.regenerate(); rb.page.write(tags.bodytext()); </rb:script> <tr> <td colspan="2"> <!-- Just a cancel button here. --> <input type="submit" name="rbcmd-do_create" value="Create" /> <input type="submit" name="rbcmd-cancel" value="cancel" /> </td> </tr> </table> </form> </rbm:command>
<!-- Display a table containing editable field values, preloaded from the currently selected member. --> <rbm:command name="editForm"> <form action="membadmin.rhtm" method="get"> <table> <rb:script> // rb$data needs to reference my member // data I read from the membership component. rb$data = aMember;
// Output the form var tags=rb.xml.tree("form"); tags.regenerate(); rb.page.write(tags.bodytext()); </rb:script> <tr> <td colspan="2"> <!-- Just a cancel button here. --> <input type="submit" name="rbcmd-do_edit" value="Apply" /> <input type="submit" name="rbcmd-cancel" value="cancel" /> </td> </tr> </table> </form> </rbm:command>
<!-- When the button is actually pressed. --> <rbm:command name="do_create"> <rb:script> // There MUST e a unique name field and an email address in the form data. var error = false;
if (rb$params.uName==null) { // No uName entered. rb$error.uName = true; error = true; } if (rb$params.email==null) { // No uName entered. rb$error.email = true; error = true; } // Any errors detected? if (error) { // YES - go back and display the create form again, // but this time load the form with the values entered by the // user last time around to save having them re-entered. rb$command="createForm" } else { // Create the new member. aMember.create();
// Now go and display the default page again. rb$command = 'default'; } </rb:script> </rbm:command> </rbm:formset>
Download form libraryThe form library in encapsulated in a single library file that you include in your
Presentation Pages as follows : <rb:include src="/include.inc"/>
(change the path if you store the file elsewhere!) |