Composants

Les composants permettent de découper une interface utilisateur en éléments indépendants et réutilisables.

En général les composants sont basés sur des fonctions ou sur des classes JavaScript. Room ne supporte que les composants basés sur des fonctions qui sont bien plus simples à coder que les autres.

Nous ne parlons pas ici de Web Components, mais dans la mesure où ces composants sont développés en JavaScript, Room peut très bien être utilisé pour cela.

Structure d’un composant

Un composant Room est simplement une fonction JavaScript qui retourne un élément HTML, SVG ou MathML ou un tableau de ces éléments.

Exemple de composant :

function HelloWorld() {
return p("Hello World");
}

Une convention est que la première lettre du nom de la fonction soit en majuscule, mais ce n’est pas une contrainte pour Room.

Paramètres d’un composant

Un composant peut recevoir des paramètres qui peuvent être exprimés comme dans une fonction JavaScript classique ou en utilisant le passage de paramètre par décomposition d’objet.

Exemple en version classique :

function HelloPerson(firstName, lastName) {
return p("Hello ", [firstName, lastName].join(" "));
}

Exemple avec la syntaxe de décomposition :

function HelloPerson({firstName, lastName}) {
return p("Hello ", [firstName, lastName].join(" "));
}

Un des avantages de la syntaxe par décomposition est que l’ordre des paramètres n’a pas d’importance à l’appel et que l’on a des paramètres nommés.

Ainsi dans les exemples suivants, l’appel à HelloPerson donne toujours le même résultat :

HelloPerson({firstName: "John", lastName: "Doe"});
HelloPerson({lastName: "Doe", firstName: "John"});
let firstName = "John";
let lastName = "Doe";
HelloPerson({firstName, lastName});
HelloPerson({lastName, firstName});

Alors que ce n’est pas le cas avec ces exemples :

HelloPerson("John", "Doe");
HelloPerson("Doe", "John");
let firstName = "John";
let lastName = "Doe";
HelloPerson(firstName, lastName);
HelloPerson(lastName, firstName);

Un autre avantage est qu’il est plus facile d’ajouter un paramètre si le composant doit évoluer et sans casser la compatibilité du code.

Si par exemple on veut ajouter un nouveau paramètre title au composant HelloPerson, alors avec un passage classique de paramètre, il faut ajouter ce paramètre en dernier pour ne pas casser la compatibilité du code utilisant le composant.

Ce qui n’est pas le cas avec la syntaxe de décomposition :

function HelloPerson({title = "", firstName, lastName}) {
return p("Hello ", [title, firstName, lastName].join(" "));
}

Le paramètre title peut là être ajouté où l’on veut, il y a juste à prévoir dans le code du composant (ici en donnant une valeur par défaut comme ceci : title = "") qu’il peut ne pas être présent à l‘appel.

Room n’impose pas l’une ou l’autre de ces méthodes pour les composants, mais la syntaxe de décomposition pour les paramètres est clairement conseillée. Le cas où elle n’est pas nécessaire est si un composant est codé dans un autre composant et est donc privé.

Un composant peut également recevoir des enfants, par exemple pour un composant qui serait un container pour d’autres composants.

Un exemple avec le composant suivant :

function Flexbox({element = "div", direction = "column"}, ...children) {
return Room.h(element,
{style: `display: flex; flex-direction: ${direction};`},
...children
);
}

Le premier paramètre du composant permet de passer des propriétés du composant, ici element et direction, le reste des paramètres est traité comme des enfants qui peuvent être des composants, des éléments, du texte, etc.

Le composant peut alors être utilisé comme ci-dessous :

return Flexbox({element: 'article', direction: 'row'},
Flexbox(
h2("Title 1"),
p("Text 1")
),
Flexbox(
h2("Title 2"),
p("Text 2")
)
);

Ceci génère un élément <article> qui est flexible en ligne et contenant deux éléments <div> qui sont flexibles en colonne et qui contiennent un élément <h2> et un élément <p>.

Utilisation d’un composant

Un composant peut être ajouter dans le DOM d’une page ou être utilisé par un autre composant.

Ajout dans le DOM d’une page

Un composant étant une fonction qui retourne un élément HTML, SVG ou MathML ou un tableau de ces éléments, son ajout dans le DOM d’une page peut se faire avec toutes les fonctions supportées par les éléments.

Par exemple :

Si le composant retourne un tableau, il est nécessaire d’utiliser la syntaxe du paramètre du reste en appelant ces fonctions.

Exemple où les éléments contenus dans un tableau retourné par un composant sont ajoutés après le dernier enfant d’un élément :

element.append(...MyComponent());

Une autre solution est d’utiliser la fonction append() de Room qui accepte des éléments ou des tableaux directement :

Room.append(element, MyComponent());

Utilisation dans un autre composant

Il est possible d’utiliser un composant dans un autre composant et c’est aussi simple que d’utiliser un élément.

Voici par exemple un nouveau composant Hello qui utilise les 2 composants précédents HelloWorld et HelloPerson :

function Hello({persons}) {
return div(
HelloWorld(),
persons.map(HelloPerson);
);
}

Ce composant attend en entrée un tableau d’objets person et retourne un élément <div> contenant plusieurs éléments <p>. Le premier élément <p> contient le texte "Hello World" donné par le composant HelloWorld et si le tableau persons n’est pas vide, les éléments <p> suivant contiennent un texte du genre "Hello John Doe" ou "Hello Peter Parker" suivant le contenu du tableau.

Notez ici que le composant HelloPerson est utilisé directement comme paramètre de la fonction map des tableaux ce qui ne pose pas de problème puisqu’un composant est une fonction et que le composant HelloPerson attend un objet person en entrée qui lui est donnée par la fonction map. La seule contrainte étant que l’objet person doit avoir 2 propriétés nommée firstName et lastName.

Un exemple plus complet de réutilisation d’un composant dans un autre composant est donné par le composant YouTube Selector.

Réactivité dans un composant

La réactivité dans un composant est obtenue en utilisant des données observables dans le composant. Ces données observables sont créées avec la fonction createData() de Room.

Dans une interface utilisateur construite en HTML, SVG ou MathML, c’est à la fois les attributs des éléments qui peuvent être réactifs ou les contenus des éléments.

Par exemple avec ce composant nommé SlideShow dont le code est le suivant :

function SlideShow({images}) {
const {div, figure, img, figcaption, button, p} = Room.elements();

const index = Room.createData(1);
const src = () => images[index - 1].src
const title = () => images[index - 1].title;

return div({class: "slideShow"},
Array.isArray(images) && images.length ? [
figure(
img({src, alt: title}),
figcaption(title)
),
div(
button("Précédent", {
onClick: () => index.value--,
disabled: () => index.value == 1
}),
p(index, " / ", images.length),
button("Suivant", {
onClick: () => index.value++,
disabled: () => index.value == images.length
})
)
] : ""
);
}

La réactivité est liée à une donnée observable index qui donne le numéro de l’image à afficher. Quand sa valeur change suite à un clic sur un des boutons, il faut mettre à jour :

  • Les attributs src et alt de l’élément <img>.
  • Le noeud texte contenu dans l’élément <figcaption>.
  • Le premier noeud texte contenu dans l’élément <p> (il contient le numéro de l’image affichée).
  • Les attributs disabled des éléments <button>, le premier devant être inactif si c’est la première image et le deuxième bouton si c’est la dernière image.

Pour que cela fonctionne, les valeurs des attributs et des noeuds de type texte sont données par des fonctions et non pas rendues directement.

Notez l‘exception du premier noeud texte de l’élément <p>, il n’y a pas besoin de passer par une fonction car la valeur est directement donnée par la donnée observable index qui, étant une donnée observable d’une primitive de JavaScript, est repérée par Room.

Dernière mise à jour :