Un bug dont je me suis aperçu récemment m'a sidéré.
Cela concerne ici un événement ô combien tapageur, intrusif, et anti-accessible : contextmenu. Cet événement est lancé lors de l'appel au menu contextuel (généralement par le clic droit, mais pas seulement : laissez votre souris sur un élément de la page, et tapez la touche généralement située entre AltGr et Ctrl Droite, représentant... un menu), et répond aux classiques.
En effet, en plaçant un handler sur cet événement (ou listener, appellez ça comme vous le souhaitez) sur un élément de la page, on effectue une action (généralement afficher son propre menu contextuel), puis le retour définit si on laisse la main au menu contextuel du browser ou non (true/false). C'est le principe de propagation des événements.
Lorsque l'on place un événement onclick sur un lien. Je parle de le placer de façon "correcte", c'est à dire avec du javascript "non-intrusif", via addEventListener ou un équivalent sous votre lib préférée. Cliquez sur le lien. Miracle, ça marche. Placez un listener de la même façon sur le même event mais sur la page. Cliquez sur le lien : cela marche. C'est un principe simple:
Lorsque l'on place un événement sur un élément, cet élément capture en premier l'événement qui lui est associé
Non seulement ça coule un peu de source, mais ça semble logique. Placez maintenant ce lien en position absolue sur la page : cela marche toujours.
J'en viens à notre cas : l'association map/area/img.
On utilise les maps afin de "placer" une carte virtuelle, une espèce d'overlay sur une image. On règle l'attribut name de la map, on règle nos différentes "zones réactives" via des balises area dans la map, qui précisent la forme (attribut shape) et surtout les coordonnées de cette forme sur la carte.
Puis, on place la carte sur l'image en associant les deux, via l'attribut usemap de la balise img qui fait une référence un peu bizarre (avec un dièse devant, chose normalement réservée aux IDs) au name de la map.
Les areas sont réactives : elles peuvent posséder un attribut href réagissant comme le href des balises a. Elles sont susceptibles de capter le clic, le survol de la souris, etc. Tout laisse à penser qu'elles réagissent comme notre petit lien de tout à l'heure.
Maintenant, testez de placer un handler d'événement contextmenu sur une area.
En javascript non-intrusif.
Firefox, Opera : okay. Pas de soucis.
IE : ah, tiens. Pas d'erreur. Menu contextuel de base. Si on va plus loin, on voit que IE ne passe même pas par l'événement contextmenu de l'area.
Maintenant, ajoutez un handler d'événement contextmenu sur l'image.
En javascript non-intrusif.
IE : ah, tiens, celui-ci il le voit. Areas ou non.
Firefox, Opera : il voit l'event sur les areas, et celui sur l'image de manière indépendante.
Et à présent, retirez le handler d'événement contextmenu des areas...
En javascript non-intrusif.
IE : pas de changement.
Firefox, Opera : WTF ?? L'événement n'est pas pris en compte sur les area.
Explication ?
Firefox, Opera & consorts placent la map comme un overlay plein. Ou plutôt, les areas. C'est comme si vous aviez placé de vieux blocs en position absolute sur la page. Ces blocs "interceptent" donc l'événement. Et là où c'est vicieux, c'est que même si aucun gestionnaire n'est présent, et donc que l'événement répond aux classiques, il n'est pas propagé à l'élément "en-dessous".
IE, lui, ne place absolument pas la map (ou les areas) en overlay. Il effectue une espèce de mapping des formes et des coordonnées, pour capturer certains (pas tous... j'ai pu constater que le clic et le survol fonctionnaient) événements.
Mais pas le contextmenu.
J'ai naïvement tenté de modifier le z-index, l'ordre html des balises... Rien à faire, c'est implémenté coté comportement général.
La seule solution que j'ai finalement trouvé : surveiller les événements contextmenu sur toute l'image, vérifier les coordonnées par rapport aux area d'une éventuelle map, pour exécuter une fonction de callback. Je sette également un handler sur chaque area pour capturer le cas "hors IE".
En somme, ce patch corrige un comportement différent sous IE et autres... comportement qui ne devrait pas être.
Le patch:
var oldPatchContextArea = function(xmc, ymc, callback) {
var found = null;
// get areas
var listAreas = $('area');
listAreas.each(function() {
var xcrds = $(this).attr('coords').split(',');
var xsc = xcrds[0];
var ysc = xcrds[1];
var xec = xcrds[2];
var yec = xcrds[3];
if((xmc > xsc) && (xmc < xec) && (ymc > ysc) && (ymc < yec)) { found = $(this).attr('id'); } }); if(found != null) { callback($('#'+found)); } return (found == null); }; var setupPatchContextArea = function(callback) { // get all bind images $('img[usemap]').each(function() { // if map has some special class, watch it if($(this).hasClass('contextMenuAreas')) { // setup image AND areas context menu event $('#'+$(this).attr('id')).bind('contextmenu', function(e) { return oldPatchContextArea((e.pageX - $(this).offset().left), (e.pageY - $(this).offset().top), callback); }); // get map name var mapName = $(this).attr('usemap').replace('#', ''); $('map[name='+mapName+'] area').bind('contextmenu', function() { return callback($(this)); }); } }); };
Pour l'initialiser:
setupPatchContextArea(votreFonctionDeCallback); // au chargement ou autre
Limitation:
Ce patch ne détectera que les areas rectangulaires. Si quelqu'un a une solution pas trop gourmande pour savoir si un point fait partie d'un polygone, idem pour un cercle, je suis preneur.
Autre petit souci : ce patch a besoin d'une classe (contextMenuAreas) sur l'image mappée. On peut normalement remplacer cela par une détection d'éventuels handlers contextmenu placés sur les areas.
Bien sûr, ce problème, encore une fois, ne devrait même pas se poser. map/area est un duo immonde, qui n'a absolument rien à faire dans HTML. La réactivité de zones placées au jugé sur la page n'a aucun sens sémantique, surtout que ceci se trouve sur un média (image)...