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
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?
This 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.
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:
Now divide the other half in two vertically, and write the date 18 months ago in one half:
Now divide the remaining space in half, and write the date 18 months earlier (or in other words 3 years ago) in one half:
Repeat until your pen is thicker than the space you have to divide in two:
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).
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.
In 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.
What this meant was that the computer's time was expensive.
So 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.
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.
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.
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.
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.
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.
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.)
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.
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.
Obvious things that were needed included:
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.
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.
XForms 2.0 is now in preparation. Based on experience with XForms 1.1, it has generalised even further.
There are two essential elements to XForms:
The first is to separate the data from the user interface.
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 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
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.
<model> <instance> <data xmlns=""> <firstname/> <secondname/> </data> </instance> </model>
<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)"/>
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>
<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)" />
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>
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).
<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.
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.
<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)"/>
Iterations: <output ref="iterations"/> Final: <output ref="final"/>
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.
<bind nodeset="end" constraint=". > ../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>
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>
The source of the instance data may be anything addressable with a URL
<instance src="http://en.wikipedia.org/w/api.php?action=opensearch&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.
So we have now seen types, calculations and constraints.
There are three other properties of data: relevant, readonly and 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>
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'"/>
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.
The final model-item property is readonly
, which
unsurprisingly conditionally makes a value read-only.
<bind nodeset="status" readonly="../role = 'beginner'" />
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.
Note in the example that there are just three repetitions of the same control (with a different appearance).
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>
Then in the body:
<select1 ref="country"> <label>Country</label> <itemset nodeset="instance('countries')/country"> <label ref="."/> <value ref="@code"/> </itemset> </select1>
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!
<instance id="default"> <data xmlns=""> <lang>Nederlands</lang> <name/> <age/> <gender/> </data> </instance> <instance id="q" src="questions3.xml"/>
<questions> <set name="Nederlands"> <language>Taal</language> <name>Naam</name> <age>Leeftijd</age> ... <set name="English"> ...
<input ref="name"> <label> <output ref="instance('q')/set[@name=instance('default')/lang]/name"/> </label> </input>
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.
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.
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.
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"/>
<submission ref="instance('reference')" action="http://whatever" method="get" replace="instance" instance="address"/>
<submission ref="instance('address')" action="..." method="..." replace="none"/>
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.
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 will add to this, by allowing the switch to be data-driven:
<switch caseref="state"> ...
Example
<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&format=json&search=',search)"/> </submission> <setfocus ev:event="xforms-ready" control="search"/> </model>
<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>
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"/>
This uses a control like <input>
, namely
<secret>,
that doesn't display the characters that you are
typing.
<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>
<instance id="req"> <!-- requirements --> <data xmlns=""> <length>9</length> <upper>3</upper> <lower>0</lower> <digits>2</digits> <other>2</other> </data> </instance>
<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=". >= instance('req')/length"/> <bind nodeset="instance('pwd')/UPPER" calculate="../LENGTH - string-length(translate(instance('data')/PWD,'ABCDEFGHIJKLMNOPQRSTUVWXYZ',''))" constraint=". >= instance('req')/upper" /> <bind nodeset="instance('pwd')/LOWER" calculate="../LENGTH - string-length(translate(instance('data')/PWD,'abcdefghijklmnopqrstuvwxyz',''))" constraint=". >= instance('req')/lower"/> <bind nodeset="instance('pwd')/DIGITS" calculate="../LENGTH - string-length(translate(instance('data')/PWD,'0123456789',''))" constraint=". >= instance('req')/digits"/> <bind nodeset="instance('pwd')/OTHER" calculate="../LENGTH - ../UPPER - ../LOWER - ../DIGITS" constraint=". >= instance('req')/other"/> <!-- Constraint on the whole password --> <bind nodeset="instance('data')/PWD" constraint="instance('pwd')/LENGTH >= instance('req')/length and instance('pwd')/UPPER >= instance('req')/upper and instance('pwd')/DIGITS >= instance('req')/digits and instance('pwd')/OTHER >= 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 < 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'"/>
<h:span style="{instance('pwd')/style}"> <output ref="instance('pwd')/SCORE"> <label>Score: </label> </output> </h:span>
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.
<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/*"/>
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.
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)
<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>
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:
<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;')" />
<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>
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.
Many companies use XForms either internally or externally, and have their own implementations, either for internal use or for licensing. For instance:
As you would expect with a new technology, first adopters are within companies and vertical industries that have control over the software environment used.
Very close to going to last call.
Already got (partial) implementations.
Hopefully ready this year.