HTML 5 : l'élément details
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.
§Présentation
§Les spécifications
Voyons d'abord ce que dit le brouillon des recommandations HTML 5 à propos de l'élément details :
- Il s'agit d'un élément de divulgation (
disclosure widget
) qui permet de fournir une information complémentaire en se dépliant. - Il peut contenir des éléments
summarydont seul le premier est affiché. - S'il ne possède pas de
summary, les navigateurs web doivent en afficher / générer un automatiquement. - Le plier / déplier du contenu est marqué par la propriété booléenne
open; le navigateur web doit fournir à l'utilisateur un moyen d'effectuer cette opération.
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>
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.
details.Si vous visualisez cette page avec Firefox <= 19, Opera <= 12.12 ou Internet Explorer <= 10, vous verrez en fait l'intégralité du contenu de l'élément affiché. Seul Chromium, à partir de la version 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.
§Implémentation 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 :
- Afficher / masquer le contenu de
detailsquand on clique sur le premiersummary. - Appliquer notre implémentation uniquement si l'élément
detailsn'est pas géré nativement par le navigateur web, ceci à l'aide d'une classe, disons.details. - Laisser l'ensemble de l'élément visible lorsque le javascript n'est pas actif.
define('rnb/dom/details', [], function () {
/**
* Implémentation en javascript du comportement plier / déplier pour un élément
* details. L'implémentation n'est pas effectuée si le navigateur supporte
* nativement le comportement.
*
* Pour que la fonction ne s'occupe pas d'un élément details en particulier, il
* suffit de lui ajouter la classe 'nojs'.
*
* @static
* @param {HTMLDetailsElement} el Elément à traiter
*/
var details = function (el)
{
if (rnb.supports.element('details') || el.classList.contains('nojs') ||
el.dataset.initialized === true) {
return;
}
var summary = el.firstElementChild,
padTop = rnb.css.get(el, 'padding-top'),
padBottom = rnb.css.get(el, 'padding-bottom'),
h = el.clientHeight;
// Créer summary s'il n'existe pas
if (summary.nodeName.toLowerCase() !== 'summary') {
summary = document.createElement('summary');
summary.appendChild(document.createTextNode('Details'));
el.insertBefore(summary, el.firstChild);
}
if (padTop) {
h -= parseInt(padTop, 10);
}
if (padBottom) {
h -= parseInt(padBottom, 10);
}
el.dataset.heightClose = summary.clientHeight;
el.dataset.heightOpen = h;
// Ecoute du click sur summary
summary.addEventListener('click', details.onClick, false);
el.classList.add('details');
el.dataset.initialized = true;
// details est ouvert en natif
if (el.open === undefined) {
el.open = true;
}
details.toggle(el);
};
/**
* Ecouteur de clic sur summary.
*
* @static
* @param {MouseEvent} e Evénement clic
*/
details.onClick = function (e)
{
var el = e.target;
// XXX Ne pas partir sur un label avec un for, un input, un button
if (el.htmlFor || rnb.dom.matchTag(el, '(input|button)')) {
return;
}
if (el.nodeName.toLowerCase() !== 'summary') {
el = rnb.dom.ascendant(el, function (node) {
return node.nodeName.toLowerCase() === 'summary';
});
}
details.toggle(el.parentNode);
};
/**
* Switcher le statut ouvert / fermer des details
*
* @static
* @param {HTMLDetailsElement} el Elément à traiter
*/
details.toggle = function (el)
{
el.open = !el.open;
if (el.open) {
el.setAttribute('open', 'open');
el.style.height = el.dataset.heightOpen + 'px';
} else {
el.removeAttribute('open');
el.style.height = el.dataset.heightClose + 'px';
}
};
/**
* Méthode d'initialisation des details présents dans le document courant.
*
* @static
*/
details.initialize = function ()
{
// Traitement des details du document
var list = document.getElementsByTagName('details'),
i = 0,
n = list.length;
for (; i < n; i++) {
details(list[i]);
}
};
// exports
return details;
});
details en javascript.Quelques remarques sur le code ci-dessus :
- Il utilise quelques méthodes de la librairie javascript rnb-js, comme
rnb.dom.hasTag,rnb.dom.ascendantournb.supports.element. Leur nom est suffisamment explicite pour en comprendre le sens et remplacer leur appel par des équivalents. - Il utilise directement des propriétés des éléments DOM comme
classListetdataset, qui sont mal ou non gérées par certains navigateurs web actuels, Internet Explorer 9/10 pour ne pas les nommer. Le support pour ces navigateurs peut aisément être obtenu grâce à des techniques de subsitutions.
/* Styles par défaut de details et summary */
details, summary {
display: block;
}
details {
overflow: hidden;
}
/* Réduire la taille des enfants de .details */
details > * {
font-size: 0.9em;
line-height: 1.25;
margin: 5px;
}
/* Afficher correctement le summary */
details > summary:first-of-type {
display: block;
font-size: 1em;
line-height: 1;
margin: 0 0 5px;
}
/* Masquer les enfants de .details */
.details > * {
visibility: hidden;
-webkit-transition: visibility 0.5s;
-moz-transition: visibility 0.5s;
-o-transition: visibility 0.5s;
transition: visibility 0.5s;
}
/* Afficher le summary */
.details > summary:first-of-type {
cursor: pointer;
visibility: visible;
}
/* Illustrer le summary d'un icône d'état plier/ déplier */
.details > summary:first-of-type:before {
content: "\25b6";
font-family: sans-serif;
font-size: 12px;
line-height: 12px;
display: inline-block;
width: 12px;
height: 12px;
margin: 0 8px 0 0;
text-align:center;
}
/* "Fermer" .details par défaut */
.details {
height: 1em;
-webkit-transition: height 0.5s;
-moz-transition: height 0.5s;
-o-transition: height 0.5s;
transition: height 0.5s;
}
/* Afficher les enfants de .details à l'état ouvert */
.details[open=open] {
height: auto;
}
.details[open=open] > * {
visibility: visible;
}
/* Style du summary pour un .details ouvert */
.details[open=open] > summary:first-of-type:before {
content: "\25bc";
}
details en javascript.Plus de détails
Ce paragraphe fournit un détail supplémentaire.
Ce second paragraphe fournit un autre détail.
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,
§Historique
- 2013-01-05
-
- Suppression des expérimentations CSS.
- Lien vers le code original utilisé dans rnb-js.
- 2011-06-25
- Création de l'article.
- 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
- Evolution: Icône plier / déplier avec le pseudo-élément
beforeplutôt qu'une image de fond.