République
française
dsfr-data
Documentation — Web Components DSFR pour la dataviz
Composant d'affichage dynamique qui genere des elements HTML repetitifs (cartes, tuiles, etc.)
a partir d'un template et d'une source de données.
Se connecte a un <dsfr-data-source>, <dsfr-data-query> ou
<dsfr-data-normalize> pour recevoir les données.
| Attribut | Type | Description |
|---|---|---|
source | String | Id de la source (ou du transformateur) dont ce composant consomme les données. |
per-row | String | Nombre d'éléments par ligne à partir de 768 px (en dessous : un par ligne) — 1, 2, 3, 4 ou 6, les diviseurs de la grille de 12 colonnes. Échelle mobile-first (#789) : per-row="1 sm:2 lg:3" — le premier terme sous 576 px, puis un palier par point de rupture DSFR (sm 576, md 768, lg 992, xl 1248 px). Remplace cols, même sens, sans l'ambiguïté du mot sur les autres composants (#790). Prime sur cols s'ils sont posés ensemble. |
cols | Number | Nombre de colonnes dans la grille (1-6, défaut 1 = pleine largeur). Même rôle que per-row, qui est préféré : cols désigne une LARGEUR sur dsfr-data-facets (#790). Toujours accepté, avec le même sens. |
pagination | Number | Nombre d'éléments par page (0 = tout afficher) |
empty | String | Message quand aucune donnee |
gap | String | Classe CSS de gap pour la grille (défaut: fr-grid-row--gutters) |
uid-field | String | Champ de données a utiliser comme identifiant unique par item. Si vide, utilise l'index |
url-sync | Boolean | Synchronise le numéro de page dans l'URL (replaceState) |
url-page-param | String | Nom du paramètre URL pour la page (défaut: "page") |
idle-message | String | Message rendu quand l'amont attend un filtre (require-where, #690). Distinct de « aucune donnée » : aucune requête n'a été faite. Vide, le libellé par défaut est utilisé. |
count-label | String | Nom compté par le compteur rendu au-dessus de la grille, à la place de « resultat » : count-label="établissement" affiche « 12 345 établissements » (#925, AM-077 — même grammaire que dsfr-data-search, #779). Une forme seule prend un « s » au pluriel ; pour un pluriel irrégulier ou un mot invariable, donner les deux formes séparées par une **barre verticale** : count-label="cheval|chevaux", count-label="prix|prix". La virgule ne sépare PAS les deux formes. Poser l'attribut fait aussi passer le nombre par le formateur fr-FR (séparateur de milliers) : « 1 234 » et non « 1234 ». Sans l'attribut, le compteur est rendu exactement comme avant — le corriger par défaut changerait le texte de toutes les pages existantes, et reste à trancher (#925, point résiduel). Cet attribut ne sait pas TAIRE le compteur : un dsfr-data-display est une liste de résultats, et sa région annoncée fait partie de son contrat (ADR-135). Pour mettre en forme une ligne sans landmark ni compteur — une fiche, un nom dans une phrase — le composant est dsfr-data-repeat. |
Événements : le composant n'émet pas d'événement custom ; il participe au data-bridge en consommateur (abonnement à la source via dsfr-data-loaded).
empty répond à « la requête est revenue vide ». Il existe un second état, qu'il ne
faut pas confondre : l'amont porte require-where
(<dsfr-data-source> ou <dsfr-data-query>) et n'a encore rien
demandé. C'est idle-message qui est alors rendu — « Choisissez un filtre pour afficher
les données » par défaut. Le mécanisme est décrit sur
dsfr-data-source.
Une grille de cartes sert souvent de sélecteur : on clique un territoire, un service, une
catégorie, et le reste de la page se recentre dessus. refine-on-click nomme le champ
dont la valeur de l'élément cliqué devient un filtre eq. Chaque élément reçoit un
bouton « Filtrer sur … », atteignable au clavier et dont l'état est annoncé
(aria-pressed) : la mise en avant visuelle n'est jamais la seule marque.
| Attribut | Type | Description |
|---|---|---|
refine-on-click | String | Champ dont la valeur de l'élément cliqué devient un filtre eq (#734). Premier clic = filtre, second clic sur le même élément = retrait, clic sur un autre élément = remplacement. Chaque élément reçoit un bouton « Filtrer sur … », atteignable au clavier et dont l'état est annoncé (aria-pressed) : la mise en avant de l'élément sélectionné n'est jamais la seule marque. Avec context="id" (recommandé), le composant s'enregistre comme filtre du dsfr-data-context : diffusion à toutes ses sources cibles au dialecte de chacune, tag dans dsfr-data-context-tags, URL portée par le contexte. Sans context, la clause part directement à source (whereKey display-select-ID) — sans tag ni URL, et la liste se filtre elle-même (seul l'élément cliqué reste, jusqu'au second clic). |
context | String | Identifiant du dsfr-data-context auquel s'enregistrer en refine-on-click (#734, ADR-104). Le contexte peut être déclaré après le composant dans la page. Vide = commande directe à source (chemin dégradé). |
label | String | Libellé du tag de contexte en refine-on-click (#734). Vide = le nom du champ filtré. |
<dsfr-data-context id="ctx" sources="src"></dsfr-data-context> <dsfr-data-context-tags for="ctx"></dsfr-data-context-tags> <dsfr-data-display source="src" cols="3" refine-on-click="departement" context="ctx" label="Département"> <template>…</template> </dsfr-data-display>
Premier clic = filtre, second clic sur le même élément = retrait, clic sur un autre élément =
remplacement. Avec context (recommandé), la grille s'enregistre comme filtre du
dsfr-data-context : diffusion à toutes ses sources cibles au
dialecte de chacune, tag supprimable, URL portée par le contexte, et label donne son
libellé au tag. Sans context, la clause part directement à source — sans
tag ni URL, et la grille se filtre elle-même, ne gardant que l'élément cliqué jusqu'au second clic.
| Syntaxe | Description |
|---|---|
{{champ}} | Valeur echappee (HTML-safe) |
{{{champ}}} | Valeur brute (non echappee) |
{{champ|défaut}} | Valeur avec fallback si null/undefined |
{{champ:number}} | Valeur avec separateurs de milliers (ex: 32073247 → 32 073 247) |
{{champ:number|0}} | Format number + fallback si null/undefined |
{{champ.sous.clé}} | Acces aux proprietes imbriquees |
{{$index}} | Index de l'element (0-based) |
{{$uid}} | Identifiant unique de l'element |
Affiche les données sous forme de cartes DSFR dans une grille a 3 colonnes.
Ministere : {{ministere}}
<dsfr-data-display source="sites" cols="3" pagination="6">
<template>
<div class="fr-card">
<div class="fr-card__body">
<div class="fr-card__content">
<h3 class="fr-card__title">{{nom}}</h3>
<p class="fr-card__desc">Ministere : {{ministere}}</p>
</div>
<div class="fr-card__footer">
<p class="fr-badge fr-badge--sm">RGAA : {{score_rgaa}}%</p>
</div>
</div>
</div>
</template>
</dsfr-data-display>
Affiche les données sous forme de tuiles dans une grille a 4 colonnes.
{{statut}}
Score DSFR : {{score_dsfr}}%
<dsfr-data-display source="sites" cols="4">
<template>
<div class="fr-tile">
<div class="fr-tile__body">
<div class="fr-tile__content">
<h3 class="fr-tile__title">{{nom}}</h3>
<p class="fr-tile__detail">{{statut}}</p>
<p class="fr-tile__desc">Score DSFR : {{score_dsfr}}%</p>
</div>
</div>
</div>
</template>
</dsfr-data-display>
Affiche une liste simple avec l'index de chaque element via {{$index}}.
{{ministere}} — Score RGAA : {{score_rgaa}}% | Score DSFR : {{score_dsfr}}%
<dsfr-data-display source="sites" cols="1" pagination="5">
<template>
<div class="fr-callout">
<h3 class="fr-callout__title">#{{$index}} -- {{nom}}</h3>
<p class="fr-callout__text">
{{ministere}} -- Score RGAA : {{score_rgaa}}%
</p>
</div>
</template>
</dsfr-data-display>
Utilise la syntaxe {{champ|défaut}} pour afficher une valeur de remplacement quand un champ est absent.
Catégorie : {{catégorie|Non renseignee}}
Statut : {{statut|Inconnu}}
<dsfr-data-display source="sites" cols="2" pagination="4">
<template>
<div class="fr-card fr-card--shadow">
<div class="fr-card__body">
<div class="fr-card__content">
<h3 class="fr-card__title">{{nom}}</h3>
<p class="fr-card__desc">
Catégorie : {{catégorie|Non renseignee}}
</p>
</div>
</div>
</div>
</template>
</dsfr-data-display>
Quand la source utilise paginate, dsfr-data-display détecté automatiquement
la pagination serveur. Chaque changement de page declenche un nouvel appel API au lieu de paginer en local.
Le nombre total de pages est calcule depuis les metadonnees de l'API (meta.total).
<dsfr-data-source id="elus"
url="https://tabular-api.data.gouv.fr/api/resources/.../data/"
paginate page-size="12">
</dsfr-data-source>
<dsfr-data-display source="elus" cols="3" pagination="12">
<template>
<div class="fr-card">
<div class="fr-card__body">
<div class="fr-card__content">
<h3 class="fr-card__title">{{Nom}} {{Prenom}}</h3>
<p class="fr-card__desc">{{Commune}}</p>
</div>
</div>
</div>
</template>
</dsfr-data-display>
Comportement : en mode pagination serveur, les données recues sont affichees telles quelles
(pas de slicing client). Le total affiche (ex: "1 743 pages") vient de meta.total / meta.page_size.
La recherche et le tri ne s'appliquent qu'aux données de la page courante.
Le gabarit n'est pas limité à du HTML inerte : rendu par innerHTML, il peut contenir des
composants dsfr-data-*, rehaussés comme le reste de la page. C'est la voie native pour
répéter un graphique, un KPI ou une liste sur les lignes d'une source — l'équivalent
d'un ng-repeat autour d'un <ods-chart>. Le scope de chaque élément est une
dsfr-data-query par ligne, dont l'id et le where sont interpolés,
sur une source chargée une fois ; le type d'un graphique peut se lire dans un champ
(type="{{champ}}").
<dsfr-data-display source="sites" per-row="3">
<template>
<div class="fr-card"><div class="fr-card__body"><div class="fr-card__content">
<h3 class="fr-card__title">{{nom}}</h3>
<!-- Le scope de la ligne : une query dont l'id et le where sont interpolés -->
<dsfr-data-query id="site-{{$index}}" source="sites" where="nom:eq:{{nom}}"></dsfr-data-query>
<dsfr-data-kpi source="site-{{$index}}" value="score_rgaa:max" label="Score RGAA" unit="/100"></dsfr-data-kpi>
</div></div></div>
</template>
</dsfr-data-display>
Mesure (0.30.0, Chromium headless, sources inline) : 119 lignes × (query + graphique)
rendues en 410 ms jusqu'au 119e canvas ; une ré-émission de la source scopée (ce que fait un
filtre de dsfr-data-context) fait ré-émettre les 119 queries en 29 ms ; aucune erreur.
Le where de chaque query reste calculé dans le navigateur (source partagée par N lectrices,
jamais déléguée) : la source scopée doit être chargée en entier (fetch-mode="export",
max-records relevé).
Les limites, écrites
Pas d'imbrication : un dsfr-data-display dans le gabarit d'un autre ne
fonctionne pas — la passe de substitution consomme aussi les {{…}} du <template>
intérieur avec la ligne extérieure (champ inconnu → chaîne vide), sans erreur.
Re-création totale : chaque émission de la source répétée réécrit tout l'innerHTML,
les composants sont détruits et recréés (≈ 640 ms pour 119 graphiques) ; garder la source répétée stable
et faire porter les filtres par la source scopée.
Pas d'adaptateur derrière un id scopé : ni dsfr-data-facets ni
dsfr-data-search sur q-{{…}}.
Un attribut booléen ne se conditionne pas dans la balise (horizontal) :
deux éléments complets sous {{#if}} et {{#unless}}.
Deux limites levées en 0.31.0
Un id réutilisé ne purge plus le cache (#893) : à la re-création, l'ancienne instance
purgeait à sa déconnexion le cache de l'id que la nouvelle venait de remplir — un
consommateur monté plus tard lisait du vide. La purge n'a désormais lieu que si plus aucun élément du
document ne porte cet id ; un composant réellement retiré de la page purge toujours.
Le gabarit est recapturé (#894) : bundle chargé dans le <head> sans
defer, le display capturait son <template> avant qu'il soit analysé, et
rien ne le rattrapait quand les données étaient déjà connues au montage. Une seconde capture a lieu à la
fin de l'analyse du document.
Quand la ligne est un pipeline : dsfr-data-repeat
L'identité des instances à la ré-émission, l'imbrication, un attribut booléen conditionnel et un rendu
sans région ni compteur ne sont pas des limites à lever ici : c'est le contrat du composant de structure
dsfr-data-repeat (ADR-135). Règle d'usage :
display quand la ligne est du contenu, repeat quand la ligne est un
pipeline. L'exemple ci-dessus reste valide tel quel.
Template : Le contenu du <template> est clone pour chaque element de données.
Utilisez n'importe quelle structure HTML DSFR (cartes, tuiles, callouts, badges...) — et des composants
dsfr-data-* (variante 6 : un graphique ou un KPI par ligne).
Performance : Pour les grands ensembles de données, utilisez l'attribut pagination
pour limiter le nombre d'elements rendus simultanement.
Securite : Les valeurs {{champ}} sont echappees par défaut (HTML-safe).
Utilisez {{{champ}}} (triple accolades) uniquement si vous faites confiance a la source de données.