Fonctionnement

Le fonctionnement de Room est basé sur la composition d’une interface où la réactivité des éléments y est indiquée et les dépendances aux données automatiquement détectées.

Composition

Pour composer une interface utilisateur avec Room il suffit d’appeler des fonctions JavaScript qui produisent des objets de la classe Elements. Ceci peut être fait avec les méthodes createElement() ou createElementNS() de l’API Document mais Room offre une méthode plus simple via sa fonction h() et surtout sa fonction elements().

La fonction elements() de Room permet d’obtenir des fonctions dont les noms sont les noms d’éléments, par exemple la fonction div() qui va produire un élément HTML <div>.

En plus de produire un élément, il est possible, avec une fonction donnée par elements(), de donner des attributs à l’élément produit en passant en paramètre à son appel un objet littéral.

Par exemple, l’appel de la fonction div() avec comme paramètre un objet {id: "main", class: "container"} permet de produire un élément <div> avec un attribut id à la valeur main et un attribut class à la valeur container.

Il est également possible de donner des éléments enfants à l’élément créé simplement en lui passant à l’appel de la fonction ces éléments comme paramètre supplémentaires.

Un exemple :

const {div, h1} = Room.elements();
const main = div({id: "main", class: "container"}, h1("Hello World")));

La première ligne de ce code récupère les fonctions div() et h1() depuis la fonction elements() de Room.
La deuxième ligne fabrique un élément <div> d’identifiant main et de classe container avec dedans, un élément <h1> contenant lui même comme enfant le texte "Hello World".

La variable main contient donc un objet de la classe Elements qui peut ensuite être injecté dans le DOM de la page avec par exemple la méthode append() de l’API Document ou avec la fonction append() de Room.

Il est donc très facile de produire une interface complexe en imbriquant des appels à des fonctions données par la fonction elements() de Room.

Si vous avez déjà le code HTML / SVG / MathML d’une interface, vous pouvez utiliser notre convertisseur pour obtenir un code JavaScript équivalent.

Réactivité

Votre interface utilisateur peut être réactive, c’est à dire qu’un évènement peut changer toute ou partie de son contenu. Cet événement peut être par exemple un clic de l’utilisateur, mais aussi un événement extérieur comme un timer qui s’exécute ou des données qui arrivent suite à une requête ou par des WebSockets.

Pour cela, Room introduit la notion de données observables qui peuvent être créées avec la fonction createData(). Room est capable de voir quand ces données sont modifiées. Donc, en utilisant ces données observables dans votre interface utilisateur, Room va pouvoir mettre à jour les parties (attributs ou contenus des éléments) de votre interface qui utilisent ces données et que vous souhaitez voir être modifiées.

Pour indiquer à Room qu’une partie (attribut ou contenu d’un élément) de votre interface est réactive, le principe de base très simple est de donner la valeur de cette partie via une fonction qui va lire une ou plusieurs données observables.

Sans fonction, Room ne verra pas de dépendance, il existe néanmoins un raccourci pour les données observables d’une primitive JavaScript qui est d’utiliser directement le nom de la variable contenant cette donnée.

Quelques exemples :

const {div} = Room.elements();
const count = Room.createData(0);
const element1 = div(() => count);
const element2 = div(count);
const element3 = div(count.value);
const element4 = div(() => count.value);

Au changement de la donnée observable count, le contenu de l’élément <div> contenu dans la variable :

  • element1 va automatiquement être modifié car son contenu est donné par une fonction qui lit la donnée observable count,
  • element2 va aussi automatiquement être modifié car son contenu est donné directement par la variable count qui est une donnée observable d’une primitive JavaScript (raccourci indiqué plus haut).
  • element3 ne va pas être modifié car la lecture directe de la propriété value de la donnée observable count n’est pas détectée par Room,
  • element4 va automatiquement être modifié car son contenu est donné par une fonction qui lit la donnée observable count (même si la lecture est via la propriété value).

Avec ce principe simple, vous indiquez précisément les parties de votre interface qui sont réactives, Room en garde la trace et est capable de faire automatiquement les modifications quand une ou plusieurs données observables changent.

En résumé, vous pilotez la réactivité de votre interface simplement en pilotant des mises à jour de données.

Précautions

Il y a un cas de figure où l’utilisation d’une fonction peut poser un problème si en plus de la consultation d’une donnée observable, la fonction utilise une autre fonction qui elle même consulte des données observables. Room dans ce cas va prendre en compte toutes les données observables consultées comme dépendance du contenu.

Un exemple :

function Input() {
const {div, input, span} = Room.elements();
const data = Room.createData("");
const onInput = e => data.value = e.target.value
return div(
input({type: "text", value: data.value, onInput}),
" ", span(data)
);
}

function ShowHideComponent() {
const {div, label, span, input} = Room.elements();
const visible = Room.createData(false);
const onChange = e => visible.value = e.target.checked;
return div(
label(
input({type: "checkbox", onChange}),
span("Montrer ou cacher")
),
() => visible.value ? Input() : div("Input caché")
);
}

Ce qui affiche l’interface suivante :

Cliquer sur la case à cocher fonctionne, mais il est impossible de saisir du texte dans le champ de saisie quand il est visible. Room considère que l’élément <div> a une dépendance sur la donnée observable visible mais aussi sur la donnée observable data du composant Input() car ce dernier consulte la donnée observable data et donc régénère tout le composant à la modification de celle-ci (lors d’une saisie).

Heureusement, il y a une solution simple à ce cas de figure, il faut utiliser la fonction untrack() de Room le temps de l’appel au composant Input(). La fonction untrack() va suspendre la recherche des dépendances au premier niveau de traitement sans empêcher la recherche des dépendances aux niveaux d’imbrication inférieurs ce qui règle le problème.

Le code du composant devient donc :

function ShowHideComponent2() {
const {div, label, span, input} = Room.elements();
const visible = Room.createData(false);
const onChange = e => visible.value = e.target.checked;
return div(
label(
input({type: "checkbox", onChange}),
span("Montrer ou cacher")
),
() => visible.value ? Room.untrack(() =>Input()) : div("Input caché")
);
}

Ce qui affiche maintenant l’interface suivante qui fonctionne correctement :

Dernière mise à jour :