XForms pour les auteurs HTML, 2 parties

 

Steven Pemberton, W3C/CWI

 

Statut du document traduit

 

Ceci est une traduction du 2éme volet d'un document du W3C traitant de la transition des formulaires HTML vers les formulaires XFORMS.

Cependant, il ne s'agit pas de la 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

 

Avertissement

 

Des erreurs ont pu survenir malgré le soin apporté à ce travail.

 

Notes sur la traduction

 

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.

 

Adresse : <A REMPLACER>

Traducteur : Olivier GUERIN (guerin.o@noos.fr)

Correcteur(s) : Eric FULLENBAUM

Date de traduction : 02 Mars 2009

Dernière mise à jour : 22 Mars 2009

Archives compressées et autres formats

Cette traduction est disponible au format HTML sous forme d'archive compressée et, le cas échéant, dans d'autres formats à l'adresse <A REMPLACER>.


 

Avis legal

 

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.

 

Introduction

 

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 n’ayant pas d’équivalent en HTML.


Table des matières

 

Statut du document traduit 1

Avertissement 1

Notes sur la traduction. 1

Avis legal 2

Introduction. 2

Table des matières. 3

Evénements et actions. 4

Le module de commutation [ndt. Switch] 7

Le module de répétition [ndt. Repeat ] 12

Ajouter des occurrences dans une répétition. 13

L’occurrence courante. 13

Initialiser les occurrences insérées. 14

La suppression d'item dans une répétition. 15

Obtenir la valeur de l'interface utilisateur depuis le modèle. 16

Sélectionner de multiples valeurs contenant des espaces. 19

Aide, bulle et alerte. 20

Définir ces propres types. 21

Valeurs privées en référence [ndt. Privacy related values] 22

Application de style. 22

Quelques techniques. 24

Compteur de pages. 24

Initialiser les valeurs d'instance. 24

Application de styles sur les boutons [ndt. Trigger styling ] 25

Navigation tabulée. 26

Commutation basée sur le modèle [ndt. Model-based switching ] 27

Vue maître et détail 1. 29

Vue maître et détail 2. 32

Vue maître et détail 3. 33

Vue maître et détail 4. 34

 


 

Evénements et actions

 

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 2 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 l’événement « clic » est appliqué aux éléments « em » ou « strong ». Nous désignons alors l'élément sur lequel on a cliqué comme la cible, et l'élément qui répond à l'événement comme 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 :

 

·        Le nom de l'événement est inscrit dans la syntaxe du langage, au lieu d’en être un paramètre. Pour être en mesure de traiter un nouveau type d'événement, vous devez ajouter un nouvel attribut. Par exemple, l’attribut « onflash » si on introduisait un événement « flash ».

·        Le nom de l'événement est habituellement très lié aux périphériques, tel « cliquer » pour une sélection à l’aide d’une souris. En fait on ne veut pas savoir comment le bouton est activé, mais seulement qu'il a été activé.

·        On ne peut utiliser qu’un seul langage de script (on ne peut pas avoir 2 attributs nommés « onclick », un avec du code « JavaScript » et un autre avec du code « VBScript »).

·        La gestion d'événements et le balisage sont mélangés et il n'existe pas de moyen pour les séparer.

 

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 (l’élément « trigger » dans ce cas). On utilise l’événement « DOMActivate » de préférence à l’événement « onclick » sur les boutons (élément « trigger ») car ils peuvent être déclenchés de différentes façon et pas uniquement en cliquant.

 

Les éléments ayant des attributs définis dans l’espace de nom « 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 événement ont comme préfixe « ev » qui correspond à l'espace de nom associé à la norme XML EVENTS. Il existe une déclaration pour ce préfixe de la forme « xmlns:ev=http://www.w3.org/2001/xml-events » en début du document.

 

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 plus d'une action 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 basé 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 le document « XForms Events Overview » pour plus de détail). La plupart des exemples de ce document mettent en œuvre l’événement « DOMActivate ».

 

Pour plus de détail sur la norme XML EVENTS, on pourra prendre connaissance du document « XML Events for HTML Authors ».


Le module de commutation [ndt. Switch]

 

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 bouton « 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 « simples/avancés » :

 

 


 

<switch>

   <case id="simple">

      <input ref="to"><label>To:</label></input>

      <input ref="subject"><label>Subject:</label></input>

      <trigger>

         <label>Advanced &gt;&gt;&gt;</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>&lt;&lt;&lt; Simple</label>

         <toggle case="simple" ev:event="DOMActivate"/>

      </trigger>

   </case>

 

 

On peut encore l’utiliser pour des interactions de type « visualisation/modification » :

 

 

 

<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 la décomposer en deux de sorte que la partie pré-remplie soit 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 :

 

·        Le numéro de compte.

·        Le nom et l'adresse de la personne associée à ce numéro de compte, qu’on peut modifier.

·        Un bouton « Submit » qui soumet les modifications, réinitialise le numéro de compte et revient au cas initial.

·        Un bouton « Cancel » qui fait la même chose sans la soumission.

 


 

<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. On ne veut pas réellement revenir au cas de démarrage tant qu’on ne sait pas si la soumission a réussi. Pour faire cela correctement, on doit seulement soumettre et attendre le signal indiquant le succès de la soumission. On peut faire cela en remplaçant le déclencheur de soumission (élément « trigger » de libellé « 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 [ndt. Repeat ]

 

Le module de répétition peut être utilisé pour implémenter un comportement de type « panier d'achat » avec des occurrences pouvant être ajoutées ou supprimées. 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ée est composée 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 connecte 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.

 

Ajouter des occurrences dans une répétition

 

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âche à 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 première occurrence (respectivement les attributs « position="after" » et « at="count(todo)" ») :

 

<insert nodeset="todo" position="after" at="count(todo)" ev:event="DOMActivate"/>

 

L’occurrence courante

 

Un index est associé à chaque module de répétition afin de pouvoir retrouver 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. Il peut être explicitement valorisé 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 un module de répétition possède un identifiant (attribut « ID »), on peut alors accéder à son index via la fonction « index() ». On utile alors la valeur de « 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"/>

 

Initialiser les occurrences insérées

 

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</xf: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 insérée avec la chaîne de caractères « unstarted ». Le second valorise la tâche 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 universelle 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.

 


 

La suppression d'item dans une répétition

 

Pour supprimer une occurrence, on peut utiliser le code ci-dessous sur un nouveau bouton placé à coté du bouton « new » :

 

<trigger>

   <label>Delete</label>

   <delete nodeset="todo" at="index('todo-repeat')" ev:event="DOMActivate"/>

</trigger>

 

Il peut toutefois être plus judicieux de placer ce type de bouton à l'intérieur du module de répétition. On obtient 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 lorsqu'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>


 

Obtenir la valeur de l'interface utilisateur depuis le modèle

 

Dans tous les exemples ci-dessus, les libellés de l’interface utilisateur (élément « label ») ont été codés directement dans les commandes de l’interface utilisateur. Bien sur, XFORMS permet de récupérer ces textes depuis l'instance de donnée elle-même.

 

Cette technique requiere la déclaration de plusieurs instances. Cela ne pose pas de problème car on peut avoir autant d'instance 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">...

 

La première instance du modèle est l’instance par défaut. 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 fichier mais aussi dans plusieurs autres offrant le même choix. La meilleur solution est alors d'externaliser cette liste de langue dans un fichier, de le charger dans l'instance et de faire ensuite des références à l'instance :

 

<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 des éléments « 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 » (évidement via un formulaire XFORMS utilisant un module de répétition). Tous les formulaires incluant ce fichier seront alors mise à jour avec la nouvelle valeur.

 

De la même façon, le texte des libellés peut être placé dans l'instance :

 

<label ref="instance('labels')/name" />

 

L’instance a alors la forme suivante :

 

<labels>

   <name>Name:</name>

   <age>Age:</age>

   ...

</labels>

 

Le processus de localisation d'un formulaire est alors extrêmement simple.

 

La négociation HTTP de la langue peut être utilisée pour charger automatiquement la bonne version 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 ressource. Plusieurs formats sont possibles pour cette ressource. Elle peut être organisée suivant une hiérarchie message/traduction :

 

<messages>

   <message name="name">

      <language code="en">Name:</lang>

      <language code="nl">Naam:</lang>

      <language code="fr">Nom:</lang>

      ...

   </message>

   <message name="age">

      ...

</messages>

 

Elle peut aussi être organisée suivant une hiérarchie langue/message :

 

<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 sur la base 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']"/>

 


 

Sélectionner de multiples valeurs contenant des espaces

 

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 peut récupérer 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 :

 

Tout d'abord, ce n'est pas très orienté XML. En plus, il sera nécessaire ultérieurement de découper la chaîne de caractères représentant les valeurs sélectionnées.

 

Ensuite, il n'est pas possible d'inclure des valeurs qui contiennent des espaces. Ainsi, on ne peut pas sélectionner une liste de ville comme « New York, San Francisco, Las Vegas ».

 

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 au format XML. Ce format de données autorise les espaces dans les données.


 

Supposons qu'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 mémorisant 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 dans la sélection ci-dessous. On note l’utilisation de l’élément « itemset » à la place de l’élément « item » pour récupérer les occurrences sélectionnables depuis l'instance. On utilise aussi un élément « copy » à la place de « value » pour copier toutes la structure au lieu de simplement ces valeurs (« <city>New York</city> » à la place de « New York »).

 

<select ref="visited">

   <label>Cities visited</label>

   <itemset nodeset="instance('places')/city">

      <label ref="."/>

      <copy ref="."/>

   </itemset>

</select>

 

Aide, bulle et alerte

 

Tout les commandes XFORMS à l'exception de « output » peuvent avoir des éléments « help », « hint » ou « alert » à l'identique de « 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 HELP par exemple.

·        « hint » : information éphémère, comme une bulle d'aide, qui indique à l'utilisateur comment il doit 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>

 

 

 

Définir ces propres types

 

Concernant la norme XSD, il est possible de référencer dans l'élément « model » des types de données définis via un schéma :

 

<model schema="http://www.example.com/schemas/types.xsd">

   ...

 

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'équivalence à toutes les facilités fournies par XSD 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 XSD est optionnel au niveau des agents utilisateurs XFORMS et la plupart des agents ne le supportent pas. On parle alors de « XForms basic » (support de XFORMS sans XSD).

 

Valeurs privées en référence [ndt. Privacy related values]

 

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, ...

 

XFORMS offre la possibilité de marquer les données privées dans les documents via un ensemble de type de données issu des spécifications P3P.

 

On met en œuvre ce marquage via des expressions de liaisons 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 XFORMS pour pré-renseigner des valeurs automatiquement et lever des alertes sur leurs utilisations.

 

Application de style

 

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 implémentations de XFORMS. Ainsi, on peut être amené à répéter certaines règles de styles pour chaque niveau de CSS.

 

CSS1 et CSS2 ne connaissent pas la notion d'espace de nom. Lorsqu'on écrit une règle de style pour l’élément XFORMS suivant :

 

<xf:label>Age:</xf:label>

 

On est obligé d'écrire nos sélecteur CSS en incluant l’espace de nom en préfixe :

 

xf\:label {background-color: yellow}

 

CSS3 et les versions supérieures de CSS n'ont pas besoin de ce préfixe. On peut donc écrire à condition de ne pas avoir d'élément « label » provenant d'un autre espace de nom dans notre document :

 

label {background-color: yellow}

 

En CSS3, on dispose de sélecteurs supplémentaires pour adresser certains cas d'utilisation dynamique de XFORMS. La plupart des implémentations de XFORMS supportent ces sélecteurs. On a en particulier :

 

« :valid » et « :invalid » qui adressent la validité des données vis-à-vis de leurs types et des contraintes exprimées sur celles-ci 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 adressent les commandes « range » dont les valeurs d'instance sont en dehors de leurs bornes de définition. Ils adressent aussi les commandes « select1 » et « select » dont les occurrences ne correspondent à aucune valeur dans l'instance.

 

« :required » et « :optional » qui adressent les expressions de liaisons « required » :

 

*:required {border: thin red solid}

 

« :read-only » et « :read-write » qui adressent les expressions de liaisons « readonly » :

 

*:read-only {color: gray}

 

« :enabled » et « :disabled » qui adressent les expressions de liaisons « relevant » :

 

*:disabled {display: none}

 

Le selecteur « ::value » désigne la partie de la commande XFORMS où la valeur est affichée ou typée (par exemple « input::value {width: 10em} »).

 

« ::repeat-item » adresse chaque occurrence de l’élément « repeat ».

« ::repeat-index » adresse l'occurrence active de l’élément « repeat » :

 

::repeat-index {background-color: #ccf}

 

Les implémentations de XFORMS qui supportent seulement CSS1 et CSS2 offrent ces fonctionnalités comme des attributs de classes particuliers :

 

input.invalid {border: thin red solid}

 

Il est nécessaire de vérifier les attributs de classes à utiliser dans la documentation de ces implémentations. A noter, les fournisseurs implémentant des processeurs XFORMS ont récemment accepté de se coordonner sur ces attributs 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- » :

 

input.-pc-invalid {border: thin red solid}

.-pe-repeat-item {background-color: yellow}

 

 

 

Quelques techniques

Compteur de pages

 

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>

 

Initialiser les valeurs d'instance

 

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 de la donnée référencée « today » est alors invariablement calculée à partir de cette expression. Elle ne peut pas être modifiée.

 

Pour simplement initialiser une valeur au chargement du formulaire et autoriser sa modification ultérieurement par l'utilisateur, on peut utiliser une action « setvalue » sur l'événement « xforms-ready ». On placera cette action dans 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>

 

Application de styles sur les boutons [ndt. Trigger styling ]

 

Bien qu'elle ne soit pas définie comme telle dans la spécification XFORMS, une nouvelle pratique des différentes implémentations de XFORMS est d'utiliser une apparence textuelle plutôt qu'un bouton pour l'élément « trigger appearance="minimal" ». Ainsi dans l'exemple ci-dessous, on peut remplacer l'utilisation du style visualiser/éditer du « switch » par des champs commutables :

 

<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.

 


 

Navigation tabulée

 

L'élément « switch » permet de présenter une interface tabulée pour des données (onglets). On a d'abord les éléments « trigger » qui permettent de sélectionner un onglet. Ensuite, 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>

 


 

Commutation basée sur le modèle [ndt. Model-based switching ]

 

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 sur la base de valeurs stockées dans l'instance. Avec une implémentation basée sur CSS, on peut utiliser cette technique appelée « commutation basée sur le 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 une action (é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 pertinence des groupes (groupe à afficher). Elle est initialisée à 1 et la première occurrence de « case» est initialement pertinente. L'action passe la valeur « toogle » à 2 et rend le cas suivant pertinent :

 

<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>

...

 


 

Vue maître et détail 1

 

Avec un formulaire de type maître/détail, on veut sélectionner une occurrence à partir d'un sommaire pour inspection au lieu d'afficher tout les occurrences d'une structure de données. Il existe plusieurs solutions pour réaliser ce type de formulaire. On va reprendre l'exemple de la liste « TODO » en affichant juste un élément de la liste (au lieu de tous les éléments).

 

La structure de la liste « TODO » était la suivante :

 

<items>

   <todo>

      <task>Update website</task>

      <status>started</status>

      <date>2004-12-31</date>

   </todo>

   <todo>

      ...

   </todo>

   ...

</items>

 

Elle persiste dans le fichier « todo-list.xml » :

 

<instance id="todo" src="todo-list.xml"/>

 

Par contre, on utilisera 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 « TODO » :

 

<group ref="todo[position()=instance('admin')/index]">

    <output ref="date"/> <output ref="status"/> <output ref="task"/>

</group>

 

Ce qui provoque l’affichage de la première occurrence :

 

 


 

On ajoute des commandes pour passer à l'occurrence suivante ou précédente :

 

<group ref="todo[position()=instance('admin')/index]">

    <trigger>

        <label>&lt;</label>

         <setvalue ev:event="DOMActivate"

              ref="instance('admin')/index" value=". - 1"/>

    </trigger>

    <output ref="date"/> <output ref="status"/> <output ref="task"/>

    <trigger>

        <label>&gt;</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] » ne doit rien afficher si « index » est inférieur à 1 ou supérieur au nombre d’occurrences de la liste « TODO ». On doit donc désactiver les boutons lorsqu'on atteint les extrémités de la liste. Pour cela, on ajoute 2 nouveaux éléments dans l'instance « admin » avec un couple d'expression de liaisons :

 

<instance id="admin">

    <data xmlns="">

        <index>1</index>

        <notfirst/>

        <notlast/>

    </data>

</instance>

<bind nodeset="instance('admin')/notfirst"

      relevant="../index &gt; 1"/>

<bind nodeset="instance('admin')/notlast"

      relevant="../index &lt; count(instance('todo')/item)"/>

 


 

Nous ne nous soucions pas de la valeur des deux nouveaux éléments car ils nous intéressent uniquement lorsqu'ils sont pertinents. Ainsi, l'élément « nofirst » est pertinent quand « index » est plus grand que 1. De même, l'élément « nolast » est pertinent quand il est inférieur au nombre d'occurrences de la liste. Ils sont utilisés pour contrôler l'état des boutons :

 

<group ref="todo[position()=instance('admin')/index]">

    <trigger ref="instance('admin')/notfirst">

        <label>&lt;</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>&gt;</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é sur la dernière occurrence de la liste « TODO ».

 

A partir de cette structure de base, on peut traiter l'occurrence sélectionnée : l’éditer, la visualiser et ainsi de suite ...

 

Vue maître et détail 2

 

Une autre approche est de sélectionner la tâche à afficher avec un élément « select1 » :

 

 

Pour cela, on va mémoriser la tâche sélectionnée dans l'instance « admin » :

 

<instance id="admin">

   <data xmlns=""><selected/></data>

</instance>

 

Le « select1 » stocke le résultat dans l'instance « admin » et récupère les tâches depuis le fichier « todo-list.xml » :

 

<select1 ref="instance('admin')/selected">

   <label>What</label>

   <itemset nodeset="instance('todo')/todo">

      <label ref="task"/>

      <value ref="task"/>

   </itemset>

</select1>

 

On peut utiliser la tâche sélectionnée pour afficher ces détails :

 

<group ref="todo[task=instance('admin')/selected]">

   <output ref="task"/>

   <output ref="status"/>

   <output ref="date"/>

</group>

 


 

On notera que l'on affiche uniquement la première tâche d'un groupe de tâches ayant le même titre. 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>

 

 

Vue maître et détail 3

 

En utilisant 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 à la place de l’élément « select1 » :

 

<input ref="instance('admin')/selected">

   <label>What</label>

</input>

 

Attention, on doit utiliser un élément « repeat » si l'on veut voir l'ensemble des occurrences qui correspondent à la saisie :

 

<repeat nodeset="todo[contains(task,instance('admin')/selected)]">

   <output ref="task"/>

   <output ref="status"/>

   <output ref="date"/>

</repeat>

 

Ci-dessous, on 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>

 


 

On note que la recherche est sensible à la différence majuscule/minuscule :

 

 

 

En effet, la fonction « contains() » de XPATH est sensible à la casse. Pour avoir une recherche non sensible à la casse, il faut utiliser la fonction « translate() » :

 

translate(string, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')

 

Elle retourne la chaîne de caractères passée en premier paramètre avec toutes les majuscules remplacées par  des minuscules. L'expression de recherche peut ainsi être recoder comme suit :

 

contains(translate(task, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),

         translate(selected, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))

 

 

 

Vue maître et détail 4

 

Finalement, on peut combiner les 2 approches : sélectionner une tâche sur la base de son titre ou traverser la collection avec les boutons.

 

 

L'instance de donnée reste la même :

 

<instance id="todo" src="todo-list.xml"/>

 

L'instance « admin » est plus ou moins la même, avec la donnée qui mémorise la tâche sélectionnée. On y ajoute toutefois 2 données pour gérer la pertinence des boutons, comme dans le premier exemple :

 

<instance id="admin">

   <data xmlns=""><selected/><notfirst/><notlast/></data>

</instance>

 

Le « select1 » est exactement le même, mais on l'encadre avec les boutons occurrence précédente et suivante. Pour l'occurrence précédente, nous voulons valoriser la tâche sélectionnée dans l'instance « admin » avec la tâche de l'occurrence précédente :

 

<trigger>

    <label>&lt;</label>

    <action ev:event="DOMActivate">

        <setvalue

             ref="instance('admin')/selected"

             value="...quelque chose ici..."/>

    </action>

</trigger>

 

Maintenant, que vaut "...quelque chose ici..." ? Nous savons comment trouver l’élément to-do qui correspond à la tâche sélectionnée :

 

todo[task=instance('admin')/selected]

 

Pour trouver l'occurrence précédente dans la liste TODO, nous utilisons la fonction « preceding-sibling » qui retourne une liste de toutes les occurrences avant l'occurrence sélectionnée :

 

todo[task=instance('admin')/selected]/preceding-sibling::todo

 

Pour trouver la première occurrence (le premier nœud « frère » précédent), on utilise :

 

todo[task=instance('admin')/selected]/preceding-sibling::todo[1]

 

Pour sélectionner le champ tâche de cette occurrence, on utilise :

 

todo[task=instance('admin')/selected]/preceding-sibling::todo[1]/task

 

Le bouton pour trouver la prochaine occurrence est exactement le même à ceci près qu’on remplace « preceding-sibling » par « following-sibling ».

 

Finalement, on veut simplement lier l'affichage de boutons à l'absence de premier ou de dernier élément. Le bouton précédent sera pertinent seulement s'il y a des occurrences précédentes disponibles :

 

 <bind

  nodeset="instance('admin')/notfirst"

  relevant="instance('todo')/todo[task=instance('admin')/selected]/preceding-sibling::todo"/>

 


 

En remplaçant l'appel à « preceding-sibling » par « following-sibling », on obtient le code suivant :

 

<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>&lt;</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>&gt;</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>