Skip to content

Descript

Structure

La descript se présente comme un arbre XML et est constituée de différents types d'éléments. Elle a la structure suivante:

descript
 +- models
 |   +- model
 |   |   +- groups
 |   |   |   +- group
 |   |   |   |   +- fields
 |   |   |   |   |   +- field
 |   |   |   |   |       +- properties
 |   |   |   |   |       |   +- property
 |   |   |   |   |       +- policies
 |   |   |   |   |           +- policy
 |   |   |   |   +- properties
 |   |   |   |   |   +- property
 |   |   |   |   +- policies
 |   |   |   |       +- policy
 |   |   +- properties
 |   |   |   +-property
 |   |   +- policies
 |   |       +- policy
 +- properties
 |   +- property
 +- policies
     +- policy
  • L'élément racine s'appelle <descript>
  • Les éléments <policies> peuvent être placés au niveau de la descript, des modèles, des groupes ou des champs. Ils permettent de spécifier les règles de sécurités relatives à la lecture/écriture.
    À noter que les policies définies au niveau du nœud <descript> sont traitées comme celles définies au niveau du fichier de configuration de l'application.
  • Les éléments <model> décrivent les modèles de dossiers que gère l'application. Le modèle est la description de la structure d'un dossier. Vu autrement, le dossier est une instance de modèle.
  • Les éléments <group> sont des regroupements de champs. Ces regroupements de champs sont appelés tuples. Donc le groupe est la description de la structure d'un tuple. Vu autrement, le tuple est une instance de groupe.

    Il existe 3 types de groupes:

    • les groupes single constitués d'un seul tuple
    • les groupes multi qui peuvent avoir 0 ou n tuples (ces groupes permettent de décrire des tables de valeurs)
    • le groupe principal (maingroup) : il s'agit du groupe dont l'identifiant (la clé primaire) fait également office d'identifiant de dossier; dans les faits, le groupe principal est forcément un groupe de type single

    Exception faite du groupe principal, les groupes single et multi sont structurellement assez similaires : ils sont constitués de tuples qui possèdent tous une clé primaire et qui référencent le groupe principal (pour permettre la liaison des tuples d'un même dossier). Du point de vue logique, la seule réelle différence vient du fait que le moteur crée automatiquement les tuples single à la création du dossier et qu'il empêche l'ajout et la suppression de tuples single.

  • Les éléments <field> décrivent les champs de l'application

  • La hiérarchie des éléments doit être respectée (si un élément <field> est placé en-dehors d'un <group>, il sera ignoré)
  • L'ordre des éléments joue un rôle pour la construction de l'arbre de sortie du moteur: les éléments (propriétés, modèles, groupes, fields) seront repris dans l'arbre dans le même ordre que celui de la descript.
  • Les éléments <properties> peuvent être placés au niveau de la descript, des modèles, des groupes ou des champs. Par ailleurs, les propriétés peuvent contenir du xml: celui-ci sera repris au niveau de l'arbre de sortie. À noter toutefois qu'il est possible de demander à Ewt d'adapter certains éléments. Le chapitre relatif aux propriétés ci-dessous donne plus de détails.

Dans la suite de la documentation, nous reviendrons fréquemment sur les notions de modèle, dossier, groupe, tuple, etc. La table ci-dessous illustre la correspondance entre un terme utilisé au niveau de la description (fichier descript.xml) et le terme correspondant pour désigner une instance. Par exemple, le dossier est une instance d'un modèle, le tuple est une instance de groupe, etc.

Description Instance
Modèle Dossier
Groupe Tuple
Champ Valeur

Modèle (<model>)

L'élément <model> permet de décrire un modèle. Un modèle est un ensemble de champs structurés en groupes. Le moteur s'appuie sur la description des modèles pour déterminer la représentation mémoire des données à traiter.

Chaque modèle reconnaît les éléments suivants:

  • attribut name: nom du groupe (nom de code interne à l'application)

  • attribut label: facultatif, libellé décrivant le modèle. Cet attribut permet de spécifier le libellé à reprendre dans l'arbre de sortie dans le cas où aucun libellé d'internationalisation n'est trouvé dans le bundle associé à la descript. L'attribut peut également être déclaré dans le namespace ch.epilogic.ewt.i18n si on veut forcer une référence de bundle de ressources.

  • attribut description: facultatif, texte court décrivant le modèle. Cet attribut permet de spécifier le descriptif à reprendre dans l'arbre de sortie dans le cas où aucun libellé d'internationalisation n'est trouvé dans le bundle associé à la descript. L'attribut peut également être déclaré dans le namespace ch.epilogic.ewt.i18n si on veut forcer une référence de bundle de ressources.

  • attribut maingroup: nom du groupe principal, c.-à-d. le groupe de type single dont le tuple sera le tuple principal du dossier. L'identifiant du dossier reprend ainsi l'identifiant de son tuple principal.

  • attribut statesmodel: facultatif, nom du modèle d'états à appliquer pour les dossiers du modèle courant; l'attribut fait référence à un modèle d'état

  • attribut statefield: facultatif, nom du champ dans lequel est enregistré le statut du dossier; le champ doit exister dans le groupe principal (maingroup).

  • attribut autoflush: facultatif, liste d'actions pour lesquelles on souhaite activer l'auto-flush. L'auto-flush est une fonctionnalité consistant à sauvegarder automatiquement les données d'un dossier en base de données lors de n'importe quelle action déclenchée depuis l'interface utilisateur. Ainsi, les données sont automatiquement sauvegardées à la fermeture d'un dossier, lors d'un ajout de tuple, etc. Par défaut l'auto-flush est activé pour toutes les actions. Il est cependant possible de spécifier explicitement les actions pour lesquelles on autorise l'auto-flush. Le cas échéant, les actions doivent être inscrites sous la forme d'une chaîne séparée par une virgule, par exemple "save,close" si on souhaite que le flush se fasse lors de l'enregistrement et lors de la fermeture d'un dossier. La liste des actions est disponible dans le chapitre qui traite des identity policies dans le document Gestion d'accès - Policies. À noter que l'action "save" est implicite : elle effectue toujours un flush (c'est son rôle) indépendamment de la valeur de l'attribut autoflush.

  • attribut locktype: facultatif, type de lock à utiliser pour les dossiers du modèle. L'attribut peut prendre l'une des valeurs suivantes:

    • optimistic (en réalité, n'importe quelle valeur différente des autres valeurs ci-dessous): gestion optimiste de la concurrence. C'est le mode par défaut si l'attribut locktype n'est pas spécifié.
    • pessimistic (en réalité, n'importe quelle valeur qui débute par le caractère p): gestion pessimiste de la concurrence
    • none ou false: pas de gestion de la concurrence

    Ces notions sont détaillées dans le chapitre Gestion de la concurrence.

  • attribut locklevel: facultatif, niveau de lock attendu pour les dossiers du modèle. L'attribut peut prendre l'une des valeurs suivantes:

    • tuple (ou toute autre valeur différente de celles ci-dessous): gestion de lock au niveau tuple. Dans ce cas, un conflit de concurrence détecté lors d'une mise à jour n'impacte que le tuple en question. Les autres tuples du dossier sont mis à jour lorsque cela est possible. Il s'agit du mode par défaut si l'attribut n'est pas spécifié.
    • doc ou document: gestion de lock au niveau document. Dans ce cas, un problème de concurrence détecté au niveau d'un des tuples du dossier entraîne le rollback de toutes les requêtes de mises à jour des tuples (aucun tuple n'est mis à jour)
    • none ou false: pas de test de concurrence. Écrasement de valeur existante ou mode "Client Wins" / "Last in Wins".

    Ces notions sont détaillées dans le chapitre Gestion de la concurrence.

  • attribut lockforce: facultatif, flag true/false indiquant que le modèle autorise l'utilisateur à forcer un enregistrement de valeurs en base de données en cas de problèmes de concurrence détecté, en effectuant en second save des valeurs; cela ne concerne que la gestion optimiste

  • attribut indexname: facultatif, nom de l'index dans lequel les dossiers doivent être indexés

  • attribut indexstore: facultatif, flag true/false indiquant que la valeur des champs doit être stockée ou non dans l'index (voir le chapitre Indexation et recherche pour comprendre ce que cela signifie)

  • attribut indexmode: facultatif, mode d'indexation des valeurs (voir Indexation et recherche)

  • attribut policy: facultatif, nom de la ou des policies à appliquer au modèle. Il est possible de référencer plusieurs policies en les séparant par une virgule. Les policies référencées via cet attribut s'ajoutent à celles définies ou référencées via l'élément policies

  • élément policies: références ou descriptions de policies qui prévalent pour le modèle et les groupes qu'il contient

  • élément groups: groupes de champs du modèle

  • élément views: types de vues du modèle

Groupe (<group>)

L'élément <group> représente un ensemble de champs. Un groupe est généralement lié à une table définie dans le fichier de description de la base de données (voir fichier schema.xml).

Il est important de voir qu'un groupe ne peut pas réunir des champs de plusieurs tables. Il est par contre possible de construire un groupe qui ne soit lié à aucune table.

Il existe 2 types de groupes:

  1. Les groupes single: un groupe single est un groupe dans lequel il ne peut exister qu'une seule instance de valeur par champ au niveau d'un dossier. Dans le cas d'un dossier "Personne", un champ single sera donc par exemple son nom ou sa date de naissance.
  2. Les groupes multi: un groupe multi est un groupe dans lequel il peut exister de 0 à n valeurs par champ au niveau d'un même dossier. Si on reprend l'exemple d'un dossier "Personne", on peut imaginer des groupes multi tels que "Adresses", "Enfants", "Comptes", etc. Dans chacun de ces groupes on pourra avoir entre 0 et n entrées.

Les attributs et éléments reconnus associés au groupe sont les suivants:

  • attribut name: nom du groupe, servant à identifier ce dernier

  • attribut type: type de groupe; le groupe peut être de type single ou multi. Un groupe single contient un et un seul tuple alors qu'un groupe multi peut en contenir 0 ou n.

    Chaque dossier doit avoir un groupe principal de type single, c'est-à-dire un groupe dont le tuple contient les données de base du dossier. L'identifiant du tuple fait de facto office d'identifiant du dossier.

  • attribut reffield (voir également l'encadré plus bas): Les groupes multi ne pouvant pas être main group, ils doivent obligatoirement spécifier comment ils sont rattachés au main group. Pour cela, la définition du groupe devra spécifier un attribut reffield qui indique quel champ du groupe multi contient la référence vers le main group.

    Les groupes single qui ne sont pas main group doivent également utiliser cet attribut pour spécifier le lien avec le groupe principal.

  • attribut table: optionnel; table qui enregistre les données des tuples du groupe.

    La plupart du temps, on associera un groupe à une et une seule table de base de données. Celle-ci doit être déclarée dans le fichier de schéma (schema.xml). Il est toutefois possible de définir un groupe sans table, par exemple si on souhaite définir des champs qui sont uniquement des éléments d'interfaces (boutons, libellés statiques, etc.) ou des champs qui ont uniquement une valeur stockée en mémoire.

  • attribut mainfield (voir également l'encadré plus bas): optionnel; champ du groupe faisant office d'identifiant au sein du dossier. Si non défini, le moteur se rabat sur le champ dont la colonne associée est la primary key (à condition que celle-ci ne repose que sur une seule colonne)

  • attribut label: optionnel; libellé par défaut à utiliser pour désigner le groupe lorsque l'application ne fournit pas d'élément d'internationalisation pour ce dernier. Dit autrement, cet attribut permet de spécifier un libellé par défaut pour désigner le groupe au niveau de l'interface utilisateur. L'attribut peut également être déclaré dans le namespace ch.epilogic.ewt.i18n si on veut forcer une référence de bundle de ressources.

  • attribut description: optionnel; texte descriptif par défaut relatif au groupe. Il s'agit d'un libellé détaillant le rôle du groupe. À noter que ce libellé est automatiquement substitué par le moteur si un descriptif est disponible dans les bundles de langues de l'application. L'attribut peut également être déclaré dans le namespace ch.epilogic.ewt.i18n si on veut forcer une référence de bundle de ressources.

  • attribut indexstore: optionnel; catalogue d'index associé au groupe. Cet attribut remplit le même rôle que pour le niveau model et vient surcharger ce dernier s'il est défini.

  • attribut indexmode: optionnel; mode d'index associé au groupe. Cet attribut remplit le même rôle que pour le niveau model et vient surcharger ce dernier s'il est défini.

  • attribut standalone: optionnel; flag indiquant que les tuples peuvent exister de façon indépendante du dossier. Concrètement, ce flag permet d'indiquer que le tuple n'appartient pas exclusivement au dossier.

    Il joue un rôle important dans un cas bien précis que nous détaillons ici. Ewt permet de référencer des tuples de dossiers tiers et de les intégrer au sein de groupes multi. La documentation de la méthode $msg.remapTargets présente un cas d'utilisation. Pour résumer, prenons le cas d'un dossier "demande" qui possède un identifiant idDemande et une référence de dossier client idClient. Depuis le dossier client, on peut reprendre le dossier de demande au sein d'un groupe multi simplement en liant la table single de la demande au niveau du groupe multi et en déclarant idDemande comme mainfield et idClient comme reffield.

    Cette construction est fonctionnelle, mais présente des effets de bord car le dossier client croit que les tuples "demandes" lui appartiennent. La méthode $msg.remapTargets explique l'effet de bord du point de vue de la gestion d'état. On peut mentionner un autre effet de bord plus problématique avec la suppression de dossier: en effet, le fait de supprimer le tuple de la demande. Cela peut amener à des dossiers incomplètement supprimés au niveau des demandes (en particulier si celle-ci contenait à son tour d'autres groupes).

    Le flag standalone, lorsqu'il est activé à true, influe sur le comportement du moteur vis-à-vis des tuples du groupe. En particulier, le moteur ne cherchera pas à supprimer les tuples lors d'un delete du dossier, il bloquera les addTuple, les delTuple ainsi que les travaux relatifs au groupe lors des clone.

    Par défaut le flag est false.

  • attribut policy: optionnel; peut être utilisé pour référencer une ou plusieurs policies à appliquer au groupe. Il est possible de référencer plusieurs policies en les séparant par une virgule. Les policies référencées via cet attribut s'ajoutent à celles définies ou référencées via l'élément policies.

  • élément policies: références ou description de policies qui prévalent pour le groupe et les champs qu'ils contient

  • élément fields: champs du groupe

primary key, reffield et mainfield

Définitions

Le moteur utilise les notions de primary key, de reffield et de mainfield. Afin de lever toute ambiguïté à leur sujet, nous allons ici les décrire brièvement.

La primary key (ou clé primaire) est une notion spécifique à la gestion de la base de données. De ce fait, la notion appraît uniquement au niveau du fichier de description de la base de données (schema.xml). La clé primaire est l'élément qui permet d'identifier de façon unique une entrée dans une table. Elle peut être constituée d'une ou plusieurs colonnes. En pratique, un groupe single devrait toujours s'appuyer sur une table possédant une clé primaire constituée d'une colonne unique. Un groupe multi quant à lui peut s'appuyer sur une table possédant une clé primaire constituée d'une ou deux colonnes.

Le reffield est une notion logique qui est propre à la description métier de l'application (descript.xml). Cet attribut permet d'indiquer quel champ fait le lien avec le groupe principal. Il n'est donc pas nécessaire sur le groupe principal, mais uniquement sur les autres groupes. Le reffield sera généralement associé à une colonne qui, au niveau du schéma, possède un attribut fk (ou FK, ou foreignReference) pointant vers le mainfield du groupe principal.

Le mainfield est une notion logique qui est propre à la description métier de l'application (descript.xml). Le mainfield est le champ qui identifie le tuple au sein de son groupe. Il ne s'agit pas forcément d'un identifiant unique au sein de la table associée, mais d'un identifiant qui permet de distinguer les tuples d'un même dossier. Dans le cas du groupe principal, le mainfield prend également le rôle d'identifiant de dossier.

On peut se représenter le rôle du mainfield en considérant les expressions de contextes utilisées dans les applications Ewt. Prenons l'exemple suivant:

vente[1000].article[12].prix

Cette expression désigne le prix de l'article 12 de la vente 1000. La valeur 1000 est la valeur du mainfield du groupe principal. La valeur 12 est la valeur du mainfield du groupe article.

Au sein d'un groupe multi lié à une table possédant une clé primaire constituée de deux colonnes, le couple reffield/mainfield devrait être l'équivalent logique de la clé primaire de la table associée: le champ reffield enregistrera le numéro de dossier alors que le champ mainfield enregistrera le numéro de ligne au sein du dossier.

Mise en application

Dans une application Ewt, on peut avoir différents types de groupes. Nous passons ces types en revue à travers l'extrait de description ci-dessous. Afin de pouvoir nous focaliser sur l'objet du propos, nous avons volontairement simplifié la syntaxe de descript dans l'exemple ci-dessous:

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<model name="personne" maingroup="base">
    <!-- Groupe principal. Ici nous désignons le champ "idPersonne" comme mainfield -->
    <group name="base" table="Personne" type="single"
           mainfield="idPersonne">
        <fields>
            <field name="idPersonne" type="hidden" column="IdPersonne"/>
        </fields>
    </group>

    <!-- Groupe single lié à la même table que le groupe principal. Ici 
         nous référençons le champ *idPersonne* à la fois en tant que 
         "reffield" (car nous ne sommes pas dans le champ principal) et
         comme "mainfield" (car il sert à identifier le tuple au sein du
         dossier) -->
    <group name="extras" table="Personne" type="single"
           mainfield="idPersonne" reffield="idPersonne">
        <fields>
            <field name="idPersonne" type="hidden" column="IdPersonne"/>
        </fields>
    </group>

    <!-- Groupe single indépendant de la table du groupe principal. Ici 
         le "reffield" référence l'identifiant de dossier (c.-à-d. 
         l'identifiant du groupe principal) et le "mainfield" référence 
         l'identifiant du groupe lui-même. -->
    <group name="ident" table="PersonneIdent" type="single"
           mainfield="IdPersonneIdent" reffield="idPersonne">
        <fields>
            <field name="idPersonne" type="hidden" column="IdPersonne"/>
            <field name="idPersonneIdent" type="hidden" column="IdPersonneIdent"/>
        </fields>
    </group>

    <!-- Groupe multi. Les groupes multi doivent indiquer les champs 
         jouant le rôle de "mainfield" et de "reffield" -->
    <group name="autorisations" table="PersonneAutorisation" type="multi"
           mainfield="IdPersonneAutorisation" reffield="idPersonne">
        <fields>
            <field name="idPersonne" type="hidden" column="IdPersonne"/>
            <field name="idPersonneAutorisation" type="hidden" column="IdPersonneAutorisation"/>
        </fields>
    </group>
</model>

Tri, filtre et pagination

Les notions de tri, de filtre et de pagination sont regroupées sous le terme générique d'arrangement, qui prend en français le sens d'organisation, de disposition ou d'agencement. Un arrangement est donc une façon d'agencer les tuples à l'écran.

On définit une règle d'arrangement au niveau du groupe via l'élément <arrangement>. L'élément est composé de trois parties: sort, filter et pagination. Les types d'arrangement sort et filter s'appuient exclusivement sur des champs du groupe. Chaque champ du groupe pourra à son tour spécifier comment la valeur doit être intégrée dans la clause de tri ou de filtre, ce qui permet de construire des clauses de tri et de filtre avancées (voir chapitre Champ (<field>)).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<arrangement>
    <sort customizable="true">
        <field name="position" order="desc"/>
        <field name="description" order="asc"/>
    </sort>
    <filter customizable="true">
        <field name="position" method="range" type="auto" mode="text">
            <value>0</value>
            <value></value>
        </field>
        <field name="date" method="contains" type="auto" mode="sql">
            <value>select Valeur from Configuration where Param=?</value>
            <param type="varchar">#info.Remarque</param>
        </field>
    </filter>
    <pagination customizable="true">
        <offset mode="text"><value>0</value></offset>
        <limit mode="text"><value>30</value></limit>
    </pagination>
</arrangement>

Tri (<sort>)

Exemple de règles de tri:

1
2
3
4
<sort customizable="true">
    <field name="position" order="desc"/>
    <field name="description" order="asc"/>
</sort>

L'attribut customizable="true" indique que l'on autorise l'utilisateur à modifier les règles de tri.

L'ordre des éléments joue un rôle important : le moteur conserve l'ordre des éléments lorsqu'il construit la clause ORDER BY.

En l'absence de règle d'arrangement de type sort, le moteur applique automatiquement une clause ORDER BY basée sur l'identifiant de tuple afin que l'ordre des tuples reste constant.

Filtre (<filter>)

Une référence de champ permet d'indiquer sur quel champ doit porter le filtre. Comme le filtre est lui-même susceptible de contenir une liste de valeurs et/ou une valeur par défaut, on peut considérer la référence de champ comme un champ à part entière. Ainsi, certaines propriétés d'un champ de filtre utilisent la même syntaxe que les champs de données eux-même, en particulier pour la définition d'options et de valeur par défaut.

Exemple de règles de filtre:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<filter customizable="true">
    <field name="position" method="exact" type="auto" class="both">
        <default mode="text">
            <value>1</value>
        </default>
    </field>
    <field name="date" method="range" type="auto" mode="sql">
        <default mode="sql">
            <value>select Valeur from Configuration where Param=?</value>
            <param type="varchar">#info.Remarque</param>
        </default>
    </field>
</filter>

L'attribut customizable="true" indique que l'on autorise l'utilisateur à modifier les filtres de colonnes.

Les attributs name et method sont obligatoires pour chaque référence de champ.

L'attribut name indique le nom du champ sur lequel porte le filtre. Il doit correspondre à un champ du groupe.

L'attribut method permet d'indiquer la méthode de filtre à appliquer. Il peut prendre les valeurs suivantes:

  • exact : Cette méthode filtre en appliquant un test d'égalité stricte.
  • contains: Cette méthode applique un filtre de type like
  • range : Cette méthode filtre de manière différente en fonction du nombre de paramètres qu'on lui passe:

    • Lorsque 2 paramètres sont spécifiés, le filtre effectue un test between sur les deux valeurs : seules les valeurs situées entre les bornes sont retenues.
    • Lorsque seul le premier paramètre est défini, le filtre effectue un test de type >=.
    • Lorsque seul le second paramètre est renseigné, le filtre effectue un test de type <=.
  • list : Ce type applique un filtre de type in.

Il est possible de spécifier que l'on souhaite appliquer une logique inversée. Ainsi, la présence de l'attribut inverse="true" fait fonctionner les méthodes de filtre en mode inverse:

  • inverse exact : Revient à faire un test !=
  • inverse contains : Revient à faire un test de type not like
  • inverse range : Revient à faire un test not between, < ou > en fonction du nombre et des valeurs de paramètres
  • inverse list : Revient à faire un test not in.

Les règles de filtre font toujours référence à un champ (référence faite par l'attribut name). La valeur de référence quant à elle peut être définie sous forme de texte (mode="text"), au moyen d'une requête SQL (mode="sql") ou au moyen d'un script (mode="script"). Dans les modes sql et script, le moteur se charge de l'évaluation afin de déterminer la valeur à utiliser comme condition.

L'attribut type joue le même rôle que dans le cas des champs (voir Champ (<field>)) : il permet d'indiquer le type de champ que l'interface doit afficher. Si la valeur de l'attribut est auto (ou si l'attribut n'est pas spécifié), alors le type reprend le type du champ référencé.

L'attribut class permet d'indiquer la classe de filtre. Il existe deux classes, et un filtre peut appartenir à l'une, l'autre ou les deux:

  • variable : Cette classe indique que le champ fait partie des éléments personnalisables par l'utilisateur. La classe variable est active par défaut lorsque l'attribut class n'est pas spécifié.
    Le moteur reprendra dans l'arbre de sortie uniquement les filtres variables.

  • permanent : Cette classe indique que le filtre est permanent, c'est-à-dire qu'il est appliqué en permanence et n'est pas modifiable par l'utilisateur. On indiquera un filtre permanent par exemple lorsque l'on souhaite qu'un groupe ne soit constitués que d'un sous-ensemble de tuples d'une table. Le filtre en question peut par exemple tenir compte des rôles de l'utilisateur connecté: ainsi le groupe n'affichera que des tuples que l'utilisateur est autorisé de voir et/ou modifier.
    Les filtres purement permanents ne sont pas repris dans l'arbre de sortie du moteur.

Il est possible de spécifier class="both" ou class="variable,permanent" (les deux notations sont équivalentes) pour indiquer que le filtre appartient aux deux classes.

Remarque sur la notation

L'utilisation d'une classe combinée (avec both ou variable,permanent) permet de simplifier la notation. Précisons cependant qu'il est également possible de faire coexister les filtres permanent et variable au sein de références de champs distinctes.

L'exemple donné plus haut utilise la classe both pour le champ de filtre position. On aurait également pu construire le filtre ainsi, ce qui donne le même résultat:

1
2
3
4
5
6
7
<filter customizable="true">
    <field name="position" method="exact" type="text" mode="text" class="permanent">
        <value>1</value>
    </field>
    <field name="position" method="exact"/>
    ...
</filter>

Il peut très bien arriver que le filtre permanent et le filtre variable n'utilisent pas la même méthode. On peut par exemple avoir un filtre permanent qui applique une méthode range et un filtre variable qui applique une méthode contains. Dans ce cas, la notation combinée n'est plus possible et il devient nécessaire de définir les filtres au moyen de deux références distinctes.

Un filtre qui est purement permanent ne sera pas repris dans l'arbre de sortie. Du point de vue de l'utilisateur final, ce filtre n'existe pas (il s'agit d'un filtre appliqué par l'application, mais non modifiable par l'utilisateur).

Un filtre variable est systématiquement repris dans l'arbre de sortie pour permettre à l'utilisateur final d'en modifier la condition (la valeur du filtre).

Toutefois si le filtre est à la fois permanent et variable, le filtre est repris au niveau de l'arbre de sortie, mais la valeur de filtre du filtre permanent n'est pas reprise dans l'arbre de sortie. Si l'utilisateur saisit une valeur de condition pour un filtre permanent et variable, alors le moteur va générer 2 clauses pour le champ : une clause pour le filtre permanent et une seconde pour le filtre variable.

Il est possible de définir une ou plusieurs valeurs par défaut pour le champ. La ou les valeurs seront utilisées comme valeur de filtre par défaut. La syntaxe est la même que pour la définition de valeur par défaut au niveau des champs. Voir Champ (<field>).

Il est possible de définir une liste d'options, c'est-à-dire une liste de valeurs possibles pour le champ. La syntaxe est la même que pour la définition d'options au niveau des champs. Voir Champ (<field>).

Autres propriétés

Il est possible de définir d'autres propriétés au niveau des champs (validation, pattern de formatage, etc.). Dans la version actuelle, les éventuelles propriétés supplémentaires définies au niveau des champs de filtre ne sont pas prises en compte pour les champs de filtre.

Pagination (<pagination>)

Les règles de pagination sont constituées de 2 paramètres: un décalage de base (offset) et un nombre d'éléments (limit).

1
2
3
4
<pagination customizable="true">
    <offset mode="text"><value>10</value></offset>
    <limit mode="text"><value>30</value></limit>
</pagination>

L'attribut customizable="true" indique que l'on autorise l'utilisateur à modifier la pagination.

Dans l'exemple ci-dessus, la pagination découpe les tuples en portions de 30 lignes, auxquelles on applique un décalage de 10 (on commence à afficher à partir du 11e tuple). Le paramètre offset est optionnel, sa valeur par défaut étant 0.

Dans la version actuelle, il n'est pas possible de spécifier les règles de pagination autrement qu'en mode text.

Prise en compte des modifications apportées à la descript

Dans la version actuelle, les modifications apportées à la descript concernant les options d'arrangements ne sont pas directement prises en compte lors du reset sur les dossiers ouverts. Elles sont bien chargées au niveau du moteur, mais elles ne sont prises en compte pour un dossier que lors de son ouverture.

Si le reset est réalisé alors qu'un utilisateur travaille sur un dossier, les modifications apportées ne seront donc pas immédiatement prises en compte pour cet utilisateur. Cela vient du fait que l'utilisateur a potentiellement modifié des paramètres de tri, de filtre ou de pagination. Les options d'arrangement définies par l'utilisateur sont donc liées à l'instance de document chargée dans la session de l'utilisateur et il n'est pas souhaitable qu'elles soient changées tant que le dossier reste édité par l'utilisateur.

En tant que développeur, il faut donc penser à fermer et rouvrir un dossier pour tester les modifications apportées à la descript.

Champ (<field>)

L'élément <field> permet de décrire un champ. Un champ est constitué des éléments suivants:

  • attribut name : nom du champ interne, utilisé pour référencer le champ au niveau des ressources de langues (i18n)

  • attribut column : nom de la colonne enregistrant la donnée du champ en base de données; si non défini, alors un attribut datatype est requis

  • attribut idform : forme de la valeur; cet attribut permet de surcharger la valeur définie dans l'entrée admin.tupleIdForm du fichier de configuration.

    Attention, l'utilisation de cet attribut est assez encadré: il n'est pas permis de modifier le format d'un identifiant de dossier. Ce dernier doit avoir le format déclaré dans l'entrée admin.tupleIdForm de la configuration et avoir un type de colonne compatible avec ce format. Cette limitation vient du fait que le moteur doit pouvoir gérer le lock des dossiers dans le modèle de concurrence pessimiste. Cela nécessite que tous les identifiants de dossiers utilisent un même format et, par conséquence, un même type de colonne au niveau de la base de données.

  • attribut datatype : cet attribut n'est à utiliser que si on ne définit pas de column mais que l'on souhaite malgré tout que la valeur du champ soit conservée en mémoire (dans la session). Si column et datatype ne sont pas renseignés, le moteur considère que le champ n'enregistre pas de valeur (c'est par exemple le cas si le champ doit représenter un bouton, un message statique, etc.). Cet attribut renseigne le moteur sur le type de donnée géré par le champ; il reprend le même type de valeur que l'attribut type de la colonne qui aurait été référencé par column (voir document Schéma de base de données pour plus d'infos sur cet attribut)

  • attribut type: facultatif, type de champ de formulaire html; quelques types sont réservés et interprétés par le moteur, les autres types sont libres. Ainsi:

    • hidden: réservé pour désigner un champ caché (le moteur a besoin de savoir si un champ est un champ caché pour gérer correctement les filtres de groupes multi)
    • binary ou bin ou file: réservé pour désigner un champ binaire (le moteur a besoin de savoir si un view affiche des champs binaires; cela permet d'indiquer dans l'arbre de sortie le type de formulaire attendu en réponse: en présence de fichiers, il indiquera un type multipart, sinon il indiquera un type url-encoded)

    Le moteur ne s'attend pas à recevoir de champs HTML qui référencent les mainfield. Lorsque cela arrive, le moteur affiche un message d'avertissement du genre "Ignore value update of id field modelName/groupName/fieldName. HTML posted form shouldn't contain inputs for fields being used as primary key. Please fix your stylesheets." lorsqu'il fonctionne en mode "dev". Pour éviter ce message, nous recommandons de ne pas spécifier de type pour ces champs et d'adapter la feuille de style pour qu'elle ne crée pas d'input HTML dans le cas où le type est vide. Les champs avec un type non défini ou vide sont considérés comme des hidden du point de vue des options d'arrangement.

  • attribut label: facultatif, libellé associé au champ. Cet attribut permet de spécifier le libellé à reprendre dans l'arbre de sortie dans le cas où aucun libellé d'internationalisation n'est trouvé dans le bundle associé à la descript. L'attribut peut également être déclaré dans le namespace ch.epilogic.ewt.i18n si on veut forcer une référence de bundle de ressources.

  • attribut description: facultatif, texte court décrivant le champ. Cet attribut permet de spécifier le descriptif à reprendre dans l'arbre de sortie dans le cas où aucun libellé d'internationalisation n'est trouvé dans le bundle associé à la descript. L'attribut peut également être déclaré dans le namespace ch.epilogic.ewt.i18n si on veut forcer une référence de bundle de ressources.

  • attribut placeholder: facultatif, texte à afficher dans le champ lorsque celui-ci est vide. Cet attribut permet de spécifier le libellé à reprendre dans l'arbre de sortie dans le cas où aucun libellé d'internationalisation n'est trouvé dans le bundle associé à la descript. L'attribut peut également être déclaré dans le namespace ch.epilogic.ewt.i18n si on veut forcer une référence de bundle de ressources.

  • élément formatting : pattern de formatage du champ; le type de pattern utilisé doit être compatible avec le type de champ SQL utilisé dans la table

  • élément default : valeur par défaut du champ

  • élément options : liste d'options pour les listes déroulantes

  • élément policies : règles de gestion des droits

  • élément validation : règles de validation de la donnée en provenance du formulaire HTML (voir chapitre Validation de données client ci-dessous)

  • attributs indexstore et indexmode: ils remplissent le même rôle que pour le niveau model, et viennent surcharger la valeur des attributs correspondants éventuellement définis au(x) niveau(x) parent(s).

  • élément arrangement : règles à appliquer lorsqu'un tri ou un filtre est demandé sur le champ; voir Arrangement.

  • attribut policy : facultatif, nom de la ou des policies à appliquer au champ. Il est possible de référencer plusieurs policies en les séparant par une virgule. Les policies référencées via cet attribut s'ajoutent à celles définies ou référencées via l'élément policies.

  • élément properties : propriétés de champs; ces propriétés ne sont pas traitées par le moteur (hormis les éléments d'internationalisation; voir le chapitre relatif à ce sujet pour plus de détails), mais transmis tels quels dans l'arbre de sortie. Les attributs peuvent être du texte simple, du XML, du JSON, du SCSS, etc.

  • élément multiple : cet élément est à utiliser dans le cas où un champ est susceptible de recevoir plusieurs valeurs. C'est par exemple le cas des champs de type case à cocher ou des champs select avec attribut multiple : le formulaire html peut envoyer plusieurs champs du même nom avec des valeurs différentes. Lorsque l'élément est spécifié, le moteur sait qu'il doit encoder les valeurs reçues du formulaire html.

    • attribut format : Il est possible d'indiquer le format d'encodage au moyen de l'attribut format. Celui-ci peut prendre l'une des valeurs suivantes:
      • separated : Les valeurs sont encodées dans une chaîne de caractères séparés par un délimiteur. C'est le format par défaut.
      • json : Les valeurs sont encodées au format json
      • bitset : Les valeurs sont encodées dans un bitset, c'est-à-dire une valeur entière qui "encode" les différentes valeurs au moyen d'un opérateur OR. Ce format n'est utilisable qu'avec des valeurs numériques. Il n'est pas conseillé d'activer le flag detail lorsque les valeurs sont grandes, car il peut être coûteux pour le moteur de reconstruire la liste de valeurs.
    • attribut separator : Dans le cas où le format est separated, l'attribut separator permet de spécifier le séparateur. Le séparateur par défaut est la virgule (,)
    • attribut detail : Ce flag demande au moteur de générer un détail de valeurs dans l'arbre de sortie. Cela se manifeste par un élément values qui est ajouté dans l'output. Par défaut ce paramètre est false.

    Le mode multiple est fonctionnel uniquement pour les valeurs litérales. Les champs binaires ne sont pas supportés en mode multiple.

    Nommage des champs multiples

    Un champ <select multiple> est envoyé différemment dans la requête POST selon le type de formulaire et les options sélectionnées dans la liste déroulante du select.

    Si aucune option n'est sélectionnée, le champ n'est pas repris dans le formulaire. Le moteur ne sera donc pas informé des éventuelles modifications apportées par l'utilisateur.

    Si une seule option est sélectionnée, le champ est envoyé comme un champ standard et le moteur peut être trompé quant à la nature du champ.

    Pour que le moteur soit capable de détecter ces deux cas de figure, il est recommandé d'appliquer les deux principes suivants:

    1. Ajouter un suffixe [] à la fin du nom de champ du select multiple: cela permet au moteur de savoir que le champ est un champ multiple
    2. Ajouter un champs caché du même nom que le select, mais sans suffixe: cela permet au moteur d'avoir une valeur par défaut à enregistrer dans le cas où le select multiple ne contient aucune option sélectionnée. Lorsqu'il reçoit à la fois le champ avec suffixe et le champ sans suffixe, le moteur ignore la valeur du champ sans suffixe.

    Voici ce que cela donne au niveau de la page HTML lorsque l'on applique les deux principes ci-dessus:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <input name="EWT:DATA-7C50CCC0C2CE0C12D3F7B5A9B4419CE1" value=""/>
    <select name="EWT:DATA-7C50CCC0C2CE0C12D3F7B5A9B4419CE1[]"
            id="input-7C50CCC0C2CE0C12D3F7B5A9B4419CE1"
            multiple="" data-allow-clear="true" data-tags-select="true">
        <option value="" hidden="hidden" disabled=""/>
        <option value="1">Création</option>
        <option value="2">Ouvert</option>
        <option value="3">Pris en charge</option>
        <!-- ... -->
    </select>
    

Validation de données client

La validation consiste à contrôler voire à nettoyer une valeur en provenance d'un champ de formulaire envoyé au moteur. Le moteur autorise donc deux types d'actions (à définir dans un attribut action):

  • check : la validation consiste à contrôler la valeur; une erreur est retournée à l'utilisateur si le contrôle échoue

  • sanitize : la validation consiste à nettoyer la valeur pour qu'elle ne contienne aucun élément susceptible de provoquer une injection XSS; lorsque la valeur est modifiée suite à une opération de nettoyage, un message d'avertissement est retourné à l'utilisateur

L'action par défaut est check. Cela signifie que si l'attribut action n'est pas spécifié, le moteur considère que la validation doit effectuer un contrôle.

Pour chaque type d'action, on indiquera la méthode de validation au moyen de l'attribut method. Ce dernier est obligatoire.

Contrôle (check)

Le contrôle de valeur est possible de trois façons:

  1. Au moyen d'une règle prédéfinie (rule) : Une règle est une description d'autorisations ou de refus relatifs au contenu du champ. La règle peut être définie sous différentes formes.

    1. La forme la plus simple est une chaîne de caractères qui référence des règles pré-définies du moteur. Dans la version actuelle, Ewt intègre les règles de contrôle pré-définies suivantes:

      • MANDATORY : indique que le champ est obligatoire et ne peut pas être laissé vide
      • EMAIL : indique que la valeur du champ doit être une adresse email

      À ces règles s'ajoutent également les règles pré-définies dans la librairie Owasp (FORMATTING, BLOCKS, STYLES, LINKS, TABLES, IMAGES). Voir le chapitre relatif au sanitize ci-dessous pour avoir une description de ces règles.

      Précisons au passage qu'il est possible de cumuler plusieurs mots-clés au sein d'une même règle de validation. Pour ce faire, il suffit de séparer les différents mots-clés par une barre verticale | (p.ex. EMAIL|MANDATORY|FORMATTING)

    2. La règle peut être définie en json. Cette forme permet un contrôle plus avancé des autorisations et des refus. La notation json reprend la syntaxe de l'action sanitize décrite plus bas dans ce document. Un exemple de check utilisant une règle définie en json est également donné dans les exemples plus bas.

  2. Au moyen d'une expression régulière (regex) : On spécifie une expression régulière qui représente la valeur. Si la valeur correspond à l'expression, la validation réussit, sinon elle échoue et une erreur est signalée.

  3. Au moyen d'un script (script) : On décrit une expression de script qui réalise le test de la valeur. Le moteur s'attend à ce que le script retourne true si la validation réussit et false si elle échoue. La valeur à tester est disponible via la variable $$.data. Elle peut également être obtenue via les notations ${data:nom_du_champ} ou #nom_du_champ.

Nous tenons à préciser que la méthode script nécessite davantage de ressources et est susceptible de sensiblement ralentir l'application.

Nettoyage (sanitize)

Le nettoyage de valeur est possible de deux façons:

  1. Au moyen d'une règle (rule) : Comme dans le cas de l'action check, les règles du sanitize peuvent être définies de différentes façons.

    1. La forme la plus simple est une chaîne de caractères qui référence des règles pré-définies de la librairie (dans la nomenclature Owasp, ces règles sont appelés policies, mais nous évitons ce terme ici pour ne pas créer de confusion avec les policies de gestion des droits d'accès).

      Par défaut, Owasp applique une politique de validation stricte, mais il est possible de spécifier des règles qui autorisent certains types d'éléments (sanitizers).

      Les sanitizers prédéfinis sont:

      • FORMATTING : Autorise les éléments standard de formatage comme <b>,<i>, etc.
      • BLOCKS : Autorise les éléments standard de blocs tels que < p>, <h1>, etc.
      • STYLES : Autorise certaines propriétés CSS sûres dans les attributs du type style="..."
      • LINKS : Autorise HTTP, HTTPS, MAILTO et les liens relatifs
      • TABLES : Autorise les éléments standard liés aux tables
      • IMAGES : Autorise les éléments <img> à partir de HTTP, HTTPS et les sources relatives

      Les sanitizers ci-dessus peuvent être chaînés comme dans l'exemple suivant:

      FORMATTING|BLOCKS|STYLES|LINKS|TABLES|IMAGES
      
    2. La règle peut être définie en json. Cette forme permet un contrôle plus avancé des autorisations et des refus. L'objet json attendu est soit un dictionnaire (map), soit un tableau (array) de dictionnaires. Les dictionnaires peuvent reprendre les méthodes de la classe HtmlPolicyBuilder.

      Les propriétés reconnues sont les suivantes:

      • allowAttributes: Autorise les attributs indiqués. La règle peut être affinée au moyen d'une propriété matching qui permet de spécifier une expression régulière de filtre, onElements qui permet d'inventorier les éléments sur lesquels la règle s'applique, ainsi qu'une propriété globally indiquant que la règle s'applique à tout type d'élément (sous réserve qu'il respecte la regex de la propriété matching). Exemple:

        { 
            "allowAttributes": [ "id" ],
            "matching": "[a-zA-Z0-9\\:\\-_\\.]+",
            "globally": true
        }
        
      • allowCommonBlocElements: Correspond à BLOCKS lorsque la règle est définie en tant que chaîne de caractères. Exemple:

        { "allowCommonBlocElements": true }
        
      • allowCommonInlineFormattingElements: Correspond à FORMATTING lorsque la règle est définie en tant que chaîne de caractères. Exemple:

        { "allowCommonInlineFormattingElements": true }
        
      • allowElements: Autorise les éléments indiqués. Exemple:

        { "allowElements": [ "i", "b" ] }
        
      • allowStandardUrlProtocols: Autorise les protocols d'URL http, https et mailto. Exemple:

        { "allowStandardUrlProtocols": true }
        
      • allowStyling: Correspond à STYLES lorsque la règle est définie en tant que chaîne de caractères. Exemple:

        { "allowStyling": true }
        
      • allowTextIn: Autorise le texte dans les éléments indiqués. Exemple:

        { "allowTextIn": [ "span", "div" ] }
        
      • allowUrlProtocols: Ajoute un set de protocols autorisés dans les attributs d'URL. Exemple:

        { "allowUrlProtocols": [ "ssh", "ldap", "ldaps" ] }
        
      • allowWithoutAttributes: En supposant que les éléments donnés sont autorisés, permet de les faire apparaître sans attributs. Exemple:

        { "allowWithoutAttributes": [ "i", "b" ] }
        
      • disallowAttributes: Inverse de allowAttributes, les propriétés matching, onElements et globally sont également applicables

      • disallowElements: Refuse les éléments indiqués
      • disallowTextIn: Refuse le text dans les éléments indiqués
      • disallowUrlProtocols: Inverse de allowUrlProtocols
      • disallowWithoutAttributes: Interdit les éléments indiqués d'apparaître sans attributs
      • requireRelNofollowOnLinks: Ajoute rel=nofollow sur les liens. Exemple:

        { "requireRelNofollowOnLinks": true }
        
      • requireRelsOnLinks: Ajoute rel="..." aux balises <a href="...">. Exemple:

        { "requireRelsOnLinks": true }
        
      • skipRelsOnLinks: Permet d'éviter l'ajout de rel="..." au liens indiqués. Exemple:

        { "skipRelsOnLinks": [ "noopener", "noreferrer" ] }
        

      Une règle est donc un tableau qui contient différents objets utilisant la syntaxe ci-dessus. Un exemple de règle est donné plus bas dans les exemples.

  2. Au moyen d'un script (script) : On décrit une expression qui retourne la valeur nettoyée de la valeur d'entrée. Si la valeur en sortie est différente de la valeur initiale, un avertissement est envoyé à l'utilisateur.

Nous tenons à préciser que la méthode script nécessite davantage de ressources et est susceptible de sensiblement ralentir l'application.

Source de la validation

Pour chaque règle de validation, on peut indiquer à l'aide de l'attribut source sur quelle valeur la règle doit s'appliquer. L'attribut peut prendre les valeurs:

  • raw : représente la valeur brute reçue du formulaire html, c'est-à-dire la valeur avant nettoyage des éventuels éléments de formatage; une date brute pourra par exemple être "15/10/2022"
  • std : représente la valeur standardisée, éventuellement retraitée et normalisée dans le cas où le champ utilise un formatage; dans le cas de notre date, on aura la valeur "2022-10-15".

Par défaut, c'est-à-dire si aucune source n'est spécifiée, la validation s'effectue sur la valeur standardisée, pour les raisons évoquées dans la note ci-dessous.

Remarques par rapport au formatage

Certains règles de validation peuvent entrer en conflit avec les règles de formatage de valeurs. Il est donc important d'indiquer si la règle de validation doit porter sur la valeur brute ou la valeur remise en forme standardisée.

Il est recommandé de s'appuyer sur la forme standard car il peut arriver que l'utilisateur apporte des variations dont il faudrait tenir compte dans la règle de validation. Il peut également arriver que la forme de la donnée brute soit différente selon la locale du navigateur du client. L'élaboration d'une règle de validation sur la donnée brute peut par conséquent vite devenir un casse-tête. La forme standard est plus simple à valider.

Message d'erreur ou d'avertissement

Lorsqu'une règle de validation n'est pas respectée, le moteur génère un message d'erreur (dans le cas des check) ou d'avertissement (dans le cas des sanitize). Un message prédéfini est utilisé, mais il est possible de surcharger ce dernier au moyen d'un message personnalisé. Pour ce faire, il suffit d'ajouter un attribut message qui contient le message à afficher. Il est possible de référencer une entrée de bundle en définissant cet attribut dans le namespace ch.epilogic.ewt.i18n (en préfixant l'attribut avec i18n:). Le libellé du message sera donc à spécifier au niveau des fichiers de langues du dossier i18n de l'application. Comme indiqué plus haut, le niveau du message dépend du type d'action. Il est possible de spécifier explicitement le niveau du message à afficher au moyen de l'attribut level. Celui-ci peut alors prendre les valeurs error, warn ou info.

Quelques exemples de règles de validation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<validation action="check" method="regex" source="raw">^[\w!#$%&amp;’*+/=?`{|}~^-]+(?:\.[\w!#$%&amp;’*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}$</validation>
<validation action="check" method="rule" source="raw">EMAIL|MANDATORY|FORMATTING|LINKS</validation>
<validation action="sanitize" method="rule" source="raw">FORMATTING|LINKS</validation>
<validation action="check" method="script" source="std" i18n:message="msg.
invalidValue">return $str.match($.data, "^[\w!#$%&amp;’*+/=?`{|}~^-]+(?:\.[\w!#$%&amp;’*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}$");</validation>
<validation action="sanitize" method="script" source="std">return $str.upper($$.data);</validation>
<validation method="rule" source="raw" action="check">
    [
        { allowElements: [ "i" ] },
        { allowAttributes: "class", onElements: "i" }
    ]
</validation>

Dans le dernier exemple ci-dessus, on définit une règle de contrôle qui vérifie que la valeur n'autorise que la présence de balise <i> et que le seul attribut autorisé pour cette dernière est l'attribut class.

Types de champs

L'élément <type> défini au niveau d'un champ correspond plus ou moins au type d'input à afficher dans le formulaire html. Ewt n'agit pas sur ce type et le reprend tel quel dans l'arbre de sortie. Il est donc du ressort de la feuille de style d'interpréter ce type pour construire les champs du formulaire affiché.

Ewt s'appuie toutefois sur le type pour identifier deux catégories de champs:

  • les champs cachés : Ewt considère qu'un champ est caché si son type est hidden
  • les champs binaires : Ewt considère qu'un champ est un champ contenant des données binaires si son type est file (il est également possible d'utiliser binary ou bin)

Notez que le type de champ peut avoir une incidence sur les règles de formatage. En effet, sur une page HTML 5, il se peut que le client (le navigateur) interprète le type et effectue de lui-même un formatage. C'est le cas des types date, time, datetime-local, number, etc.

Il est donc important de tenir compte du type et du formatting de manière cohérente. Le chapitre qui suit tente de donner plus d'éléments à ce sujet.

Formatting

Le formatting consiste à mettre en forme une donnée pour qu'elle soit plus facilement lisible à l'écran. Il est possible de gérer le formatting au niveau de l'application via la définition d'un pattern de formatage (élément <formatting>). Il est également possible de s'appuyer sur HTML5 pour gérer le formatage de valeurs (en particulier les dates et les heures - le type prévu pour les nombres n'est quant à lui pas idéal pour le formatage). Ces deux aspects sont présentés ci-dessous.

Nombres

Le formatting des nombres peut utiliser les règles de pattern définies dans la documentation de la classe java DecimalFormat. Le champ HTML devra alors être de type text, avec éventuellement un pattern de validation (voir pattern).

Remarque concernant l'élément input type="number" en HTML5

Le type de champ number est prévu pour gérer des nombres. Le navigateur effectuera automatiquement un filtre des entrées pour n'autoriser que la saisie de caractères permettant de définir un nombre. Toutefois il faut émettre quelques réserves:

  • Ce champ ne gère pas le formatting de nombres; selon la documentation de mozilla, l'attribut pattern des inputs HTML n'est pas adapté à ce type de champ. Par conséquent, le moteur enverra toujours une valeur non formatée dans l'arbre de sortie lorsqu'il détecte qu'un champ utilise le type number.
  • Le champ input type="number" peut être une source d'erreur chez certains utilisateurs : si un utilisateur saisit un champ de ce type, puis souhaite scroller dans le formulaire à l'aide de la souris, il se peut que la valeur de l'input soit modifiée par le scroll mais que l'utilisateur ne s'en rende pas compte.

Pour ces raisons, nous recommandons de définir un champ numérique avec formatage de la manière suivante:

<field name="monChamp" type="text" column="MonChamp">
    <formatting>#,##0.00</formatting>
</field>

Algorithme de suppression du formatage

La gestion du formatage des nombres est intrinsèquement liée à celui de la locale. En effet, le rendu final d'une valeur formatée dépend de la locale pour laquelle la valeur est formatée. Pour ne rien arranger, la représentation peut également changer en fonction de la valeur de la propriété java.locale.providers : par exemple la valeur COMPAT force java à appliquer les règles de locale telles que définies pour java 8.

Ainsi, une valeur 1234567.89 aura différentes représentations en fonction de la locale utilisée, par exemple pour la locale "fr-CH", cette valeur sera formatée en 1 234 567,89 en mode standard et 1'234'567.89 en mode COMPAT.

D'autres types de séparateurs de milliers et de délimiteurs de décimales sont également utilisés par d'autres locales, ce qui complique encore la tâche.

Partant de ce constat, un algorithme de suppression du formatage a été mis au point dans Ewt pour tenter d'interpréter un nombre de la manière la plus juste possible et qui soit le moins lié possible avec la locale. Cette fonction cherche à retirer tous les délimiteurs et utilise le . comme délimiteur de décimales. L'algorithme est amené parfois à faire des suppositions. Ainsi, s'il trouve un nombre comportant différents types de délimiteurs, il considérera que le délimiteur placé le plus à droite dans le nombre sera le délimiteur de décimales. Cela qui peut donner lieu à des interprétations surprenantes. Ainsi, une valeur 1a2a3b4 sera interprétée comme le nombre 123.4. Il peut donc se révéler utile de mettre en place des contrôles de validité de la valeur brute afin de limiter les risques d'erreur d'interprétation des données lors de la suppression de formatage.

Date

Il est possible de déléguer la gestion des dates à l'élément input en lui spécifiant un type date. Ce type de champ HTML5 filtre la saisie et fournit nativement un picker de date. Ewt reconnaît ce type et adaptera la valeur au niveau de l'arbre de sortie pour qu'elle soit compatible avec ce type de champ.

Il est également possible d'utiliser une règle de formatting via l'élément <formatting>. Dans ce cas, la syntaxe de pattern est définie par la classe SimpleDateFormat.

Il n'est pas recommandé d'utiliser à la fois un pattern et un type d'input spécifique. En effet, l'input s'attend à recevoir les données brutes non formatées.

Exemple:

1
2
3
4
5
6
7
<!-- variante utilisant un formatage personnalisé -->
<field name="dateCreation" type="text" column="DateCreation">
    <formatting>dd/MM/yyyy</formatting>
</field>

<!-- variante utilisant HTML5 -->
<field name="dateCreation" type="date" column="DateCreation"/>

Remarque concernant le format des dates

En interne, Ewt gère les dates selon le format ISO 8601 au format standard (par opposition au format basique). Concrètement, la date du 15 octobre 2015 sera enregistrée dans la forme "2015-10-15".

Time

Idem que pour "Date". Dans ce cas, le type de l'élément input sera time.

Si l'option pattern est choisie, la syntaxe doit respecter les règles définies par la classe SimpleDateFormat.

Il n'est pas recommandé d'utiliser à la fois un pattern et un type d'input spécifique. En effet, l'input s'attend à recevoir les données brutes non formatées.

Exemple:

1
2
3
4
5
6
7
<!-- variante utilisant un formatage personnalisé -->
<field name="heureCreation" type="text" column="HeureCreation">
    <formatting>HH:mm:ss</formatting>
</field>

<!-- variante utilisant HTML5 -->
<field name="heureCreation" type="time" column="HeureCreation"/>

Remarque concernant le format des heures

En interne, Ewt gère les heures selon le format ISO 8601. Concrètement, l'heure 9 heures 28 minutes 32 secondes sera enregistrée dans la forme "09:28:32".

Timestamp

Idem que pour "Date". Dans ce cas, le type de l'élément input sera datetime-local.

Si l'option pattern est choisie, la syntaxe doit respecter les règles définies par la classe SimpleDateFormat.

Il n'est pas recommandé d'utiliser à la fois un pattern et un type d'input spécifique. En effet, l'input s'attend à recevoir les données brutes non formatées.

Exemple:

1
2
3
4
5
6
7
<!-- variante utilisant un formatage personnalisé -->
<field name="tsCreation" type="text" column="TSCreation">
    <formatting>dd/MM/yyyy HH:mm:ss.SSS</formatting>
</field>

<!-- variante utilisant HTML5 -->
<field name="tsCreation" type="datetime-local" column="TSCreation"/>

Remarque concernant le format des timestamps

En interne, Ewt gère les heures selon le format ISO 8601. Un timestamp sera par exemple représenté ainsi : "2015-10-15 09:28:32.123". La précision au niveau des milli/micro/nano secondes varient en fonction de la machine virtuelle java et de l'OS utilisé.

Valeur par défaut

La définition de valeur par défaut, c'est-à-dire la valeur attribuée au champ à la création d'un tuple, se fait au moyen de l'élément <default>. Cet élément prend plusieurs formes en fonction du mode d'obtention de la valeur (texte, sql ou script).

Valeur textuelle (mode="text")

La valeur est passée via l'élément <value>. Elle peut contenir des références de variables ou de données:

1
2
3
<default mode="text">
    <text:value>test ${data:numero}</text:value>
</default>

Valeur obtenue par SQL (mode="sql")

L'élément <value> attend une requête SQL. Les éventuels paramètres à passer au prepared statements sont à placer dans des éléments <param>. La requête SQL peut référencer les paramètres de façon anonyme (avec le caractère ?) ou par nom (par exemple avec :myparam). Il n'est par contre pas possible de mélanger les deux formes de référence au sein d'une même requête.

Les paramètres doivent être typés au moyen de l'attribut type. Dans le cas de références par nom, il faut également spécifier un attribut name.

Exemple de requête utilisant des références anonymes. Dans ce cas, il doit y avoir autant de paramètre que de références.

1
2
3
4
5
6
<default mode="sql">
    <value>select coalesce(max(numero),0)+1 from Vendeur
           where categorie=? and status=?</value>
    <text:param type="integer">${data:categorie}</text:param>
    <param type="varchar">active</param>
</default>

Exemple de requête utilisant des références par nom. Dans l'exemple ci-dessous, on voit que le paramètre "cat" est référencé deux fois dans la requête.

1
2
3
4
5
6
<default mode="sql">
    <value>select coalesce(max(numero),0)+1 from Vendeur
           where (categorie=:cat or -1=:cat) and status=:status</value>
    <text:param type="integer" name="cat">${data:categorie}</text:param>
    <param type="varchar" name="status">active</param>
</default>

Valeur calculée par script (mode="script")

L'élément <value> attend un script. Dans la version actuelle, le script doit être inscrit inline et ne peut pas prendre de paramètre.

1
2
3
<default mode="script">
    <value>return $cal.timestamp();</value>
</default>

Il est prévu de permettre une référence de fichier de script et le passage de paramètres dans une version future.

Champs non persistents

Comme indiqué en début de section, les valeurs par défaut sont calculées et assignées au champ à la création des tuples uniquement. Il n'est donc pas approprié d'utiliser ce type d'élément pour assigner une valeur à un champ non persistent, c'est-à-dire un champ purement informatif mais non enregistré en base de données.

En effet, dans ce cas la valeur serait bien assignée au champ à la création du tuple (typiquement à la création d'un dossier ou d'un tuple de groupe multi), mais elle ne le serait plus par la suite lorsque l'on recharge le tuple (à l'ouverture de dossier).

Pour assigner une valeur à un champ non persistent, il est recommandé d'utiliser un script déclenché par les notifications doc-create et doc-open. Voici un exemple de test script:

...
<notifications>
    <notification name="doc-create">calcMemFields</notification>
    <notification name="doc-open">calcMemFields</notification>
</notifications>
...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@accept(trigger = "notification")

if ($$.notification.name == "doc-open" || $$.notification.name == "doc-create") {
    context [ "consultationcss", "consultationasp" ] {
        #nbreConsultPrecedentesAnnee {
            if (#nbreConsultPrecedentes != null && #nbreConsultAnnee != null) {
                return #nbreConsultPrecedentes & " / " & #nbreConsultAnnee;
            }
        }
    }
}

Il est également possible d'utiliser les notifications doc-save ou gen-output si la valeur est susceptible de changer durant le temps où le dossier est ouvert.

Listes d'options

Certains champs peuvent proposer une liste de valeurs possibles. C'est le cas des listes déroulantes, des listes de cases à cocher, des listes de puces, etc. La liste d'options associe généralement un libellé à une clé. Du point de vue de l'application, la clé correspond à la valeur enregistrée en base de données alors que le libellé est un texte sans réelle signification du point de vue métier et qui peut également est adapté en fonction de la locale.

Pour définir une liste d'options, on utilise l'élément <options> et on indique le type de liste décrite au moyen de l'attribut mode, qui peut prendre les valeurs sql ou inline:

  • sql : Ce mode permet de définir la liste d'options au moyen d'une requête SQL. La requête peut utiliser la notation prévue pour les prepared statements. Dans ce cas, les paramètres sont à placer dans des éléments param. Exemple:

    1
    2
    3
    4
    5
    <options mode="sql" level="field">
        <value>SELECT idVendeur,resumeText FROM Vendeur WHERE idVendeur=? ...</value>
        <text:param type="int">${data:benevoles.idVendeur}</text:param>
        <text:param>#benevoles.idVenteEchange</text:param>
    </options>
    

    Dans l'exemple ci-dessus, le premier paramètre référence un champ via la notation ${data:xxx}. Ce type de référence retourne toujours une valeur de type string, c'est pourquoi un attribut type est exigé afin d'indiquer au moteur le type de la valeur à reprendre dans le statement. Le second paramètre utilise quant-à-lui la notation "sharp". La valeur retournée par cette référence étant déjà typée, il n'est pas nécessaire de spécifier le type dans ce cas. On note également la présence du préfixe text: qui indique au moteur qu'il doit effectuer la substitution des références trouvées.

    Il est également possible de référencer les paramètres par nom. Dans ce cas, les éléments <param> doivent posséder un attribut name qui indique le nom.

    1
    2
    3
    4
    5
    <options mode="sql" level="field">
        <value>SELECT :idVendeur,resumeText FROM Vendeur WHERE idVendeur=:idVendeur ...</value>
        <text:param name="idVendeur" type="int">${data:benevoles.idVendeur}</text:param>
        <text:param name="idVenteEchange">#benevoles.idVenteEchange</text:param>
    </options>
    

    Attention, il n'est pas possible de mélanger les références anonymes (avec ?) et les références nommées (avec :myparam) au sein d'une même requête.

  • inline : Ce mode permet de spécifier les valeurs directement dans la descript, en dur. Exemple:

    1
    2
    3
    4
    5
    <options mode="inline" level="field">
        <i18n:option value="val1">some.label.from.language.bundle</i18n:option>
        <option value="val2">Libellé en dur</option>
        <text:option value="val3">user ${env:USERNAME}</text:option>
    </options>
    

    Dans l'exemple ci-dessus, la première option est préfixée de i18n:. Cela indique au moteur que le texte de l'élément est une référence de bundle. Le moteur se chargera de substituer cette référence par le libellé approprié. La seconde option passe une valeur litérale en dur.

L'attribut level permet d'indiquer à quel niveau dans l'arbre de sortie on souhaite voir apparaître les valeurs d'options du champ. Cela permet d'éviter les répétitions de listes d'options, en particulier sur les groupes "multi". L'attribut level peut prendre les valeurs suivantes:

  • descript : La liste d'options est reprise dans l'élément field de la partie descript de l'arbre de sortie; cette option est souhaitable lorsque la liste d'option peut être longue (ce qui évite de la répéter sur chaque dossier ou chaque champ) et que ses données ne sont pas dépendantes d'un dossier ou d'un champ en particulier.
  • doc ou document : La liste d'options est reprise au niveau du dossier; cette option est souhaitable lorsque la liste peut être longue et dépendante du dossier ouvert
  • group : La liste d'options est reprise au niveau du groupe; cette option est souhaitable lorsque la liste peut être longue et dépendante du groupe
  • tuple : La liste d'option est reprise au niveau du tuple; cette option est souhaitable lorsque la liste est dépendante du champ ou d'un autre champ du tuple, et pour autant que la liste d'option ne soit utilisée que par un seul champ du tuple
  • field : La liste d'option est reprise au niveau du champ; cette option est souhaitable lorsque la liste est dépendante du champ ou d'un autre champ du tuple

La valeur par défaut du champ est field.

Références

Il est possible de partager une liste d'options entre plusieurs champs d'un même modèle. Par exemple, si une vue contient plusieurs champs qui doivent afficher une même liste (une liste de statuts, une liste d'utilisateurs, etc.), il n'est pas nécessaire de redéfinir la liste d'options sur chaque champ, mais il est possible de référencer la liste d'un autre champ avec l'attribut ref. Cette référence doit se rapporter à une autre liste qui sera identifiée au moyen d'un attribut id. L'identifiant défini avec id doit être unique sur toute la descript (le moteur générera une erreur s'il détecte plusieurs listes avec un même id).

Voici un exemple dans lequel on définit une liste d'utilisateurs au niveau du groupe base (lignes 5 à 7). Cette liste est référencée depuis le champ "idAuteur" du groupe commentaires (ligne 19):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<group name="base" table="Ticket" type="single" mainfield="idTicket">
    <fields>
        <field name="idTicket" type="hidden" column="IdTicket"/>
        <field name="idUtilisateur" type="label" column="IdUtilisateur">
            <options mode="sql" level="descript" id="liste-utilisateurs">
                <value>select IdUtilisateur, Resume from Utilisateur order by Resume</value>
            </options>
        </field>
        <field name="numero" type="label" column="Numero"/>
        <field name="titre" type="text" column="Titre"/>
    </fields>
</group>
<group name="commentaires" table="TicketCommentaire" type="multi" 
       reffield="idTicket" mainfield="idCommentaire">
    <fields>
        <field name="idCommentaire" type="hidden" column="IdCommentaire"/>
        <field name="idTicket" type="hidden" column="IdTicket"/>
        <field name="idAuteur" type="select" column="IdAuteur">
            <options ref="liste-utilisateurs"/>
        </field>
        <field name="texte" type="textarea" column="Texte"/>
    </fields>
</group>

Séparateur et valeurs supplémentaires

Par défaut en mode sql, les options sont des couples formés d'une valeur (la valeur réellement enregistrée dans le champ) et d'un libellé (le libellé affiché à l'écran dans la locale de l'utilisateur). En réalité le libellé peut être découpé en plusieurs éléments. Ainsi, pour une requête

select idUser, firstName, ' ', lastName from User order by firstName, lastName

le moteur retiendra idUser comme valeur et la concaténation des éléments firstName, ' ', lastName comme le libellé.

Il peut cependant arriver que l'on doive récupérer des éléments supplémentaires, comme des éléments de style par exemple. Le moteur autorise l'ajout d'éléments supplémentaires à intercaler entre la valeur est le libellé. Si on souhaite par exemple récupérer un avatar en même temps que le nom et prénom, on peut modifier la requête précédente ainsi:

select idUser, avatar, '::', firstName, ' ', lastName from User order by firstName, lastName

Dans cette requête, on a ajouté deux colonnes:

  • La colonne "avatar" qui sert à récupérer une donnée supplémentaire qui n'apparaîtra pas en tant que libellé. On pourrait également ajouter d'autres colonnes supplémentaires si on a besoin
  • La valeur "::" qui fait office de séparateur entre les colonnes constituant le libellé et les autres

Par défaut, Ewt reconnaît le séparateur "::". Pour être considérée comme séparateur, la valeur "::" doit figurer comme contenu entier de la colonne. Ainsi le moteur ne détectera pas de séparateur dans une chaîne 'foo::bar'.

Il est possible de personnaliser le séparateur. Pour ce faire, il suffit d'ajouter un attribut separator à l'élément options et d'y inscrire le nouveau séparateur. Celui-ci n'est valable que pour la liste d'options actuelle.

L'utilisation de colonnes supplémentaires et de séparateur n'est possible qu'en mode sql. Les valeurs des colonnes supplémentaires sont ajoutées à l'élément option de l'arbre de sortie via des attributs val## est un entier commençant à 1.

Voici un exemple complet de champ utilisant la notation expliquée ici:

<field name="idUtilisateur" type="label" column="IdUtilisateur">
    <options mode="sql" level="descript" id="liste-utilisateurs">
        <value>select IdUtilisateur, Avatar, '::', Resume from Utilisateur order by Resume</value>
    </options>
</field>

Voici l'arbre de sortie généré pour le champ ci-dessus:

<field name="idUtilisateur" fullname="ticket_base_idUtilisateur" type="label" 
       label="Ouvert par" description="" placeholder="" cloneable="false" 
       datatype="string" maxlength="36">
    <options id="liste-utilisateurs">
        <option value="d6a47d61-41f9-4137-9a3f-3fa8045a914a"
                val1="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8?&gt;&lt;svg ...&lt;/svg&gt;">
            Edith Avuleur
        </option>
        <option value="e8228589-4d4e-402c-ab4a-be53b32f1974"
                val1="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8?&gt;&lt;svg ...&lt;/svg&gt;">
            Oussama Lairbon
        </option>
        <option value="2f19b510-462a-409c-a9fe-53118513a732"
                val1="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8?&gt;&lt;svg ...&lt;/svg&gt;">
            Maude Zarella
        </option>
    </options>
</field>

Champs binaires

Le servlet /web supporte aussi bien les formulaires standards url-encoded que les formulaires multipart. Ainsi les données binaires peuvent être envoyées dans le même formulaire HTML que les données textuelles.

Pour uploader un fichier, on utilise un champ HTML de type <input type="file">. Le formulaire HTML lui-même sera de type multipart:

1
2
3
4
5
6
<form id="form" method="post"
      enctype="multipart/form-data"
      accept-charset="UTF-8">
    ...
    <input type="file" name="EWT:DATA-2ecc5dec-b127-4c94-9787-455fca2277a4"/>
</form>

Aide au choix du type de formulaire

Le traitement de formulaire url-encoded est habituellement plus léger que le traitement de formulaires multipart. Il est donc conseillé d'éviter d'utiliser un formulaire multipart lorsque cela n'est pas nécessaire. Au moment de construire l'arbre de sortie, Ewt analyse les types de champs des dossiers à traiter et renseigne l'entrée /output/session/formType de l'arbre de sortie avec le type de formulaire le plus adapté. La feuille de style qui construit le formulaire HTML peut donc s'appuyer sur cette entrée pour alimenter l'attribut enctype du formulaire HTML.

Voici un exemple de code XSL qui construit la balise <form> en s'appuyant sur le type de formulaire proposé par Ewt:

1
2
3
4
<form id="form" method="post" accept-charset="UTF-8"
      enctype="{/output/session/formType}" autocomplete="off">
    ...        
</form>

Au niveau de la description de champ, le champ peut être décrit de la manière suivante:

<field name="monDocument" type="binary" column="MonDocument"/>

Le champ référence une colonne MonDocument (définie au niveau de la description de schéma de base de données) qui pourra utiliser l'un des types "large object" prévus par le SGBD. Le chapitre PostgreSQL et les blobs explique les différents types de champs que propose PostgreSQL pour le stockage des "large objects".

Attributs de fichiers

Nous avons abordé ci-dessus le stockage d'un fichier en base de données. Toutefois cela ne concerne que le corps du fichier. Il peut être utile de conserver également certaines metadonnées supplémentaires à propos du fichier, à commencer par son nom initial, sa taille et son mime-type. Ces informations doivent être stockées dans des champs distincts au niveau de la base de données, mais rattachés au fichier du point de vue de la descript.

Au niveau du fichier schema.xml, les champs prévus pour l'enregistrement des attributs sont à déclarer comme tout autre champ, par exemple:

1
2
3
<column name="NomDocPublipostage" type="varchar(100)"/>
<column name="TailleDocPublipostage" type="integer"/>
<column name="MimeTypeDocPublipostage" type="varchar(100)"/>

Ewt s'attend toutefois à ce que ces champs se trouvent dans la même table que le champ qui stocke les données binaires. La conséquence de cela est que le champ doit posséder une valeur en base de données pour posséder des metadonnées. Il n'est pas possible d'associer des métadonnées à un champ virtuel. Il n'est pas non plus possible d'associer des métadonnées au champ faisant office de clé primaire.

Au niveau du fichier descript.xml, on pourra alors référencer ces derniers en tant qu'attributs pour le fichier selon la syntaxe suivante:

1
2
3
4
5
6
7
<field name="docPublipostage" type="binary" column="DocPublipostage">
    <metadata>
        <attribute name="file:name" column="NomDocPublipostage"/>
        <attribute name="file:size" column="TailleDocPublipostage"/>
        <attribute name="file:mimetype" column="MimeTypeDocPublipostage"/>
    </metadata>
</field>

Remarque : Le préfixe file: permet d'indiquer à Ewt qu'il s'agit d'un attribut de fichier. Il est possible que le concept de metadonnée soit étendu à l'avenir à d'autres types de champs. Le préfixe sert donc à distinguer les attributs "réservés" par Ewt des autres.

À noter que l'enregistrement de metadonnées n'est pas obligatoire, mais recommandé.

Chargement des champs binaires

Lorsqu'on édite un dossier qui contient des champs binaires, Ewt ne télécharge pas systématiquement les données binaires. Les métadonnées sont bien récupérées dans la session, mais, pour des questions de performances, le contenu du large object n'est pas téléchargé. Celui-ci n'est téléchargé que lorsque cela est nécessaire, c'est-à-dire lorsque l'utilisateur le demande explicitement (par exemple en cliquant sur un lien de téléchargement, ou via un appel XHR).

Arrangement

Généralités

Le terme "arrangement" désigne les options de tri, de filtre et de pagination appliquées aux groupes multi. L'arrangement peut être exclusivement défini par l'application (au niveau de la descript), laissé au choix de l'utilisateur (l'interface utilisateur doit alors gérer les champs de saisie dans lesquels l'utilisateur peut définir les règles de tri, de filtre et de pagination), ou être un assemblage des deux (p.ex. des règles de filtres statiques utilisés pour filtrer le contenu d'une table et des options modifiables par l'utilisateur pour trier le contenu ou ajouter des filtres supplémentaires). De manière générale, les filtres définis par la descript ne peuvent pas être annulés ou remplacés par l'utilisateur. Ils sont fixes.

Au niveau de la descript, les règles d'arrangement sont définies au moyen d'un élément arrangement au niveau de l'élément group. Ces règles peuvent être complétées par des règles d'arrangement au niveau des éléments field. Pour donner l'idée générale:

  • les règles d'arrangement définies au niveau du group indiquent quelles types d'arrangement l'application doit gérer
  • les règles d'arrangement définies au niveau des field indiquent avec quelle valeur ces arrangements peuvent se faire.

Arrangement au niveau group

L'élément arrangement (niveau group) permet donc d'indiquer au moteur quel critère de tri et quelle condition de filtre il doit appliquer pour le champ lorsqu'un arrangement de tuples est demandé au niveau du groupe.

Exemple de règles d'arrangement définies au niveau group.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<group name="parainages" table="Parainage" type="multi" reffield="idVendeur">
    <arrangement>
        <sort customizable="true"></sort>
        <filter customizable="true">
            <field name="idPersonne" method="contains" type="text"/>
            <field name="agePersonne" method="exact" type="select">
                <options mode="inline">
                    <option value="1">1 - Adulte</option>
                    <option value="2">2 - Enfant</option>
                </options>
            </field>
        </filter>
        <pagination customizable="true"></pagination>
    </arrangement>
    <!-- ... -->
</group>

Arrangement au niveau field

Par défaut (c'est-à-dire si aucune règle d'arrangement n'est définie au niveau du champ), le moteur s'appuie sur la valeur brute du champ, c.-à-d. la valeur en base de données. Cela fonctionne bien lorsque le champ contient une valeur numérique ou une chaîne de caractères standard. Par contre c'est problématique lorsque le champ est une référence vers un autre dossier ou lorsque l'on souhaite trier/filtrer sur une version modifiée de la valeur brute (p.ex. une date ou une valeur composée, comme une valeur "nom + prénom").

Par exemple, si un groupe demande un arrangement qui trie selon une colonne idVendeur et que ce dernier est une référence d'identifiant, un tri standard aurait pour conséquence d'ajouter une clause ORDER BY idVendeur ASC à la requête d'extraction des tuples, ce qui n'est pas très judicieux car il y a toutes les chances que l'utilisateur ne comprenne pas le tri et pense que le tri ne fonctionne pas. À la place, il est plus efficace de trier d'après le nom ou le prénom du vendeur que référence cet identifiant.

C'est ici qu'intervient l'élément arrangement défini au niveau du field. Il permet justement de spécifier quel critère appliquer lorsqu'un groupe demande un tri ou un filtre sur le champ et que la valeur brute de ce dernier n'est pas exploitable directement.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!-- Élément <arrangement> défini au niveau <field> -->
<arrangement>
    <rule type="sort" mode="field" model="vendeur">
        <field name="nom"/>
        <field name="prenom"/>
    </rule>
    <rule type="filter" mode="sql" table="Vendeur">
        <value type="varchar">concat(Vendeur.Nom, ' ', Vendeur.Prenom)</value>
    </rule>
</arrangement>

Dans l'exemple ci-dessus, on définit une règle pour le tri qui s'appuie sur les champs nom et prenom du modèle vendeur (le moteur s'attend à trouver ces champs dans le groupe principal du modèle), ainsi qu'une règle de filtre qui s'appuie sur une clause SQL à appliquer sur la table Vendeur. Le fait d'utiliser le mode field pour le tri et le mode sql pour le filtre est un exemple, mais l'inverse est également possible. Il est même possible de combiner les deux types d'arrangement en une seule règle. Dans ce cas on spécifierait le type selon la forme type="sort,filter".

Mode SQL
Arrêtons-nous quelques instants sur le mode sql pour aborder différents aspects:

Typage
Les clauses SQL définies au moyen de l'élément value doivent être typées. En effet, le moteur n'est pas en mesure de déterminer le type attendu par l'expression SQL et demande que celui-ci soit clairement spécifié pour permettre le passage de valeur selon la notation des prepared statements, afin d'éviter tout risque d'injection. Le type attendu par la clause SQL doit donc être spécifié à l'aide de l'attribut type. La valeur de cet attribut doit contenir le type. Il n'est par contre pas nécessaire de spécifier la taille. L'exemple ci-dessus déclare le type varchar sans mentionner sa longueur.
Table

Dans le mode SQL, lorsqu'on souhaite référencer des colonnes d'une autre table comme dans l'exemple ci-dessus, on doit préfixer les noms de colonnes avec un nom de table. Ce nom de table doit être spécifié via l'attribut table. Dans l'exemple ci-dessus on a indiqué le nom "Vendeur" (qui correspond au vrai nom de la table), mais on peut très bien spécifier n'importe quel nom en réalité. En définissant un attribut table="HelloWorld", on pourrait alors écrire la clause SQL ainsi:

<value type="varchar">concat(HelloWorld.Nom, HelloWorld.Prenom)</value>

En effet en présence d'arrangement selon le mode SQL le moteur construit une requête dans laquelle il intègre les tables externes à l'aide de jointures gauches (LEFT JOIN). Pour éviter les doublons, les noms de tables sont affublés d'un alias (généré dynamiquement). Le moteur doit alors adapter le clause SQL spécifiée dans le champ pour remplacer le nom de la table par le nom de l'alias.

À noter en passant qu'il est également possible de découper la clause en deux:

<value type="varchar">HelloWorld.Nom</value>
<value type="varchar">HelloWorld.Prenom</value>

Cette notation est d'ailleurs préférable pour des raisons de compatibilité. En effet, l'utilisation du mot-clé concat comme dans l'exemple initial peut poser des problèmes sur certains SGBD. Il n'est par exemple pas reconnu sur SQLite.

On peut se passer de spécifier un nom de table lorsque la clause n'implique pas de référence à une colonne. Par exemple la clause suivante est autorisée:

1
2
3
<rule type="sort" mode="sql">
    <value>1</value>
</rule>

Sur certains SGBD, cette clause signifie que l'on souhaite trier selon la première colonne du SELECT. Attention toutefois, il s'agit ici d'un exemple pour illustrer le propos, mais ce type de notation n'est pas recommandé car l'ordre des colonnes utilisées dans la requête générée par le moteur peut changer d'un appel à l'autre (en particulier si on rajoute de nouveaux champs dans la descript).

Notons enfin que le mode SQL autorise l'utilisation de paramètres selon la même notation que pour les éléments option ou default du champ.

Vue (<view>)

Une view est un assemblage de groupes. Elle permet de décrire comment afficher les éléments à l'écran, où plutôt, elle permet de savoir quelles données Ewt doit fournir dans l'arbre de sortie en fonction du style à utiliser.

Chaque vue (view) est constituée des éléments suivants:

  • attribut name : nom de la vue
  • attribut style : nom du style (attention, pour rappel ce nom ne désigne pas une stylesheet XSL, mais un style déclaré au niveau du fichier de configuration et faisant lui-même référence à une stylesheet XSL)
  • attribut public : facultatif; cet attribut permet de statuer sur l'accessibilité de la vue : une vue est considérée publique (valeur true) si l'utilisateur a la possibilité de la sélectionner explicitement. À l'inverse, une vue privée (valeur false) n'est pas visible de l'utilisateur. Typiquement, on qualifiera une vue en public="false" s'il s'agit d'une vue activable uniquement par un script, mais non directement par l'utilisateur au niveau de l'interface
  • élément(s) group : liste des groupes qui composent la vue.
    • L'élément doit contenir un attribut name qui référence le groupe visé.
    • Tout autre attribut supplémentaire qui ne figure pas parmi une série de noms réservés (name, label, description, read, write, etc.) est repris dans l'arbre de sortie au niveau de l'élément group.
  • élément(s) section : sous-ensemble de groupes au sein de la vue. Cet élément est facultatif. L'idée est de pouvoir structurer des groupes pour composer une section dans la page web.
    • Une section doit posséder un attribut name
    • Elle peut à son tour contenir des éléments group et/ou d'autres éléments section.

Rôle des view

Les vues permettent donc de construire des écrans. Elles permettent de décrire, pour un style donné, quels groupes doivent être repris dans l'arbre de sortie. À ce titre, elles remplissent deux rôles importants:

  1. Comme mentionné en introduction, les vues permettent d'indiquer quoi afficher en fonction du style actuel. Le style pouvant être forcé depuis un script, la vue permet d'indiquer quels groupes afficher en fonction du style imposé par le script. Ainsi il est possible de filtrer le contenu affiché en fonction du style.

  2. Les vues remplissent un second rôle plus subtil. Elles permettent la cohabitation de plusieurs dossiers différents au sein d'un même écran. Pour rappel, Ewt permet d'afficher plusieurs dossiers simultanément. Par conséquent, il peut très bien arriver que l'on doive afficher conjointement des dossiers de modèles différents. Si chaque modèle nécessite une feuille de style spécifique, il ne serait pas possible d'afficher simultanément tous les dossiers car la feuille de style prévue pour un modèle ne serait pas capable d'afficher les données d'un autre modèle. Avec les vues, il est possible de décrire comment un dossier doit s'afficher si telle ou telle feuille de style est utilisée. Ainsi, lorsque le moteur se trouve devant le défi d'afficher simultanément plusieurs dossiers de modèles différents, il recherche les vues qui sont communes entre les différents modèles. Pour ce faire, il s'appuie sur les styles associés à ces différentes vues. Il sélectionne alors une vue qui est capable d'afficher tous les dossiers à la fois.

Clônage

La descript permet de spécifier le comportement du moteur concernant le clônage de dossier. Par défaut, le clônage de dossier est désactivé pour tous les modèles.

Il est possible, pour un modèle donné, d'activer le clônage. Pour ce faire, on ajoute l'attribut cloneable à l'élément <model>:

1
2
3
4
<model name="demande" maingroup="info"
       workflow="demande" statefield="idStatut" cloneable="true">
    ...
</model>

La déclaration ci-dessus indique que le clônage est possible sur ce modèle.

Il est possible de déclarer des modes de clônage. Cela permet par exemple de générer une copie de dossier différente en fonction de l'utilisateur qui effectue la copie, du statut du dossier, etc. Il est possible de gérer plusieurs modes différents pour un même modèle. Pour déclarer les modes, on utilisera la syntaxe suivante:

1
2
3
4
<model name="demande" maingroup="info"
       workflow="demande" statefield="idStatut" cloneable="foo,bar">
    ...
</model>

Les valeurs true, false et inherit priment sur les éventuels modes. Cela signifie que la valeur cloneable="foo,true,bar" est équivalente à cloneable="true" : les autres modes sont simplement ignorés et inclus dans true.

La valeur true indique que le clônage de l'élément est possible dans tous les cas. La valeur false indique que le clônage de l'élément n'est pas possible. La valeur inherit indique que le clônage de l'élément est possible dans les mêmes modes que pour son parent.

Lorsque le clônage est activé pour un modèle, il est valable pour tous les tuples et tous les champs du dossier, pour tous les modes déclarés. Il est cependant possible de court-circuiter cela au niveau du groupe ou du champ au moyen d'un sous-arbre du genre:

<clone>false</clone>

ou

1
2
3
4
<clone>
    <mode name="foo">false</mode>
    <mode name="bar">false</mode>
</clone>

Il est recommandé de spécifier explicitement la règle de clônage des champs qui sont des références internes de dossier (typiquement le champ qui fait le lien entre un tuple multi et le maintuple). En effet, en principe ces champs ne devraient pas être clonés, sinon cela reviendrait à rajouter un tuple au dossier source. Le moteur effectue donc un check dans ce sens et affiche un avertissement lorsqu'il rencontre ce genre de champ. Il est possible d'indiquer au moteur d'appliquer la règle du parent au moyen de la valeur:

1
<clone>inherit</clone>

Le mode entre en jeu lorsqu'on déclenche l'action clone: on indiquera à l'action le mode de clônage et le moteur sera en mesure de clôner uniquement les champs qui correspondent au mode sélectionné.

Propriétés (<properties>)

Comme indiqué plus haut, les propriétés sont des valeurs personnalisées que l'on peut spécifier au niveau de la descript, des modèles, des groupes ou des champs.

Les propriétés peuvent contenir du texte ou du xml. Par défaut, la valeur passée à une propriété est reprise quasi telle quelle dans l'arbre de sortie. Il est cependant possible de demander au moteur de retraiter ladite valeur en lui spécifiant une règle de traitement sous la forme d'un namespace.

Namespaces

Les éléments <property> peuvent contenir des sortes d'instructions de traitement. Ces instructions s'expriment au moyen de namespaces que l'on place au niveau des éléments ou de leurs attributs. Il est ainsi possible de demander au moteur d'effectuer une traduction de libellé ou une substitution de variable (voir ci-dessous). L'arbre de sortie reprendra alors l'attribut ou l'élément qui était marqué d'un namespace dans l'arbre de sortie (le namespace lui-même n'est pas repris dans l'arbre de sortie) et la valeur de l'attribut ou de l'élément sera adaptée en fonction de la règle attendue.

Les namespaces s'appliquent aux éléments <property>, à leurs sous-éléments et à leurs attributs. Il est ainsi possible de déléguer à Ewt la charge d'adapter un libellé en fonction de la locale, de substituer une référence de champ en fonction du contexte, etc.

Les namespaces reconnus sont:

  • ch.epilogic.ewt.i18n
  • ch.epilogic.ewt.script
  • ch.epilogic.ewt.text
  • ch.epilogic.ewt.sql
  • ch.epilogic.ewt.scss
  • ch.epilogic.ewt.instr

Les chapitres ci-dessous donnent plus d'information sur le rôle de ces namespaces.

Il est important de préciser que Ewt ne traite les namespaces sur les attributs que si l'élément ne possède pas d'autre attribut du même nom. Ainsi, pour un élément

<foo label="Salut"
     i18n:label="hello.world.label"/>

le moteur n'effectuera aucun traitement de l'attribut i18n:label car cela engendrerait in fine un doublon d'attribut label au niveau de l'arbre de sortie, ce qui ne serait pas valide du point de vue xml.

Namespace ch.epilogic.ewt.i18n

Ce namespace (mappé par convention sur le préfixe i18n) fait référence à l'internationalisation. Il permet d'indiquer au moteur qu'il doit substituer la valeur de l'attribut ou de l'élément par la valeur correspondante dans la langue du client.

Par exemple, un attribut i18n:label="hello.world.label" sera automatiquement changé en label="Salut monde" dans le cas où le client utilise une locale fr et à condition évidemment que l'entrée hello.world.label soit définie dans le bundle de langue. Le libellé sera adapté automatiquement en fonction de la locale.

Le namespace peut également s'appliquer à un élément. Dans ce cas, c'est la valeur de l'élément qui doit contenir la référence de bundle. Par exemple, l'élément <i18n:value>hello.world.label</i18n:value> sera remplacé par <value>Salut monde</value> dans l'arbre de sortie.

Le document 05 - internationalisation revient également sur les aspects de namespace. Un exemple y est donné.

Exemple de requête d'extraction utilisant le namespace ch.epilogic.ewt.i18n sur différents éléments/attributs:

 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
29
<properties>
    <property name="extractions" instr:destination="document">
        <sql:extraction name="liste-consultations" instr:destination="document"
                        format="xhtml" header="true" type="xml" escape="false"
                        sanitize="{ &quot;allowElements&quot;: [ &quot;button&quot;, &quot;i&quot; ], &quot;allowAttributes&quot;: [ &quot;type&quot;, &quot;class&quot;, &quot;onclick&quot; ], &quot;onElements&quot;: [ &quot;button&quot;, &quot;i&quot; ] }"
                        attributes="{ &quot;class&quot;: &quot;table table-responsive&quot; }"
                        i18n:title="extraction.historiqueConsultations.title"
                        position="before">
            <text:value><![CDATA[select '<button type="button" class="btn btn-primary btn-sm" onclick="ewt.submit(''open'', { modelName: ''' || mod || ''', docId: ''' || doc || ''' })"><i class="bi bi-folder2-open"></i></button>' as '',
                                        c as '${i18n:extraction.historiqueConsultations.label.code::Q}',
                                        d as '${i18n:extraction.historiqueConsultations.label.date::Q}',
                                        case when ctr.label${var:language} is null then l else ctr.label${var:language} end as '${i18n:extraction.historiqueConsultations.label.lieu::Q}',
                                        usr.Nom || ' ' || usr.Prenom as '${i18n:extraction.historiqueConsultations.label.conseiller::Q}',
                                        m as '${i18n:extraction.historiqueConsultations.label.motif::Q}'
                                 from ( select IdConsultation doc, 'consultationcss' mod, CodeConsultation c, DateConsultation d, LieuConsultation l, IdConseiller u, :typeConsultationCSS m from ConsultationCSS where IdPersonne=:personne
                                        union
                                        select IdConsultation doc, 'consultationasp' mod, CodeConsultation c, DateConsultation d, LieuConsultation l, IdConseiller u, :typeConsultationASP m from ConsultationASP where IdPersonne=:personne
                                        union
                                        select IdConsultation doc, 'consultationessr' mod, CodeConsultation c, DateConsultation d, LieuConsultation l, IdConseiller u, :typeConsultationESSR m from ConsultationESSR where IdPersonne=:personne) x
                                      left join ListeLieuxConsultations as ctr on ctr.code=l
                                      left join Conseiller usr on usr.IdConseiller=u
                                 order by d desc, c desc]]></text:value>
            <i18n:param name="typeConsultationCSS" type="varchar">general.label.css</i18n:param>
            <i18n:param name="typeConsultationASP" type="varchar">general.label.asp</i18n:param>
            <i18n:param name="typeConsultationESSR" type="varchar">general.label.essr</i18n:param>
            <text:param name="personne">#idPersonne</text:param>
        </sql:extraction>
    </property>
</properties>

Namespace ch.epilogic.ewt.text

Ce namespace (mappé par convention sur le préfixe text) indique que la valeur du paramètre est un texte pouvant contenir des références de variables à substituer. Par exemple, un attribut text:id="${data:idVendeur}" apparaîtra comme id="12" dans l'arbre de
sortie dans le cas où le champ idVendeur du dossier courant contient la valeur "12".

Namespace ch.epilogic.ewt.sql

Ce namespace (mappé par convention sur le préfixe sql) indique que la valeur du paramètre est une requête SQL, éventuellement accompagnée de paramètres. Le moteur se chargera alors d'évaluer la requête et de reprendre le résultat en valeur dans l'arbre de sortie.

Dans la version actuelle du moteur, le namespace n'est pris en charge que pour le traitement des éléments XML. Le fait d'appliquer le namespace à un attribut est sans effet (la requête sera reprise telle quelle dans l'arbre de sortie).

L'élément XML qui est défini avec ce namespace doit respecter une certaine forme. Il s'agit en réalité de la même forme que celle utilisée dans le cas des requêtes SQL pour les valeurs par défaut de champ ou pour les listes d'options: l'élément doit posséder un sous-élément <value> dans lequel la requête (ou plutôt le statement) est donnée. Il est également possible de définir des sous-éléments <param> qui seront les valeurs à passer au prepared statement. Ces paramètres doivent être typés (attribut type) et peuvent être nommés (attribut name) dans le cas où la requête fait des références de paramètres nommés. Voici un exemple:

1
2
3
4
5
6
7
8
9
<property name="extractions" instr:destination="document">
    <sql:extraction name="last-entries" position="after" 
                    format="html" attributes='{ "class": "search-result" }' 
                    instr:destination="document"
                    i18n:title="model.client.extraction.lastEntries.title">
        <value>select idEntry, FileName, FileSize, StoreTimestamp from Entry where idClient=?</value>
        <text:param type="auto">${data:idClient}</text:param>
    </sql:extraction>
</property>

Dans l'exemple ci-dessus, on utilise la référence ? pour représenter une donnée. Les paramètres ne doivent donc pas être nommés. Ils doivent par contre être typés. Ici on spécifie un paramètre avec le type auto. Ce type peut être utilisé pour les identifiants de tuples. Le moteur détermine alors le type en fonction du type des primary key spécifié dans la config de l'application.

Le même exemple peut être adapté pour utiliser des paramètres nommés (seules les lignes 5 et 6 sont modifiées par rapport à l'exemple précédent):

1
2
3
4
5
6
7
8
9
<property name="extractions" instr:destination="document">
    <sql:extraction name="last-entries" position="after" 
                    format="html" attributes='{ "class": "search-result" }' 
                    instr:destination="document"
                    i18n:title="model.client.extraction.lastEntries.title">
        <value>select idEntry, FileName, FileSize, StoreTimestamp from Entry where idClient=:refClient</value>
        <text:param type="auto" name="refClient">${data:idClient}</text:param>
    </sql:extraction>
</property>

Le moteur reconnaît les attributs format, sanitize, header, escape et attributes définis sur l'élément auquel le namespace est déclaré (élément extraction dans l'exemple ci-dessus). Ils influent sur la forme de la valeur générée en sortie. Ils jouent le même rôle que dans les options de la méthode $sql.mselect. L'attribut format peut prendre les mêmes valeurs que pour la méthode $sql.mselect, à la différence qu'ici la valeur auto correspond à la valeur xml. L'attribut sanitize peut être spécifié sous forme de texte ou de json. L'attribut attributes doit être spécifié sous forme de json, comme c'est le cas dans l'exemple ci-dessus. Notez au passage l'utilisation de délimiteur ' (apostrophe) pour pouvoir spécifier du json valide. On aurait également pu noter l'attribut attributes="{ &quot;class&quot;: &quot;search-result&quot; }", mais cela aurait été moins lisible.

Les attributs format, sanitize, header, escape et attributes peuvent également tous être spécifiés en tant qu'éléments au même niveau que <value> et <param>. Par contre le moteur ne s'attend pas à trouver ces éléments s'ils sont déjà définis en tant qu'attributs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<property name="extractions" instr:destination="document">
    <sql:extraction name="last-entries" position="after" 
                    instr:destination="document"
                    i18n:title="model.client.extraction.lastEntries.title">
        <value>select idEntry, FileName, FileSize, StoreTimestamp from Entry where idClient=:refClient</value>
        <text:param type="auto" name="refClient">${data:idClient}</text:param>
        <format>html</format>
        <attributes>{ "class": "search-result" }</attributes>
    </sql:extraction>
</property>

Dans le cas des formats xml, html ou xhtml, il est possible d'indiquer au moteur sous quelle forme le résultat doit être fourni. Par défaut, il est retourné sous forme d'XML directement dans l'arbre de sortie. Il est cependant possible d'ajouter un attribut ou un élément output avec la valeur text pour demander au moteur de retourner le résultat sous forme de texte.

Utilisation de caractères spéciaux

Le fichier de descript étant un fichier XML, la requête SQL doit respecter la syntaxe XML et les caractères spéciaux doivent être échappés. Ainsi, un test where somefield < 10 devra être noté somefield &lt; 10.

Pour éviter cette syntaxe peut lisible, nous recommandons d'inscrire les requêtes SQL dans un bloc CDATA. La requête pourra donc être notée:

<value><![CDATA[select ... where somefield < 10]]></value>

Si vous devez écrire une requête sql qui utilise des caractères spéciaux

Namespace ch.epilogic.ewt.script

Ce namespace (mappé par convention sur le préfixe script) indique que la valeur de l'élément ou du paramètre est un script ou une référence de script à évaluer. Par exemple, un attribut script:label="return 1+2;" apparaîtra comme label="3" dans l'arbre de sortie.

Le namespace peut être utilisé sur les attributs (comme ci-dessus) ou sur les éléments. Dans le cas des attributs, le script ne peut prendre aucun paramètre. Dans le cas des éléments, le comportement s'apparente au cas du namespace ch.epilogic.ewt.sql, à savoir que:

  • Le script ou la référence de script doit être passée via un élément <value>. La valeur peut donc être un script inline ou une référence de script de l'application.
  • Il est possible de spécifier 0 ou n paramètres via des éléments param. Ces derniers DOIVENT avoir un attribut name qui donne le nom du paramètre que recevra le script. Un attribut type permet de spécifier le type du paramètre. Attention, ici le type doit désigner le type d'objet. Les types autorisés sont string, number, date, time, timestamp, map, array, file ou null. Dans le cas des types map et array, le moteur s'attend à recevoir une version json de l'objet.

Dans l'exemple ci-dessous, on définit un script inline qui extrait la propriété foo d'un map défini en json.

1
2
3
4
<script:extraction name="test" position="after">
    <text:value>return $$.myparam.foo;</text:value>
    <text:param type="map" name="myparam">{ "foo": 1, "bar": "end" }</text:param>
</script:extraction>

Par défaut la valeur de retour inscrite dans l'arbre de sortie est du texte simple. Il est possible d'ajouter un attribut output à l'élément racine pour indiquer au moteur sous quelle forme la valeur doit être inscrite dans l'arbre de sortie. Lorsque cet attribut vaut xml, le moteur force la valeur à être inscrite sous la forme d'élément XML (pour autant que la valeur générée par le script s'y prête).

1
2
3
<script:extraction name="test" position="after" output="xml">
    <value>return `&lt;a href="https://www.epilogic.ch"&gt;https://www.epilogic.ch&lt;/a&gt;`;</value>
</script:extraction>

Namespace ch.epilogic.ewt.scss

Ce namespace (mappé par convention sur le préfixe scss) indique que la valeur du paramètre est du SCSS qui doit être compilé en CSS. L'arbre de sortie affichera donc la valeur compilée. La compilation est effectuée au chargement de la descript. Cela évite de la refaire au runtime à chaque génération d'écran.

Exemple de propriété utilisant le namespace ch.epilogic.ewt.scss pour compiler une feuille de style CSS.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<property name="inline-css">
    <scss:style>
        .model-offre .group-detail {
            .header-position    { width:  5%;   min-width:  60px; }
            .header-description { width: 45%;   min-width: 250px; }
            .header-remarque    { width: 25%;   min-width: 200px; }
            .header-chiffrage   { width:  7.5%; min-width: 125px; }
            .header-tarif       { width:  7.5%; min-width: 100px; }
            .header-montantHT   { width: 10%;   min-width: 125px; }
            .field-chiffrage    { text-align: right; }
            .field-tarif        { text-align: right; }
            .field-montant      { text-align: right; }
        }
    </scss:style>
</property>

Namespace ch.epilogic.ewt.instr

Ce namespace (mappé par convention sur le préfixe instr) désigne des instructions de traitement passées au moteur. Ces instructions ne sont pas reprises dans l'arbre de sortie mais influent sur la façon de structurer son contenu. Elles sont donc réservées au moteur et permettent d'indiquer à ce dernier comment il doit traiter la propriété.

La version actuelle du moteur prévoit l'instruction suivante :

  • attribut destination : indique dans quelle(s) branche(s) de l'arbre de sortie l'élément doit être repris; la valeur peut être l'une de valeurs suivantes, ou une combinaison de plusieurs valeurs séparées par une virgule:

    • descript-base : La propriété doit être reprise au niveau du sous-arbre <descript> de l'arbre de sortie, uniquement lorsque celui-ci affiche un résumé du modèle (sans les groupes et les tuples)
    • descript-full : La propriété doit être reprise au niveau du sous-arbre <descript> de l'arbre de sortie, uniquement lorsque celui-ci est détaillé, c'est-à-dire lorsqu'un dossier du modèle est ouvert.
    • descript : cette propriété correspond à la combinaison descript-base,descript-full; la propriété doit donc être reprise au niveau du sous-arbre <descript> de l'arbre de sortie, quelle que soit la forme (résumé ou détaillé).
    • document : la propriété doit être reprise dans l'élément correspondant à celui dans lequel elle est déclarée, mais au niveau du sous-arbre <document> de l'arbre de sortie. Par exemple, une propriété déclarée dans un élément <field> de la descript sera reprise au niveau de l'élément <field> du sous-arbre <document> de l'arbre de sortie
    • header : la propriété doit être reprise au niveau du sous-arbre <header> de l'arbre de sortie (uniquement dans le cas de groupes multi)

    Il est possible, comme dans l'exemple ci-dessous, de spécifier plusieurs destinations en les séparant par une virgule. Ici l'instruction destination demande au moteur de reprendre le nœud property au niveau des documents et des headers.

    1
    2
    3
    4
    5
    <property name="extractions" instr:destination="document,header">
        <extraction position="after">
            ...
        </extraction>
    </property>
    

    En l'absence d'instruction destination, le moteur détermine une valeur par défaut en fonction de l'élément auquel il est appliqué et au contexte dans lequel cet élément est intégré. Ainsi, une propriété définie au niveau racine de la descript aura une destination par défaut correspondant à descript, alors qu'une propriété définie au niveau d'un modèle, d'un groupe ou d'un champ aura une destination par défaut équivalente à la combinaison descript-full,document.

⚠️ Il convient de mentionner que certaines substitutions (notamment les références à des valeurs de dossiers) ne pourront se faire que dans le cas où la destination inclut document. En effet, le moteur ne pourra résoudre une expression du genre #projet.name que dans le contexte d'un dossier.