"Please don't assume that the audience already knows XForms"
Designed originally for forms, now much more general.
A declarative programming language.
Large projects have shown it can cut production costs and time by an order of magnitude.
Used world-wide by large corporations (e.g. BBC, NHS, Xerox, EMC, Governments)
Implementations from Belgium, France, Germany, NL, UK, USA.
"The term form refers to the work's style, techniques and media used, and how the elements of design are implemented.
Content, on the other hand, refers to a work's essence, or what is being depicted." - Wikipedia
This is a nearly-perfect description of XForms
XForms is all about state. (Which means it meshes well with REST - Representational State Transfer).
The data can be internal or come from external sources.
You describe the data: properties and relationships.
Initially the system is in a state of stasis.
When a value changes, by whatever means, the system updates related values to bring it back to stasis.
This is like spreadsheets, but much more general.
The result is: programming is much easier, since the system does all the administrative work for you.
Is about describing the problem, not the solution.
Is about describing the problem, not the solution.
Simple example of procedural programming:
function f a: { x ← a x' ← (a + 1) ÷ 2 epsilon ← 1.19209290e-07 while abs(x − x') > epsilon × x: { x ← x' x' ← ((a ÷ x') + x') ÷ 2 } return x' }
This is a solution to something. But what?
Is about describing the problem, not the solution.
Simple example of procedural programming:
function f a: { x ← a x' ← (a + 1) ÷ 2 epsilon ← 1.19209290e-07 while abs(x − x') > epsilon × x: { x ← x' x' ← ((a ÷ x') + x') ÷ 2 } return x' }
This is a solution to something. But what?
The square root of a number n is the number r such that r × r = n
I've got a position in the world as x and y coordinates, and I want to display the map tile of that location at a certain zoom.
My data:
x, y, zoom
Openstreetmap has a REST interface for getting such a thing.
Ideally you would just say:
http://openstreetmap.org/<zoom>/<x>/<y>
Unfortunately, their interface has a couple of problems, that expose the implementation, rather than providing an opaque interface.
The first problem: you have to mention that it is a PNG image.
http://openstreetmap.org/<zoom>/<x>/<y>.png
This is a mistake, because they can now never change that decision.
Although so many sites do:
you do not have to expose your media types in your URLs!
Mediatypes are part of the protocol, not the URL.
The second problem, is that the coordinate system changes at each level of zoom. (This problem has more of a justification)
As you zoom out, there are in each axis half as many tiles, so there are ¼ as many tiles. And the interface indexes tiles, not locations.
So to get a tile:
They now cannot change how big their tiles are.
The data: x, y, zoom
scale = 226 - zoom
tilex = floor(x/scale)
tiley = floor(y/scale)
url = concat("http://tile.openstreetmap.org/", zoom, "/", tilex, "/", tiley, ".png")
That is really all that is needed (modulo syntax, which looks like this:)
<bind ref="tilex" calculate="floor(../x div ../scale)"/>
That's the form. Now the content:
<input ref="x" label="x"/> <input ref="y" label="y"/> <input ref="zoom" label="zoom"/> <output ref="url" mediatype="image/*"/>
and the tile will be updated each time any of the values change.
Separation of data from UI, similar to separation of style from content with CSS, with similar advantages.
Instances of data + properties.
<instance src="sale.xml"/> <bind ref="something" property="something"/>
Properties include:
Whenever a value changes, the related values are automatically updated, in spread-sheet style.
<bind ref="address/state" required="../country = 'USA'" label="State"/>
This means that state
is only required for the USA. The field
will always be visible.
<bind ref="address/state" relevant="../country = 'USA'" required="true()" label="State"/>
This means that state
will only be visible for the USA, but
once visible will be required.
Controls in the UI then bind to data nodes, inheriting their properties.
<input ref="address/state"/>
Here is an example (try it). If you select USA as country, the control for state appears.
While typically XForms works automatically, it is possible to hook into the processing model to respond in special ways.
To allow this, there are events that announce changes in the state, and actions that effect changes to the state.
For instance the xforms-ready event announces that the system has initialised. You could respond to this for instance by setting a value to today's date and time:
<action ev:event="xforms-ready"> <setvalue ref="today" value="now()"/> </action>
Other events announce when a value changes, or when it changes validity, relevance, etc.
Other useful actions include activating a button, and inserting and deleting elements and attributes from data.
In fact the only way to get a vanilla button to do anything is to listen for the activation events, and then respond with an action.
<trigger label="Restart"> <action ev:event="DOMActivate"> <setvalue ref="score" value="0"/> </action> </trigger>
It is good practice to define a test suite to go along with a specification.
In fact at W3C it is required.
Advantages:
XForms 1 and 1.1 have a test-suite where each feature is tested by a separate file.
A new version of XForms is in preparation, and therefore a new version of the test suite.
The test suite is a self-testing, data-driven form.
(This is work in progress; the example above is live, but may not always work).
This makes it really easy to add new tests to test cases.
We want to test that this function call returns the right result:
boolean-from-string('true')
We enclose the parameter in an element:
<test>true</test>
add attributes for the required result, actual result, and whether the test case passes:
<test pass="" res="" req="true">true</test>
<instance> <tests pass="" name="boolean-from-string() function" xmlns=""> <description>...</description> <test pass="" res="" req="true">true</test> <test pass="" res="" req="true">TRUE</test> <test pass="" res="" req="true">tRue</test> <test pass="" res="" req="true">1</test> <test pass="" res="" req="false">false</test> <test pass="" res="" req="false">FALSE</test> <test pass="" res="" req="false">faLse</test> <test pass="" res="" req="false">0</test> <test pass="" res="" req="false">qwertyuiop</test> <test pass="" res="" req="false"></test> ... </tests> </instance>
Calculate the results:
<instance> <tests pass="" name="boolean-from-string() function" xmlns=""> <test pass="" res="" req="true">true</test> ... </instance> <bind ref="test/@res" calculate="boolean-from-string(..)"/>
(this is the only line that is different in any test file, apart from the
content of the test
instance).
Check whether each passes:
<bind ref="test/@pass" calculate="if(../@res = ../@req, 'yes', 'no')"/>
Check whether the whole set passes:
<bind ref="@pass" calculate="if(test[@pass!='yes'], 'FAIL', 'PASS')"/>
These are also the same for each test set:
<group> <label class="title" ref="@name"/> <output class="block" ref="description"/> <output class="{@pass}" ref="@pass"/> <repeat ref="test"> <output value="."/> → <output ref="@res"/> <output class="wrong" value="if(@pass!='yes', concat(' expected: ', @req), '')"/> </repeat> </group>
<test pass="" res="" req="invalid"/> <test pass="" res="" req="valid">2018-01-20</test> <test pass="" res="" req="invalid">2018/01/20</test> <test pass="" res="" req="valid">-2018-01-20</test> <test pass="" res="" req="invalid">+2018-01-20</test> <test pass="" res="" req="invalid">02018-01-20</test> <test pass="" res="" req="valid">12018-01-20</test>
Bind the type
<bind ref="test" type="date"/>
The easiest way to check if these are valid values:
<bind ref="test/@res" calculate="if(valid(..), 'valid', 'invalid')"/>
However, the function valid
is an XForms 2.0 feature.
One of our aims is to minimise the use of XForms 2.0 features in the infrastructure of the testbed (since these are the features most likely not to work (yet)).
So how do we work out if a data value is valid or not? By catching the events that announce validity.
If a control is bound to a value, whenever the value changes, events are dispatched to the control announcing validity and other properties, which we can catch and record:
<repeat ref="test"> <output ref="."> <action ev:event="xforms-valid"> <setvalue ref="@res">valid</setvalue> </action> <action ev:event="xforms-invalid"> <setvalue ref="@res">invalid</setvalue> </action> </output> ...
During initialisation certain events are sent to announce stages of initialisation.
Last is xforms-ready
, announcing that the system is ready to
go.
The problem: before xforms-ready
, there is little you can do.
The original test suite just popped up a dialog box to announce each initialisation event as it occurred:
<action ev:event="xforms-model-construct"> <message>xforms-model-construct received</message> </action>
There's not much you can do during initialisation, but there's one more thing you can do: dispatch an event.
<action ev:event="xforms-model-construct"> <dispatch name="XMC" delay="1000" ... /> </action>
This dispatches a new event, with a delay of 1 second (more than enough to allow the system to initialise), which we can then catch, and record:
<action ev:event="XMC"> <setvalue ref="test[1]">xforms-model-construct received</setvalue </action>
Lots of flexibility.
Easy to add new test cases, and tests.
Much easier to run.
Much easier to tell if a test passes or fails.
A small number of tests still need interaction (Does clicking a button send
the right event? Does now()
really return today's date?). But once
those are out of the way, the rest can self-test.
Still work in progress; next steps: run and record results automatically.
Check out the XForms 2.0 spec at https://www.w3.org/community/xformsusers/wiki/XForms_2.0
Join the group if you would like to help!