L’exemple d’application donné ci-dessus fait appel à une classe Table, qui a été écrite pour donner un exemple relativement simple d’utilisation du framework Generic System, elle est donc moins complexe que la table utilisée dans la démonstration disponible sur genericsystem.com.
Ce composant se contente d’afficher toutes les instances d’un type donné avec les valeurs de leurs holders et des composants de leurs liens.
Nous verrons plus bas comment l’arbre de tags créé est transformé en arbre de DOM nodes affichable par le navigateur.
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 |
@StyleClass("table") @StyleClass(path = FlexDiv.class, value = "row") @FlexDirectionStyle(FlexDirection.COLUMN) @Children({ TitleRow.class, ContentRow.class }) public class Table extends FlexDiv { @ReverseFlexDirection @StyleClass(path = HtmlDiv.class, pos = 0, value = { "cell", "subcell", "title-cell", "title-first-cell" }) @StyleClass(path = HtmlDiv.class, pos = 1, value = { "cell", "subcell", "title-cell" }) @Children({ ValueComponents.class, ValueComponents.class }) @ForEach(path = ValueComponents.class, pos = 1, value = ObservableListExtractor.ATTRIBUTES_OF_TYPE.class) @ForEach(path = { ValueComponents.class, HtmlSpan.class }, pos = { 1, 1 }, value = ObservableListExtractor.OTHER_COMPONENTS_1.class) public static class TitleRow extends FlexDiv { } @ReverseFlexDirection @ReverseFlexDirection(path = HtmlDiv.class, pos = 1) @StyleClass("instance-row") @StyleClass(path = HtmlDiv.class, pos = 0, value = { "cell", "subcell", "instance-name" }) @StyleClass(path = HtmlDiv.class, pos = 1, value = "cell") @FlexDirectionStyle(path = HtmlDiv.class, pos = 1, value = FlexDirection.COLUMN) @ForEach(ObservableListExtractor.SUBINSTANCES.class) @Children({ FlexDiv.class, FlexDiv.class }) @Children(path = HtmlDiv.class, pos = 1, value = ValueComponents.class) @BindText(path = HtmlDiv.class, pos = 0) @ForEach(path = HtmlDiv.class, pos = 1, value = ObservableListExtractor.ATTRIBUTES_OF_INSTANCES.class) @ForEach(path = { HtmlDiv.class, ValueComponents.class }, value = ObservableListExtractor.HOLDERS.class) public static class ContentRow extends FlexDiv { @StyleClass("subcell") @Children({ HtmlSpan.class, HtmlSpan.class }) @BindText(path = HtmlSpan.class) @Select(path = HtmlSpan.class, pos = 0, value = ObservableValueSelector.STRICT_ATTRIBUTE_SELECTOR.class) @ForEach(path = HtmlSpan.class, pos = 1, value = ObservableListExtractor.OTHER_COMPONENTS_2.class) public static class ValueComponents extends FlexDiv { } } } |
Les annotations présentes sur les classes servent à définir le comportement de l’application.
Définition de l’arbre des tags
Le composant présenté ici définit 4 classes : Table, TitleRow, ContentRow et ValueComponents. Il utilise également d’autres classes déjà définies dans le réacteur : HtmlDiv et HtmlSpan qui indiquent seulement que les DOM nodes à créer sont des div ou des span, ainsi que FlexDiv qui est une div avec un layout CSS flexbox qui possède une propriété correspondant à la direction.
La structure de l’arbre des tags est définie par les annotations @Children : la table contient un tag correspondant à la ligne de titre et un tag correspondant aux lignes de contenu, ce qui est indiqué par l’annotation
1 |
@Children({ TitleRow.class, ContentRow.class }) |
Les classes correspondant aux lignes utilisent toutes deux la classes ValueComponents, dont le rôle est d’afficher la valeur ou les composants d’un générique donné, son fonctionnement est expliqué dans la sous-partie suivante.
TitleRow a comme enfants deux instances de ValueComponents : la première servira à afficher le nom du type, la seconde à afficher les attributs qui y sont rattachés.
ContentRow a elle pour enfants deux instances de FlexDiv : la première contiendra la valeur de l’instance et la seconde contiendra les holders pour un attribut donné. Il n’est pas suffisant d’utiliser ValueComponents pour afficher les holders puisqu’un attribut peut avoir plusieurs holders, on indique donc que la deuxième FlexDiv contient des ValueComponents grâce à l’annotation
1 |
@Children(path = HtmlDiv.class, pos = 1, value = ValueComponents.class) |
Cette annotation est placée sur la classe ContentRow, mais les paramètres path et pos indiquent qu’elles ne s’appliquent pas à l’instance de ContentRow mais à la seconde HtmlDiv contenue dans celle-ci. Notez qu’on utilise HtmlDiv dans le chemin bien que l’instance soit de la classe FlexDiv : pour vérifier si un descendant est concerné par une annotation ayant un chemin, on regarde seulement si sa classe étend la classe indiquée.
Création de DOM nodes à partir des tags
L’arbre des tags créé grâce aux annotations @Children ne définit pas complètement l’arbre des DOM nodes que l’on souhaite obtenir : parfois on souhaite qu’un tag corresponde à plusieurs DOM nodes, ou au contraire qu’il ne corresponde à aucun DOM node dans certains cas. On définit ces comportement à l’aide des annotations @ForEach et @Select.
Voyons d’abord les annotations placées sur la classe ValueComponents. Celle-ci a deux enfants de classe HtmlSpan. Elle comporte également une annotation @Select :
1 |
@Select(path = HtmlSpan.class, pos = 0, value =ObservableValueSelector.STRICT_ATTRIBUTE_SELECTOR.class) |
Cette annotation concerne le premier enfant de classe HtmlSpan. La valeur est une classe qui définit un test spécifiant dans quels cas il faut créer un DOM node correspondant à ce tag. La classe donnée ici indique qu’il faut créer un DOM node seulement pour les attributs stricts : les génériques n’ayant qu’un composant.
La classe ValueComponents a également une annotation @ForEach qui concerne elle le second enfant :
1 |
@ForEach(path = HtmlSpan.class, pos = 1, value = ObservableListExtractor.OTHER_COMPONENTS_2.class) |
La valeur est une classe qui définit une façon d’obtenir une liste de génériques à partir des génériques du contexte courant. Un DOM node sera créé pour chaque générique obtenu. Ici on affiche tous les composants du lien sauf celui qui correspond à l’instance de la ligne. Dans le cas des holders, aucun DOM node ne sera créé pour ce tag.
Trois annotations @ForEach sont placées sur la classe ContentRow. La première indique qu’une ligne doit être créée pour chaque instance du type courant. La seconde :
1 |
@ForEach(path = HtmlDiv.class, pos = 1, value = ObservableListExtractor.ATTRIBUTES_OF_INSTANCES.class) |
indique que la seconde div contenue dans ContentRow doit être répétée pour chaque attribut de l’instance. Enfin, la troisième :
1 |
@ForEach(path = { HtmlDiv.class, ValueComponents.class }, value =ObservableListExtractor.HOLDERS.class) |
indique que le tag ValueComponents contenu dans cette dernière div doit être répété pour chaque holder d’un attribut et d’une instance donnés. On remarque ici que le chemin permet de désigner non seulement des enfants d’un tag, mais aussi des descendants à n’importe quel niveau. Ici l’annotation concerne un petit-enfant de l’instance de ContentRow.
Enfin, la classe TitleRow a deux annotations @ForEach. La première indique qu’il faut créer un DOM node par attribut pour le second enfant de classe ValueComponents :
1 |
@ForEach(path = ValueComponents.class, pos = 1, value=ObservableListExtractor.ATTRIBUTES_OF_TYPE.class) |
La seconde s’applique au second HtmlSpan contenu dans cet enfant :
1 |
@ForEach(path = { ValueComponents.class, HtmlSpan.class }, pos = { 1, 1 }, value = ObservableListExtractor.OTHER_COMPONENTS_1.class) |
Elle permet de répéter cet élément span pour chaque composant de l’attribut à l’exclusion du type affiché. Le filtre est différent de celui à utiliser pour l’affichage des liens car les contextes d’utilisation sont différents.
On remarque qu’une annotation @ForEach placée sur la classe ValueComponents concernait déjà le même tag. Dans ce cas, c’est l’annotation placée sur l’ancêtre placée le plus haut — celle qui a le chemin le plus long — qui est prioritaire. On peut ainsi redéfinir localement le comportement d’un tag.
Remarque : Les listes retournées par les fonctions définies dans les @ForEach sont observables, et l’affichage est mis à jour automatiquement lorsque ces listes sont modifiées.
Contenu texte
Les annotations @BindText, avec éventuellement un chemin et une position, signifient que le HTML DOM node créé doit avoir comme contenu texte une chaîne calculée à partir du générique du contexte.
Style
Les autres annotations utilisées définissent le style des composants. @StyleClass permet d’ajouter une ou plusieurs classes CSS aux DOM nodes à créer, @Style permet d’ajouter directement un style en précisant le nom et la valeur de la propriété.
La direction flex est toutefois traitée différemment des autres styles. On utilise les annotations spécifiques @FlexDirectionStyle et @ReverseFlexDirection, qui ne peuvent être appliquées qu’à des tags d’une classe étendant FlexDiv, et pour @ReverseFlexDirection, dont le parent est d’une classe étendant FlexDiv également. En effet, FlexDirectionStyle définit la valeur d’une propriété créée à la création d’un DOM node correspondant à un tag de classe FlexDiv. @ReverseFlexDirection indique que les DOM nodes doivent avoir la direction perpendiculaire à celle de leur parent, et lancent l’écoute de cette dernière. Ainsi, pour afficher le tableau sous forme de colonnes plutôt que de lignes, il suffit de changer la valeur de la direction de Table, sans modifier les annotations sur les autres classes.
Il existe également @KeepFlexDirection qui indique qu’un composant doit avoir la même flex-direction que son parent.
Autres annotations
Il existe d’autres annotations que celles introduites ici. Elles permettent par exemple de définir l’action à exécuter au clic sur un bouton ou un lien, de spécifier comment obtenir la chaîne à afficher à partir d’un générique, etc.