Ceci est une traduction du deuxième volet d'un document du W3C, intitulé « XForms for HTML authors, Part 2 », traitant de la transition des formulaires HTML vers les formulaires XForms.
Cependant, il ne s'agit pas d'une version officielle en français. Seul le document original en anglais a valeur de référence. On peut l'obtenir à : http://www.w3.org/MarkUp/Forms/2006/xforms-for-html-authors-part2.html
Des erreurs ont pu survenir malgré le soin apporté à ce travail.
Certains concepts sont difficiles à rendre en français ou peuvent bénéficier d'une explication. Par moment, les expressions originales en anglais viennent en renfort dans le texte sous cette forme :
ex. traduction [ndt. translation]
De façon générale, le vocabulaire utilisé est celui défini dans la traduction française des spécifications de XForms.
Cette traduction est également disponible au format HTML sous forme d'archive compressée.
Copyright © 1994-2006 World Wide Web Consortium, (Massachusetts Institute of Technology, European Research Consortium for Informatics and Mathematics, Keio University).
Tous droits réservés. Consulter la notice de copyright pour les productions du W3C.

Steven Pemberton, W3C/CWI
Date de la version : 2006-08-08
Ceci est la seconde partie de « XForms pour les auteurs HTML ». La première partie introduisait la plupart des fonctionnalités ayant quelques équivalences avec des fonctionnalités HTML. Cette 2ème partie introduit de nouveaux concepts qui n'ont pas d'équivalents en HTML.
Le langage XForms utilise la norme XML Events pour la gestion
d'événements : c'est la façon la plus flexible de réaliser une gestion
d'événements dans le style du onclick HTML. En effet, XML Events utilise
exactement le même mécanisme d'événement que le langage HTML. Il n'y a que la
syntaxe qui diffère.
Si l'on considère l'exemple HTML ci-dessous :
<button name="OK" onclick="alert("You clicked me!"); return true;">
Il signifie que si l'élément button (ou n'importe
lequel de ses fils) reçoit l'événement click, alors le fragment de code
associé à l'attribut onclick est traité.
Les deux cas ci-dessous illustrent la capture des événements par la descendance d'un élément :
<a href="..." onclick="...">A <em>very</em> nice place to go</a>
ou
<a href="..." onclick="..."><strong>More</strong></a>
La fonction onclick sera traitée même si le clic s'applique
aux éléments em ou strong. Nous désignons alors
l'élément sur lequel on a cliqué comme étant la cible, et l'élément qui répond à
l'événement comme étant l'observateur (bien que cible et observateur soient souvent
le même élément).
Il y a donc trois notions importantes en jeu : un événement, un observateur et un fragment de script appelé gestionnaire d'événements. On ne se préoccupe habituellement pas de savoir quel est l'élément cible de l'action utilisateur.
Les relations entre ces 3 notions telles que le langage HTML les spécifie posent les problèmes suivants :
onflash si on
introduisait un événement flash ;click pour
une sélection à l'aide d'une souris. En fait on ne veut pas savoir de quelle façon le
déclencheur est activé, mais seulement qu'il a été activé ;onclick, un avec du code « JavaScript » et un autre avec du code
« VBScript ») ;Avec la norme XML Events, on spécifie les relations entre l'événement, l'observateur et le gestionnaire d'événements de manière différente. Les codes HTML et XForms suivants sont équivalents :
<button name="OK" onclick="alert("You clicked me!"); return true;">
et
<trigger> <label>OK</label> <message level="modal" ev:event="DOMActivate">You clicked me!</message> </trigger>
L'élément message est un gestionnaire d'événements
pour l'événement DOMActivate. En l'absence d'autre information, l'élément
parent est l'observateur (ici l'élément trigger). On utilise
l'événement DOMActivate de préférence à click sur les
déclencheurs (élément trigger) car ils peuvent être déclenchés de différentes
façons et pas uniquement en cliquant.
Les éléments ayant des attributs définis dans l'espace de noms ev:
sont évalués seulement lorsque l'événement se produit et non lorsque
le document est en cours de chargement (contrairement à l'élément script de
HTML).
Les attributs de type d'événement ont comme préfixe ev:
qui correspond à l'espace de noms associé à la norme XML Events. Cela suppose la
déclaration de ce préfixe à un endroit convenable dans votre document :
xmlns:ev="http://www.w3.org/2001/xml-events".
En mettant en œuvre plusieurs gestionnaires d'événements, on peut intercepter plus d'un événement pour un élément :
<trigger> <label>OK</label> <message level="modal" ev:event="DOMActivate">You clicked me!</message> <message level="modal" ev:event="DOMFocusIn">You focused on me!</message> </trigger>
Si on a besoin de réaliser plusieurs actions pour un
événement, on peut encapsuler celles-ci dans un élément action :
<trigger>
<label>Restore limits</label>
<action ev:event="DOMActivate">
<setvalue ref="min" value="0"/>
<setvalue ref="max" value="100"/>
</action>
</trigger>
(L'élément setvalue permet de fixer une valeur dans
l'instance).
Nous présenterons d'autres types d'actions plus loin.
Depuis le chargement initial de l'instance jusqu'à la
soumission, le modèle de traitement de XForms est entièrement fondé sur des événements
conformes à la norme XML Events. On peut capturer presque tous les états de ce
modèle de traitement via ces événements (voir XForms Events Overview
pour plus de détails). La plupart des exemples de ce document emploient l'événement DOMActivate.
Pour plus de détails sur la norme XML Events, on pourra prendre connaissance du document XML Events for HTML Authors.
L'élément switch permet d'exposer ou de masquer
différentes parties d'une interface utilisateur et d'obtenir ainsi un
comportement de type « wizard ». Dans l'exemple ci-dessous, on commence par
demander le nom, la ville et l'adresse électronique. On demande ensuite les
plats, boissons et musiques préférés.


Par défaut, le premier cas est d'abord sélectionné.
L'élément toggle déclenche la sélection d'un autre cas :
<switch>
<case id="start">
<group>
<label>About you</label>
<input ref="name"><label>Name:</label></input>
<input ref="city"><label>City:</label></input>
<input ref="email"><label>Email:</label></input>
</group>
<trigger>
<label>Next</label>
<toggle case="preferences" ev:event="DOMActivate"/>
</trigger>
</case>
<case id="preferences">
<group>
<label>Your preferences</label>
<input ref="food"><label>Food:</label></input>
<input ref="drink"><label>Drink:</label></input>
<input ref="music"><label>Music:</label></input>
</group>
<trigger>
<label>Next</label>
<toggle case="history" ev:event="DOMActivate"/>
</trigger>
</case>
<case id="history">
...
</case>
...
</switch>
On ajoute simplement un déclencheur « retour » de la façon suivante :

<switch>
<case id="start">
...
</case>
<case id="preferences">
<group>
<label>Your preferences</label>
<input ref="food"><label>Food:</label></input>
<input ref="drink"><label>Drink:</label></input>
<input ref="music"><label>Music:</label></input>
</group>
<trigger>
<label>Back</label>
<toggle case="start" ev:event="DOMActivate"/>
</trigger>
<trigger>
<label>Next</label>
<toggle ev:event="DOMActivate" case="history"/>
</trigger>
</case>
<case id="history">
...
</case>
...
</switch>
On peut aussi utiliser la commutation pour implémenter des vues de type « simple/avancé » :


<switch>
<case id="simple">
<input ref="to"><label>To:</label></input>
<input ref="subject"><label>Subject:</label></input>
<trigger>
<label>Advanced >>></label>
<toggle case="advanced" ev:event="DOMActivate"/>
</trigger>
</case>
<case id="advanced">
<input ref="to"><label>To:</label></input>
<input ref="subject"><label>Subject:</label></input>
<input ref="cc"><label>Cc:</label></input>
<input ref="bcc"><label>Bcc:</label></input>
<trigger>
<label><<< Simple</label>
<toggle case="simple" ev:event="DOMActivate"/>
</trigger>
</case>
On peut encore l'utiliser pour des interactions de type « afficher/éditer » :
![]()

<switch>
<case id="show">
<output ref="name"><label>Name:</label></output>
<output ref="city"><label>City:</label></output>
<output ref="email"><label>Email:</label></output>
<trigger>
<label>Edit</label>
<toggle case="edit" ev:event="DOMActivate"/>
</trigger>
</case>
<case id="edit">
<input ref="name"><label>Name:</label></input>
<input ref="city"><label>City:</label></input>
<input ref="email"><label>Email:</label></input>
<trigger>
<label>Done</label>
<toggle case="show" ev:event="DOMActivate"/>
</trigger>
</case>
</switch>
Enfin, on considère l'exemple « Nom et adresse de banque » de la première partie de cette présentation. Nous pouvons le décomposer afin que la partie pré-remplie constitue un premier cas :

Quand le numéro de compte a été rempli, une pression sur le bouton « Find » déclenche la soumission et l'affichage du cas suivant. On voit alors :

<switch>
<case id="start">
<input ref="accountnumber"><label>Account</label></input>
<trigger>
<label>Find</label>
<action ev:event="DOMActivate">
<send submission="prefill"/>
<toggle case="show"/>
</action>
</trigger>
</case>
<case id="show">
<output ref="accountnumber"><label>Account: </label></output>
<input ref="name"><label>Name; </label></input>
<textarea ref="address"><label>Address</label></textarea>
<trigger>
<label>Submit</label>
<action ev:event="DOMActivate">
<send submission="change"/>
<toggle case="start"/>
<setvalue ref="accountnumber"/>
</action>
</trigger>
<trigger>
<label>Cancel</label>
<action ev:event="DOMActivate">
<toggle case="start"/>
<setvalue ref="accountnumber"/>
</action>
</trigger>
</case>
</switch>
En fait, le code ci-dessus est un peu trop simple. Il ne faudrait pas réellement revenir au cas de démarrage tant qu'on ne sait pas si la soumission a réussi. Pour bien faire les choses, on devrait seulement effectuer la soumission puis attendre le signal indiquant son succès. On peut corriger cela en remplaçant le déclencheur « Submit » ci-dessus par le code suivant :
<submit submission="change">
<label>Submit</label>
<action ev:event"xforms-submit-done" ev:observer="change">
<toggle case="start"/>
<setvalue ref="accountnumber"/>
</action>
</submit>
Notons que l'événement xforms-submit-done est envoyé à
l'élément submission. L'observateur n'est donc pas l'élément submit. Il
faut donc explicitement valoriser l'observateur avec la soumission identifiée
par change dans l'élément action.
Le module de répétition peut être utilisé pour mettre en œuvre un comportement de type « panier d'achat » avec des articles que l'on peut ajouter ou supprimer. Par essence, un module de répétition est lié à des données à occurrences multiples dans l'instance.
Pour exemple, nous étudions le cas d'une application de type « TODO » :

Dans ce cas, l'instance consiste en un certain nombre d'occurrences de données : les tâches à réaliser. Chaque occurrence de données se compose d'une description, d'un état et d'une date. On notera la différence entre les éléments de type commande XForms définissant l'interface utilisateur et ceux de type données dans l'instance du modèle XForms. Il s'agit ici de mettre en place une interface utilisateur permettant de modifier ces occurrences de données en utilisant des commandes XForms.
<items>
<todo>
<task>Update website</task>
<status>started</status>
<date>2004-12-31</date>
</todo>
<todo>
...
</todo>
...
</items>
On définit d'abord la structure de données de notre
application dans le modèle XForms. Les valeurs initiales sont obtenues depuis
un fichier. Une soumission est ajoutée qui va nous permettre de sauvegarder
l'instance dans ce même fichier. Nous définissons enfin un type pour le champ
date.
<model> <instance src="todo-list.xml"/> <submission id="save" method="put" action="todo-list.xml" replace="none"/> <bind nodeset="todo/date" type="xsd:date"/> </model>
Dans le corps du document, on relie les commandes XForms de l'interface utilisateur à cette structure de données de la façon suivante :
<repeat nodeset="todo">
<input ref="date"><label>Date</label></input>
<select1 ref="status" selection="open">
<label>Status</label>
<item><label>Not started</label><value>unstarted</value></item>
<item><label>In Progress</label><value>started</value></item>
<item><label>Done</label><value>finished</value></item>
</select1>
<input ref="task"><label>Task</label></input>
</repeat>
Le résultat affiche la liste des tâches à réaliser
existantes et autorise leur édition. On note l'attribut selection=open sur
le select1 qui permet de saisir des valeurs qui n'existent pas dans la
liste des valeurs affichées.
Pour ajouter des occurrences de tâches à réaliser, on
utilise une action insert. Dans le code ci-dessous, le déclencheur d'action
(élément trigger) insère un nouvel élément avant la première occurrence de
la liste de tâches à réaliser (respectivement les attributs
position="before" et at="1") :
<trigger> <label>New</label> <insert nodeset="todo" position="before" at="1" ev:event="DOMActivate"/> </trigger>
Pour ajouter une occurrence en fin de la liste, on insère
celle-ci après la dernière occurrence (respectivement les attributs
position="after" et at="count(todo)") :
<insert nodeset="todo" position="after" at="count(todo)" ev:event="DOMActivate"/>
Un index est associé à chaque module de répétition afin de
pouvoir déterminer son occurrence courante. Il vaut initialement 1 mais peut
être valorisé avec l'index de la ligne associée à une commande XForms présente
dans le module de répétition. On peut aussi le valoriser explicitement via une
action <setindex/>. Enfin, on peut rendre la ligne associée visible en
appliquant un style via le sélecteur CSS ::repeat-index (voir la section style un peu plus loin).
<style type="text/css">
...
::repeat-index {background-color: yellow}
...
</style>
Si le module de répétition possède un identifiant
(attribut id), on peut alors accéder à son index via la fonction
index(). On utilise alors la valeur id pour identifier le module de
répétition. On peut aussi ajouter des occurrences à une répétition sur sa
position courante, ainsi qu'en début ou fin de répétition. Si nous ajoutons un
identifiant à la répétition précédente :
<repeat nodeset="todo" id="todo-repeat">
On peut insérer une nouvelle occurrence après la position
courante (respectivement via les attributs position="after" et
at="index('todo-repeat')") avec le code :
<insert nodeset="todo" position="after" at="index('todo-repeat')" ev:event="DOMActivate"/>
Chaque nouvelle occurrence insérée est initialisée par
défaut avec les valeurs de la dernière occurrence correspondante dans
l'instance initiale. Généralement, on préfère copier ses propres valeurs dans
les nouvelles occurrences insérées. Au lieu d'un simple élément insert, on
encapsule ce traitement dans un élément action et on valorise chaque valeur
de l'occurrence via un élément setvalue :
<trigger>
<label>New</label>
<action ev:event="DOMActivate">
<insert nodeset="todo" position="after" at="count(todo)"/>
<setvalue ref="todo[last()]/status">unstarted</setvalue>
<setvalue ref="todo[last()]/task"/>
<setvalue ref="todo[last()]/date" value="substring-before(now(), 'T')"/>
</action>
</trigger>
Le premier élément setvalue valorise juste l'état de
l'occurrence (status) insérée avec la chaîne de caractères « unstarted ». Le second
valorise la tâche (task) avec une chaîne de caractères vide et le troisième calcule la
date du jour. La fonction now() retourne date et heure au format
« 2005-11-26T09:19:33+1:00 » (il s'agit du format standard décrit par l'ISO).
Ce format consiste en : la date, la lettre T, l'heure locale (de l'ordinateur)
et enfin le décalage entre l'heure locale et le fuseau horaire universel UTC
(+ ou - un nombre d'heures et de minutes, ou Z dans le cas du fuseau horaire
UTC). L'expression substring-before() retourne juste le texte avant la
lettre T qui correspond à la date du jour.
Pour supprimer une occurrence, on peut utiliser ce code-ci pour un nouveau déclencheur placé à coté du bouton « new » :
<trigger>
<label>Delete</label>
<delete nodeset="todo" at="index('todo-repeat')" ev:event="DOMActivate"/>
</trigger>
Toutefois il est préférable de placer ce type de déclencheur à l'intérieur du module de répétition.
On a ainsi un bouton de suppression par occurrence (comme dans l'écran ci-dessus). L'occurrence à
supprimer n'est alors plus à sélectionner (l'occurrence courante du module de
répétition est positionnée sur la ligne du bouton lorsque l'on presse celui-ci).
On peut donc utiliser nodeset="." car le contexte du module de répétition
est correctement positionné :
<repeat nodeset="todo" id="todo-repeat">
<input ref="date"><label>Date</label></input>
<select1 ref="status" selection="open">
<label>Status</label>
<item><label>Not started</label><value>unstarted</value></item>
<item><label>In Progress</label><value>started</value></item>
<item><label>Done</label><value>finished</value></item>
</select1>
<input ref="task"><label>Task</label></input>
<trigger>
<label>Delete</label>
<delete ev:event="DOMActivate" nodeset="." at="index('todo-repeat')" />
</trigger>
</repeat>
Enfin, on place le bouton de sauvegarde du résultat :
<submit submission="save"><label>Save</label></submit>
Dans tous les exemples précédents, les libellés de
l'interface utilisateur (élément label) ont été codés directement dans les
commandes de l'interface utilisateur. Bien sûr, XForms permet de récupérer ces
textes depuis les valeurs d'instance elles-mêmes.
Cette technique requiert la déclaration de plusieurs instances. Cela ne pose pas de problème car on peut avoir autant d'instances que l'on veut dans le modèle.
<model> <instance><data xmlns=""><a/><b/><c/><lang/></data></instance> <instance id="languages"><items xmlns=""><written/><spoken/>...</items></instance> <instance id="currencies" src="currencies.xml"/> ... </model>
Pour identifier l'instance que l'on veut adresser, on
utilise la fonction instance() :
<input ref="instance('languages')/written">...
<output ref="instance('currencies')/eur">...
L'instance par défaut est la première du modèle. Elle est explicitement sélectionnée par les références non décorées comme :
<input ref="a">...
Typiquement, la sélection de valeur dans l'instance se
rencontre sur les éléments select et select1. On veut par exemple
offrir le choix de la langue de l'interface utilisateur via un élément
select1 :
<select1 ref="lang"> <label>Language:</label> <item><label>English</label><value>en</value></item> <item><label>Français</label><value>fr</value></item> <item><label>Deutsch</label><value>de</value></item> </select1>
Au dernier moment, on découvre qu'il faut rajouter une langue dans ce formulaire mais aussi dans plusieurs autres qui offrent le même choix. La meilleure solution est alors d'externaliser cette liste de langue dans un fichier, de le charger dans l'instance et d'y faire ensuite référence :
<instance id="languages" src="languages.xml"/>
Le fichier « languages.xml » contient les éléments suivants :
<languages> <language><name>English</name><code>en</code></language> <language><name>Français</name><code>fr</code></language> <language><name>Deutsch</name><code>de</code></language> </languages>
On peut alors réécrire le select1 pour utiliser
l'instance en remplaçant les éléments <item> par un élément <itemset> :
<select1 ref="lang">
<label>Language:</label>
<itemset nodeset="instance('languages')/language">
<label ref="name"/>
<value ref="code"/>
</itemset>
</select1>
Pour ajouter une langue, il suffit alors d'éditer le fichier « languages.xml » (bien évidemment via un formulaire XForms utilisant un module de répétition). Tous les formulaires incluant ce fichier seront alors mis à jour avec la nouvelle valeur.
De la même façon, le texte des libellés (<label>) peut être placé
dans une instance :
<label ref="instance('labels')/name" />
L'instance a la forme suivante :
<labels> <name>Name:</name> <age>Age:</age> ... </labels>
Le processus de localisation d'un formulaire est alors extrêmement simple.
Bien que l'on puisse utiliser une négociation HTTP pour charger automatiquement la bonne version de la langue des libellés, on peut aussi en faire un choix de l'utilisateur comme dans l'exemple ci-dessous :
<model> <instance id="labels" src="labels.xml"/> <submission id="en" action="labels-en.xml" replace="instance" method="get"/> <submission id="nl" action="labels-nl.xml" replace="instance" method="get"/> </model> ... <submit submission="en"><label>English</label></submission> <submit submission="nl"><label>Nederlands</label></submission>
Une autre option est de centraliser tous les messages de toutes les langues dans une seule ressource. Plusieurs formats sont possibles, par exemple un message dans chaque langue puis le suivant, et ainsi de suite :
<messages>
<message name="name">
<language code="en">Name:</lang>
<language code="nl">Naam:</lang>
<language code="fr">Nom:</lang>
...
</message>
<message name="age">
...
</messages>
Ou tous les messages dans une langue puis tous les messages dans la suivante :
<translations>
<language code="en>
<message name="name">Name:</message>
<message name="age">Age:</message>
...
</language>
<language code="nl">
<message name="name">Naam:</message>
<message name="age">Leeftijd:</message>
...
</translations>
La sélection de la langue de l'interface utilisateur se fait via :
<select1 ref="instance('choices')/lang">
<item><label>English</label><value>en</value></item>
<item><label>Nederlands</label><value>nl</value></item>
...
</select1>
Les libellés peuvent être sélectionnés en fonction de ce choix. Dans le cas d'une hiérarchie message/traduction, on a le code :
<label ref="instance('messages')/message[@name='age']/language[@code=instance('choices')/lang]"/>
Dans le cas d'une hiérarchie langue/message, on a le code :
<label ref="instance('translations')/language[@code=instance('choices')/lang]/message[@name='age']"/>
Avec l'introduction de la sélection multiple via l'élément
<select>, on notera que les valeurs sélectionnées se retrouvent concaténées
sous forme d'une chaîne de caractères. Ainsi, si l'on considère la sélection
multiple ci-dessous :
<select ref="colors"> <label>Colors</label> <item><label>Red</label><value>red</value></item> <item><label>Green</label><value>green</value></item> <item><label>Blue</label><value>blue</value></item> </select>
On récupèrera la chaîne de caractères "red green blue" comme valeur de la donnée référencée colors.
Il en résulte deux inconvénients :
La sélection multiple utilise cette forme de restitution des données pour des raisons de compatibilité avec HTML. Les formulaires XForms doivent pouvoir dialoguer avec des serveurs acceptant des données au format HTML.
Bien entendu, XForms autorise aussi la sélection multiple avec des données structurées dans un format XML qui autorise les espaces dans les données.
Supposons que l'on veuille sélectionner et retourner un ensemble de villes sous la forme :
<instance>
<country xmlns="">
<name>USA</name>
<visited>
<city>Las Vegas</city>
<city>New York</city>
<city>San Francisco</city>
</visited>
</country>
</instance>
Pour cela, on a besoin d'une instance qui conserve les valeurs des villes que l'on veut utiliser :
<instance id="places">
<cities xmlns="">
<city>Atlanta</city>
<city>Boston</city>
<city>Las Vegas</city>
<city>New Orleans</city>
<city>New York</city>
<city>San Francisco</city>
</cities>
</instance>
On référence alors cette instance avec la sélection ci-dessous. Notez l'utilisation de l'élément itemset à la place de
l'élément item, puisque les valeurs proviennent d'une instance. On utilise aussi un élément copy à la place de value
puisque nous copions toute une structure (telle que <city>New York</city> et non juste "New York") :
<select ref="visited">
<label>Cities visited</label>
<itemset nodeset="instance('places')/city">
<label ref="."/>
<copy ref="."/>
</itemset>
</select>
Toutes les commandes XForms sauf <output> peuvent avoir des éléments <help>, <hint>
ou <alert> ainsi qu'un élément <label>. Ces éléments fournissent plusieurs sortes d'informations
supplémentaires à l'utilisateur :
help : information affichée lorsque l'utilisateur demande de l'aide, en pressant la touche d'aide par exemple ;hint : information éphémère, comme une bulle d'aide, qui indique à l'utilisateur comment remplir une valeur ;alert : information affichée lorsque la valeur saisie est invalide.
<input ref="code">
<label>Security code</label>
<hint>The 3 or 4 digit number on the back or front of your card</hint>
<help>This is a three or four digit security code that is usually either
on the front of your card just above and to the right of your
credit card number, or the last three digits of the number printed
on the signature space on the back of the card.</help>
<alert>Must be three or four digits.</alert>
</input>
Sans faire un tutoriel XML Schema, si l'on dispose déjà d'un schéma qui définit quelques types de données,
il suffit alors de l'utiliser dans son formulaire XForms en le référençant depuis l'élément model :
<model schema="http://www.example.com/schemas/types.xsd"> ... </model>
On peut aussi inclure un schéma directement dans le corps
du model :
<model> <instance>...</instance> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">...</xsd:schema> ... </model>
Enfin, XForms dispose d'équivalents pour toutes les facilités fournies par XML Schema pour la définition de type de données à l'exception des
définitions par motifs d'expressions régulières. Avec ce type de définition,
une valeur doit être conforme à une expression régulière. On définit ci-dessous
un nouveau type simple appelé curse par restriction de la plage de valeur du type de base string :
<model>
<instance><data xmlns=""><flw/></data></instance>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
<simpleType name="curse">
<restriction base="xsd:string">
<pattern value="[a-z][a-z][a-z][a-z]"/>
</restriction>
</simpleType>
</schema>
<bind nodeset="flw" type="curse" />
...
</model>
Le support de XML Schema est optionnel au niveau des agents utilisateurs XForms (faute de support XML Schema on parle alors de « XForms Basic »), quoique la plupart des agents supportent l'utilisation de schémas.
Les formulaires sont souvent utilisés pour recueillir des données qui sont liées à la vie privée, telles que nom, adresse, date de naissance, etc.
XForms offre la possibilité de marquer les données privées dans les documents via un ensemble de types de données issus des spécifications P3P.
On met en œuvre ce marquage via des expressions de liaison de type P3P (élément bind et attribut p3ptype) :
<bind nodeset="surname" p3ptype="user.name.family"/> <bind nodeset="tel" p3ptype="user.home-info.telecom.telephone"/>
En plus de la documentation des données relevant de la vie privée, ce type d'expression peut être utilisé par les agents utilisateurs pour pré-renseigner automatiquement des valeurs et alerter de leur utilisation.
On utilise les styles CSS pour régler le rendu des éléments XForms. Toutefois, il faut noter que différents niveaux de version CSS peuvent s'appliquer en fonction des mises en œuvre XForms. Ainsi, on peut être amené à répéter certaines règles de style pour chaque niveau de CSS.
CSS1 et CSS2 ne connaissent pas la notion d'espace de noms. Lorsqu'on écrit une règle de style pour l'élément XForms suivant :
<xf:label>Age:</xf:label>
On est obligé d'écrire ses sélecteur CSS en incluant l'espace de noms en préfixe :
xf\:label {background-color: yellow}
Les feuilles de style CSS3 et ultérieures n'ont pas besoin de ce préfixe, et on peut donc écrire
(à condition de ne pas avoir d'éléments <label> provenant d'autres espaces de noms dans le document) :
label {background-color: yellow}
En CSS3, on dispose de sélecteurs supplémentaires pour traiter certains cas d'utilisation dynamique de XForms. Beaucoup de mises en œuvre XForms supportent ces sélecteurs. On a en particulier :
:valid et :invalid, qui filtrent la validité des types de données et des contraintes exprimées via des expressions de liaisons.
*:invalid { background-color: red}
On notera que lors d'une saisie invalide, l'élément
<alert> sera activé vraisemblablement en utilisant une règle comme celle-ci :
alert {display: none}
*:invalid alert {display: block; border: thin red solid}
L'élément <hint> peut être activé en utilisant une
technique similaire :
hint {display: none}
*:hover hint {display: block}
:in-range et :out-of-range, qui filtrent les commandes range dont les valeurs
d'instance sont en dehors de leurs limites de définition, et les commandes select1 et select
dont la valeur d'instance ne correspond à aucune des occurrences.
:required et :optional, qui filtrent les expressions de liaison « required » :
*:required {border: thin red solid}
:read-only et :read-write, qui filtrent les expressions de liaison « readonly » :
*:read-only {color: gray}
:enabled et :disabled, qui filtrent les expressions de liaison « relevant » :
*:disabled {display: none}
::value, un sélecteur qui désigne la partie de la commande XForms où la valeur est affichée ou tapée :
« input::value {width: 10em}
::repeat-item, qui traite chaque occurrence d'un élément repeat.
::repeat-index, qui traite l'occurrence active d'un élément repeat :
::repeat-index {background-color: #ccf}
Les mises en œuvre XForms qui supportent seulement CSS1 et CSS2 offrent ces fonctionnalités comme des valeurs spéciales de l'attribut class. Par exemple :
input.invalid {border: thin red solid}
Il faudra vérifier lesquelles utiliser dans la documentation de ces mises en œuvre. Toujours est-il que les implémenteurs se sont récemment mis d'accord pour coordonner ces valeurs afin d'utiliser les mêmes noms. Il a en particulier été convenu de préfixer le nom des pseudo-classes par « -pc- » et le nom des pseudo-éléments par « -pe- ». Par exemple :
input.-pc-invalid {border: thin red solid}
.-pe-repeat-item {background-color: yellow}
Une technique classique pour afficher le nombre de personnes ayant visité une page sur un site web est de compter le nombre de hits et d'inclure une image de ce nombre dans la page. On aura recalculé l'image à chaque fois que la page est consultée. Bien entendu, une image de plusieurs milliers d'octets est toujours une façon moins efficace de transférer une douzaine d'octet d'information.
Avec XForms, la technique est plus simple. On conserve juste un fichier avec le nombre de hits :
<n>56356</n>
Ensuite, on importe ce fichier dans l'instance :
<instance src="hits.xml" />
Enfin, on affiche le nombre de hits via une commande output :
<output ref="/n"><label>Number of hits:</label></output>
Si l'on veut utiliser une expression de liaison pour calculer la valeur d'une donnée, comme dans le cas ci-dessous :
<bind nodeset="today" calculate="substring-before(now(), 'T')"/>
La valeur « today » est alors invariablement calculée à partir de cette expression. Elle ne peut pas être
modifiée. En revanche, si l'on veut simplement initialiser une valeur au chargement du
formulaire et autoriser sa modification par l'utilisateur, on peut utiliser une action setvalue,
à l'écoute de l'événement xforms-ready, qui sera envoyée à l'élément model :
<model>
<instance>
<data xmlns="">
<date/>
...
</data>
</instance>
<action ev:event="xforms-ready">
<setvalue ref="date" value="substring-before(now(), 'T')"/>
...
</action>
</model>
Bien qu'elle ne soit pas définie comme telle dans la spécification XForms, une nouvelle tendance des mises en œuvre
est de donner au déclencheur l'aspect d'un texte (trigger appearance="minimal") plutôt que celui d'un bouton.
Ainsi, au lieu des interactions afficher/éditer d'un exemple précédent avec switch,
on peut rendre chaque champ commutable :
<switch>
<case id="showName">
<trigger appearance="minimal">
<label ref="name"/>
<toggle case="editName" ev:event="DOMActivate"/>
</trigger>
</case>
<case id="editName">
<input ref="name">
<label>Name:</label>
<toggle case="showName" ev:event="DOMFocusOut"/>
</input>
</case>
</switch>
On notera l'utilisation de l'événement DOMFocusOut
pour inverser la valeur affichée. L'affichage est inversé lorsqu'on quitte le
champ en fin d'édition.
L'élément switch permet de présenter une interface à onglets
pour des données. On a d'abord des éléments trigger
pour sélectionner un onglet. Puis l'élément switch implémente
les différents rendus en fonction de l'onglet sélectionné. Le reste est affaire
de style :
<trigger id="togglehome" appearance="minimal">
<label>Home</label>
<toggle case="home" ev:event="DOMActivate"/>
</trigger>
<trigger id="toggleproducts" appearance="minimal">
<label>Products</label>
<toggle case="products" ev:event="DOMActivate"/>
</trigger>
<trigger id="togglesupport" appearance="minimal">
<label>Support</label>
<toggle case="support" ev:event="DOMActivate"/>
</trigger>
<trigger id="togglecontact" appearance="minimal">
<label>Contact</label>
<toggle case="contact" ev:event="DOMActivate"/>
</trigger>
<switch>
<case id="home">
<h1>Home</h1>
...
</case>
<case id="products">
<h1>Products</h1>
...
</case>
<case id="support">
<h1>Support</h1>
...
</case>
<case id="contact">
<h1>Contact</h1>
...
</case>
</switch>

Il est courant d'exposer ou de cacher une partie de
l'interface utilisateur via un élément switch. Parfois, il est plus facile
de gérer l'affichage en fonction de valeurs stockées dans l'instance. Avec les mises en œuvre
fondées sur CSS, on peut utiliser une technique appelée
« commutation à base de modèle ».
Il s'agit de lier un élément group à une donnée de
l'instance. En fonction de la valeur de la donnée, le groupe sera pertinent ou
non pertinent. On cachera les groupes non pertinents :
<group ref="..."> ... </group>
Dans la CSS, on a la règle :
group:disabled {display: none}
Par exemple, on ne veut pas poser de question sur le conjoint si la personne n'est pas mariée :
<instance>
<details xmlns="">
<name/>
<age/>
<maritalstatus/>
<spouse>
<name/>
<age/>
...
</spouse>
</details>
</instance>
<bind nodeset="spouse" relevant="../maritalstatus='m'" />
...
<select1 ref="maritalstatus">
<label>Marital status</label>
<item><label>Single</label><value>s</value></item>
<item><label>Married</label><value>m</value></item>
<item><label>Widowed</label><value>w</value></item>
<item><label>Divorced</label><value>d</value></item>
</select1>
...
<group ref="spouse">
<label>Spouse</label>
<input ref="name"><label>Name</label></input>
...
</group>
Avec cette technique, on peut utiliser un déclencheur (élément
trigger) pour modifier la donnée de contrôle et rendre certaines commandes
actives. Ci-dessous, la donnée toogle est utilisée pour contrôler la
visibilité des cas. Elle est initialisée à 1 et la première valeur (case[1]) est pertinente.
Un déclencheur passe la valeur toogle à 2 et rend pertinent le cas qui suit :
<instance id="control">
<cases xmlns="">
<toggle>1</toggle>
<case>1</case>
<case>2</case>
<case>3</case>
<case>4</case>
</cases>
</instance>
<bind nodeset="instance('control')/case" relevant=". = ../toggle"/>
...
<group ref="instance('control')/case[1]">
<input ...>
...
<trigger>
<label>Next</label>
<setvalue ref="instance('control')/toggle" value="2" ev:event="DOMActivate"/>
</trigger>
</group>
<group ref="instance('control')/case[2]">
...
<trigger>
<label>Next</label>
<setvalue ref="instance('control')/toggle" value="3" ev:event="DOMActivate"/>
</trigger>
</group>
...
Parfois on ne souhaite pas voir tous les éléments d'une structure répétitive mais effectuer une sélection, ou voir un résumé et sélectionner des éléments pour une inspection détaillée. On utilise alors des formulaires de type « maître/détail ». Plusieurs solutions sont possibles pour y parvenir. Reprenons et traitons autrement l'exemple précédent de la liste des tâches. Dans celui-ci, on affichait tous les éléments de la liste. Nous en afficherons juste un.
Rappelons la structure de la liste des tâches :
<items>
<todo>
<task>Update website</task>
<status>started</status>
<date>2004-12-31</date>
</todo>
<todo>
...
</todo>
...
</items>
Elle est stockée dans le fichier todo-list.xml :
<instance id="todo" src="todo-list.xml"/>
Cette fois nous utiliserons une deuxième instance pour stocker la valeur indiquant l'occurrence en cour de consultation :
<instance id="admin">
<data xmlns="">
<index>1</index>
</data>
</instance>
En utilisant cet index, on peut consulter une unique occurrence de la liste des tâches :
<group ref="todo[position()=instance('admin')/index]">
<output ref="date"/>
<output ref="status"/> <output ref="task"/>
</group>
Ce qui provoque l'affichage de la seule première occurrence :
![]()
On peut ajouter des commandes pour passer d'une occurrence à l'autre :
<group ref="todo[position()=instance('admin')/index]">
<trigger>
<label><</label>
<setvalue ev:event="DOMActivate" ref="instance('admin')/index" value=". - 1"/>
</trigger>
<output ref="date"/>
<output ref="status"/>
<output ref="task"/>
<trigger>
<label>></xforms:label>
<setvalue ev:event="DOMActivate" ref="instance('admin')/index" value=". + 1"/>
</trigger>
</group>
On peut maintenant parcourir la liste des occurrences une par une en cliquant sur les boutons :
![]()
Logiquement l'expression todo[position()=instance('admin')/index] n'affichera rien
si index est inférieur à 1 ou supérieur au nombre d'occurrences de la liste des tâches.
On doit donc désactiver les boutons lorsqu'on atteint les extrémités de la liste.
Pour cela, on ajoute deux nouveaux éléments à l'instance admin, en même temps qu'une
paire d'expressions de liaison :
<instance id="admin">
<data xmlns="">
<index>1</index>
<notfirst/>
<notlast/>
</data>
</instance>
<bind nodeset="instance('admin')/notfirst" relevant="../index > 1"/>
<bind nodeset="instance('admin')/notlast" relevant="../index < count(instance('todo')/item)"/>
Nous ne nous soucions pas de la valeur des deux nouveaux éléments (qui est une chaîne vides),
seulement de leur pertinence. Ainsi, l'élément nofirst est pertinent
quand index est supérieur à 1, l'élément nolast quand
index est inférieur au nombre d'occurrences de la liste.
Leur présence est nécessaire pour y lier les déclencheurs :
<group ref="todo[position()=instance('admin')/index]">
<trigger ref="instance('admin')/notfirst">
<label><</label>
<setvalue ev:event="DOMActivate" ref="instance('admin')/index" value=". - 1"/>
</trigger>
<output ref="date"/>
<output ref="status"/>
<output ref="task"/>
<trigger ref="instance('admin')/notlast">
<label>></xforms:label>
<setvalue ev:event="DOMActivate" ref="instance('admin')/index" value=". + 1"/>
</trigger>
</group>
Lorsque index vaut 1, le premier bouton sera désactivé
car notfirst ne sera pas pertinent :
![]()
De même, le second bouton sera désactivé pour la dernière occurrence de la liste des tâches.
A partir de cette structure de base, on peut traiter l'occurrence sélectionnée : l'éditer, la visualiser et ainsi de suite.
Une autre approche est de sélectionner la tâche à afficher
à l'aide d'un élément select1 :

Pour cela, on stocke la tâche sélectionnée dans l'instance admin :
<instance id="admin">
<data xmlns=""><selected/></data>
</instance>
Le select1 y stocke le résultat et récupère les tâches dans le fichier des tâches (todo-list.xml) ;
le libellé et la valeur sont la même chose :
<select1 ref="instance('admin')/selected">
<label>What</label>
<itemset nodeset="instance('todo')/todo">
<label ref="task"/>
<value ref="task"/>
</itemset>
</select1>
On peut alors utiliser la tâche sélectionnée pour afficher tous les détails de l'occurrence :
<group ref="todo[task=instance('admin')/selected]">
<output ref="task"/>
<output ref="status"/>
<output ref="date"/>
</group>
Notez que si plusieurs tâches ont le même titre seule la première sera affichée.
On peut corriger ce problème en remplaçant group par repeat :
<repeat nodeset="todo[task=instance('admin')/selected]">
<output ref="task"/>
<output ref="status"/>
<output ref="date"/>
</repeat>
En suivant plus ou moins la même approche, on peut avoir
une vue maître/détail avec une boîte de recherche. On utilise alors un champ de saisie (input)
à la place de l'élément select1 :
<input ref="instance('admin')/selected">
<label>What</label>
</input>
Comme nous voulons voir toutes les occurrences qui correspondent à la saisie,
on utilise encore un élément repeat :
<repeat nodeset="todo[contains(task,instance('admin')/selected)]">
<output ref="task"/>
<output ref="status"/>
<output ref="date"/>
</repeat>
Cela sélectionne toutes les tâches dont la description contient la chaîne de caractères sélectionnée :

Si on ajoute une propriété incremental=true sur
l'élément input, le résultat de l'élément repeat sera mis à jour en
même temps que la saisie !
<input incremental="true" ref="instance('admin')/selected">
<label>What</label>
</input>
Notez que la recherche dépend de la casse :


En effet, la fonction contains() de XPATH est sensible
à la casse. Pour avoir une recherche indépendante de la casse, il faut utiliser
la fonction translate() :
translate(string, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
Celle-ci retourne la chaîne de caractères passée en premier
paramètre avec toutes les lettres en majuscules remplacées par des minuscules.
On écrira donc l'expression contains() comme suit :
contains(translate(task, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),
translate(selected, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))

Finalement, on peut combiner les deux approches : sélectionner une tâche par son titre ou se déplacer dans la collection avec les boutons.

L'instance de données reste la même :
<instance id="todo" src="todo-list.xml"/>
L'instance admin est plus ou moins la même, avec un élément gardant la tâche sélectionnée,
hormis les deux éléments ajoutés pour la pertinence du déclenchement, comme dans le premier exemple :
<instance id="admin">
<data xmlns=""><selected/><notfirst/><notlast/></data>
</instance>
Le select1 est exactement le même, mais encadré des deux boutons, l'un pour l'occurrence précédente et l'autre pour la suivante.
Pour l'occurrence précédente, nous voulons valoriser la chaîne selected dans l'instance admin
avec la tâche de l'occurrence précédente :
<trigger>
<label><</label>
<action ev:event="DOMActivate">
<setvalue ref="instance('admin')/selected" value="...quelque chose ici..."/>
</action>
</trigger>
Maintenant, qu'y a-t'il dans "...quelque chose ici..." ? Nous savons comment trouver l'élément de la liste des tâches qui correspond à la tâche sélectionnée :
todo[task=instance('admin')/selected]
Pour trouver l'occurrence précédente dans la liste des tâches,
on utilise la fonction preceding-sibling qui retourne la liste de
toutes les occurrences avant l'occurrence sélectionnée :
todo[task=instance('admin')/selected]/preceding-sibling::todo
puis on trouve la première occurrence (c'est-à-dire le premier nœud « frère » précédent) :
todo[task=instance('admin')/selected]/preceding-sibling::todo[1]
Et enfin on sélectionne le champ tâche de cette occurrence :
todo[task=instance('admin')/selected]/preceding-sibling::todo[1]/task
Le déclencheur pour trouver la prochaine occurrence est exactement le même, sauf que l'on remplace
preceding-sibling par following-sibling.
Pour finir, que faut-il lier à notfirst et notlast pour rendre les déclencheurs inopérants
pour la première et la dernière occurrence ? Le bouton précédent n'est pertinent que s'il y a des occurrences précédentes :
<bind nodeset="instance('admin')/notfirst"
relevant="instance('todo')/todo[task=instance('admin')/selected]/preceding-sibling::todo"/>
Pour notlast, on remplace encore preceding-sibling par following-sibling.
Tout ça mis bout à bout :
<model>
<instance id="todo" src="todo.xml" />
<instance id="admin">
<data xmlns="">
<notfirst/>
<selected/>
<notlast/>
</data>
</instance>
<bind nodeset="instance('admin')/notfirst"
relevant="instance('todo')/todo[task=instance('admin')/selected]/preceding-sibling::todo"/>
<bind nodeset="instance('admin')/notlast"
relevant="instance('todo')/todo[task=instance('admin')/selected]/following-sibling::todo"/>
</model>
...
<trigger ref="instance('admin')/notfirst">
<label><</label>
<action ev:event="DOMActivate">
<setvalue
ref="instance('admin')/selected"
value="instance('todo')/todo[task=instance('admin')/selected]/preceding-sibling::todo[1]/task"/>
</action>
</trigger>
<select1 ref="instance('admin')/selected">
<label>What</label>
<itemset nodeset="instance('todo')/todo">
<label ref="task"/>
<value ref="task"/>
</itemset>
</select1>
<trigger ref="instance('admin')/notlast">
<label>></label>
<action ev:event="DOMActivate">
<setvalue
ref="instance('admin')/selected"
value="instance('todo')/todo[task=instance('admin')/selected]/following-sibling::todo[1]/task"/>
</action>
</trigger>
<group ref="todo[task=instance('admin')/selected]">
<output ref="task"/>
<output ref="status"/>
<output ref="date"/>
</group>