HTML 5 : l'élément details

Auteur
Nibau Rui
Date
()
Catégories
Web
Tags
,

J'ai développé en 2007, au boulot, un composant javascript appelé « InfoBox » qui fait ce qui est aujourd'hui proposé par l'élément details dans les spécifications HTML 5 (et même un peu plus). Cet élément n'est encore implémenté que par très peu de navigateurs web. Voici quelques pistes pour l'utiliser / le simuler.

Mises à jour
  • 2012-02-11 : Jouer sur la taille et l'overflow de l'élément plutôt que la visibilité des enfants pour afficher / masquer le contenu.
  • 2012-04-09 : Icône plier / déplier avec le pseudo-élément before plutôt qu'une image de fond.

Présentation

Les spécifications

Voyons d'abord ce que dit le brouillon des recommandations HTML 5 à propos de l'élément details :

Voilà pour la théorie. Pour la pratique, prenons un exemple simple qui nous servira de base de travail par la suite :

<details>
    <summary>Plus de détails</summary>
    <p>Ce paragraphe fournit un détail supplémentaire.</p>
    <p>Ce second paragraphe fournit autre détail.</p>
</details>
# Exemple simple d'utilisation de l'élément details.

Avec ce code, nous devrions voir affiché dans le document web uniquement le texte du summary, le reste étant masqué. Par une interaction quelconque avec le document (clic sur l'élément par exemple), l'utilisateur pourrait afficher / masquer les deux paragraphes. Et dans les faits ?

Plus de détails

Ce paragraphe fournit un détail supplémentaire.

Ce second paragraphe fournit un autre détail.

# Rendu original d'un élément details.

Si vous visualisez cette page avec Firefox <= 10, Opera <= 11.61 ou Internet Explorer <= 9, vous verrez en fait l'intégralité du contenu de l'élément affiché. A l'heure actuelle (juin 2011), seul Chromium 14 semble appliquer un rendu en accord avec les recommandations W3C.

Pourquoi cet élément ?

Il n'est pas question ici de discuter du bien fondé de l'élément details : les composants de ce type sont assez courant dans les librairies graphiques pour application de bureau, d'où leur simulation dans les applications web à l'aide de CSS et de javascript. Comme le HTML 5 est très orienté « application web », et qu'il est toujours préférable - pour des questions de performances principalement - d'utiliser une fonctionnalité native...

Le comportement plier / déplier pourrait aussi être utile ailleurs ; comme sur un élément section par exemple, avec son header qui aurait le même comportement que le summary quand on clique dessus : afficher / masquer le reste du bloc ; ou bien un comportement plier / déplier pour des listes hiérarchisées : cliquer sur un item plie / déplie ses sous-items.

D'aucuns diraient que le web, c'est avant tout du contenu avant d'être de l'applicatif ; pas faux. Toujours est-il que l'on peut envisager de nombreux usages pour l'élément details dans un cadre « classique », comme par exemple dans des critiques de films / romans : on voit souvent l'auteur de la critique avertir le lecteur que le texte qui va suivre dévoile une partie de l'intrigue ; on pourrait dés lors imaginer structurer le texte avec des éléments details de telle sorte que ces parties soient masquées par défaut.

Encore faut-il que l'élément soit fontionnel, ce qui n'est aujourd'hui pas le cas pour la plupart des navigateurs web. Nous allons voir comment corriger cela.

Expérimentations en CSS

Ce chapitre peut poser problème si vous utilisez Chromium >= 14 : la mise en forme forcée avec les CSS dans les exemples perturbe la bonne implémentation de l'élément. J'aurai pu appliquer ces styles dynamiquenemnt, en javascript, pour tout autre navigateur que Chromium, mais l'intérêt est justement de voir ce que l'on peut faire - ou pas - sans javascript...

Règles CSS par défaut

Essayons tout d'abord de simuler le rendu de l'élément avec de simples règles CSS, en ne laissant visible que son premier enfant summary.

/* Faire de details et summary des éléments block */
details, summary {
    display: block;
}
/* Appliquer une hauteur à details */
details {
    overflow: hidden;
    height: 1em;
    margin: 0 0 1.5em;
}
/* Afficher le premier enfant summary de details
   avec une icône illustrant l'état plié */
details > summary:first-of-type:before {
    content: "\25b6";
    font-family: sans-serif;
    line-height: 1;
    display: inline-block;
    width: 16px;
    margin: 0 4px 0 0;
}
# Styles par défaut à appliquer à details et summary
Plus de détails

Ce paragraphe fournit un détail supplémentaire.

Ce second paragraphe fournit un autre détail.

# Masquer le contenu de detail à l'exception du summary.

Nous choisissons de rendre l'icône illustrant l'état plié / déplié avec un caractère dans le pseudo-élément before au lieu d'une image pour avoir une plus grande liberté dans sa mise en forme ; Il nous faudrait par exemple plusieurs jeux d'images pour simplement changer la couleur de l'icône.

Nous faisons face à ici à un problème majeur : le contenu de details ne sera jamais visible. Il nous font donc gérer une interaction de l'utilisateur qui permette de l'afficher / masquer.

Afficher le contenu : hover

Une première solution pour interagir avec l'élément consisterait à afficher son contenu lorsque l'utilisateur passe la souris au dessus :

/* Afficher le contenu de details quand on passe 
   la souris au-dessus */
details:hover {
    height: auto;
}
/* Icône sur summary illustrant l'état déplié */
details:hover > summary:first-of-type:before {
    content: "\25bc";
}
# Règle CSS pour afficher le contenu de details en passant la souris dessus.

Le résultat :

Plus de détails

Ce paragraphe fournit un détail supplémentaire.

Ce second paragraphe fournit un autre détail.

# Afficher le contenu de details en passant la souris au-dessus.

Pas vraiment facile à utiliser quand on promène constamment la souris sur le document… Cela peut même devenir agaçant !

Afficher le contenu : focus

Oublions le hover et pensons à l'événement qui nous permettrait en CSS de réagir à un clic : le focus(1). Pour que cela soit possible, il faut définir à l'élément details un attribut tabindex avec une valeur nulle ou positive(2).

<details tabindex="0">
    <summary>Plus de détails</summary>
    <p>Ce paragraphe fournit un détail supplémentaire.</p>
    <p>Ce second paragraphe fournit un autre détail.</p>
</details>
# Permettre aux éléments details d'acquérir le focus.
/* Afficher le contenu de details quand il acquiert 
   le focus */
details:focus {
    height: auto;
}
/* Icône sur summary illustrant l'état déplié */
details:focus > summary:first-of-type:before {
    content: "\25bc";
}
# Règles CSS pour afficher le contenu des details en cliquant.
Plus de détails

Ce paragraphe fournit un détail supplémentaire.

Ce second paragraphe fournit un autre détail.

# Afficher le contenu de details en cliquant sur le summary.

Avantage de cette technique : nous pouvons accéder au détail uniquement quand nous le voulons vraiment (clic) et par navigation clavier (tabulation). Inconvénient : il faut cliquer ailleurs dans le document pour refermer l'élément (ce qui n'est d'ailleurs pas forcément le comportement souhaité).

Une variante de cette technique consiterait à permettre au summary de recevoir le focus et non plus au details dans son ensemble, mais cela ne changerait fondamentalement rien au comportement de l'implémentation.

Gestion en javascript

Toutes les règles CSS ci-dessus ont été appliquées aux éléments details et summary directement. Il est évident qu'il s'agissait uniquement là d'illustrer le propos - ce n'est pas ce qu'il faut faire en production car cela détruit les éventuelles implémentations natives. Pour assurer une compatibilité entre navigateurs web, il nous faut passer par du code javascript.

Il ne serait pas judicieux de publier ici le code du composant InfoBox développé au boulot il y a quelques années, non pas que cela me soit interdit (il est sous licence libre), mais c'est un code (1) qui ne se base pas sur details, (2) qui est un peu trop spécifique à la libraire javascript/java sur laquelle il repose et (3) qui possède plus de fonctionnalités que l'élément HTML5. On peut cependant et très simplement penser un code javascript qui obéirait à quelques règles de bases :

if (!('open' in document.createElement('details'))) {
    /**
     * Initialiser les éléments details
     * @param {HTMLDetailsElement} details Elément à traiter
     */        
    function initDetails (details) {
        var summary = details.firstElementChild;
        // Créer summary s'il n'existe pas
        if (summary.nodeName.toLowerCase() !== 'summary') {
            summary = document.createElement('summary');
            summary.appendChild(document.createTextNode('Details'));
            details.insertBefore(summary, details.firstChild);
        }
        // Ecoute du click sur summary
        summary.addEventListener('click', onSummaryClick, false);
        details.className = 'details';
        // details est ouvert en natif
        if (details.open === undefined) {
            details.open = true;
        }
        toggleDetails(details);
    }
    /**
     * Switcher le statut ouvert / fermer des details
     * @param {HTMLDetailsElement} details Elément à traiter
     */
    function toggleDetails (details) {
        details.open = !details.open;
        if (details.open) {
            details.setAttribute('open', 'open');
        } else {
            details.removeAttribute('open');
        }
    }
    /**
     * Ecouteur de clic sur summary
     * @param {MouseEvent} e Evénement clic
     */
    function onSummaryClick (e) {
        toggleDetails(e.target.parentNode);
    }
    
    // Traitement des details du document
    var list = document.getElementsByTagName('details'),
        i = 0,
        n = list.length;
    if (n > 0) {
        for (i = 0; i < n; i++) {
            initDetails(list[i]);
        }
    }
}
# Gestion des details en javascript.
/* Styles par défaut de details et summary */
details, summary {
    display: block;
}
/* Appliquer une marge à details */
details {
    margin: 0 0 1.5em;
}
/* Masquer les enfants de .details */
.details {
    overflow: hidden;
    height: 1em;
    display: none;
}
/* Styles du premier summary de .details */
.details > summary:first-of-type:before {
    content: "\25b6";
    font-family: sans-serif;
    line-height: 1;
    display: inline-block;
    width: 16px;
    margin: 0 4px 0 0;
}
/* Afficher les enfants pour un details ouvert */
.details[open=open] {
    height: auto;
}
/* Style du summary pour un details ouvert */
.details[open=open] > summary:first-of-type:before {
    content: "\25bc";
}
# Code CSS associé à la gestion des details en javascript.
Plus de détails

Ce paragraphe fournit un détail supplémentaire.

Ce second paragraphe fournit un autre détail.

# Afficher le contenu de details en cliquant dessus.

Le code javascript ci-dessus est très basique et très simplifiée ; il nécessiterait quelques adaptations pour se retrouver en production et être utilisable avec tous les navigateurs web. La gestion de l'état fermer / ouvert se base sur le principe qu'un attribut booléen (open) est représenté dans le code HTML par l'absence (false) ou la présence (true) de cet attribut.

On pourrait aussi penser à augmenter ces fonctionnalités, comme gérer l'événement focus et permettre par exemple d'utiliser la touche ENTER ou SPACE pour ouvrir / fermer l'élément.

L'exemple est cependant fonctionnel sous Firefox 5, Opera 11.11 et Internet Explorer 9.

Ressources et références

HICKSON, Ian. HTML 5. W3C, . 4.11.1 The details element

HUNT, Lachlan. Styling <details>. Public mailing list for the WHAT working group, . Pistes de reflexion sur l'implémentation de details dans Opera.

LAWSON, Bruce. HTML5 details element, built-in and bolt-on accessibility. brucelawson.co.uk,