Skip to content

Releases: klee-contrib/focus4

v11.22

06 Jan 20:46
Compare
Choose a tag to compare

Remarque importante : attention lors de la mise à jour à vos peerDependencies, on n'est plus sur la dernière version de React ni d'i18next, il faudra bien avoir un 18 en face des deux packages react et react-dom et un 23 en face d'i18next. Une version (majeure !) ultérieure de Focus prendra en compte ces montées de versions.

hasChanged

Vous pouvez désormais utiliser node.form.hasChanged ou field.hasChanged dans un noeud de formulaire, pour savoir si une (ou plusieurs) valeur(s) a (ont) été modifiée(s) par rapport à leur(s) valeur(s) correspondantes dans le noeud source.

Remarque : un champ ajouté sera toujours identifié avec hasChanged = false puisqu'il n'existe pas dans le noeud source

router.confirmation

De nouvelles APIs ont été ajoutées sur le routeur afin de pouvoir entrer dans un mode de "confirmation", qui permet de bloquer toute navigation avant que l'utilisateur confirme son intention.

Si la méthode router.confirmation.toggle() permet d'activer/désactiver le mode manuellement, il peut se brancher directement à useFormActions via la méthode .withConfirmation(router) dans son configurateur. Cela aura pour effet d'activer le mode de confirmation si le formulaire est en édition et qu'au moins un champ a été modifié (via node.form.hasChanged), et de le désactiver sinon.

Vous pouvez interagir avec ce mode via :

  • router.confirmation.active => Pour savoir si le mode est actif ou non
  • router.confirmation.pending => Pour savoir si une confirmation est attendue de la part de l'utilisateur : vous pouvez l'utiliser pour afficher un Dialog par exemple
  • router.confirmation.commit(save?: boolean) => Pour confirmer la navigation en attente. Si vous l'appelez avec true, alors la sauvegarde du formulaire sera effectuée avec la confirmation (le service de sauvegarde est passé au routeur via withConfirmation(router)
  • router.confirmation.cancel() => Pour annuler la navigation en attente.

Vous pouvez utiliser ces APIs pour créer votre propre Dialog pour gérer la fonctionnalité. Le starter kit en implémente un exemple.

De plus, la documentation est évidemment à jour 🙂.

Remarques

  • Si vous déclenchez une navigation en dehors de l'application, alors vous aurez le popup natif du navigateur (au lieu de votre Dialog), qui n'est pas personnalisable. Il ne permettra en particulier pas de déclencher la sauvegarde.
  • Vous pouvez tout à fait avoir plusieurs formulaires sur la page qui sont branchés au mode de confirmation. Il sera actif dès lors qu'au moins un formulaire est en édition, et la confirmation avec sauvegarde appellera tous les services de sauvegarde des formulaires en édition.
  • Le routeur a été partiellement réimplémenté, en internalisant la dépendance à yester (une librairie développé par un mec dans son coin il y a 8 ans), qui n'était une légère surcouche à history, la librairie de référence pour gérer la navigation en JS.

v11.21

18 Oct 17:08
Compare
Choose a tag to compare

Refonte du Layout

Cette verrsion réimplémente le Layout/Scrollable (et les choses qui y sont liées comme le Header, le ScrollspyContainer et l'AdvancedSearch) pour implémenter tous les comportement "sticky" avec des position: sticky en CSS au lieu de calculer des positions à la main en Javascript pour tout faire.

En particulier :

  • Le Header n'est plus posé en double, c'est le même Header qui reste entre le header déplié (s'il y en a un) et le Header sticky. Si vous posez un HeaderContent, il scrollera maintenant avec le reste de la page et il passera sous le HeaderTopRow. Les HeaderActions seront posées sous le HeaderContent comme aujourd'hui et scrolleront pour rester "sticky" à leur place sur le header sticky. Il n'y a donc plus de notion de canDeploy/header plié ou déplié.

  • Le menu de gauche du Scrollspy (et de l'AdvancedSearch pour les facettes) est un composant dédié appelé LateralMenu qui se pose directement à côté (avec un display: flex à priori), au lieu d'avoir un portal qui remonte le poser dans le Scrollable (Layout), et il est entièrement géré en CSS. Cela vous laisse donc par exemple la possibilité de changer complètement la mise en page du Scrollspy (par exemple pour mettre le menu au dessus au lieu d'à gauche).

    Le MenuComponent du Scrollspy par défaut contient désormais le LateralMenu, donc si vous voulez le surcharger et garder son positionnement à gauche vous devrez aussi poser un LateralMenu
    Les facettes de l'AdvancedSearch sont maintenant toujours posées en sticky à gauche avec un LateralMenu (enfin sauf si avec facetBoxPosition à none ou action-bar. D'ailleurs, vous pouvez maintenant facilement le mettre à droite en inversant le sens du flex qui le contient.

  • Le "top row" (donc Summary + ActionBar) de la recherche avancée est désormais "sticky" par défaut, et un nouveau hook useStickyClip, utilisé par la recherche avancée du coup, permet de faire en sorte que les résultats soient bien "coupés" lorsqu'ils passent sous la "top row" (au lieu d'apparaitre derrière). tableFor peut aussi le faire avec les headers de colonnes, mais ce n'est pas le cas par défaut (à cause du box-shadow qui est posé par défaut en CSS qui fait que c'est moche 😄 )

  • Tous les calculs en Javascript n'ont pas été retirés, car on a toujours besoin de déterminer le top des divers position: sticky. Par défaut, pour les composants qui ont des comportements sticky, il sera calculé comme avant avec la hauteur du header + --content-padding-top, mais vous pouvez le surcharger avec le prop overrideOffsetTop dans le Scrollspy, AdvancedSearch et tableFor.

  • Ah, et le Panel est désormais dans le module layout au lieu du module forms, désolé ça va faire beaucoup d'imports à changer... 🥺

Cette version intègre également la documentation complète du module layout, maintenant qu'il a été refait tout beau ✨
Vous pourrez donc y voir ce que donnent les évolutions ainsi présentées dans cette release note 🙂

Résumé des breaking changes

  • Les imports de Panel viennent désormais de @focus4/layout au lieu de @focus4/forms
  • Le HeaderScrolling n'a plus de prop canDeploy (elle ne sert plus à rien)
  • Le HeaderContent est affiché différemment
  • Les menus latéraux du ScrollspyContainer et de l'AdvancedSearch ont un nouveau design (et AdvancedSearch n'a plus de facetBoxPosition: "sticky" car elle est toujours sticky maintenant)
  • Vous ne pouvez plus utiliser le ScrollableContext (qui est désormais exposé depuis @focus4/layout) pour poser un menu latéral, il faut le faire avec le composant LateralMenu. Si vous l'utilisiez pour faire une surcharge bizarre de la taille du header, vous pouvez maintenant la passer directement au ScrollspyContainer.

Fil d'Ariane

Pour répondre à la demande populaire, un composant de fil d'Ariane a été ajouté dans le module layout, qui s'intègre nativement avec le routeur et les traductions i18n.

Il est utilisé dans le starter kit et possède évidemment sa propre page de documentation 😉

v11.20

30 Sep 18:51
Compare
Choose a tag to compare

Cette release contient un grand nombre de petites évolutions suite à des demandes utilisateurs diverses :

Composants par défaut sur les domaines

Les composants par défaut (InputComponent, SelectComponent, DisplayComponent...) sont désormais portés par le domaine au lieu d'être définis dans le composant de champ (Field, posé par fieldFor/selectFor/autocompleteFor). Cette évolution est accompagnée d'un renforcement des types de composants que l'on peut passer aux domaines, afin de pouvoir être sûr que le composant passé corresponde bien au type de domaine (exemple : utiliser une Checkbox sur un domaine de type "boolean" uniquement).

Cela implique les changements (potentiellement breaking changes) suivants :

  • Si vous utilisez makeField ou .add() pour créer un champ sans domaine, alors vous n'aurez plus de composants d'affichage ou de saisie par défaut. Il est donc impératif de renseigner un domaine ou les composants d'affichage dont vous avez besoin.

  • Les composants génériques, ceux qui fonctionnent pour plusieurs types comme SelectAutocomplete par exemple, doivent spécifier le type dans la définition, car il ne peut pas être inféré automatiquement 🥺

    Cela veut dire qu'écrire domain({type: "number", SelectComponent: SelectAutocomplete}) est désormais une erreur et que vous devez écrire domain({type: "number", SelectComponent: SelectAutocomplete<"number">}) pour que ça fonctionne. En contrepartie, vous aurez bien le bon type partout dans selectProps, ce qui n'était pas forcément le cas avant.

  • Les domaines multiples ("boolean-array", "number-array" et "string-array") utilisent désormais SelectChips et AutocompleteChips comme composants par défaut (et aucun InputComponent), puisque les composants doivent désormais correspondre au type du domaine. Les domaines de type "object" n'ont plus aucun composant de saisie par défaut. Cela ne devrait casser personne puisqu'il fallait de toute façon impérativement renseigner d'autres composants pour utiliser ces domaines, vous pouvez en revanche maintenant vous en abstenir si les composants par défaut vous conviennent.

  • Vous devez désormais impérativement utiliser la fonction domain() pour créer un domaine. Si vous utilisiez simplement {type: "string"} à certains endroits par exemple, vous devrez le wrapper, ou bien spécifier manuellement les 5 composants de champs.

Autres évolutions

  • b4768c7 - showIfNoData sur les operationList de ActionBar/tableFor
    Vous pouvez renseigner cette propriété (à true) dans une définition d'action dans une ActionBar ou dans un tableFor pour qu'elle soit affichée en permanence, au lieu d'être conditionnée au fait d'avoir sélectionné au moins un élément. A noter que dans l'action est toujours appelée avec les éléments sélectionnés, donc ici ça serait une liste vide. Cette propriété doit être renseignée même s'il n'y a pas de sélection du tout (ce qui est un breaking change pour tableFor).

  • 6743d7a - noSuggestionsOnEmptyQuery sur Autocomplete
    Le composant d'Autocomplete (et donc SelectAutocomplete également) peuvent désormais ne pas afficher les suggestions tant que la query est vide, à la manière de AutocompleteSearch (dont le comportement inverse pouvait déjà être activé avec searchOnEmptyQuery)

  • 34827f3 - noBlurOnClick => noFocusOnClick
    Dans la définition d'un "trailing button" dans un dérivé de TextField, la propriété noBlurOnClick a été renommée en noFocusOnClick puisqu'elle n'effectue plus de "blur" (l'implémentation précédente le faisait à tort, ce qui pouvait entraîner des effets secondaires indésirables, comme dans l'InputDate par exemple)

  • 0775e83 - Conservation des modifications de criteriaBuilder dans le type de CollectionStore
    Le type d'un CollectionStore qui a patché son critère (comme avec useFormNode, via criteriaBuilder), prend désormais en compte le type résultant de ces modifications, au lieu de l'ignorer silencieusement.

  • c39d60e - Fix critères "vides" affichés quand même dans le Summary
    En particulier pour les listes vides.

  • d05d66c - Ajout mouseEvent sur onLineClick sur tableFor
    Il est passé en deuxième paramètre.

  • ab3eddd - Extraction de useInput de Input
    Le comportement du composant Input qui gère le masque de saisie et la saisie de nombres (décimaux) a été extrait dans un hook, si vous voulez l'utiliser dans un autre composant que le TextField.

v11.19

30 Aug 15:04
Compare
Choose a tag to compare

(Pour une fois, cette release n'apporte aucun breaking change 😄)

Compositions isRequired: false

Les compositions ("object", "list", et "recursive-list") peuvent désormais renseigner leur caractère obligatoire via isRequired, comme les champs. Si une composition est marquée comme étant non obligatoire, alors tous ses champs seront également non obligatoires si la composition est entièrement vide (tous les champs à undefined et toutes les listes vides). toFlatValues retirera la composition de son résultat (au lieu de mettre {}) dans ce cas.

isRequired reste facultatif dans les définitions d'entité et il sera true pour coller à l'existant sinon. Si vous utilisez TopModel, vous pouvez le générer avec l'option extendedCompositions du générateur JS.

required()

Vous pouvez désormais utiliser la fonction required() sur un builder de noeud de formulaire, qui fonctionne de la même manière que edit() :

  • required(false) rendra le noeud non obligatoire (comme décrit dans le paragraphe précédent)
  • required(() => /* truc calculé */, "champ1", "champ2") patchera les deux champs pour modifier leur isRequired (cela appelera metadata()).

Cette évolution a nécessité une petite refonte interne de la gestion des surcharges de metadata dans Focus, qui permet en plus de pouvoir spécifier plusieurs patch sur le même champ sans perdre leur caractère calculé (un second patch après un metadata calculé sortait d'ailleurs une erreur, ce qui n'est plus le cas).

Champs retirés sur Patch

Vous pouvez maintenant spécifier les champs retirés dans un formulaire avec Patch via son nouveau troisième paramètre optionnel : Patch<MyEntity, {}, "champRetiré1" | "champRetiré2">.

v11.18

10 Jul 19:31
Compare
Choose a tag to compare

Retrait des styles inline sur les champs

(fix partiel de #194)

Les champs ne posent plus de style inline à base de labelRatio/valueRatio (et disableInlineSizing pour le désactiver). A la place, deux variables CSS sont définies globalement, --field-label-width et --field-value-width, initialisées à 33% et 67% pour conserver l'existant par défaut, qui sont utilisées dans le CSS pour définir les largeurs dans les champs. Par conséquent, toute la mise en page des champs est désormais gérée en CSS, ce qui devrait largement faciliter sa personnalisation (à défaut de proposer d'autres mises en page par défaut, pour l'instant).

Il reste possible de spécifier de manière "inline" labelWidth et valueWidth sur Form et Field, à la place de labelRatio et valueRatio (ce sont donc plus des ratios mais simplement la valeur des deux variables CSS, donc vous pouvez y mettre ce que vous voulez). Ce n'est donc plus la manière recommandée de gérer la mise en page (puisqu'il est possible de tout faire en CSS maintenant), mais cela permet une mise à jour simple de l'existant. Pour émuler le comportement de disableInlineSizing, vous pouvez simplement affecter auto aux deux variables par exemple.
(Ces propriétés vont simplement poser les variables en style inline sur le <form> ou le <div> principal du Field)

TL:DR breaking changes :

  • labelRatio: x => labelWidth: "x%", et valueWidth: "(100 - x)%" si valueRatio n'était pas renseigné (valueRatio était calculé automatiquement avant, mais on ne peut plus le faire puisqu'on n'utilise plus nécessairement un pourcentage)
  • valueRatio: x => valueWidth: "x%"
  • disableInlineSizing: true => labelWidth: "auto", valueWidth: "auto"

react-transition-group => react-transition-state

Focus n'utilise plus react-transition-group qui n'est plus maintenu et ne sera jamais mis à jour pour React 19 (la lib est incompatible), mais une autre librairie plus flexible qui elle sera compatible. Cela ne change rien à l'utilisation (c'est utilisé pour les dialogs et les popins), mais cela devrait retirer un warning en console.

v11.17

28 Apr 19:40
Compare
Choose a tag to compare

Typage de FormNode

Cette version traite enfin l'issue #190 et propose de nouveaux types (Patch et consorts) pour décrire le type de formulaires patchés avec useFormNode pour utilisation dans des fonctions ou composants.

La documentation sur le sujet est complète 😉

Autres mises à jour

  • Focus vient avec React 18.3, qui est une version préliminaire à la future version 19 qui vient de sortir en bêta. Cette version n'a pour seul but que de mettre des warnings sur des choses qui ne marcheront plus en 19. A priori c'est pas grand chose. Il y a eu de tous petits fixes dans Focus vis-à-vis de ça (j'ai enlevé le seul findDOMNode qu'il me restait quoi)
  • Focus nécessite maintenant Node 20 pour s'installer (suite à une mise à jour de mon outil de test unitaire, Vitest c'est bien si vous en cherchez un 😉)

v11.16

04 Mar 22:56
Compare
Choose a tag to compare

Cette release adresse essentiellement l'issue #191.

errorDisplay

La nouvelle propriété errorDisplay, disponible dans les options de champ (fieldFor/selectFor/autocompleteFor), le <Form> et les actions de formulaires (a.errorDisplay() dans useFormActions), permet de renseigner le mode d'affichage des erreurs des champs. Elle remplace forceErrorDisplay (sur actions et <Form>) et noError sur les champs. Elle peut avoir 3 valeurs :

  • always => les erreurs sont toujours affichées
  • after-focus => les erreurs sont affichées après avoir focus le champ en question au moins une fois
  • never => les erreurs ne sont jamais affichées

(Les erreurs ne sont quand même jamais affichées sur un champ quand il a le focus)

Dans les actions de formulaires :

  • after-focus passera le mode à always après l'appel de save(), et reviendra à after-focus une fois la sauvegarde effectuée (ou un cancel)
  • Si le mode n'est pas renseigné, il sera égal à after-focus si le formulaire est initialement en édition, et always sinon

Ce fonctionnement correspond presque exactement au fonctionnement précédent, mais implémenté de manière plus simple, claire, et flexible (en particulier parce qu'on peut surcharger l'affichage champ par champ).

breaking change: forceErrorDisplay n'existe plus, vous pouvez remplacer vos éventuelles assignations manuelles à false par errorDisplay: "after-focus" et à true par errorDisplay: "always".

a.successMessage()

Vous pouvez désormais surcharger le message de succès d'un formulaire directement via successMessage(), au lieu de passer i18nPrefix() pour que le message pointe sur {i18nPrefix}.detail.saved. Cela implique aussi que vous pouvez appeler successMessage("") pour désactiver le message. C'est un breaking change évidemment... que vous pouvez résoudre soit en changeant i18nPrefix(prefix) par successMessage(prefix + ".detail.saved"), ou en simplifiant.

Réécriture useLoad/useFormActions

Les deux hooks ont été réécrits pour qu'ils soient effectivement conçus de la même façon. Cela a permis de :

  • Autoriser le changement de node sur useLoad et useFormActions
  • Autoriser un array de dépendances sur useFormActions

Toute modification de node ou des dépendances va mettre à jour toutes les fonctions définies dans useLoad et useFormActions (params, load, save, on...), et relancer la fonction de chargement. Si c'est probablement toujours souhaitable pour useLoad (et c'était déjà le cas de toute façon), ça l'est peut être moins pour useFormActions si les dépendances sont utilisées dans le save ou un on, mais pas dans le load. Dans ce cas, il vaut mieux séparer le load dans un useLoad sans dépendances. Il y a un exemple dans la doc.

De plus, useFormActions ne supportent plus d'avoir plusieurs services de sauvagarde, une fonctionnalité qui n'a jamais été utilisée... (théoriquement un breaking change aussi mais bon...)

Réécriture Input (gestion des masques)

Le composant Input a été réécrit, en internalisant la gestion des masques de saisie (au lieu de dépendre d'une librairie externe inchangée depuis 7 ans et difficile à utiliser). S'il est fonctionnellement identique au composant précédent, certains bugs ont été corrigés (en particulier liés aux couper/copier/coller), et il est entièrement documenté dans la doc Focus 😉.

v11.15

14 Feb 09:08
Compare
Choose a tag to compare

Améliorations tableFor

En plus d'avoir revu et amélioré le CSS des tableaux, il est désormais possible dans un tableau :

  • D'afficher des actions sur chaque ligne comme sur listFor avec lineOperationList
  • D'afficher une checkbox dans le header du tableau et d'y associer des actions globales comme ActionBar avec hasSelectAll et operationList (si operationList est renseigné sans hasSelectAll, alors les actions globales seront tout le temps affichées et s'appliqueront sur tous les éléments du tableau)
  • D'afficher un LoadingComponent après le tableau comme la liste (Le "Chargement..." par défaut).

Les checkbox et les actions des tableaux et des listes sont désormais tout le temps affichées, au lieu de ne l'être qu'au survol ou dès qu'au moins un élément est sélectionné (une nouvelle classe CSS .list--selected/.table--selected est ajoutée sur la liste/tableau dès qu'un élément est sélectionné dedans si jamais vous voudriez reproduire l'ancien comportement en surchargeant le CSS...)

Les actions de listes et de tableau peuvent désormais prendre les props color, variant et disabled, qui seront répercutées sur les composants de boutons qu'elles posent. Pour les actions secondaires, les propriétés color et variant seront répercutées sur le bouton du menu, qui peut aussi être muni d'une tooltip via secondaryLabel.

Par ailleurs, il n'est plus possible de passer un isLoading aux composants de listes lorsqu'ils sont associés à un store, puisque le store porte déjà un isLoading. En mode local, vous pouvez désormais l'assigner manuellement du coup.

ThemeProvider

Le nouveau composant ThemeProvider permet de reproduire ce que fait le Layout pour pouvoir passer un appTheme et surcharger tous les classes CSS des composants qui sont posés dedans. Ainsi, vous avez désormais une option de plus pour surcharger du CSS entre "surcharger toutes les classes via le Layout" et "surcharger toutes les props theme dans tous les composants". Les classes posées par un ThemeProvider s'ajouteront à celles posées par un ThemeProvider parent (et donc celles du Layout).

Icônes dans les Panels

Vous pouvez désormais poser une icône devant le titre dans un Panel :)

Documentation

La refonte de la documentation est enfin terminée ! Vous pouvez désormais y trouver en plus :

  • Une documentation sur le modèle métier et le CSS entièrement refaite
  • Une documentation sur les composants de liste et les stores de collection qui inclue des exemples interactifs comme les composants de base
  • La documentation de toutes les variables CSS de tous les composants avec showcase (toolbox, forms et collections) avec prise en compte du thème sombre et surcharges locales.

Tout information dans la doc doit désormais être à jour, et la doc a vocation à être à peu près exhaustive. N'hésitez pas à remonter toute info manquante ou imprécise ;)

v11.14

29 Jan 14:43
Compare
Choose a tag to compare

Refonte de la gestion des icônes

Focus ne met plus "material-icons" en dur pour toute icône renseignée avec simplement un nom, ni "icon-{name}" pour une icône dite "personnalisée". Désormais, toutes les icônes sont définies comme ayant un name et une className, et un className par défaut est défini dans config.defaultIconClassName (qui est toujours "material-icons" en revanche).

Vous pouvez donc passer "home", ou {name: "home", className: "material-icons-outlined"}, ou encore {i18nKey: "focus.icons.list.showAll"} à toute propriété de composant qui prend une icône. Si vous utilisez une clé i18n, library a été remplacé par className, mais la clé doit toujours pointer sur un objet {name, className?}.

L'API de <FontIcon> a évolué pour suivre ce changement :

<FontIcon>home</FontIcon>
<FontIcon iconClassName="material-icons-outlined">home</FontIcon>
<FontIcon iconI18nKey="focus.icons.list.showAll" />

// ou vous pouvez utiliser la prop combinée `icon` qui prend directement un des 3 types de définition d'icône : 
<FontIcon icon={{className: "material-icons-round"}}>home</FontIcon>

La classe CSS de l'icône peut également être un template (si elle utilise {name}), dans le cas où vous avez besoin d'avoir une classe CSS différente par icône (si votre police ne convertit pas directement le nom comme Material Icons ou si vous n'avez pas de police).

Breaking changes

  • getIcon() n'existe plus, à remplacer par les nouvelles façons de décrire des icônes
  • Une icône custom ne pose plus 2 spans (celui du FontIcon + celui de l'icône custom en "icon-{name}", mais plus qu'un seul.

referenceStore.get

Vous pouvez enfin forcer la récupération d'une liste de référence en faisant await referenceStore.get("droit"), qui appellera le serveur si besoin. Il n'y a plus besoin de tricher avec des await when(() => referenceStore.droit.length > 0) !

useReferenceTracking (referenceStore.track)

Le tracking automatique de toutes les références dans les formulaires (withReferenceTracking / config.trackReferenceLoading) s'est révélé être une mauvaise idée (ça ne marche pas bien sur les pages qui ont plusieurs formulaires), il a donc été retiré (RIP janvier 2024 - janvier 2024).

A la place, vous pouvez désormais le faire explicitement via useReferenceTracking :

    const actions = useFormActions(entity, a => / * */):
    useReferenceTracking(actions.trackingId, referenceStore, "droit");

useReferenceTracking utilise une nouvelle API referenceStore.track qui permet d'ajouter un ou plusieurs IDs de suivi sur des listes de référence. Vous n'aurez à priori jamais besoin d'utiliser cette API directement.

Refonte de la documentation

Ce n'est pas encore tout à fait #195 en entier (il y a encore pas mal de boulot à faire dessus), mais cette release introduit une refonte de la documentation, qui contient en particulier un showcase de tous les composants de base, à peu près entièrement fonctionnel.

Les évolutions de la documentation se sont pour l'instant concentrées sur les évolutions récentes de Focus, donc attendez vous à voir de la documentation à jour sur tous les composants de la refonte toolbox/forms et sur les fonctionnalités de base (messages, requêtes, store de références). Et une page d'accueil à jour aussi 😄

La partie sur les formulaires, les listes, le CSS et la mise en page seront mises à jour plus tard, et incluront évidemment un bout de showcase 😉

Elle est toujours au même endroit

loading sur Button et IconButton

Vous pouvez désormais afficher un spinner à la place de l'icône dans un bouton. Vous pouvez regarder à quoi ça ressemble dans la doc 😉

v11.13

19 Jan 12:56
Compare
Choose a tag to compare

Refonte du RequestStore, et suivi de requêtes

Le RequestStore ne suit désormais que les requêtes en cours (dans requestStore.pending) et expose (enfin !) requestStore.isLoading pour savoir s'il y a au moins une requête en cours (il n'y a plus besoin de le calculer vous même en faisant requestStore.pending.size > 0)

Vous pouvez aussi maintenant utiliser le RequestStore pour gérer vos propres isLoading avec requestStore.track :

const id = useId(); // ou v4() du package "uuid" si vous n'est pas dans un composant

/* ---- */

const user = await requestStore.track(id, () => getUser(id)) // Enregistre le service sur cet ID et l'appelle

/* ---- */

requestStore.isLoading(id) // La requête est-elle en cours ?

En plus de proposer une façon standardisée de gérer un isLoading, vous pouvez passer plusieurs IDs à requestStore.track et surtout utiliser le même ID pour plusieurs requêtes. De cette façon, vous pouvez obtenir simplement un état isLoading pour plusieurs requêtes en même temps.

requestStore.track est intégré à useLoad et useFormActions. Les deux méthodes utilisent leur propre ID de suivi, et il est possible d'en passer d'autres avec .trackingId(id) , par exemple :

const actions = useFormActions(form, f => f.params().load().save());

useLoad(otherStore, f => f.params().load().trackingId(actions.trackingId));

// => actions.isLoading sera donc `true` si l'un des 3 services est en cours de chargement !

De plus, par défaut, le isLoading de useLoad et useFormActions prennent également en compte le chargement des listes de références (sans distinction). Vous pouvez changer ce comportement par défaut en changeant la configuration globale config.trackReferenceLoading à false, ou bien appeler withReferenceTracking(active) dans useLoad ou useFormActions pour activer/désactiver localement.

CollectionStore.criteriaMode

Vous pouvez enfin finalement renseigner criteriaMode à la création de votre CollectionStore pour choisir entre "direct" (lancement automatique immédiat de la recherche après modification, comportement par défaut), "debounced" (lancement de la recherche après un petit délai, précédemment configuré avec debounceCriteria: true), et "manual", où les critères ne seront appliqués qu'après avoir appelé search() explicitement.

Vous n'avez donc plus besoin de créer un formulaire distinct pour reproduire ce comportement et appliquer les critères à la sauvegarde. De plus, il est également possible de renseigner un criteriaBuilder dans la définition du CollectionStore pour personnaliser le critère, comme un formulaire classique.

const searchStore = new CollectionStore(search, CriteriaEntity, {
    sortBy: "date",
    top: 20,
    criteriaMode: "manual",
    criteriaBuilder: x => x.patch("dateDebut", g => g.metadata({label: "Date de début"}))
});

Breaking changes

  • Dans tout ce qu'exposait le RequestStore avant, il ne reste plus que pending, qui est désormais une liste au lieu d'un set
  • useLoad ne renvoie plus isLoading mais [isLoading, trackingId]. Il faudra donc ajouter des [] autour de vos isLoading existants
  • useLoad et useFormActions incluent désormais l'état de chargement de toutes les listes de référence dans leurs isLoading. Si cela ne convient pas, cela peut se désactiver globalement avec config.trackReferenceLoading = false (de @focus4/core)
  • debounceCriteria: true dans le CollectionStore a été remplacé par criteriaMode: "debounced"