Skip to content

Tableau - DsfrTable

🌟 Introduction

Le composant DsfrTable est obsolète. Il n’est plus maintenu. Il reste utilisable pour des tableaux simples. Pour tout tableau plus complexe et/ou plus personnalisé, veuillez utiliser le composant DsfrDataTable.

Il est remplacé par le composant DsfrDataTable qui a été enrichi pour répondre aux besoins en matière de tableaux conforme à la version 1.14.3 du DSFR

Bien qu'il soit déconseillé de l'utiliser nous laissons la docmentation.

:::

🛠️ Props

NomTypeDéfautObligatoireDescription
titlestringLes en-têtes de votre tableau.
headersArray<string>[]Les en-têtes de votre tableau.
rowsArray<DsfrTableRowProps | string[] | DsfrTableCellProps[]>[]Les données de chaque rangée dans le tableau.
rowKeystring | FunctionundefinedUne clé unique pour chaque rangée, utilisée pour optimiser la mise à jour du DOM.
currentPagenumber1La page actuelle dans la pagination du tableau.
resultsDisplayednumber10Le nombre de résultats affichés par page dans la pagination.

📡Événements

NomDescription
update:currentPageÉmis lors du changement de la page actuelle.

🧩 Slots

  • header: Ce slot permet de personnaliser les en-têtes du tableau. Par défaut, il utilise DsfrTableHeaders avec les props headers.
  • Slot par défaut: Utilisé pour le corps du tableau. Par défaut, il affiche les rangées de données via DsfrTableRow.

📝 Exemples

Exemple Basique

vue
<script lang="ts" setup>
import DsfrTable from '../DsfrTable.vue'
</script>

<template>
  <DsfrTable
    title="Exemple de tableau simple"
    :headers="['Nom', 'Age', 'Ville']"
    :rows="[
      { rowData: ['Alice', '30', 'Paris'] },
      { rowData: ['Bob', '24', 'Lyon'] },
    ]"
  />
</template>

Exemple utilisant des composants dans les cellules

vue
<script lang="ts" setup>
import type { DsfrTableCellProps, DsfrTableRowProps } from '../DsfrTable.types'

import { getCurrentInstance, ref } from 'vue'

import DsfrTag from '../../DsfrTag/DsfrTag.vue'
import DsfrTable from '../DsfrTable.vue'

getCurrentInstance()?.appContext.app.component('DsfrTag', DsfrTag)

const title = 'Utilisateurs'
const headers = ['Nom', 'Prénom', 'Email', 'Statut']
const rows: (string | DsfrTableRowProps | DsfrTableCellProps | { component: string, [k: string]: unknown })[][] = [
  [
    'SÖZE',
    'Keyser',
    'keyser.soze@mastermind.com',
    {
      component: 'DsfrTag',
      label: 'Info',
      class: 'info',
    },
  ],
  [
    'HUNT',
    'Ethan',
    'ethan.hunt@impossible.com',
    {
      component: 'DsfrTag',
      label: 'Erreur',
      class: 'error',
    },
  ],
  [
    'HOLMES',
    'Sherlock',
    'sherlock.holmes@whodunit.com',
    {
      component: 'DsfrTag',
      label: 'Succès',
      class: 'success',
    },
  ],
  [
    'JONES',
    'Indiana',
    'indiana.jones@marshall-college.com',
    {
      component: 'DsfrTag',
      label: 'Info',
      class: 'info',
    },
  ],
  [
    'WAYNE',
    'Bruce',
    'bruce.wayne@batmail.com',
    {
      component: 'DsfrTag',
      label: 'Erreur',
      class: 'error',
    },
  ],
]

const noCaption = true
const currentPage = ref(1)
const resultsDisplayed = 5
</script>

<template>
  <DsfrTable
    :title="title"
    :headers="headers"
    :rows="rows"
    :no-caption="noCaption"
    :current-page="currentPage"
    :results-displayed="resultsDisplayed"
  />
</template>

<style scoped>
:deep(.info) {
  color: var(--info-425-625);
  background-color: var(--info-950-100);
}
:deep(.error) {
  color: var(--error-425-625);
  background-color: var(--error-950-100);
}
:deep(.success) {
  color: var(--success-425-625);
  background-color: var(--success-950-100);
}
</style>

⚙️ Code source du composant

vue
<script lang="ts" setup>
import type { DsfrTableProps } from './DsfrTable.types'
import type { DsfrTableRowProps } from './DsfrTableRow.vue'

import { computed, ref } from 'vue'

import DsfrTableHeaders from './DsfrTableHeaders.vue'
import DsfrTableRow from './DsfrTableRow.vue'

import { useRandomId } from '@/utils/random-utils'

export type { DsfrTableProps }

const props = withDefaults(defineProps<DsfrTableProps>(), {
  headers: () => [],
  rows: () => [],
  rowKey: undefined,
  currentPage: 1,
  resultsDisplayed: 10,
})

// Permet aux utilisateurs d'utiliser une fonction afin de charger des résultats au changement de page
const emit = defineEmits<{ (event: 'update:currentPage'): void }>()

const getRowData = (row: DsfrTableProps['rows']) => {
  return Array.isArray(row) ? row : (row as unknown as DsfrTableRowProps).rowData
}

const currentPage = ref(props.currentPage)
const selectId = useRandomId('resultPerPage')
const optionSelected = ref(props.resultsDisplayed)
const pageCount = computed(() =>
  props.rows.length > optionSelected.value
    ? Math.ceil(props.rows.length / optionSelected.value)
    : 1,
)
const paginationOptions = [5, 10, 25, 50, 100]
const returnLowestLimit = () => currentPage.value * optionSelected.value - optionSelected.value
const returnHighestLimit = () => currentPage.value * optionSelected.value

const truncatedResults = computed(() => {
  if (props.pagination) {
    return props.rows.slice(returnLowestLimit(), returnHighestLimit())
  }

  return props.rows
})

const goFirstPage = () => {
  currentPage.value = 1
  emit('update:currentPage')
}
const goPreviousPage = () => {
  if (currentPage.value > 1) {
    currentPage.value -= 1
    emit('update:currentPage')
  }
}
const goNextPage = () => {
  if (currentPage.value < pageCount.value) {
    currentPage.value += 1
    emit('update:currentPage')
  }
}
const goLastPage = () => {
  currentPage.value = pageCount.value
  emit('update:currentPage')
}
</script>

<template>
  <div
    class="fr-table"
    :class="{ 'fr-table--no-caption': noCaption }"
  >
    <table>
      <caption class="caption">
        {{ title }}
      </caption>
      <thead>
        <!-- @slot Slot "header" pour les en-têtes du tableau. Sera dans `<thead>` -->
        <slot name="header">
          <DsfrTableHeaders
            v-if="headers && headers.length"
            :headers="headers"
          />
        </slot>
      </thead>
      <tbody>
        <!-- @slot Slot par défaut pour le corps du tableau. Sera dans `<tbody>` -->
        <slot />
        <template v-if="rows && rows.length">
          <DsfrTableRow
            v-for="(row, i) of truncatedResults"
            :key="
              rowKey && getRowData(row as string[][])
                ? typeof rowKey === 'string'
                  ? getRowData(row as string[][])?.[headers.indexOf(rowKey)]?.toString()
                  : rowKey(getRowData(row as string[][]) ?? [])
                : i
            "
            :row-data="getRowData(row as string[][])"
            :row-attrs="'rowAttrs' in row ? row.rowAttrs : {}"
          />
        </template>
        <tr v-if="pagination">
          <td :colspan="headers.length">
            <div class="flex justify-right">
              <div class="self-center">
                <label :for="selectId">Résultats par page : </label>
                <select
                  :id="selectId"
                  v-model="optionSelected"
                  title="Résultats par page - le nombre résultats est mis à jour dès sélection d’une valeur"
                  @change="emit('update:currentPage')"
                >
                  <option
                    v-for="(option, idx) in paginationOptions"
                    :key="idx"
                    :value="option"
                  >
                    {{ option }}
                  </option>
                </select>
              </div>
              <div
                class="flex ml-1"
                aria-live="polite"
                aria-atomic="true"
              >
                <p class="self-center fr-m-0">
                  Page {{ currentPage }} sur {{ pageCount }}
                </p>
              </div>
              <div class="flex ml-1">
                <button
                  class="fr-icon-arrow-left-s-first-line"
                  @click="goFirstPage()"
                >
                  <span class="fr-sr-only">Première page du tableau</span>
                </button>
                <button
                  class="fr-icon-arrow-left-s-line"
                  @click="goPreviousPage()"
                >
                  <span class="fr-sr-only">Page précédente du tableau</span>
                </button>
                <button
                  class="fr-icon-arrow-right-s-line"
                  @click="goNextPage()"
                >
                  <span class="fr-sr-only">Page suivante du tableau</span>
                </button>
                <button
                  class="fr-icon-arrow-right-s-last-line"
                  @click="goLastPage()"
                >
                  <span class="fr-sr-only">Dernière page du tableau</span>
                </button>
              </div>
            </div>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<style scoped>
.flex {
  display: flex;
}

.justify-right {
  justify-content: right;
}

.ml-1 {
  margin-left: 1rem;
}

.self-center {
  align-self: center;
}
</style>
ts
import type VIcon from '../VIcon/VIcon.vue'
import type { HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from 'vue'

export type DsfrTableRowProps = {
  rowData?: (string | Record<string, any>)[]
  rowAttrs?: HTMLAttributes
}

export type DsfrTableHeaderProps = {
  header?: string
  headerAttrs?: ThHTMLAttributes & { onClick?: (e: MouseEvent) => void }
  icon?: string | InstanceType<typeof VIcon>['$props']
}

export type DsfrTableHeadersProps = (string | (DsfrTableHeaderProps & { text?: string }))[]

export type DsfrTableCellProps = {
  field: string | Record<string, unknown>
  cellAttrs?: TdHTMLAttributes
  component?: string
  text?: string
  title?: string
  class?: string
  onClick?: Promise<void>
}

export type DsfrTableProps = {
  title: string
  headers?: DsfrTableHeadersProps
  rows?: (DsfrTableRowProps | (DsfrTableCellProps | { component: string, [k: string]: unknown } | string)[])[]

  rowKey?: ((row: (string | Record<string, any>)[] | undefined) => string | number | symbol | undefined) | string
  noCaption?: boolean
  pagination?: boolean
  currentPage?: number
  resultsDisplayed?: number
}