forms-lite and data models

A key goal for the forms-lite testbed [1] was to work on a wide 
variety of current web browsers. The next decision was whether to 
make use of the built-in submission mechanism or to come up with 
something else based upon XmlHttpRequest (Ajax). I found a couple of 
disadvantages for using Ajax. The first is that it is hard to fully 
emulate the conventional semantics for submit, where the current 
page is pushed onto the browser history and replaced by a new page 
sent by the server. The second is that you couldn't support file 
upload as part of the form's data. The Web API working group has 
recently published a new working draft for uploading files from web 
page scripts, but I don't believe it is widely supported as yet. I 
therefore decided to focus initially on using the built in support 
for form submission.

The HTML forms data model can be described as a set of named forms
where each form has a collection of named fields. The HTML DOM is
(mostly) defined in the W3C DOM2 Recommendation [2]

    document.forms[0] is the first form

You can access the field by its name, e.g.

   document.forms[0].x for the field named x

Fields have properties, e.g. name, type, value, checked, readonly, 
and disabled.

If there is more than one instance of a field with the same name 
then the "form.name" evaluates to an array of the instances. This
is particularly pertinent to radio buttons (type is "radio"), but 
also applies to other types and this can be taken advantage of when 
you want to make use of repeating sets of fields.

The value of a field is generally given by its value attribute
with exceptions for radio buttons and checkboxes. For checkboxes
the checked property is a boolean that indicates whether or not
the checkbox is ticked. The field's value is only included in the
submitted data if checked is true. For radio buttons, you need
to iterate through the array of fields with the same name to find
which one has checked equal to true. The value for this field is
then included with the submitted data. If the disabled property
is true then the field's value won't be included in the submitted
data -- this overrides the checked property.

When it comes to expressions provided by attribute values set on
particular fields, then the form associated with the field provides
a scope for resolving references to other fields. Forms-lite
lets you write the following:

   <input name="x" type="number"/>
   <input name="y" type="number"/>
   <input name="z" type="number" calculate="x+y"/>

where x, y and z are all part of the same form. The type of the 
field is important to how the expression is evaluated. In 
particular, ECMAScript defines the + operator to behave differently 
according to the types of its arguments. On strings it acts as a 
concatenator, whilst on numbers it adds them.

ECMAScript also supports the notion of nested scopes. Thus locally
defined variables take precedent over globally defined variables.
Forms-lite takes advantage of this to allow you to refer to
global variables and functions from within expressions. Names of
fields for the current form take precedence over global variables.

Forms-lite allows you to define a repeating set of fields. This
is analogous to a set of rows in a spreadsheet, where the field
name identifies the column, and a row index identifies the row.

For expressions defined on fields within repeating set of fields,
the scope is the current row, then the set of non repeating
fields for the current form, and then the global scope. The
expressions never include a row index. Thus in the above example,
all three fields could be part of a repeating fieldset, and
correspond to a set of rows, where in each row the value of
z is the sum of x and y.

Forms-lite also allows you to write expressions that are
evaluated over a set of rows. So far I have implemented
two such second order functions:

  count(name)
     Where name identifies a checkbox in a repeating set
     of fields. This function counts the number of ticked
     checkboxes in the column.

  sumover(name, expr)
     Where name identifies a repeating set of fields. This
     function sums the result of evaluating the expression
     expr over each row in the repeating set of fields.

Forms-lite provides a mechanism for defining repeating sets of
fields using the HTML fieldset element as a container for the
set of fields to be repeated. The fieldset element defines a
scope, although this isn't reflected in the HTML DOM directly.
The count function described above should perhaps take the
name of a fieldset and use that to restrict its scope to just
the checkboxes contained by that fieldset.

Repeating sets of fields and radio buttons don't mix well in the 
HTML DOM. This is because the group of radio buttons is represented 
in the DOM as an array of fields. This makes it impractical to 
include radio buttons in a repeated fieldset element. The work 
around is to use an HTML select element instead.

Another issue with repeated fieldsets that are generated dynamically 
relates with how web browsers support the "back" and "reload" 
operations. When you move away from a page, the browser saves the 
current state of the form as part of the history. When you go back 
to that page, the browser restores that state based upon the page's 
markup. If a repeating set of fields is generated dynamically as a 
result of the page's onload event, only the values for the first row 
will be restored. Forms-lite deals with this problem by clearing the 
state of such repeating fieldsets.

Ideally, the DOM would provide a means for the script to access the 
state recorded in the history as that would then allow the state of 
all rows to be restored. This is something that the Web API working 
group could perhaps be asked to provide as an extension to the DOM 
for the browser history.

Some weird things I discovered:

  - On IE you need to use the htmlFor property when you want the
    the value of the for attribute on the label element

  - On IE you need to use the className property when you want
    to get or set the value of the class attribute. Luckily
    most other browsers also support className for this purpose

  - On Opera, you can't access the legend element within a fieldset
    element by traversing the DOM tree. You can however access it
    via calling getElementsByTagName("legend") on the fieldset.

  - Some browsers will fall back to the name attribute if the id
    attribute is mising on the input element when it comes to
    binding a label via its for attribute

  - Some browsers will include the fieldset element as part of
    the form when you provide a name attribute on the fieldset.

  - Opera coerces the value of the type attribute returned with
    getAttribute to "text" if Opera doesn't recognize the type.

There needs to be a well defined interoperable way to identify
whether a browser has a native implementation so that you can
take advantage of that rather than relying on interpreting the
attributes via a script library.

Native implementations may have weaknesses, so that you may still 
want to use a script library to define the behavior, to ensure that 
end-users get the same behavior on all browsers. It would be 
desirable to have a well defined way to disable the native 
implementation in such cases.

In HTML, the form element encloses the fields that belong to it.
This is occasionally inconvenient, and it would be desirable to
be able to instead use an attribute to reference the form by
name. I have yet to experiment with this and to determine whether
this can be made to work on existing browsers. It would involve
inserting the fields into the form's object model via a script,
but whether the submit mechanism would work in that case needs
experimental verification.

[1] http://people.w3.org/~dsr/forms-lite/
[2] http://www.w3.org/TR/DOM-Level-2-HTML


  Dave Raggett <dsr@w3.org> http://www.w3.org/People/Raggett

Received on Tuesday, 24 October 2006 12:06:15 UTC