Je profite de quelques beaux jours pour coder en plein soleil et améliorer les outils que je propose à mes clients. Notamment à passer mon interface d'administration en XHTML5 pour profiter des milles avantages qu'amènent ces nouvelles normes. Ce qui demande à la fois une lecture complète des documents, des tests sur les navigateurs modernes quant à l'implémentation des nouveautés, des heures et des heures de lecture des fora du WHATWG. Et bien évidemment de coder en Javascript par manipulations DOM une implémentation.

Le brouillon de la norme HTML5 essaie notamment de rendre facilement implémentable ses apports sur la plupart des navigateurs déjà déployés. Un principe directeur en réaction au rigide de la norme XHTML2 envisagée par le W3C et actuellement abandonné. Quant au XHTML5, le passage en une gestion XML stricte améliore la sécurité du code, permet une extension de la grammaire (microformats embarqués, SVG, schémas de données,...) mais se heurte actuellement au fait que pas mal de bibliothèques JS (éditeurs visuels notamment), abusent du document.write() qui est à la fois à bannir et strictement absent de la grammaire Javascript/DOM d'une page XML.

Parmi les extensions proposées à la grammaire du HTML, j'en ai repéré une assez intéressante : <input placeholder="" />. L'attribut placeholder indique par un aspect fantômatique le type ou un exemple de valeur que l'on attend de la part de l'utilisateur dans un champ. C'est une indication supplémentaire pour les éléments de formulaires.

Arrivée dans la norme en Novembre dernier, cette fonction a déjà été implémentée en catimini dans Safari. Même si l'idée originale d'Apple était l'implémentation d'éléments du GUI uniquement dans des applications comme iTunes dans sa partie store qui utilisent WebKit, le moteur de rendu de Safari pour sa présentation (Songbird fait de même avec XULrunner, wrapup du moteur Gecko de Firefox), leur adoption dans le web correspond à la fois à une demande, et au besoin de ne plus continuellement réinventer la roue mais de standardiser l'idée.

Sur ce blog, lors de sa remise à neuf de 2007, j'avais mis en place un équivalent graphique pour le champ de recherche dans la sidebar : Une image qui disparait si le champ a le focus ou si du texte est présent.

Et comme je suis un mauvais ergonome, j'ai choisi le paradigme de la loupe, ce qui est une très mauvais idée : La même image pourrait être utilisée pour zoomer la page.

Exemples

Supposons que dans un champ d'un formulaire, nous attendons un numéro de téléphone portable en France. Celui-ci peut d'écrire de plusieurs manières :

  • En numérotation nationale, sans séparateur : 06XXXXXXXX
  • Idem mais en écriture “humaine”, avec des espaces séparant les paires de nombres : 06 XX XX XX XX
  • Idem mais avec des tirets : 06-XX-XX-XX-XX
  • La numérotation internationale standard : +336XXXXXXXX
  • La numérotation internationale avec code pays et indicatif national de réseau : [+33](0)6XXXXXXXX

J'ai bien évidemment pris cet exemple parce que j'ai vécu plein de particularismes à vous rendre chèvre le Mouton.

Comme on est des gros flemmards, on va même pas supposer que votre code côté serveur corrige de lui-même parmi ces possibilités en obligeant l'utilisateur du site à utiliser la méthode de notation que l'on a prévu.
Si on veut dans le champ une notation internationale standard, il suffit d'indiquer l'exemple. Si votre navigateur supporte déjà placeholder, vous devriez les voir :

Il est probable, mais peu pausible, que des navigateurs ultra-consciencieux ignorent l'attribut comme mon blog est en XHTML1, mais ça entrainerait l'existence du Père Noël, ce qui bouleverserait toutes mes théories. Si jamais vous entriez dans ce cas hautement improbable, ou que plus simplement, votre navigateur ne connait pas l'attribut placehover, et parce que je suis un garçon gentil et poli, voici ce que vous avez manqué :

Le champ texte, sans valeur par défaut et sans le focus

Le même champ, avec le focus

Pour terminer sur le chapitre du support côté HTML, il est utile de noter qu'à l'heure actuelle, la balise <textarea></> ne gère pas l'attribut placehover.

Béquilles

Il se trouve qu'émuler cette fonction dans la plupart des navigateurs (j'écarte MSIE6 dans mes interfaces d'administrations pour que mes clients aient un niveau minimum de sécurité quand ils gèrent leurs sites. Et paraît que MSIE8 va enfin comprendre correctement le XHTML natif) se code en une dizaine de lignes JS. La solution que j'ai développée est néanmoins imparfaite car je ne connais pas le rendu de l'attribut placehover dans les clients web pour déficients visuels.

Il y a aussi un autre problème, nettement plus gênant : placehover est déjà géré à l'heure actuelle par certains navigateurs, Safari entres autres. Or mon code Javascript se substitue au fonctionnement natif de cette propriété. Ce qui pose plusieurs problèmes éthiques :

  • Mon code remplace le fonctionnement normal du GUI du navigateur web, voire du système d'exploitation. Ce qui perturbe le paradigme de l'interface habituelle de l'utilisateur. Tous ne sont pas chevronnés et on imagine mal comme le simple changement d'un curseur de souris ou une couleur de survol perturbent les usages ;
  • Cela alourdi, certes très très très légèrement, l'applicatif embarqué de la page, ce qui peut poser des plantages (pour dix lignes, m'étonnerais quand même) ou des incohérences voire des comas en cas de ralentissement. J'avoue que ces derniers cas sont moins fréquent avec des interpréteurs Javascripts de plus en plus véloce et l'arrivée du JIT. Idéalement, le moins de code serait le mieux ;
  • C'est un ajoût inutile, comme donner des béquilles à un valide en parfaite santé, ou des lunettes de correction à quelqu'un qui a une excellente vision. Ce n'est même pas du confort, c'est encombrant et contre-productif ;
Pour palier à cela, il existe une méthode de programmation qui s'appelle la dégradation élégante. Si une fonction n'est pas supportée, le site web ne doit pas être inutilisable mais doit y pallier de la manière la plus standard possible. Si un formulaire est une fonctionnalité AJAX, en l'absence de Javascript, celle-ci s'exécute mais par le rechargement complet de la page. Là, je souhaite utiliser le paradigme inverse : Si une fonction est implémentée, les fonctions Javascript d'émulation ne doivent pas forcer la main au navigateur client.

Si on utilise ce type de programmation plutôt que repérer la disponibilité de la fonction via son user-agent, c'est que dans le cas inverse, on est stratégiquement sûr à 100% de faire des oublis. Le user-agent sniffing est une méthode qui est facilement mis en défaut. Par exemple, de nombreux addons modifient le UA, des faux navigateurs camouflent le moteur réel utilisé (par exemple Trident, le moteur d'Internet Explorer pour Maxthon, ou WebKit Iphone pour Incognito et WebMate). Et oblige à mettre à jour très régulièrement le code avec de multiples tests, sans compter tous les oublis. Ni même envisagé un addon navigateur qui émule ce fonctionnement.

L'approche DOM de l'élément HTML

Et c'est là que commence ma galère. Pour savoir si je dois activer ma dizaine de ligne de code ou laisser le navigateur faire son travail tranquillement, je tente différentes approches, mais toutes échouent pitoyablement.

Le principal obstacle, c'est qu'on ne tente pas de chercher si une propriété javascript existe, comme le font les bibliothèques d'émulations des propriétés comme JSON.* , mais si un attribut HTML est utilisé dans un comportement d'interface.
Ça semble pas clair ?

Tant pis, j'aurais dû prévenir en haut du billet. Supposons que objet est un objet DOM lié à la balise <input placeholder />. On essaie avec cette conditionnelle :

if ( typeof(objet.placeholder)=='undefined' )
{
    // ici, mon code
}

Ici, l'idée est de travailler avec objet.placeholder, propriété que je suppose équivalente à objet.title par exemple. Mais cette approche échoue. Car il n'y aura plus de nouveaux attributs accessibles en brut. Cette méthode est déjà bannie des documents (correctement déclarés en) XHTML, et elle ne sera pas backportée en HTML.
Désormais, il faut utiliser les méthodes objet.getAttribute() et objet.getAttribute(). Mais que la fonction soit ou non implémentée, l'attribut sera toujours accessible par cette méthode, car elle permet justement de créer les émulations en Javascript. Je sais, ça fait râler.

OK, loupé.

L'approche DOM d'une propriété de style

if ( typeof(objet.style.inputPlaceholder)=='undefined' )
{
    // ici, mon code
}

Une variante de l'approche précédente, sur l'idée que si l'attribut placeholder="" est implémenté, alors son stylage l'est aussi :

input { input-placeholder : gray ; }

Pour l'instant, cette propriété CSS expérimentale (comme les colonnes en leur temps) ne propose que de changer la couleur du texte. Comme seule Safari l'a implémenté, celle-ci est préfixée par le nom du moteur comme le demande la norme CSS : -webkit-input-placeholder. En connaissant le nom de la propriété, j'en ai déduit le nom de la propriété DOM correspondante. Or cela ne reste qu'une supposition, surtout dans les lettres en Majuscules, et comme les variables, fonctions et méthodes javascripts sont des noms sensibles à la casse, c'est pas garanti.
Et comme la norme n'est pas fixée, rien n'interdit que la propriété CSS finale soit complètement différente. Notamment par le fait que cette propriété ne prend qu'une valeur de couleur, ce qui pose un problème d'accessibilité pour les terminaux non-visuels. Après tout, pourquoi pas utiliser un pseudo-élément ?

input:placeholder { color : gray ; }

Comme dit mon confrère Caleb du R&D de la Flander's « Ben t'es pas dans la merde »....

Appel à commentaires

Et là, une fois bloqué sur ce message, il ne me reste plus qu'à espérer que l'un de mes savants lecteurs trouvera la bonne solution.
Sachant que lesdites normes (HTML5, CSS3 et JS2) ne sont encore qu'à l'état de brouillon, ça peut être l'occasion de trouver une solution standard et de la faire remonter au WHATWG.

Mais avant de commenter, vous serez gentils de copier-coller le bloc qui suit (et regardez pas le code source, il est ignoble exprès).

  • user-agent :
  • objet.getAttribute('id') :
  • objet.getAttribute('placeholder') :
  • objet.getAttribute('inexistant') :
  • typeof(objet.placeholder) :
  • typeof(objet.style.inputPlaceholder) :