Skip to content

Gestion d'états

Modèle d'états

Ewt permet de décrire un ou plusieurs modèles d'états au format XML. On appelle modèle d'états l'enchaînement de plusieurs états qui définissent ainsi un cycle de vie de dossier. Précisons au passage qu'une application Ewt peut parfaitement fonctionner sans gestion d'états.

Le modèle d'états permet de décrire des règles d'enchaînement d'états. Cela se fait au moyen de transitions, auxquelles on associe un nom (attribut name) et un état cible (target).

Lorsque l'on souhaite mettre en place une gestion d'états, on commence par définir des modèles d'états (ou cycles de vie) sous la forme de fichiers XML dans le sous-dossier states de l'application. Chaque fichier XML permet de décrire un modèle d'état. Exemple:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<states name="simple" changemode="both">
    <state name="traitement" class="initial"
           onleave="stateLeave">
        <transition name="vendeur.archiver" target="archive"/>
    </state>
    <state name="archive" class="final"/>
</states>

Le modèle d'états doit être identifié au moyen d'un nom (attribut name). Ce nom servira au référencement du modèle d'état depuis le modèle de dossier (fichier descript.xml).

En option, on peut également définir un attribut changemode qui permet de personnaliser le mode de changement d'état que l'on souhaite autoriser. Il est possible de changer d'état de différentes façons:

  1. De façon explicite : on indique explicitement quel nouvel état le dossier doit avoir
  2. Par transition : on active une transition et le dossier prendra l'état cible associé à cette transition
  3. Par événement : un événement déclenche une transition (ce cas peut être associé au mode précédent)

Le changement d'état peut également être réalisé par un script (via les méthodes $doc.setState ou $doc.fireStateTransition), mais il s'agit ici d'un cas particulier sur lequel nous revenons plus bas.

L'attribut peut donc prendre les valeurs name (ou state, ou explicit), transition ou both, cette dernière étant la valeur par défaut.

États

Chaque état est décrit au moyen d'un élément state et doit avoir un attribut name qui permet de nommer l'état. Il est également possible de spécifier une classe (attribut class) et des transitions (éléments transition).

La classe permet de spécifier si l'état est l'état initial (classe initial) et/ou l'état final (classe final). Un état peut être à la fois initial et final, auquel cas on notera class="initial final" (il est aussi possible de séparer les termes au moyen d'une virgule).

Il est possible d'associer des événements à un état. Un événement est une sorte de notification (en référence aux notifications décrites dans le fichier de configuration), c'est-à-dire une référence de script qui sera exécuté automatiquement par le moteur dans un contexte particulier. Le moteur prévoir deux types d'événements à associer aux états

  • onleave: cet événement est déclenché lorsque le dossier quitte un état donné
  • onenter: cet événement est déclenché lorsque le dossier arrive dans un état donné

Ces attributs permettent de spécifier un ou plusieurs noms de scripts (séparés par une virgule) à évaluer lorsque l'événement se produit. Les scripts sont évalués selon le même principe que les notifications déclarées dans le config.xml. Veuillez lire la note plus bas concernant la cohabitation entre les notifications de la configuration et les événements du modèle d'états.

Comme dans le cas des notifications, les événements ont la possibilité d'interrompre le changement d'état en invoquant la méthode $thread.abortNotifiedAction(). L'exécution du script peut également être contrainte à l'aide de l'annotation @accept. Cela évite que le script ne soit évalué dans un autre contexte qu'une notification.

@accept(trigger = "notification")

Transitions

Les transitions indiquent les règles de passage d'un état à l'autre. Chaque transition doit au minimum indiquer un nom (attribut name) et l'état de destination (attribut target).

Il est possible de spécifier un événement à déclencher lors de la transition via l'attribut ontransition. Cet attribut indiquera le nom d'un ou de plusieurs scripts à évaluer lorsque la transition est invoquée. Les noms de scripts peuvent être séparés par une virgule, par exemple:

<transition name="ticket.cloturer" target="ticketTermine"
            ontransition="stateChange,finalizeTicket"/>

Il est également possible de spécifier des événements onleave et onenter sur une transition. Lorsqu'ils sont définis, ces derniers surchargent les événements correspondants des états source/destination lorsque la transition est invoquée. Cela signifie que si l'application déclenche une transition, le moteur évaluera, dans l'ordre:

  1. L'événement onleave de l'état de départ
  2. L'événement ontransition de la transition
  3. L'événement onenter de l'état d'arrivée

Les étapes 1 et 3 ci-dessus peuvent être modifiées voire neutralisées en redéfinissant les attributs correspondants au niveau de la transition.

Transitions publiques/privées

Par défaut, les transitions sont publiques, ce qui signifie qu'elles sont déclenchables par un utilisateur depuis l'interface web via l'action setState.

Il est possible de définir des transitions non publiques en ajoutant un attribut public="false" sur la transition. Les transitions non publiques ne pourront pas être déclenchées explicitement par l'utilisateur. Elles
seront uniquement déclenchables par un script.

Pour comprendre l'idée, voyons l'exemple suivant: un dossier "Demande" peut prendre les états "création", "accepté" ou "refusé". À partir de l'état "création", le changement d'état vers "accepté" ou "refusé" dépend de règles avancées définies au sein d'un script. L'utilisateur n'est pas en mesure de déterminer quelle transition appliquer. Il peut uniquement déclencher l'exécution du script qui décidera lui-même si le changement d'état est possible et, le cas échéant, vers quel état ce changement doit être fait.

En pratique, on représentera ce cas de figure ainsi:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<states name="demande" xmlns:i18n="ch.epilogic.ewt.i18n">
    <state name="creation" class="initial">
        <transition name="demande.accepter" target="accepte" public="false"/>
        <transition name="demande.refuser" target="refuse" public="false"/>
        <properties>
            <property name="action" i18n:label="states.demande.action.traiter"><![CDATA[ 
                ewt.submit('script', { name: 'toolboxDemande', parameters: { action: 'traiter-demande' } });
            ]]></property>
        </properties>
    </state>
    <state name="accepte">
        <!-- ... -->
    </state>
    <state name="refuse">
        <!-- ... -->
    </state>
    <!-- ... -->
</states>

Événements d'état et notifications

Ewt permet de définir des notifications doc-leavestate et doc-enterstate dans le fichier de configuration, ainsi que des événements1 au niveau des éléments <state> ou <transition> du modèle d'états. Les notifications du config et les événements d'états remplissent le même rôle. La majeure différence se situe au niveau de la condition de déclenchement:

  • Les notifications du config sont déclenchées lors d'une action envoyée par le client (l'utilisateur) via une requête HTTP. Le moteur envoie les notifications doc-leavestate et doc-enterstate pour tout changement d'état.

    Les notifications ne sont par défaut pas envoyées automatiquement lors des changements d'états déclenchés par script via les méthodes $doc.setState ou $doc.fireStateTransition. La notification peut toutefois être forcée via le paramètre notify de ces méthodes.

  • Les événements d'état ou de transition sont déclenchés uniquement lors d'un changement porte sur l'état ou la transition pour lesquels ils sont définis.

    Au contraire des notifications, les événements sont automatiquement déclenchés lors d'un changement d'état par script. Les méthodes $doc.setState ou $doc.fireStateTransition permettent toutefois de piloter le déclenchement d'événements au moyen du paramètre events.

Lorsque les notifications et les événements sont activés simultanément, les opérations sont réalisées dans l'ordre suivant:

  1. notification doc-leavestate:before
  2. notification doc-enterstate:before
  3. événement onleave
  4. événement ontransition
  5. événement onenter
  6. notification doc-leavestate:after
  7. notification doc-enterstate:after
Notification Événement
Déclaration Déclaré via les notifications doc-leavestate et doc-enterstate du config.xml Déclaré via les attributs onleave, onenter et ontransition des états et transitions d'un modèle d'état
Changement d'état par action Déclenché lors de tout changement d'état Uniquement déclenché lors d'un changement relatif à l'état ou la transition concernée
Changement d'état par script Non déclenché automatiquement Déclenché automatiquement si le changement porte sur l'état ou la transition concernée

Propriétés

De façon analogue à la descript (fichier descript.xml), le modèle d'états et ses éléments (state et transition) peuvent définir des propriétés. La syntaxe et les namespaces utilisables sont les mêmes que pour la descript. Voici un exemple dans lequel on utilise des propriétés pour lier une icone et une couleur aux états, et extraction des valeurs depuis la base de données. Ces dernières seront reprises dans l'arbre de sortie.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<states name="ticket"
        xmlns:i18n="ch.epilogic.ewt.i18n"
        xmlns:text="ch.epilogic.ewt.text"
        xmlns:sql="ch.epilogic.ewt.sql">
    <state name="ticketCreation" class="initial">
        <transition name="ticket.ouvrir" target="ticketOuvert"/>
        <transition name="ticket.standby" target="ticketStandby"/>
        <properties>
            <property name="icone">add_box</property>
            <property name="couleur">#716a65</property>
        </properties>
    </state>
    <!-- ... -->
    <properties>
        <property name="foo">
            <sql:extraction>
                <value><![CDATA[ select '...' from SomeTable ]]></value>
            </sql:extraction>
        </property>
    </properties>
</states>

Référencement de modèle d'état

Pour appliquer un modèle d'états à un modèle de dossier, il suffit de référencer le premier dans le second à l'aide de l'attribut statesmodel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<descript>
    <models>
        <model name="vendeur" maingroup="info" mainfield="idVendeur"
               statesmodel="simple" statefield="idStatut">
            ...
            <groups>
                <group name="info" table="Vendeur" type="single">  
                    <field name="idStatut" type="hidden" column="idStatut"/>
                </group>
            </groups>
            ...
        </model>
    </models>
</descript>

Un modèle d'états n'est pas forcément lié à un et un seul modèle de dossier. Plusieurs modèles de dossier peuvent référencer un même modèle d'états.

En plus du modèle d'états, le modèle de dossier qui souhaite appliquer une gestion d'états doit fournir un champ de statut. Il s'agit d'un champ de la base de données qui servira à enregistrer l'état du dossier. Pour ce faire, on spécifie le nom de champ en question au moyen de l'attribut statefield dans le fichier descript.xml.

Internationalisation

La gestion des libellés reprend le même principe que celui qui vaut pour le fichier de descript, à savoir: les libellés peuvent être définis inline sur les états et transitions via un attribut label ou à l'aide des bundles de langues. Il en va de même pour les descriptions (attribut description).

Lorsque les libellés doivent être internationalisés, il est recommandé de s'appuyer sur les bundles de langue. Ewt prévoit un format d'étiquettes pour les éléments des bundles de langues. Les libellés décrivant les états et les transitions sont à spécifier au niveau des bundles de l'application.

Par convention, une étiquette désignant un nom d'état doit avoir la forme suivante:

states.<states-model-name>.state.<state-name>.label

<states-model-name> est le nom du modèle d'états et <state-name> est le nom de l'état.

Pour les transitions, il existe deux syntaxes d'étiquettes possibles: la version standard et la version précise.

La version standard utilise la notation suivante:

states.<states-model-name>.transition.<transition-name>.label

<transition-name> est le nom de la transition.

La version précise peut être utilisée s'il existe plusieurs transitions de même nom et qu'on souhaite leur attribuer un libellé spécifique selon l'état de départ. La version précise reprend le nom de l'état dans le libellé, ce qui donne:

states.<states-model-name>.state.<state-name>.transition.<transition-name>.label

state-name est le nom de l'état à partir duquel la transition est effectuée.

La notation précise prime sur la notation standard qui n'inclut pas le nom d'état.

Prenons l'exemple suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="utf-8"?>
<states name="ticket">
   <state name="preparation" class="initial">
       <transition name="ticket.ouvrir" target="ouvert"/>
   </state>
   <state name="ouvert">
       <transition name="ticket.cloturer" target="termine"/>
       <transition name="ticket.traiter" target="traitement"/>
   </state>
   <state name="traitement">
       <transition name="ticket.cloturer" target="termine"/>
       <transition name="ticket.tester" target="test"/>
   </state>
   <state name="test">
       <transition name="ticket.cloturer" target="termine"/>
       <transition name="ticket.valider" target="resolu"/>
   </state>
   <state name="resolu">
       <transition name="ticket.cloturer" target="termine"/>
   </state>
   <state name="standby">
       <transition name="ticket.activer" target="traitement"/>
       <transition name="ticket.cloturer" target="termine"/>
   </state>
   <state name="termine" class="final">
       <transition name="ticket.modifier" target="traitement"/>
   </state>
</states>

Pour renseigner les libellés de chaque élément, on pourra par exemple utiliser la définition suivante:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# états
states.ticket.state.preparation.label = Préparation
states.ticket.state.ouvert.label = Ouvert
states.ticket.state.traitement.label = Traitement
states.ticket.state.test.label = Test
states.ticket.state.resolu.label = Résolu
states.ticket.state.standby.label = Standby
states.ticket.state.termine.label = Terminé

# transistions
states.ticket.transition.ticket.ouvrir.label = Ouvrir
states.ticket.transition.ticket.cloturer.label = Clôturer
states.ticket.transition.ticket.traiter.label = Traiter
states.ticket.transition.ticket.tester.label = Tester
states.ticket.transition.ticket.valider.label = Valider
states.ticket.transition.ticket.activer.label = Activer
states.ticket.state.standby.transition.ticket.cloturer.label = Abandonner
states.ticket.transition.ticket.modifier.label = Modifier

Dans l'exemple ci-dessus, on surcharge le libellé de la transition "ticket.cloturer" pour l'état "standby" par un libellé différent.

Le principe de nommage est le même pour la description : l'attribut description permet de spécifier un texte explicatif sur l'état ou la transition. Ce libellé peut être défini au niveau des bundles de langues. Dans ce cas les noms d'étiquettes sont les mêmes, à la différence que le suffixe .label est changé en .description.


  1. Le terme "événement" est utilisé ici dans le but de clarifier le propos (et de reprendre la nomenclature de la norme SCXML de laquelle Ewt s'inspire), mais en réalité événements et notifications fonctionnent selon le même principe du point de vue du moteur.