Improving the Web Forms Experience
A CHI 2004 Tutorial
Steven Pemberton
CWI and W3C
Kruislaan 413
1098 SJ Amsterdam
The Netherlands
Steven.Pemberton@cwi.nl
www.cwi.nl/~steven
The day is split into four blocks, each of 90 minutes.
Each block consists of about 45 minutes lecture, followed by 45 minutes practical.
The breaks between blocks are 30 minutes, with 90 minutes for lunch.
Introduction, equivalents for HTML features.
Break
Types, bindings and constraints.
Lunch
Instance data, XPath and submission.
Break
Events, wizards and shopping baskets.
Steven Pemberton is a researcher at the CWI, The Centre for Mathematics and Computer Science, a nationally-funded research centre in Amsterdam, The Netherlands, the first non-military Internet site in Europe.
Steven's research is in interaction, and how the underlying software architecture can support the user. At the end of the 80's he built a style-sheet based hypertext system called Views.
Steven has been involved with the World Wide Web since the beginning. He organised two workshops at the first World Wide Web Conference in 1994, chaired the first W3C Style Sheets workshop, and the first W3C Internationalisation workshop. He was a member of the CSS Working Group from its start, and is a long-time member (now chair) of the HTML Working Group, and co-chair of the XForms Working Group. He is co-author of (amongst other things) HTML 4, CSS, XHTML and XForms.
Steven is also Editor-in-Chief of ACM/interactions.
HTML Forms, introduced in 1993, were the basis of the e-commerce revolution. After 10 years experience, it has become clear how to improve on them, for the end user, the author, and the owners of the services that the forms are addressing. XForms is a new technology, announced in October 2003, intended to replace HTML Forms.
The advantages of XForms include:
The presenter is one of the authors of the XForms specifications, and is chair of the Forms Working Group that produced the technology.
This tutorial works from a basis of HTML Forms, and introduces XForms step-by-step. It covers essentially all of XForms except some technical details about events, and no more than a passing reference to the use of Schemas.
Emphasis is on how to improve the user experience, and how XForms improves accessibility and device independence, and makes the author’s life easy in producing a better experience.
Soundbite: "Javascript accounts for 90% of our headaches in complex forms, and is extremely brittle and unmaintainable."
Take this simple HTML form:
<html> <head><title>Search</title></head> <body> <form action="http://example.com/search" method="get"> Find <input type="text" name="q"> <input type="submit" value="Go"> </form> </body> </html>
The main difference in XForms is that details of the values collected and
how to submit them are gathered in the head, in an element called
model
; only the form controls are put in the body.
So in this case the minimum you need in the head is (XForms elements and attributes are in lower case):
<model> <submission action="http://example.com/search" method="get" id="s"/> </model>
The <form>
element is now no longer needed; the
controls in the body look like this:
<input ref="q"><label>Find</label></input> <submit submission="s"> <label>Go</label> </submit>
What you can hopefully work out from this is that form controls have a
<label>
element as child, the <input>
uses "ref
" instead of "name
", and there is a
separate submit
control that links to the details of the
submission in the head. So the complete example is:
<h:html xmlns:h="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/2002/xforms"> <h:head> <h:title>Search</h:title> <model> <submission action="http://example.com/search" method="get" id="s"/> </model> </h:head> <h:body> <h:p> <input ref="q"><label>Find</label></input> <submit submission="s"><label>Go</label> </submit> </h:p> </h:body></h:html>
h:
prefixes<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://www.w3.org/2002/xforms"> <head><title>Search</title> <f:model> <f:submission method="get" id="s" action="http://example.com/search"/> </f:model> </head> <body> <p><f:input ref="q"> <f:label>Find</f:label> </f:input> <f:submit submission="s"> <f:label>Go</f:label> </f:submit> </p> </body></html>
h:
or x:
or
html:
or form:
, or ...<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://www.w3.org/2002/xforms"> <head> <object width="0" height="0" id="FormsPlayer" classid="CLSID:4D0ABA11-C5F0-4478-991A-375C4B648F58"> <strong>FormsPlayer failed to load</strong> </object> <?import namespace="f" implementation="#FormsPlayer"?>
Now to compare one for one HTML forms controls with XForms equivalents
To input a single text element
First name: <input type="text" name="fname">
is written
<input ref="fname"><label>First name:</label> </input>
There is no need to indicate that it is text: in the absence of any other
information, by default it is text (called string
in XForms).
We will see later how to give any control an initial value.
To input multiline text
Message: <textarea name="message" rows="20" cols="80"> </textarea>
is written
<textarea ref="message"><label>Message:</label> </textarea>
Styling is done using a style sheet. For instance:
textarea[ref="message"] { font-family: sans-serif; height: 20em; width: 80em }
or
textarea[ref="message"] { font-family: serif; height: 2cm; width: 20% }
If you want all your textareas to have the same dimensions, you can use
textarea { font-family: sans-serif; height: 20em; width: 80em }
The easiest way to include a style sheet in your document is to add this at the beginning of the document:
<?xml version="1.0"?> <?xml-stylesheet href="style.css" type="text/css"?>
where 'style.css' is the name of your stylesheet, although in XHTML you can also say in the <head>:
<link rel="stylesheet" type="text/css" href="style.css"/>
Radio buttons select one value from a set:
Gender: <input type="radio" name="sex" value="M"> Male <input type="radio" name="sex" value="F"> Female
becomes
<select1 ref="sex"> <label>Gender:</label> <item> <label>Male</label><value>M</value> </item> <item> <label>Female</label><value>F</value> </item> </select1>
select
and select1
may be presented as radio
buttons, a (scrollable) select area, or a menu.appearance="full"
to suggest presentation as
radio buttons.appearance="compact"
to suggest a select areaappearance="minimal"
to suggest a menuWe will see later how to preselect a value.
HTML Checkboxes select zero or more from a list.
Flavors: <input type="checkbox" name="flavors" value="v"> Vanilla <input type="checkbox" name="flavors" value="s"> Strawberry <input type="checkbox" name="flavors" value="c"> Chocolate
is written
<select ref="flavors" appearance="full"> <label>Flavors:</label> <item> <label>Vanilla</label><value>v</value> </item> <item> <label>Strawberry</label><value>s</value> </item> <item> <label>Chocolate</label><value>c</value> </item> </select>
Depending on the presence of the multiple
attribute in HTML,
menus select one, or zero or more from a list of options. You either use
<select1>
to select a single choice, or
<select>
to select zero or more.
Month: <select multiple name="spring"> <option value="Mar">March</option> <option value="Apr">April</option> <option>May</option> </select>
would be written:
<select ref="spring" appearance="compact"> <label>Month:</label> <item> <label>March</label><value>Mar</value> </item> <item> <label>April</label><value>Apr</value> </item> <item> <label>May</label><value>May</value> </item> </select>
If multiple
isn't on the HTML select
, use
select1
instead.
To select a file to be uploaded
<form method="post" enctype="multipart/form-data" ...> ... File: <input type="file" name="attachment">
is written
<submission method="form-data-post" .../> ... <upload ref="attachment"> <label>File:</label> </upload>
Password: <input type="password" name="pw">
is written
<secret ref="pw"> <label>Password:</label> </secret>
<input type="reset">
is therefore written
<trigger> <label>Clear all fields</label> <reset ev:event="DOMActivate"/> </trigger>
Buttons have no predefined behavior, but have a behavior attached to them which is triggered when a relevant event occurs.
The button element
<input type="button" value="Show" onclick="show()">
can be written
<trigger><label>Show</label> <h:script ev:event="DOMActivate" type="text/javascript">show() </h:script> </trigger>
or
<trigger ev:event="DOMActivate" ev:handler="#show"> <label>Show</label> </trigger>
where "#show
" locates the element (for instance a
script
element) that implements the behavior:
<script id="show" ...>...
XForms has a number of built in actions that can be executed by a button; see the reset button above for an example.
The fact that the event
attribute has a prefix, means that
you have to add the following XML Namespace to the head:
xmlns:ev="http://www.w3.org/2001/xml-events"
We will be dealing more with events later.
<input type="image" src="..." ...>
is written by putting an image into the <label> element:
<trigger...><label><h:img src="star.gif" .../> </label></trigger>
or by specifying it in a stylesheet
<trigger id="activate" ...>
with a stylesheet rule
trigger#activate { background-image: url(button.png); background-repeat: none}
(Likewise for
<submit>
.)
Drink: <select name="drink"> <option selected value="none">None</option> <optgroup label="Soft drinks"> <option value="h2o">Water</option> <option value="m">Milk</option> <option value="oj">Juice</option> </optgroup> <optgroup label="Wine and beer"> <option value="rw">Red Wine</option> <option value="ww">White Wine</option> <option value="b">Beer</option> </optgroup> </select>
is written
<select1 ref="drink"> <label>Drink:</label> <item><label>None</label><value>none</value></item> <choices> <label>Soft drinks</label> <item><label>Water</label><value>h2o</value></item> <item><label>Milk</label><value>m</value></item> <item><label>Juice</label><value>oj</value></item> </choices> <choices> <label>Wine and beer</label> <item><label>Red wine</label><value>rw</value></item> <item><label>White wine</label><value>ww</value></item> <item><label>Beer</label><value>b</value></item> </choices> </select1>
<fieldset> <legend>Personal Information</legend> Last Name: <input name="lastname" type="text"> First Name: <input name="firstname" type="text"> Address: <input name="address" type="text"> </fieldset>
is written
<group> <label>Personal Information</label> <input ref="lastname"><label>Last name:</label></input> <input ref="firstname"><label>First name:</label></input> <input ref="address"><label>Address:</label></input> </group>
Note the consistent use of <label>
.
As you will see shortly, there is no need for hidden controls in XForms.
Take the file practical1.html and view it in your browser. Now create an equivalent XForm
You will find a template file template.xfm
that you can copy
to start from
XForms has two controls that are not in HTML, output
and
range
.
The output
control allows you to include values as text in
the document.
Your current total is: <output ref="sum"/>
or
<output ref="sum"><label>Total</label></output>
This can be used to allow the user to preview values being submitted.
You can also calculate values:
Total volume: <output value="height * width * depth"/>
(where height
, width
and depth
are
values collected by other controls.)
This control allows you to specify a constraint on a value.
<range ref="volume" start="1" end="10" step="0.5"/>
A user agent may represent this as a slider or similar.
ref
on each control actually refers to
a child of an instance
element in the model, where the
values are gathered before submission.It is good practice to include an explicit instance, like this for the search example:
<model> <instance> <data xmlns=""><q/></data> </instance> <submission action="http://example.com/search" method="get" id="s"/> </model> ... <input ref="q"> <label>Search</label> </input>
ref="q"
that there really is a q
in the instance.xmlns=""
on your instance
data, to tell the processor that the elements here are neither XHTML nor
XForms elements.<data>
here, but you can choose
any tag you like.For initialising controls including initialising checked boxes, and selected menu items etc., you just supply an instance with pre-filled values. For the search example:
<instance> <data xmlns=""><q>Keywords</q></data> </instance>
would pre-fill the text control with the word Keywords.
<select ref="flavors"> <label>Flavors:</label> <item> <label>Vanilla</label><value>v</value> </item> <item> <label>Strawberry</label><value>s</value> </item> <item> <label>Chocolate</label><value>c</value> </item> </select>
You can preselect vanilla and strawberry like this:
<instance> <data xmlns=""><flavors>v s</flavors></data> </instance>
Similarly for the menus example, which looked like this:
<select ref="spring"> <label>Month:</label> <item><label>March</label><value>Mar</value></item> <item><label>April</label><value>Apr</value></item> <item><label>May</label><value>May</value></item> </select>
You can preselect March
and April
like this:
<instance> <data xmlns=""><spring>Mar Apr</spring></data> </instance>
And for the optgroup
example:
<select1 ref="drink"> <label>Drink:</label> <item><label>None</label><value>none</value></item> <choices> <label>Soft drinks</label> <item><label>Water</label><value>h2o</value></item> <item><label>Milk</label><value>m</value></item> <item><label>Juice</label><value>oj</value></item> </choices> <choices> <label>Wine and beer</label> <item><label>Red wine</label><value>rw</value></item> <item><label>White wine</label><value>ww</value></item> <item><label>Beer</label><value>b</value></item> </choices> </select1>
Preselect the value none
like this:
<instance> <data xmlns=""><drink>none</drink></data> </instance>
results
to the search form, we
change the instance to:<instance> <data xmlns=""> <q/> <results>10</results> </data> </instance>
<instance src="http://example.org/templates/t21.xml"/>
<data><w>640</w><h>480</h><d>8</d></data>
xmlns=""
in external instances, though
it doesn't do any harm either.src="
file:data.xml"
ref
attribute can be any XPath expression<title>
element in an
XHTML document
<input ref="h:html/h:head/h:title">...(i.e. the
title
element within the head
element
within the html
element, all in the XHTML namespace)class
attribute on the body
element:
<input ref="h:html/h:body/@class">...
Suppose a shop has very unpredictable opening hours (perhaps it depends on the weather), and they want to have a Web page that people can go to to see if it is open. Suppose the page in question has a single paragraph in the body:
<p>The shop is <strong>closed</strong> today.</p>
Well, rather than teaching the shop staff how to write HTML to update this, we can make a simple form to edit the page instead:
<model> <instance src="http://www.example.com/shop.xhtml"/> <submission action="http://www.example.com/shop.xhtml" method="put" id="change"/> </model ... <select1 ref="/h:html/h:body/h:p/h:strong"> <label>The shop is now:</label> <item><label>Open</label><value>open</value></item> <item><label>Closed</label><value>closed</value></item> </select1> <submit submission="change"><label>OK</label></submit>
XPath selectors look like filename selectors
employees/person[1]
employees/person[position()=last()]
tutorial[name="xforms"]/tutor
We shall now look at details of submission, like multiple submissions, submission methods, and what happens after submission.
<model> <instance><data xmlns=""><q/></data></instance> <submission id="com" action="http://example.com/search" method="get"/> <submission id="org" action="http://example.org/search" method="get"/> </model>
and then in the body:
<input ref="q"><label>Find:</label></input> <submit submission="org"> <label>Search example.org</label> </submit> <submit submission="com"> <label>Search example.com</label> </submit>
Find:
method
and enctype
method
onlyHTML | XForms |
---|---|
method="get" | method="get" |
method="post" enctype="application/x-www-form-urlencoded" |
method="urlencoded-post" |
method="post" enctype="multipart/form-data" |
method="form-data-post" |
method="post"
: posts the results as XML
method="put":
puts the results as XML.<submission action="file:results.xml" method="put"/>which saves your results to the local filestore by using the
file:
scheme.replace
on the submission
element.replace="instance"
replaces only the instancereplace="none"
leaves the form document as-is without
replacing it.<model> <instance><data xmlns=""> <accountnumber/><name/><address/> </data></instance> <submission method="get" action="http://example.com/prefill" id="prefill" replace="instance"/> <submission method="get" action="http://example.com/change" id="change" replace="none"/> </model> ... <input ref="accountnumber"><label>Account Number</label></input> <submit submission="prefill"><label>Find</label></submit> <input ref="name"><label>Name</label></input> <textarea ref="address"><label>Address</label></textarea> <submit submission="change"><label>Submit</label></submit>
The 'model binding' properties that you can control are:
Note that in XForms it is the collected value that has the property, not the control, but the property shows up on all controls bound to the value.
These properties use a <bind>
element that goes in the
<model>
. To use bind
, you must have an
explicit <instance>
element.
To disable controls you use the relevant
property. For
instance, to say that the credit card number only needs to be filled in if
the person is paying by credit, you can write:
<model> <instance><data xmlns=""> <amount/><method/><cc/><expires/> </data></instance> <bind nodeset="cc" relevant="../method='credit'"/> <bind nodeset="expires" relevant="../method='credit'"/> </model>
cc
and expires
are only relevant when method
has the value
credit
, and will therefore be disabled for other values of
method
.../method
" rather than just
method
, because in a bind
you are talking about
the thing referred to in the nodeset
(which might be a
structured element itself).method
", it would refer to a child
element of cc
or expires
./data/method
,
which would have the same effect as ../method
in this
case.The controls could be written like this (but note that there is no indication that they may get disabled: that is inherited from the value they refer to):
<select1 ref="method"> <label>Method of payment:</label> <item> <label>Cash</label> <value>cash</value> </item> <item> <label>Credit card</label> <value>credit</value> </item> </select1> <input ref="cc"><label>Card number:</label></input> <input ref="expires"><label>Expiry date:</label></input>
If we used a structured instance, we could simplify this:
<model> <instance><data xmlns=""> <amount/><method/> <cc> <number/><expires/> </cc> </data></instance> <bind nodeset="cc" relevant="../method='credit'"/> </model>
and the controls then reference the children of 'cc
':
<input ref="cc/number"><label>Card number:</label></input> <input ref="cc/expires"><label>Expiry date:</label></input>
Instead of:
<input ref="cc/number"><label>Card number:</label></input> <input ref="cc/expires"><label>Expiry date:</label></input>
grouping can be used to reset the context of the ref
s:
<group ref="cc"> <input ref="number"><label>Card number:</label></input> <input ref="expires"><label>Expiry date:</label></input> </group>
Although putting a ref on a trigger has no effect on the instance value being referred to, the relevance of the value can be used to affect the trigger:
<trigger ref="nextok"> <label>Next</label> ... </trigger>
Similarly to relevant
, you can specify a condition under
which a value is read-only. For instance:
<model> <instance><data xmlns=""> <variant>basic</variant> <color>black</color> </data></instance> <bind nodeset="color" readonly="../variant='basic'"/> </model>
This example says that the default value of color
is
black
, and can't be changed if variant
has the
value basic
.
A useful new feature in XForms is the ability to state that a value must be supplied before the form is submitted.
The simplest case is just to say that a value is always required. For instance, with the search example:
<model> <instance><data xmlns=""><q/></data></instance> <bind nodeset="q" required="true()"/> <submission .../> </model>
but like the readonly
and relevant
attributes,
you can use any XPath expression to make a value conditionally required:
<bind nodeset="state" required="../country='USA'"/>
which says that the value for state
is required when the
value for country
is "USA
".
It is up to the browser to decide how to tell you that a value is required, but it may also allow you to define it in a stylesheet.
This property allows you to add extra constraints to a value. For instance:
<bind nodeset="year" constraint=". > 1970"/>
constrains the year to be after 1970.
Note the XPath use of "." to mean "this value".
">" has to be written as > because of XML rules, but you should be used to that already.
It is possible to indicate that a value in the instance is calculated from other values. For instance:
<bind ref="volume" calculate="../height * ../width * ../depth"/>
When a value is calculated like this, it automatically becomes
readonly
.
There are a number of functions available, including:
<bind nodeset="taxrate" calculate="if(../salary > 50000, 50, 33)"/>
<bind nodeset="q" type="xsd:integer"/>
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
to the root
element.<bind nodeset="homepage" type="xsd:anyURI"/>
There are a number of useful built-in types you can use, including:
select
)If you have several binds referring to the same value, you can combine them:
<bind nodeset="q" type="xsd:integer" required="true()"/>
id
attribute on each model, and a
model
attribute on each control:<model id="search"> <instance><data xmlns=""><q/></data></instance> <submission id="s" .../> </model> <model id="login"> <instance><data xmlns=""><user/><passwd/></data></instance> <submission id="l" .../> </model> ... <input model="search" ref="q"><label>Find</label></input> <submit submission="s"><label>Go</label></submit> ... <input model="login" ref="user"><label>User name</label></input> <secret model="login" ref="passwd"><label>Password</label></input> <submit submission="l"><label>Log in</label></submit>
<model> <instance id="currencies"> <currencies> <currency name="USD">125</currency> ... </instance> <instance id="costs"> <item> <date/><amount/><currency/> ... </item> </instance> </model> ... <input ref="instance('costs')/date"> <label>Date</date> </input>
<model> <instance id="tax" src="/finance/taxes"/> <instance> <employee xmlns=""> <name/><number/> <salary/><taxrate/> ... </employee> </instance> <bind nodeset="taxrate" calculate="if(../salary > instance('tax')/limit, 50, 33)"/>
bind
in the model, you can refer to that
from the control instead of directly to the instance value.<model> <instance><data xmlns=""><q/></data></instance> <submission id="s" .../> <bind id="query" nodeset="q" required="true()"/> </model> ... <input bind="query"><label>Find</label></input>
bind
attribute is a reference to an
id
on a bind
element; it is not an XPath
expression.<input type="submit" onclick="verify(); return true;">
says that if the <input>
element (or any of its
children) gets the click
event, then the piece of code in
the onclick
attribute is performed.
<a href="..." onclick="...">A <em>very</em> nice place to go</a>
you want the onclick
to be performed even if the click
actually happens on the <em>
element.
XML Events specifies the relationship between the event, observer and handler in a different way: (HTML example)
<input type="button"> <script ev:event="DOMActivate" type="text/javascript"> DoSomething(); </script> </input>
<script>
element is a handler
for the event DOMActivate
and in the
absence of any other information, the parent element is the
observer (<input>
in this case).This approach allows you to specify handlers for different scripting languages: (HTML example)
<input type="button"> <script ev:event="DOMActivate" type="text/javascript"> ... </script> <script ev:event="DOMActivate" type="text/vbs"> ... </script> </input>
This approach allows you to specify handlers for different events: (HTML example)
<input type="button"> <script ev:event="event1" type="text/javascript"> ... </script> <script ev:event="event2" type="text/javascript"> ... </script> </input>
<trigger> <label>Clear all fields</label> <reset ev:event="DOMActivate"/> </trigger>
<setvalue ref="total" value="0"/>
<send submission="s1"/>
<message>Done!</message>
level="ephemeral": hover style
level="modeless": window style
level="modal": "OK" style
<setfocus control="inputdate"/>
<action> <setvalue .../> <setvalue .../> </action>
<load resource="doc.html" show="new"/>
or
<load ref="homeurl" show="..."/>
<dispatch name="DOMActivate" target="btn1"/>
All forms controls have, as well as a <label> element, also <help>, <hint> and <alert>.
<input ref="return"> <label>Return</label> <alert>Must be a date later than today</alert> </input>
There are very many events you can catch in XForms, including initialisation events, error notifications, values changing, validity changing, and submission done.
<submission id="save" action="file:results.xml" method="put" replace="none"> <message ev:event="xforms-submit-done"> Saved! </message> </submission> ... <submit submission="save"> <label>Save</label> </submit>
One way is to move the handler to some other part of the document, and
specify the relationship there (like some variants of HTML use the
for
attribute on the <script>
element):
<action ev:observer="#button" ev:event="DOMActivate"> ... </action> ... <trigger id="button"/>
Another way is to move the handler somewhere, and specify the relationship
in another place with the <listener>
element:
<ev:listener observer="button" handler="dosomething" event="DOMActivate"/> ... <action id="dosomething">...</action> ... <input type="submit" id="button"/>
And finally, you can specify the relationship on the observer itself:
<action id="dosomething"> ... </action> ... <trigger ev:handler="dosomething" ev:event="DOMActivate"/>
These are used to reveal and hide parts of the interface.
<switch> <case id="inputname"> <input ref="name">...</input> <trigger> <label>Next</label> <toggle case="inputage" ev:event="DOMActivate" /> </trigger> </case> <case id="inputage"> <input ref="age">...</input> <trigger>...</trigger> </case> ... </switch>
Repeat allows you to bind to repeating items in an instance
<shoppinglist> <buy>eggs</buy> <buy>milk</buy> ... </shoppinglist>
<repeat ref="buy" id="shoprepeat"> <input ref="."><label>Buy</label></input> </repeat>
A repeat sets the XPath context.
You can use these with <repeat> to add and delete items, and to focus on a specific item.
<trigger> <label>Add</label> <insert ev:event="DOMActivate" nodeset="buy" at="index('shoprepeat')" position="after"/> </trigger>
<trigger> <label>Delete</label> <delete ev:event="DOMActivate" nodeset="buy" at="index('shoprepeat')"/> </trigger>
"The age of the fat client is past" -- an implementor
Experience with XForms 1.0 has revealed a number of things:
A future iteration of XForms will address these issues.
The origin: www.w3.org/Markup/Forms, and if your company is a member: www.w3.org/Markup/Forms/Group
XForms: http://www.w3.org/TR/xforms/
XPath: http://www.w3.org/TR/xpath
XPath quick reference: http://www.mulberrytech.com/quickref/XSLTquickref.pdf
XML Events: http://www.w3.org/TR/xml-events/