République
française
dsfr-data
Documentation — Web Components DSFR pour la dataviz
Répète des instances vivantes : pour chaque ligne d'une source, le
<template> enfant est cloné et ses placeholders résolus — texte, attributs, et donc les
composants dsfr-data-* qu'il contient. Un graphique par question, un KPI par service.
Règle d'usage
dsfr-data-display quand la ligne est du contenu ; dsfr-data-repeat quand
la ligne est un pipeline. display est une liste de résultats (région nommée, compteur
annoncé, pagination, sélection). repeat est un composant de structure (ADR-135) :
transparent — aucun role, aucun aria-live, aucun compteur, aucune pagination — la
structure de la page vient des titres que vous écrivez dans le gabarit.
| Attribut | Type | Défaut | Description |
|---|---|---|---|
source | String | "" | Id de la source (ou du transformateur) dont chaque ligne devient une instance du gabarit. Requis. |
key-field | String | "" | Champ dont la valeur identifie une ligne entre deux émissions (chemin a.b accepté). Une clé qui subsiste garde ses nœuds et ses instances ; vide, la clé est le rang. Clé nulle ou vide sur une ligne : le rang, sans erreur. Clé en double : erreur de configuration nommant la clé, et le rang pour les doublons. |
per-row | String | "" | Nombre de lignes par rangée à partir de 768 px (en dessous : une par rangée) — diviseur de 12 (1, 2, 3, 4, 6, 12), ou une échelle par point de rupture "1 md:2 lg:3". Vide : pas de grille, un bloc par ligne. Grille fr-grid-row avec gouttières. |
empty | String | "" | Texte rendu quand la source émet zéro ligne — dans un <p> sans role="status" : la balise n'annonce rien, c'est l'auteur qui décide de l'annonce. Vide : rien. |
scopes | String | "" | Partitionne une ou plusieurs sources par champ et émet **un id scopé par ligne répétée** — la voie native pour « un graphique par question » sans écrire une dsfr-data-query par ligne dans le gabarit (#891). Grammaire (#888, proposition A, celle de champ:fonction:alias) : source:champ:alias, entrées séparées par |, termes par :. L'alias est facultatif — à défaut, c'est l'id de la source. scopes="scores:code_unifie:q | effectifs:code_unifie:e" émet, pour chaque ligne de clé 001, les ids q-001 et e-001 ; le gabarit les lit par {{$scope.q}} et {{$scope.e}} ({{$scope}} quand une seule entrée est déclarée). Les composants du gabarit les consomment par leur attribut source habituel : rien ne change pour eux. La clé d'appariement est celle de la ligne répétée (key-field, requis) : une clé sans lignes dans la source scopée émet un **tableau vide** — le graphique de la ligne est vide, pas absent. Les états loading, error et idle (require-where) de la source scopée sont relayés sur chaque id scopé, pour que la ligne affiche le bon message. Une ré-émission de la source scopée re-partitionne et ré-émet **sans toucher aux lignes**. La partition est faite **une fois** par émission (une Map par champ), là où N dsfr-data-query refiltraient chacune la source entière. Les ids scopés sont purgés du cache quand leur ligne disparaît et à la déconnexion du répéteur. Entrée fausse (nombre de termes, terme vide, alias en double, source introuvable, champ absent des lignes) : reportConfigError nommant l'entrée — rien de silencieux. |
lazy | Boolean | false | N'estampe les composants d'une ligne qu'à son approche du viewport (IntersectionObserver, marge 200 px — la même que dsfr-data-map). Les conteneurs et le contenu ORDINAIRE du gabarit (titres, textes, liens) sont rendus d'emblée : la page garde sa structure de titres, sa hauteur et son plan d'accessibilité. Seuls les éléments dsfr-data-* sont retenus hors du document, attributs déjà interpolés, et insérés à l'entrée de la ligne dans la marge — ils ne s'abonnent donc à rien et ne dessinent rien avant. Les ids scopés, eux, sont émis pour **toutes** les lignes dès le départ : le cache est là quand la ligne s'estampe. Sans lazy, comportement du lot 1 (tout est estampé d'emblée). Sans IntersectionObserver (environnement de test), lazy est sans effet. |
{{$index}} — rang de la ligne (0-based) ;{{$key}} — valeur de key-field (ou le rang) ;{{$uid}} — identifiant DOM unique dérivé de la clé, sûr pour id= et
aria-labelledby, distinct d'un répéteur à l'autre ;{{$scope.alias}} — l'id scopé de la ligne pour l'entrée alias de
scopes ; {{$scope}} quand une seule entrée est déclarée.repeat prometkey-field) subsiste garde ses nœuds : les instances ne sont ni déconnectées ni recréées,
leurs attributs sont mis à jour en place ; les clés disparues sont retirées, les nouvelles insérées à leur
rang, l'ordre du DOM suit les données. Mesuré : 119 graphiques ré-émis en ~110 ms sans un canvas détruit
(contre ~4,7 s de recréation totale avec display).<template> intérieur n'est pas parcouru : un
dsfr-data-display ou un second dsfr-data-repeat dans le gabarit rend ses propres
placeholders.data-if-horizontal="champ" pose
horizontal quand champ est vrai (ni null, undefined, « », [] ni false) et le
retire sinon ; data-unless-… inverse. L'attribut de convention est retiré du DOM.source absent, gabarit absent, key-field
absent des lignes ou en double, per-row invalide, bloc {{#if}} coupé entre deux
éléments : erreur de configuration nommée (data-dsfr-config-error, console).
Le moteur est celui de dsfr-data-display et de
dsfr-data-map-popup — {{chemin[:format[:arg]][|défaut]}},
{{#if}}, {{#unless}}, {{#each}} — sans syntaxe nouvelle. Parce que le
rendu est par nœuds : {{{brut}}} n'a pas de sens sur un nœud texte (rendu comme
{{brut}}, avec un avertissement) ; un bloc {{#if}}…{{/if}} doit tenir dans
un nœud texte ou une valeur d'attribut — ouvert avant un élément et fermé
après, il ne peut pas être un bloc : erreur de configuration, et le contenu est rendu quelle que soit la
condition.
<dsfr-data-repeat source="questions" key-field="code" per-row="1 md:3"> <template> <h4 id="{{$uid}}">{{libelle}}</h4> <dsfr-data-query id="q-{{code}}" source="scores" where="code:eq:{{code}}"></dsfr-data-query> <dsfr-data-chart source="q-{{code}}" type="{{pres}}" label-field="annee" value-field="score" data-if-horizontal="long"></dsfr-data-chart> </template> </dsfr-data-repeat>
La troisième question porte long: true : son graphique reçoit l'attribut
horizontal, les deux autres non. La source scores est lue par trois queries :
son where reste calculé dans le navigateur (règle #765), c'est voulu — un fetch, N filtres.
{{$index}}. {{libelle}}
<dsfr-data-repeat source="chapitres" key-field="chapitre"> <template> <h4>{{nom}}</h4> <dsfr-data-query id="qc-{{chapitre}}" source="questions-par-chapitre" where="chapitre:eq:{{chapitre}}"></dsfr-data-query> <dsfr-data-repeat source="qc-{{chapitre}}" key-field="code"> <template><p>{{$index}}. {{libelle}}</p></template> </dsfr-data-repeat> </template> </dsfr-data-repeat>
Le <template> intérieur arrive intact au répéteur intérieur : {{libelle}} et
{{code}} sont résolus par la ligne intérieure, {{nom}} par la ligne extérieure.
Avec dsfr-data-display, les placeholders intérieurs seraient consommés par la ligne extérieure.
empty : zéro ligne, un texte sans annonce{{libelle}}
<dsfr-data-repeat source="vide" empty="Aucune question pour ce périmètre."> <template><p>{{libelle}}</p></template> </dsfr-data-repeat>
Le texte est rendu dans un <p> sans role="status" : c'est l'auteur qui décide
de l'annonce, la balise ne parle pas.
scopes : la même page sans une seule query<dsfr-data-repeat source="questions" key-field="code" scopes="scores:code:s" lazy> <template> <h4 id="{{$uid}}">{{libelle}}</h4> <dsfr-data-chart source="{{$scope.s}}" type="{{pres}}" label-field="annee" value-field="score"></dsfr-data-chart> </template> </dsfr-data-repeat>
Même rendu que l'exemple 1, sans une seule dsfr-data-query : scopes partitionne
scores par code — une passe, une Map — et émet
s-001, s-002, s-003. Sur 119 lignes, le refiltre d'une ré-émission
de la source scopée passe de 13,8 ms (119 queries) à 2,3 ms, et les
écouteurs document de 2,03 à 1,03 par ligne. Une clé sans
lignes émet un tableau vide : la ligne existe, son graphique est vide, pas absent. Les
états loading, error et idle de la source scopée sont relayés sur
chaque id scopé, et le volet Diagnostic attribue chaque id à son répéteur
(s-001 ← dsfr-data-repeat#demo-scopes). Ajoutez lazy pour n'insérer les
composants d'une ligne qu'à son approche du viewport — les titres, eux, restent rendus d'emblée.
fetch-mode="export",
max-records au volume réel). Ni facets ni search ne se répètent.q-001) se marchent dessus, comme
deux auteurs qui écriraient le même id. Préfixer par répéteur.scopes, qui partitionne la source une fois. La query par ligne reste la voie quand la ligne a
besoin d'un regroupement ou d'un agrégat propre —
scopes livre les lignes brutes de la clé, il ne calcule pas.lazy ne réserve pas de hauteur à votre place : sans min-height sur le gabarit
(ou sur .dsfr-data-repeat__row), des conteneurs réduits à un titre tiennent tous dans le
viewport et s'estampent d'un coup.