The author

Web Applications with XForms 2.0

Steven Pemberton, CWI, Amsterdam

About me

Researcher at the Dutch national research centre CWI (first European Internet site - 1988, whole of Europe connected to North America with 64kb link!).

Co-designed the programming language ABC, that was later used as the basis for Python.

In the late 80's designed and built a browser, with extensible markup, stylesheets, vector graphics, client-side scripting, etc. Ran on Mac, Unix, Atari ST.

Organised 2 workshops at the first Web conference in 1994, incuding one on Client-side Computing (this was before Javascript)

Co-author of HTML4, CSS, XHTML, XML Events, XForms, RDFa, etc

Chaired the W3C HTML WG for a decade. Still chair Forms WG

Computer power

We all know Moore's Law (not really a law by the way) which postulates that computers double in power every 18 months at constant cost.

But is it true?

Yes, it is true

My computers 1985-2013This is a graph of the power of the computers I have used over the last 30 years, split into 4 price classes, on a logarithmic scale.

What exponential growth really means to you and me

Often people don't understand the true effects of exponential growth.

A BBC reporter recently: "Your current PC is more powerful than the computer they had on board the first flight to the moon". Right, but oh so wrong (Closer to the truth: your current computer is several times more powerful than all the computers they used to land a man on the moon put together.)

Take a piece of paper, divide it in two, and write this year's date in one half:

Paper

2013

Now divide the other half in two vertically, and write the date 18 months ago in one half:

Paper

2013
2011

Now divide the remaining space in half, and write the date 18 months earlier (or in other words 3 years ago) in one half:

Paper

2013
2011
2010

Repeat until your pen is thicker than the space you have to divide in two:

Paper

2013
2011
2010
2008
2007
2005
2004
2002
01
99
98
96
95
93

This demonstrates that your current computer is more powerful than all other computers you have had put together (and way more powerful than the computer they had on board the first moonshot -- way more powerful than all the computers put together that they used for the moon landing).

Programming

The web is now 22 years old.

That means in that time, our computers have become something like 30,000 times more powerful: 4 orders of magnitude.

And how about us as programmers? Have we become any more productive in that time? Barely. We wait less time for our compilations to finish.

What's the problem?

A large 50's computerIn the 50's, when (so-called) high-level languages first started emerging, computers cost in the millions. Nearly no one bought computers, nearly everyone leased them.

When you leased a computer in those days, you would get programmers for free to go with it. Programmers were essentially free (in comparison with the cost of the computer).

Nowadays it is exactly the reverse of course. Computers are essentially free. It is the programmers who are expensive.

The design of programming languages

What this meant was that the computer's time was expensive.

TypingSo a programmer would write the program, copy it to special paper, give it to a typist, who would type it out, then give the result to another typist who would then type it out again to verify that it had been typed correctly the first time.

Why all this extra work? Because it was much cheaper to let 3 people do this work, than to let the computer discover the errors for you.

The Design of Programming Languages

And so programming languages were designed around the needs of the computer, not the programmer. It was much cheaper to let the programmer spend lots of time producing a program than to let the computer do some of the work for you.

Programming languages were designed so that you can tell the computer what to do, in its terms, not what you want to achieve in yours.

Almost all present-day programming languages still bear the marks of this design. They still talk in terms of the computer.

1970's

By the 1970's computers were becoming two orders of magnitude cheaper, and programmers weren't: the cost of software was starting to hurt.

The DoD did some research and discovered that 90% of the cost of software production was in debugging.

Interestingly, Fred Brookes in his book "The Mythical Man Month" reported that the number of bugs in a program is not linear with the length of a program but quadratic:

b ∝ L1.5

Which means: if a program is ten times as long, it has 30 times as many bugs, which means it costs 30 times as much to make.

Conversely, a program that is 10 times smaller costs 3% of the larger program.

What would it mean to be an order of magnitude more productive?

A program that you now would write in a week, you could write in a morning.

A program that now would take a month you could write in two days.

A program that would take a year to write, you could produce in a month.

Will Moore's Law come to an end?

Surely, but don't hold your breath. Over the years, I have heard many predictions that it was "nearly" at an end, in the mid 70's the first time, so I have become rather blasé.

What is true is that processor clock speeds have reached their maximum.

The increase is now in number of cores. (Although a CPU now has a handful of cores, GPUs -- which contain maybe 90% of the power of a computer -- have around 1000).

This in itself may finally affect how we program: we may have to find a new computing paradigm to deal with the ever-increasing number of cores.

XForms

Because (as you will see) you specify what you are trying to achieve and not how to achieve it, there is far less administration to worry about. This means: shorter programs.

How much shorter?

One correspondent who was converting a large collection of apps for a company from Javascript to XForms reported that the XForms were about ¼ the size.

So that means we should expect the production time and cost to reduce to one eighth, about an order of magnitude. And indeed this seems to match experience.

Data point: 150 person years becomes 10!

A certain company makes BIG machines (walk in): user interface is very demanding — traditionally needed 5 years, 30 people.

With XForms this became: 1 year, 10 people.

Do the sums. Assume one person costs 100k a year. Then this has gone from a 15M cost to a 1M cost. They have saved 14 million! (And 4 years.)

Data point: A true story

Manager: I want you to come back to me in 2 days with estimates of how long it will take your teams to make the application

Two days later:

Programming man: I'll need 30 days to work out how long it will take to program it

XForms man: I've already done it.

So, What is XForms

XForms, first released in 2002, is a markup language originally designed -- as the name suggests -- to update how forms are handled on the web.

The design was done by doing requirements analysis, and observation of how forms were being done on the web then, and trying to do it better.

Requirements

Obvious things that were needed included:

Initial experience

Particularly in the use of fixed strings rather than (potentially) calculated values for such things as the submission URI.

As a consequence this restricted what was possible with the language.

XForms 1.0 → 1.1

As a consequence, XForms 1.1 (2009) addressed these shortcomings, as well as adding a number of 'low-hanging fruits' which various implementations had added.

The resultant language turned out to be far more than a forms language, but a declarative application language.

Since XForms has input, output, and a processing engine, XForms is Turing-complete, and much more than just forms was now possible with the language.

And now XForms 2.0

XForms 2.0 is now in preparation. Based on experience with XForms 1.1, it has generalised even further.

XForms: the essence

There are two essential elements to XForms:

  1. Separation of data from controls
  2. Abstraction of controls

The Essence (1): Separation of Values from Controls

The first is to separate the data from the user interface. Separation of values from controls

Some advantages of the separation

You can see at a glance what data is being used and what is being returned

You can see much more clearly the relationships between differerent values

You can reuse models in different forms.

You can liken them to "data sheets" in the same way as CSS "style sheets".

The Essence (2): Intent-based Controls

The second part is that the controls, rather than expressing how they should look (radio buttons, menu, etc), express their intent ("this control selects one value from a list").

You then use styling to say how they should be represented, possibly with different styling for different devices (as a menu on a small screen, as radio buttons on a large screen).

Colour: red green blue

Advantages of intent-based controls

In a similar way to the use of style sheets, particular choices of interaction are not hard-wired.

Device independence: you can use use different presentations on different devices

Accessibility: since the control states what it does, you can present a suitable alternative for accessibility use.

An Example

What this looks like: The model

<model>
   <instance>
      <data xmlns="">
         <firstname/>
         <secondname/>
      </data>
   </instance>
</model>

The body

<input incremental="true" ref="firstname">
   <label>First name:</label>
</input>
<input incremental="true" ref="secondname">
   <label>Second name:</label>
</input>
Your name is: 
   <output value="concat(firstname, ' ', secondname)"/>

Calculations

Example

This calculates the increase of an exponential growth over a number of cycles.

<instance>
   <data xmlns="">
      <init>1</init>
      <cycle>1.5</cycle>
      <start>1991</start>
      <end>2013</end>
   </data>
</instance>

The body

<input incremental="true" ref="init">
   <label>Initial</label>
</input>
<input incremental="true" ref="cycle">
   <label>Cycle</label>
</input>
<input incremental="true" ref="start">
   <label>Start</label>
</input>
<input incremental="true" ref="end">
   <label>End</label>
</input>

Final: 
<output value="init * power(2, (end - start) div cycle)" />

Bind

Now to restrict the input to numbers:

<model>
   <instance>
      <data xmlns="">
         <init>1</init>
         <cycle>1.5</cycle>
         <start>1991</start>
         <end>2013</end>
      </data>
   </instance>
   <bind nodeset="init" type="double" />
   <bind nodeset="cycle" type="double"/>
   <bind nodeset="start" type="double"/>
   <bind nodeset="end" type="double"/>
/model>

Bind

In fact this can be expressed even shorter in this case:

<model>
   <instance>
      <data xmlns="">
         <init>1</init>
         <cycle>1.5</cycle>
         <start>1991</start>
         <end>2013</end>
      </data>
   </instance>
   <bind nodeset="*" type="double" />
/model>

This is because the nodeset attribute (and the ref attribute on input etc.) is an XPath expression. (XForms 1: XPath 1; XForms 2: XPath 2).

Example

Adding an error message

<input incremental="true" ref="init">
   <label>Initial</label>
   <alert>Must be a number</alert>
</input>

How this gets displayed depends on the implementation, and the use of style sheets.

XForms 2 detail

In XForms 1, a distinction was made between a single-node binding and a multiple-node binding. You used ref for one and nodeset for the other.

In XForms 2.0, this distinction is gone at the markup level: you use ref for both.

Moving the calculation to the model

<instance>
   <data xmlns="">
      <init>1</init>
      <cycle>1.5</cycle>
      <start>1991</start>
      <end>2013</end>
      <iterations/>
      <final/>
   </data>
</instance>
<bind nodeset="*" type="xf:double"/>
<bind nodeset="iterations" 
 calculate="(../end - ../start) div ../cycle"/>
<bind nodeset="final" 
 calculate="../init * power(2, ../iterations)"/>

In the body

Iterations: <output ref="iterations"/>
Final: <output ref="final"/>

XForms 2.0 detail

XForms 1 allowed only one bind per node, so you had to combine binds:

<bind nodeset="sum" type="integer" 
      calculate="../a + ../b" />

XForms 2 allows you to have several binds for one node, as long as they don't conflict.

Constraining values

<bind nodeset="end" 
      constraint=". &gt; ../start"/>

You can use an <alert> with this too:

<input incremental="true" ref="end">
   <label>End</label>
   <alert>Must be a number, and greater than 'start'</alert>
</input>

Example

Moving the initial values out of the form

We have our initial data that looks like this:

<instance>
   <data xmlns="">
      <init>1</init>
      <cycle>1.5</cycle>
      <start>1991</start>
      <end>2013</end>
      <iterations/>
      <final/>
   </data>
</instance>

However, it is entirely possible to move that out of the form:

<instance src="data.xml"/>

and the data in a file:

   <data>
      <init>1</init>
      <cycle>1.5</cycle>
      <start>1991</start>
      <end>2013</end>
      <iterations/>
      <final/>
   </data>

External data

The source of the instance data may be anything addressable with a URL

<instance src="http://en.wikipedia.org/w/api.php?action=opensearch&amp;format=xml..."

XForms 1: data must be XML

XForms 2: data can be XML, JSON, CSV, and optionally other types, such as VCard. The data still looks like XML to the form.

Binds summary so far

So we have now seen types, calculations and constraints.

There are three other properties of data: relevant, readonly and required.

Required

You can make any value required, either conditionally or unconditionally.

Example

Required

<model>
   <instance>
      <data xmlns="">
         <name/>
         <country/>
         <state/>
      </data>
   </instance>
   <bind nodeset="name" required="true()"/>
   <bind nodeset="state"
         required="../country = 'USA'"/>
</model>

And in the body (nothing special here):

<input incremental="true" ref="name">
   <label>Name</label>
   <alert>May not be empty</alert>
</input>
<input incremental="true" ref="country">
   <label>Country</label>
</input>
<input incremental="true" ref="state">
   <label>State</label>
   <alert>May not be empty</alert>
</input>

Relevant

But in this case, the state isn't relevant for all countries.

So in this version we will say that the state is only relevant if the country is USA, and if relevant, always required:

<bind nodeset="name" required="true()"/>
<bind nodeset="state" required="true()" 
      relevant="../country = 'USA'"/>

Example

Note that we have made no change to the body in this case. The control appears and disappears based purely on the state of the value.

Readonly

The final model-item property is readonly, which unsurprisingly conditionally makes a value read-only.

<bind nodeset="status" readonly="../role = 'beginner'" />

Countries

Of course you don't normally want people to enter their country textually, but by selecting. Here you use the <select> control:

<select1 appearance="full" ref="colour">
   <label>Colour:</label>
   <item>
      <label>Red</label><value>R</value>
   </item>
   <item>
      <label>Green</label><value>G</value>
   </item>
   <item>
      <label>Blue</label><value>B</value>
   </item>
</select1>

"Appearance" is a hint to the implementation, and may have the values full, compact or minimal.

Example

Note in the example that there are just three repetitions of the same control (with a different appearance).

Moving the selection data to the model

Especially in the countries case, it is better to have the data in one central place, even more so if there are several controls using the same data.

We can do that by creating an additional instance for the country data (you have some freedom here in the format). We'll keep it small here:

<instance id="countries">
  <countries xmlns="">
    <country code="BR">Brazil</country>
    <country code="NL">The Netherlands</country>
    <country code="UK">United Kingdom</country>
    <country code="USA">United States of America</country>
  </countries>
</instance>

Selecting from model data

Then in the body:

<select1 ref="country">
  <label>Country</label>
  <itemset nodeset="instance('countries')/country">
    <label ref="."/>
    <value ref="@code"/>
  </itemset>
</select1>

Example

Even better

Of course, even better is to put the countries data in one place centrally, and then use.

<instance id="countries" src="countries.xml"/>

Then when a country changes its name, or a country disappears, or new one appears, you can change it once, and all your forms will be updated!

Using data in the user interface

Example

<instance id="default">
    <data xmlns="">                   
      <lang>Nederlands</lang>
      <name/>
      <age/>
      <gender/>
    </data>
</instance>

<instance id="q" src="questions3.xml"/>

The questions

<questions>
  <set name="Nederlands">
    <language>Taal</language>
    <name>Naam</name>
    <age>Leeftijd</age>
    ...
  <set name="English">
    ...

In the body

<input ref="name">
   <label>
     <output
       ref="instance('q')/set[@name=instance('default')/lang]/name"/>
   </label>
</input>

Submission

Some forms/apps are perfectly standalone (for instance a form to calculate the date of Easter for a year.)

But you often need to submit data.

XForms allows you to submit any instance, serialized in a number of different ways, and then specify what should happen with the returned data. You have fairly fine-grain control over when data is submitted.

Submission

For instance

<submission action="http://example.com/search" method="get" />

Would submit the instance to the URL listed, with the data URL-encoded

http://example.com/search?q=test&n=10

This would also replace the whole document with the result.

Staying alive

However, an application normally wants to get the results and do something with it. To this end, the submission element has an attribute replace. Usually you want to put the results in a named instance, for which you use replace="instance"

<submission action="http://example.com/search" method="get"
   replace="instance" instance="results" />

In XForms 1: returned data must in general be XML if it is to replace an instance

In XForms 2: also allowed JSON, CSV, and optionally other types.

Example

Suppose you want to preload an instance with some data, for instance an address, change the address, and then save it.

First step is to get the address:

<instance id="reference">
   <data xmlns="">
      <reference/>
   </data>
</instance>
<instance id="address"/>

Filling the address

<submission ref="instance('reference')"
    action="http://whatever"
    method="get" replace="instance"
    instance="address"/>

Then edit and post back

<submission ref="instance('address')"
     action="..."
     method="..."
     replace="none"/>

Event processing

Similar to the onclick style of processing, but generalised, XForms allows you to catch and respond to events. The whole of the XForms processing model had events that you can respond to. For instance

<setvalue ev:event="xforms-ready" 
          ref="state" value="0"/>

Which sets an instance value when XForms starts.

<setfocus ev:event="xforms-ready" 
    control="search"/>

which positions the focus on an initial control when XForms starts.

<send ev:event="xforms-value-changed"
      submission="submit1"/>

Which causes data to be submitted when the value bound to a control changes.

Example

Hiding and revealing interface elements

There is a control <switch> that allows you to switch between different interface elements:

<switch>
  <case id="init">
     ...
  </case>
  <case id="next">
     ...
  </case>
  ...
<switch>

You toggle between the different cases with a <toggle> action:

<toggle ev:event="..." case="next"/>

XForms 2 switch

XForms 2 will add to this, by allowing the switch to be data-driven:

<switch caseref="state">
  ...

Example

An example: suggestions

Example

How this works

<model>
<instance id="isearch">
  <root xmlns="">
     <search/>
  </root>
</instance>

<instance id="iresults">
   <root xmlns=""/>
</instance>

<bind nodeset="search" constraint="instance('iresults')/*[2]/*[translate(.,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') = translate(current(),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')]"/>



<submission id="s1" method="get"
      replace="instance" instance="iresults"
   serialization="none" mode="synchronous" 
   mediatype="text/jsonp">
      <resource value="concat('http://en.wikipedia.org/w/api.php?action=opensearch&amp;format=json&amp;search=',search)"/>
</submission>

<setfocus ev:event="xforms-ready" 
          control="search"/>
</model>

In the body

<input id="search" ref="search" incremental="true" delay="500">
   <label>Subject : </xf:label>
   <send submission="s1"
      ev:event="xforms-value-changed"/>

   <toggle ev:event="DOMFocusIn"
           case="show-autocompletion" />
</input>

<switch>
  <case id="show-autocompletion">
    <repeat id="results" nodeset="instance('iresults')/*[2]/*">
      <trigger appearance="minimal">
        <label><output value="."/></label>
        <action ev:event="DOMActivate">
          <setvalue ref="instance('isearch')/search" value="instance('iresults')/*[2]/*[index('results')]" />
          <toggle case="hide-autocompletion" />
        </action>
      </trigger>
    </repeat>
  </case>
  <case id="hide-autocompletion" />
</switch>

AVTs

In XForms 2.0 Attribute Value Templates have been added to allow greater freedom in the use of calculated values. For instance

<output class="{instance('admin')/class}"
        ref="balance"/>

Passwords

Example

This uses a control like <input>, namely <secret>, that doesn't display the characters that you are typing.

Code

<instance id="data">
   <data xmlns=""> <!-- The real data -->
         <PWD/>
   </data>
</instance>
<instance id="pwd"> <!-- admin -->
   <data xmlns="">
       <PWD1/>
       <LENGTH/>
       <UPPER/>
       <LOWER/>
       <DIGITS/>
       <OTHER/>
       <SCORE/>
       <SCORE1/>
       <show>false</show>
       <showpwd/>
       <doshow/>
       <donotshow/>
       <style>background: hsl(240, 100%, 50%)</style>
       <pwdalert>Must contain lower case, upper case, symbols and digits, and be longer than 8 characters</pwdalert>
   </data>
</instance>

Code

<instance id="req"> <!-- requirements -->
   <data xmlns="">
      <length>9</length>
      <upper>3</upper>
      <lower>0</lower>
      <digits>2</digits>
      <other>2</other>
   </data>
</instance>

Code

<bind nodeset="instance('pwd')/show" type="xf:boolean"/>
<bind nodeset="instance('pwd')/showpwd"
  calculate="if(../show, instance('data')/PWD, '')"/>
    <!-- if the password isn't being shown, then the copy is relevant, and must be equal -->

<bind nodeset="instance('pwd')/PWD1"
  relevant="../show" constraint=". = instance('data')/PWD"/>
    <!-- Properties of the password, and their constraints -->
            <bind nodeset="instance('pwd')/LENGTH"
  calculate="string-length(instance('data')/PWD)" constraint=". &gt;= instance('req')/length"/>
            <bind nodeset="instance('pwd')/UPPER"
  calculate="../LENGTH - string-length(translate(instance('data')/PWD,'ABCDEFGHIJKLMNOPQRSTUVWXYZ',''))"
  constraint=". &gt;= instance('req')/upper" /> 
            <bind nodeset="instance('pwd')/LOWER"
  calculate="../LENGTH - string-length(translate(instance('data')/PWD,'abcdefghijklmnopqrstuvwxyz',''))"
  constraint=". &gt;= instance('req')/lower"/> 
            <bind nodeset="instance('pwd')/DIGITS" 
   calculate="../LENGTH - string-length(translate(instance('data')/PWD,'0123456789',''))"
  constraint=". &gt;= instance('req')/digits"/> 
            <bind nodeset="instance('pwd')/OTHER" 
  calculate="../LENGTH - ../UPPER - ../LOWER - ../DIGITS"
  constraint=". &gt;= instance('req')/other"/> 
    <!-- Constraint on the whole password -->
            <bind nodeset="instance('data')/PWD" 
  constraint="instance('pwd')/LENGTH &gt;= instance('req')/length and instance('pwd')/UPPER &gt;= instance('req')/upper and instance('pwd')/DIGITS &gt;= instance('req')/digits and instance('pwd')/OTHER &gt;= instance('req')/other"/> 

    <!-- Score -->
<bind nodeset="instance('pwd')/SCORE1" 
  calculate="../LENGTH * 4 + ((../LENGTH - ../UPPER) * 2) + ((../LENGTH - ../LOWER) * 2) + ../DIGITS * 4 + ../OTHER * 6"/> 

<bind nodeset="instance('pwd')/SCORE" 
  calculate="if(../SCORE1 &lt; 100, ../SCORE1, 100)"/>

<bind nodeset="instance('pwd')/style"
   calculate="concat('background: hsl(', ../SCORE, ', 100%, 50%)')" />

<!-- These are used for the model-based switch below -->
<bind nodeset="instance('pwd')/doshow" 
  relevant="../show = 'true'"/>
<bind nodeset="instance('pwd')/donotshow" 
  relevant="../show = 'false'"/>

In the body

<h:span style="{instance('pwd')/style}">
   <output ref="instance('pwd')/SCORE">
      <label>Score: </label>
   </output>
</h:span>

Maps

To display an image in XForms, you use the <output> element, with a URL, and an extra attribute, mediatype

<instance>
   <data xmlns="">
      <url>http://a.tile.openstreetmap.org/10/511/340.png</url>
   </data>
</instance>

And then with the output

<output ref="url" mediatype="image/*"/>

This gives you one of the tiles from Openstreetmap.

More interesting though, would be to construct the URL from its consituent parts.

Constructing a URL

<model>
    <instance>
        <data xmlns="">
            <zoom>10</zoom>
            <x>511</x>
            <y>340</y>
            <url/>
        </data>
    </instance>
    <bind nodeset="url"
        calculate="concat('http://a.tile.openstreetmap.org/', 
          ../zoom, '/' , ../x, '/', ../y, '.png')
</model>
 ...
<output ref="url" mediatype="image/*"/>

Example

How OSM URLs are constructed

OSM has 18 levels of zoom.

At level 0 there is just 1 tile, of the whole world.

At level 1 there are 4 tiles. (2 x 2)

At level 2, 16 tiles (4 x 4)

At level 3, 64 tiles (8 x 8 = 2³ x 2³)

...

At level 18, ...

Each tile is 256 x 256 pixels, which is 28 x 28

So in each direction there are 218 x 28 = 226 possible values of x and y.

Finding the right tile

So if we retain our position as an x, y pair, then to find the tile we need at a particular zoom:

At zoom 18, x, y identifies the tile

At zoom 17, x/2, y/2

At zoom 16, x/4, y/4

And so on: tilex= x/2**(18-zoom)

(rounded appropriately)

Code

<model xmlns="http://www.w3.org/2002/xforms">
    <instance>
        <data xmlns="">
            <x>130980</x>
            <y>87168</y>
            <zoom>10</zoom>
            <scale/>
            <tilex/>
            <tiley/>
            <url/>
        </data>
    </instance>
    <bind nodeset="scale"
      calculate="power(2, 18 - ../zoom)"/>
    <bind nodeset="tilex"
      calculate="floor(../x div ../scale)"/>
    <bind nodeset="tiley"
      calculate="floor(../y div ../scale)"/>
    <bind nodeset="url"
          calculate="concat('http://a.tile.openstreetmap.org/',
           ../zoom, '/' , ../tilex, '/', ../tiley, '.png')"/>
</model> 

Example

Positioning on the tile

You will have noticed from that example that:

This is not surprising, since if it is in the middle of a tile at zoom n, it will be in one of the quadrants at zoom n-1.

So what we do:

code

<instance>
   <data xmlns="">
      <zoom>10</zoom>
      <posy>22307840</posy>
      <posx>33530624</posx>
      <tilesize>256</tilesize>
      <x/><y/>
      <scale/>
      <maxpos/>
      <offx/><offy/>
   <loc>http://a.tile.openstreetmap.org/</loc>
      <urltl/><urltm/><urltr/>
      <urlml/><urlmm/><urlmr/>
      <urlbl/><urlbm/><urlbr/>
      <style/>
   </data>
</instance>
    <bind nodeset="scale" calculate="power(2, 26 - ../zoom)"/>
    <bind nodeset="maxpos" calculate="power(2, 26)-1"/>
    <bind nodeset="x" calculate="floor(../posx div ../scale)"/>
    <bind nodeset="y" calculate="floor(../posy div ../scale)"/>
    <bind nodeset="offx"
      calculate="floor(((../posx - ../x * ../scale) div ../scale)*../tilesize)" />
    <bind nodeset="offy" calculate="floor(((../posy - ../y * ../scale) div ../scale)*../tilesize)" />

    <bind nodeset="urltl" calculate="concat(../loc, ../zoom, '/', ../x - 1, '/', ../y - 1, '.png')"/>
    <bind nodeset="urltm" calculate="concat(../loc, ../zoom, '/', ../x,     '/', ../y - 1, '.png')"/>
    <bind nodeset="urltr" calculate="concat(../loc, ../zoom, '/', ../x + 1, '/', ../y - 1, '.png')"/>
    <bind nodeset="urlml" calculate="concat(../loc, ../zoom, '/', ../x - 1, '/', ../y,     '.png')"/>
    <bind nodeset="urlmm" calculate="concat(../loc, ../zoom, '/', ../x,     '/', ../y,     '.png')"/>
    <bind nodeset="urlmr" calculate="concat(../loc, ../zoom, '/', ../x + 1, '/', ../y,     '.png')"/>
    <bind nodeset="urlbl" calculate="concat(../loc, ../zoom, '/', ../x - 1, '/', ../y + 1, '.png')"/>
    <bind nodeset="urlbm" calculate="concat(../loc, ../zoom, '/', ../x,     '/', ../y + 1, '.png')"/>
    <bind nodeset="urlbr" calculate="concat(../loc, ../zoom, '/', ../x + 1, '/', ../y + 1, '.png')"/>

    <bind nodeset="style"
     calculate="concat('margin-left: ', 0 - (../offx), 'px; margin-top: ',
       0 - (../offy), 'px;')" />

In the body

<div class="map">
   <div  style="{style}">
<group xmlns="http://www.w3.org/2002/xforms">
  <group>
    <output ref="urltl" mediatype="image/*"/>
    <output ref="urltm" mediatype="image/*" />
    <output ref="urltr" mediatype="image/*" />
  </group>
  <group>
    <output ref="urlml" mediatype="image/*" />
    <output ref="urlmm" mediatype="image/*" />
    <output ref="urlmr" mediatype="image/*" />
  </group>
  <group>
    <output ref="urlbl" mediatype="image/*" />
    <output ref="urlbm" mediatype="image/*" />
    <output ref="urlbr" mediatype="image/*" />
  </group>
</group>
      </div>
    </div>

Implementations

There are a number of ways you can implement XForms

A big advantage of server-side implementations is 'write once, run anywhere', since the server can sniff the device and deliver a version suitable for that device. Novell had a ground-breaking example of that.

Enterprise implementations

Many companies use XForms either internally or externally, and have their own implementations, either for internal use or for licensing. For instance:

Freely available implementations

Users

As you would expect with a new technology, first adopters are within companies and vertical industries that have control over the software environment used.

XForms 2.0

Very close to going to last call.

Already got (partial) implementations.

Hopefully ready this year.

Resources

Tutorial

Quick reference

Community Group

XForms 2.0 live version