Mes impressions sur le web, les standards et autres…


DOM et évènements

Je suis dernièrement confronté à quelques problèmes de compréhension du fonctionnement du gestionnaire d’évènements du DOM. Voici la page de test.

Je place un évènement "click" sur un lien, lequel englobe une image. Lorsque cet évènement est activé, je récupère l’élément à la source de l’évènement et affiche le nom du tag. Lorsque le lien englobe une image, le tag qui m’est renvoyé est img (??), tandis que si c’est un lien sous forme de texte, c’est bien le tag a qui m’est renvoyé. J’aurais pensé que ce serait toujours l’élément auquel on a accroché l’évènement qui serait renvoyé, et non un de ses éléments fils. Qu’en pensez vous ? Est-ce un comportement normal ?

DOM ou le parcours du combattant

Toujours plongé ces dernières semaines dans ce magnifique outil qu’est le DOM, j’ai de nouveau été confronté aux insuffisances de divers navigateurs. Il est vraiment dommage que IE fasse bande à part, hormis pour une grande partie du module Core. Cela complique réellement la tâche du développeur et l’oblige à réaliser toute une série de tests pour obtenir une compatibilité avec tous les navigateurs du marché.

Comme je le disais, IE (surtout la version 6.x) reconnait, à peu de choses près, l’ensemble du module Core. Là où il diverge passablement des normes, c’est avec les modules d’évènements et de styles (je ne me suis pas encore attaqué aux modules de vues et de traversée). Dans ces deux cas, Microsoft a défini ses propres objets, méthodes et propriétés, quand il en a défini, ce qui n’est pas toujours le cas.

Définir ses propres méthodes

Mon objectif est simple : Pas de code propriétaire dans mes scripts. Je me suis donc mis en tête de créer un script ajoutant les méthodes standard manquantes dans IE. Exemple :

if( !window.addEventListener )
{
    window.addEventListener = function(eventType, listener, useCapture) {
        this.attachEvent("on" + eventType, listener);
    }
}

Un petit problème cependant, il me faut faire cela pour chaque élément du document, ce qui m’impose de faire une boucle qui traitera chaqu’un de ces éléments. Un peu lourd… D’autant plus que ce n’est pas la seule boucle, il y a d’autres objets à "normaliser". J’ai donc commencé à chercher un moyen d'ajouter automatiquement une méthode à toutes les instances d’un objet, m’appuyant sur un passage de la documentation sur JavaScript 1.5 dont voici un extrait :

If you add a new property to an object that is being used as the prototype for a constructor function, you add that property to all objects that inherit properties from the prototype. For example, you can add a specialty property to all employees with the following statement :

Employee.prototype.specialty = "none";

Mais évidemment, car cela eût été trop simple, cela n'a pas fonctionné, ou du moins de la façon dont j’ai testé:

if( !window.addEventListener )
{
    document.getElementById('body').prototype.addEventListener = function(eventType, listener, useCapture) {
        this.attachEvent("on" + eventType, listener);
    }
}

Propriété inexistante… Puis je suis tombé sur cette page: The prototype object of JavaScript 1.1 (cette page et la suivante). Cet objet prototype existe donc depuis belle lurette, les tests sont d’ailleurs concluants, de plus, l’exemple de la dernière page me confirme qu’il est possible d’ajouter une méthode à un objet prédéfini, ce qui n’était pas forcément évident à la lecture de la documentation de Netscape.

J’ai cherché longtemps. C’est simple, j’ai bien dù y perdre mon week-end. Mais j’ai fini par trouver la solution. Ahh, le pied quand on cherche inlassablement et qu’on finit enfin par trouver :) Donc pour ajouter une méthode à un objet, et qu’elle soit héritée par toutes les instances en cours et à venir de l’objet, il fallait finalement faire :

if( !window.addEventListener )
{
	// Objet HTMLElement, l'objet constructeur de tous les éléments du document 
	HTMLElement.prototype.addEventListener = function(eventType, listener, useCapture) {
        this.attachEvent("on" + eventType, listener);
    }
}

// ou encore

if( !window.addEventListener )
{
	// Objet 'constructor' pour accéder à l'objet constructeur
    document.getElementById('body').constructor.prototype.addEventListener = function(eventType, listener, useCapture) {
        this.attachEvent("on" + eventType, listener);
    }
}

Les deux façons de faire fonctionnement parfaitement dans Firefox, la seconde seulement fonctionne dans Opera 7.22 et sur IE 6.x, … aucune… Autant vous dire que je suis extrèmement frustré, et ce, pour plusieurs raisons.

  • Cette possibilité est vraiment interessante, pas très connue ou utilisée de ce que j’ai pu en voir cependant.
  • Je tombe à nouveau sur une carence de IE alors que je cherchais à en combler une autre

Du coup, je suis à court d’idées et me suis rabattu sur les boucles. Je ne désespère pas de trouver une solution (il doit forcément y avoir un moyen d’accéder au constructeur d’un objet quel qu’il soit non ?).

Évènement souris et bouton cliqué

Alors là, c’est pire que d’habitude. Il n’y a plus deux modèles (W3C et Microsoft) pour connaitre le bouton de souris sur lequel a cliqué l’utilisateur mais trois ! Et oui, Opera fait lui aussi bande à part sur ce coup là.

Lorsqu'un évènement est lancé, on reçoit en argument de la fonction appellée un objet contenant diverses informations sur l’évènement enclenché. Ça, c’est pour le modèle du W3C. Chez microsoft, on dispose d’un objet similaire (du moins dans son origine) mais il se trouve rattaché à l’objet window. Bref, cet objet a une propriété button contenant le numéro du bouton de la souris qui a été pressé. Voici la valeur de cette propriété selon les différents modèles existants :

W3C Microsoft Opera
Bouton gauche 0 1 1
Bouton milieu 1 4 3
Bouton droite 2 2 2

Mhhh…, difficile de s’y retrouver n’est ce pas ? Il y a une autre propriété interessante dans l'objet event, c’est la propriété which, présente uniquement sur Mozilla, Safari et Opera. Pour un évènement souris adèquat, elle contient la même valeur que button sous Safari et Opera, sous Mozilla, cette valeur est incrémentée de un. En mixant tout ça, je pense être parvenu à une solution adéquat pour obtenir la même valeur sous ces trois navigateurs ainsi que sous IE. Si ça peut aider :

var button = event.button;

if( event.button != 0 && event.button != 2 && ( typeof(event.which) == 'undefined' || event.which > 0 ) )
{
    if( typeof(event.which) != 'undefined' )// Mozilla, Safari et Opera
    {
        button = event.which;
    }
    
    button--;
    
    if( button == 2 || button == 3 )// Correction Opera et MS
    {
        button = 1;
    }
}