Aperçu de l’attribut contenteditable en HTML5

by GF on 19 April 2010

L’HyperText Markup Language, ou HTML, est le langage permettant de décrire la composition des pages Web que le navigateur interprète afin d’afficher le contenu à l’écran. Les spécifications de ce langage sont établies par le World Wide Web Consortium (W3C). La cinquième version du HTML, en cours d’élaboration, est déjà partiellement intégrée à la plupart des navigateurs modernes. La balise <video> a monopolisé l’attention pendant un temps, mais l’HTML 5 offre d’autres nouveautés, dont le très important attribut contenteditable.

Utilité de l’attribut contenteditable

Le Web 2.0 est caractérisé par la transformations des internautes de simples lecteurs passifs en producteurs de contenu. L’exemple typique est Wikipédia, l’encyclopédie collaborative dans laquelle chaque internaute peut apporter sa contribution. D’un point de vue technique, cette transformation s’appuie sur des technologies bien connues depuis quelques années, comme Ajax, qui permet aux pages Web d’abandonner leur état purement statique pour devenir interactives. L’attribut contenteditable permet également cela, mais de manière plus simple et plus efficace.

Cet attribut permet de rendre n’importe quel conteneur, dans une page Web, directement modifiable. L’internaute a l’impression d’être face à un logiciel de traitement de texte plutôt qu’à une page Web.

Reprenons l’exemple de Wikipédia. Le lecteur qui souhaite corriger une erreur dans un article devra cliquer sur un lien, appelé “Modifier”, qui le redirigera vers une page avec une zone d’édition du texte. Il devra ensuite rechercher le passage à corriger dans la zone d’édition (ce qui peut être pénible, en fonction de la longueur du texte), effectuer la modification, puis soumettre en formulaire en cliquant sur un bouton “Enregistrer les modifications”. Au final, il aura dû charger deux pages, en plus de la page contenant l’article, avec tout ce que cela implique — lenteur du chargement, perte de vue du texte avec le passage d’une page à l’autre, etc. La balise contenteditable résout ce problème en permettant d’effectuer les modifications directement sur la page d’origine.

Démonstration de l’attribut contenteditable

Voici une démonstration. Il suffit de cliquer sur le texte ci-dessous pour le rendre directement modifiable :

Modifiez-moi !

L’utilisation de cette balise est un jeu d’enfant. Pour réaliser l’exemple ci-dessus, il a suffi d’écrire ceci :

<span contenteditable="true">Modifiez-moi !</span>

Le conteneur utilisé est un <span>, mais cela fonctionne de la même manière avec les autres conteneurs, notamment <div> et <p>. A l’heure actuelle, l’attribut contenteditable fonctionne avec Firefox 3.6, Safari 4, Opera 10.5, Chrome 5.

Récupération des modifications avec JavaScript

Définir un conteneur comme étant modifiable –c’est-à-dire offrir à l’utilisateur la possibilité de modifier le contenu– ne sert à rien si l’on n’enregistre pas les modifications. L’attribut contenteditable ne permet pas, à lui seul, de faire cela. Son rôle se limite à rendre un conteneur modifiable. Il faudra donc utiliser JavaScript pour récupérer les modifications, puis pour les enregistrer (dans une base de données, par exemple).

Voici un exemple complet de la construction, pas à pas, d’une page qui offre à l’internaute de modifier un paragraphe de texte, et qui récupère ensuite les modifications grâce à JavaScript.

On commence par définir la structure de la page. Sur ce point, le HTML 5 ne change rien par rapport à la version précédente :
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>test html5</title>
</head>
<body>
</body>
</html>

On insère ensuite, dans le corps de cette page, un paragraphe modifiable avec l’id test :
<body>
<p id="test" contenteditable="true">
Lorem Impsum...
</p>
</body>

On place, entre les balises <head> et </head>, un code JavaScript permettant :

  • De récupérer le conteneur grâce à son id (1ère fonction)
  • De récupérer le contenu (2ème fonction)
  • D’afficher un message avec ce contenu (3ème fonction)

<script type="text/javascript" charset="utf-8">
function $(element) { return document.getElementById(element) }
function $H(element) { return $(element).innerHTML }
function test() { alert($H('test')) }
</script>

Pour finir, on place un code JavaScript à la fin de la page, juste avant la balise </html>, qui permet :

  • D’attacher un gestionnaire d’événements au paragraphe modifiable : lorsque l’utilisateur valide sa modification, en cliquant en dehors de la zone modifiable, n’importe où sur la page, la fonction test() précédemment définie est appelée.
  • D’activer la correction orthographique du navigateur dans le paragraphe modifiable.

<script type="text/javascript" charset="utf-8">
$('test').addEventListener("blur", test, false)
$('test').spellcheck = 'true'
</script>

L’élément modifiable, doté de l’attribut contenteditable, gère deux actions : focus et blur. La première est déclenchée lorsque l’utilisateur clique sur l’élément afin de le modifier, et la seconde lorsqu’il valide les modifications (un clic ailleurs sur la page, ou une pression sur la touche Tab, par exemple).

A noter qu’il est possible de rendre une page modifiable dans son ensemble, par JavaScript, grâce à l’instruction suivante :
document.designMode = 'true'

La page est terminée. Notre paragraphe peut être édité et les modifications sont récupérées par Javascript lorsque l’utilisateur les valide en sortant de la zone modifiable.

Voir le code source complet de la page test.html

Enregistrer les modifications avec Ajax

Dans l’exemple précédent, le nouveau contenu du paragraphe est simplement affiché dans une boîte de dialogue modale — alert() — : c’est tout ce que fait la fonction test(). Mais il est bien entendu possible de modifier cette fonction, afin d’enregistrer le nouveau contenu dans une base de données. Une requête AJAX serait particulièrement adaptée à cela.

Il existe de nombreux frameworks Ajax. Pour n’en citer que trois, parmi les plus célèbres : jQuery, Prototype, MooTools. Chacun a sa propre syntaxe ; c’est pourquoi il est difficile — et inutile — de créer ici un exemple qui conviendra à tous les lecteurs.

Voyons tout de même, dans les grandes lignes, comment faire. Côté JavaScript, on modifie la fonction test() pour récupérer l’id du paragraphe et son contenu :

function test()
{
test_contenu = document.getElementById('test').innerHTML

NB: JQuery et Prototype implémentent la fonction $() ; il faudra donc supprimer la fonction $(element) définie ci-dessus.

On rajoute ensuite l’appel Ajax, de manière différente selon le framework utilisé.

Exemple avec JQuery :

$.ajax({
type: "POST",
url: "test_update.php",
data: {contenu:test_contenu}
});
}

Exemple avec Prototype :

new Ajax.Updater("",
"test_update.php",
{
method:'post',
parameters: {contenu:test_contenu}
}
)
}

Reste ensuite à créer la page test_update.php, appelée par Ajax, afin de traiter les données transmises par la requête asynchrone.

<?php
if(!empty($_POST['contenu']))
{
$contenu = addslashes($_POST['contenu']);
$sql = "UPDATE ma_table SET contenu = '$contenu' WHERE...";
@mysql_query($sql);
}
?>

Le code présenté ici n’est pas sécurisé (pas de contrôle de la donnée de formulaire transmise), et il ne fonctionne pas car il n’est pas indiqué (clause WHERE) les modifications doivent être faites. Il est cependant très facile, en modifiant un peu le code JavaScript, de récupérer l’id du paragraphe et d’identifier ainsi la bonne ligne dans la base de données.


A Paris, le 18 avril 2010

{ 2 comments… read them below or add one }

Yannick Cadin February 1, 2011 at 15:08

Bonjour,

Je vais peut-être dire une grosse bêtise mais le problème de la concurrence d’accès (envoi quasi simultané d’un document modifié par 2 personnes distinctes) n’a-t-il pas été complètement laissé de côté ?
C’est bien de faire allusion un moment aux bases de données mais dans le cas présent et contrairement à ces dernières, si un internaute commence à éditer le contenu, aucun mécanisme de verrou me semble-t-il ne permet d’éviter qu’un autre larron ne commence de son côté à modifier le contenu.
L’exemple de Wikipédia me semble pertinent. Beaucoup de monde et des gros documents. La haine si le document que je viens de réviser pendant 3 heures est ignoré sous prétexte qu’un autre quidam a validé son édition une seconde après moi.
Désolé si une subtilité m’a échappé qui prévient ce souci.
J’imagine bien un début de solution (au moins un pis-aller). Ajouter une fonction pour gérer l’événement “Focus” qui irait interroger le serveur pour vérifier si un verrou a été positionné et le positionner justement si ce n’est déjà le cas. Gros défaut, il faut prévoir un mécanisme de “rafraîchissement” régulier pour éviter qu’un inconscient qui fermerait sa page Web SANS valider ou annuler ne bloque indéfiniment le document qu’il aurait commencé à modifier.
D’ailleurs je suis en train de penser à un autre “détail” alors que j’écris le mot “annuler”. Ce nouvel attribut “contenteditable” autorise-t-il justement l’abandon des modifications en cours ?
Je me pose une autre question. Comment les sites et outils existants gèrent cette problématique jusqu’à maintenant ? (Je viens de procéder à quelques tests rapides sur Wikipédia et le résultat est assez “surprenant”. C’est plus subtil que du verrouillage et il s’en sort globalement pas trop mal. Ce qui est certain c’est que ce n’est géré qu’au moment de la “publication”, donc en phase finale, pas avant.)

D’avance merci pour tout éclaircissement.

Reply

GF February 1, 2011 at 17:13

En effet, il y a un problème de synchronisation. L’attribut contenteditable lui-même ne gère absolument pas cela : c’est du HTML, et il se contente de rendre un bloc éditable directement dans le navigateur. L’enregistrement du contenu n’est plus l’affaire du HTML, mais du langage de script (quel qu’il soit) utilisé pour communiquer avec la base de données.

L’exemple très simple que je donne ici, avec PHP et JavaScript, ne gère pas non plus la synchronisation des modifications (précisément parce qu’il est simple et qu’il doit le rester).

Il faut donc implémenter un système dans le logiciel qui gère les accès à la base de données, si c’est nécessaire. Car cela ne l’est pas forcément. Dans le cas d’un wiki, comme Wikipédia, il est évident qu’il faut gérer les modifications simultanées d’un article. Mais dans un forum ou un blog, par exemple ? Ce n’est généralement pas nécessaire, car la seule personne autorisée à modifier un message est celle qui a écrit ce message (hormis les modérateurs et administrateurs, mais leur intervention est gérée par un système de droits, et celle-ci prévaut sur celles des autres utilisateurs).

Bref, sans n’avoir jamais implémenté un tel système (je n’en ai jamais eu besoin), on pourrait imaginer une première requête vers la base de données avec AJAX, lorsque le champ devient éditable (événement onclick, par exemple) qui insère un verrou dans une table spéciale (table verrou : id_article, id_utilisateur, timestamp…), ou dans une colonne de la table contenant le texte (table texte : id, contenu, auteur, is_locked). On pourrait ensuite modifier la requête d’enregistrement qui, en plus de faire un UPDATE sur le texte, retirerait le verrou. Il ne resterait qu’à prévoir un “failsafe” dans le cas où le verrou n’est pas fermé au bout d’un moment (l’auteur est parti prendre un looong café sans enregistrer les modifications…), ainsi qu’un message avertissant les autres utilisateurs qu’ils ne peuvent pas modifier le texte tant qu’il est verrouillé.

Tout cela serait plutôt simple à implémenter, mais pas très élégant pour les utilisateurs qui devraient prendre leur mal en patience lorsqu’un texte est verrouillé (et l’on sait qu’avec le Web 2.0, la patience n’est pas de mise). Il faudrait alors créer un véritable système de gestion de versions, comme SVN ou Subversion, ou comme ceux de Wikipédia ou de Wordpress, et s’inspirer de leur manière de gérer les accès (COMMIT) concurrents au même contenu. Mais cela est une autre affaire.

Si c’est trop complexe, on peut faire plus léger (mais aussi moins sûr), en se contentant d’avertir l’utilisateur de l’existence d’un accès concurrent, et en le laissant agir de manière appropriée. On pourrait ainsi créer un DIV spécial dans la page, et le mettre à jour avec AJAX, pour dire à l’utilisateur soit “il y a X utilisateurs en train de modifier cette page”, soit “attention le contenu de la page a changé pendant que vous écriviez” (on pourrait dans ce cas, toujours avec AJAX, surligner ou mettre en évidence d’une manière ou d’une autre ce qui a changé).

Ma conclusion est donc la suivante : la gestion des versions concurrentes du même document est un problème complexe et indépendant de la manière d’éditer les données. L’attribut contenteditable ne s’occupe que de l’édition des données. Il permet de rendre l’expérience utilisateur plus agréable, mais il est impuissant à gérer les versions concurrentes. D’ailleurs, que l’on utilise contenteditable et AJAX ou les bonnes vieilles requêtes GET/POST synchrones, cela ne change rien au problème des versions concurrentes. Donc, si le logiciel est susceptible de générer des versions concurrentes, il doit être capable de les gérer.

Reply

Leave a Comment

{ 1 trackback }

Previous post:

Next post: