v12.9
Unification (et séparations) des APIs de création de stores
makeEntityStore et buildNode ont été fusionnées dans la même fonction makeStoreNode
Il n'y a donc plus de différence entre les deux APIs, cela clarifie qu'il s'agit bien de la même chose (le EntityStore a toujours été un StoreNode), et cela unifie aussi l'implémentation côté Focus.
En particulier, cela implique que vous pouvez utiliser de manière interchangeable MyEntity/[MyEntity] et e.object(MyEntity)/e.list(MyEntity) (ou la version complète {type: "object"/"list", entity: MyEntity} pour définir vos noeuds dans les entités ou les définitions de store.
C'est donc un breaking change, mais que vous pouvez résoudre très simplement avec un "search and replace" global makeEntityStore => makeStoreNode et buildNode => makeStoreNode.
De plus, makeStoreNode accepte aussi un deuxième paramètre pour renseigner la valeur initiale du store.
Le CollectionStore a été séparé en LocalCollectionStore et ServerCollectionStore
CollectionStore est désormais une classe abstraite qui a deux implémentations. Cela permet de clarifier le fait que les deux "modes" ont des implémentations parfois différentes, et que toutes les fonctionnalités ne sont pas disponibles dans les deux (en particulier, la gestion du criteria n'existe maintenant que dans le SearchCollectionStore).
Côté création, il existe désormais 2 APIs distinctes pour les instancier : makeLocalCollectionStore et makeServerCollectionStore. Leurs signatures sont identiques aux surcharges existantes du constructeur de CollectionStore.
Le breaking change ici consiste donc à remplacer new CollectionStore par makeLocalCollectionStore ou makeServerCollectionStore selon le cas (vous ne pourrez pas vous tromper vu que les signatures des deux méthodes sont complètement différentes). Aussi, si vous utilisez le type CollectionStore en comptant sur le fait qu'il ait la propriété criteria, il faudra soit changer le type en ServerCollectionStore, soit faire un check instanceof ServerCollectionStore.
useXXXStore
Cette release ajoute aussi les APIs useStoreNode, useLocalCollectionStore et useServerCollectionStore. Il s'agit juste de wrappers autour des fonctions précédentes makeXXX dans un useState, dans l'objectif de simplifier l'usage de stores locaux dans les composants, et de rendre ça plus homogène avec useFormNode.
A noter qu'utiliser makeStoreNode directement dans un composant reste tout à fait possible, puisque vous n'avez pas forcément besoin de persister le StoreNode dans votre composant :
const node1 = useStoreNode(MyEntity, data); // Crée un store stable dans le composant, avec `data` comme valeur initiale.
const node2 = makeStoreNode(MyEntity, data); // Crée un store à la volée dans le composant, qui sera recréé à chaque rendu avec `data` comme valeur.node1 est un state du composant qui a vocation à être modifié, tandis que node2 sert à wrapper data dans un store pour ce composant (et donc à ne pas être modifié).
Ajout de sous-noeuds dans les stores de formulaire
Dans useFormNode, en plus de pouvoir ajouter des champs dans votre formulaire, vous pouvez désormais également ajouter des sous-noeuds :
const formNode = useFormNode(
{}, // On peut tout à fait créer un store de formulaire à partir d'une entité vide 😉
e =>
e
.add("field", f => f.domain(DO_NUMBER)) // Ajoute un champ "field", qui pourra ensuite être personnalisé comme n'importe quel champ existant.
.add("subObject", f => f.object(SubObjectEntity)) // Ajoute un sous-noeud "subObject"
.add("subList", f => f.list(SubObjectEntity)) // Ajoute un sous-noeud list "subList"
);La distinction entre les 3 possibilités se fait via la première méthode appelée (.domain(), .object() ou .list()). Il s'agit donc d'un petit breaking change, puisque vous êtes désormais obligé d'appeler .domain() en premier sur vos champs ajoutés (les évolutions précédentes vous ont de toute façon plus ou moins déjà forcé à le faire...).
Vous pouvez également utiliser .field() à la place de .domain() si vous pouvez passer une définition de champ entière (au lieu d'appeler .domain() puis .metadata().
Remarque : Cette évolution a été l'occasion de faire refaire une passe sur les formulaires et de corriger des bugs existants, en particulier sur le calcul de hasChanged dans les listes.
.patchAllTo(OtherEntity)
Dans useFormNode, vous pouvez désormais patcher votre entité pour qu'elle prenne la forme d'une autre entité en une seule fois, via la méthode .patchAllTo(). Cela permet en particulier de simplifier et de fiabiliser l'utilisation de deux entités différentes en lecture et en écriture, par exemple :
const formNode = useFormNode(ProfilReadEntity, e => e.patchAllTo(ProfilWriteEntity))Ainsi, votre formulaire sera bien "automatiquement" typé comme votre entité d'écriture, sans avoir à retirer manuellement les champs en trop, ou modifier certaines métadonnées champ par champ pour que ça corresponde.
patchAllTo écrase évidemment tout patch précédent (donc il est fortement conseillé de l'appeler en premier), et ne gardera les correspondances avec le sourceNode que si les propriétés sont de même type (de manière générale, si vous avez des champs dans votre objet d'écriture qui ne sont pas dans votre objet de lecture, il vous faudra encore gérer des choses à la main vu que les champs ajoutés ne sont pas envoyé au serveur par défaut).
Evolutions du Layout
- Le Panel peut désormais être repliable (via les props
collapsibleetinitiallyCollapsed). - La prise en compte de la taille du header dans le Scrollable a été refondue afin de pouvoir gérer plusieurs headers proprement (la taille du header est utilisée pour calculer des positions sticky et calculer correctement les intersections avec le viewport)
Merci à @GabrielMAYOUD pour avoir contribué à ces deux évolutions 🙂