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.

Attributs

AttributTypeDescription
sourceStringId de la source (ou du transformateur) dont ce composant consomme les données.
per-rowStringNombre 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.
colsNumberNombre 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.
paginationNumberNombre d'éléments par page (0 = tout afficher)
emptyStringMessage quand aucune donnee
gapStringClasse CSS de gap pour la grille (défaut: fr-grid-row--gutters)
uid-fieldStringChamp de données a utiliser comme identifiant unique par item. Si vide, utilise l'index
url-syncBooleanSynchronise le numéro de page dans l'URL (replaceState)
url-page-paramStringNom du paramètre URL pour la page (défaut: "page")
idle-messageStringMessage 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-labelStringNom 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).

En attente d'un filtre

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.

Filtrer d'un clic sur un élément

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.

AttributTypeDescription
refine-on-clickStringChamp 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).
contextStringIdentifiant 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é).
labelStringLibellé 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 du template

SyntaxeDescription
{{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

Variantes

1. Cartes DSFR

Affiche les données sous forme de cartes DSFR dans une grille a 3 colonnes.

<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>

2. Tuiles DSFR

Affiche les données sous forme de tuiles dans une grille a 4 colonnes.

<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>

3. Liste avec index

Affiche une liste simple avec l'index de chaque element via {{$index}}.

<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>

4. Valeurs par défaut

Utilise la syntaxe {{champ|défaut}} pour afficher une valeur de remplacement quand un champ est absent.

<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>

5. Pagination serveur

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.

6. Un composant dsfr-data par élément (un graphique par ligne)

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.

Conseils d'utilisation

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.