Evénéments DOM : attache, suppression, activation et désactivation

Petite présentation rapide, donc partielle, de l'écoute des événements DOM, et sur la manière dont je la gère ou que je contourne ses manques dans ma librairie javascript.

Un écouteur d'événement s'attache à un élément du DOM par la méthode addEventListener :

void addEventListener(
    DOMString type, 
    EventListener listener, 
    boolean useCapture
)
Définition de la méthode addEventListener.

Ainsi, pour écouter le « click » sur un élément el, nous écrivons :

el.addEventListener('click', function (e) {
    // Do something on click events
}, false);
Exemple d'écoute d'un événement « click ».

La méthode ne renvoit rien, contrairement à d'autres modèles d'écoute où on récupère un identifiant, comme en GTK par exemple ; il est ainsi (plus) aisé de supprimer l'écouteur. Dans le DOM, la suppression se fait par la méthode removeEventListener :

void removeEventListener(
    DOMString type,
    EventListener listener, 
    boolean useCapture
)
Définition de la méthode removeEventListener.

Avec l'exemple ci-dessus, il nous serait impossible d'utiliser la méthode de suppression car l'écouteur défini est une fonction anonyme. Pour pouvoir l'utiliser, il faut garder une référence de la fonction :

// référence de l'écouteur.
var listener = function (e) {
    // Do something on click events
};
// Ecoute de l'événement
el.addEventListener('click', listener, false);
// ...
// Plus loin dans le code, on peut supprimer l'écouteur
el.removeEventListener('click', listener, false);
Ecoute d'un événement « click » et suppression de l'écouteur.

Notons que l'écouteur d'événements peut être soit une fonction, soit un objet implémentant l'interface EventListener, c'est-à-dire un objet possèdant une méthode handleEvent. Ce qui veut dire que le même écouteur peut être utilisé plusieurs fois pour observer différents événements.

var manager =
{
    handleEvent: function (e) 
    {
        // Do something on DOM events.
    },
    handleClick: function (e) 
    {
        // Do something on click events.
    },
    handleMouseOver: function (e)
    {
        // Do something on mousover events.
    }
};

el.addEventListener('click', manager, false);
el.addEventListener('mouseover', manager, false);

// ou

el.addEventListener('click', manager.handleClick, false);    
el.addEventListener('mouseover', manager.handleMouseOver, false);
2 manières de définir un écouteur : une fonction pour chaque événement ou une fonction pour tous les événements.

Il n'est par ailleurs pas possible de savoir si un élément possède des écouteurs d'évenements, ni si un événement particulier est écouté. Il y eu dans les spécifications écrites en 2001 la définition d'une interface EventListenerList pour une liste d'écouteurs d'événements et d'une propriété eventListeners pour les objets sur lesquels on peut attacher des écouteurs ; mais plus rien dans les spécifications suivantes.

Enfin, toujours en regardant du côté de GTK, il peut parfois être utile de bloquer un écouteur temporairement, puis de le réactiver.

Donc, pour pouvoir gérer plus simplement les « lacunes » ou manques du modèle d'événemments DOM, l'attache, la suppression, l'activation ou la désactivation d'écouteurs, j'utilise depuis plusieurs années la série de méthodes suivante(1) :

// Objet qui regroupe l'ensemble des méthodes 
// d'interaction avec le DOM
rnb.dom = {
    /**
     *  Mise en cache des écouteurs d'événements.
     *  @type Object
     *  @private
     */
    listeners: {},
    
    /**
     *  Attacher un écouteur d'événement à un élément DOM.
     *
     *  @param  {HTMLElement} element Elément sur lequel attacher l'écouteur.
     *  @param  {String} event Type de l'événement.
     *  @param  {Function} listener Ecouteur d'événement.
     *  @param  {Boolean} capture Capture de la phase de rebond.
     *  @return {String} identifiant de l'écouteur d'événement. 
     */
    addEventListener: function (element, event, listener, capture)
    {
        if (!listener.__lid__) {
            listener.__lid__ = rnb.guid('l');
        }
        if (!rnb.dom.listeners[listener.__lid__]) {
            rnb.dom.listeners[listener.__lid__] = {
                enabled: false,
                listener: listener,
                targets: []
            };
        }
        rnb.dom.listeners[listener.__lid__].targets.push({
            element: element, 
            event: event, 
            capture: capture
        });
        rnb.dom.enableEventListener(listener.__lid__);
        
        return listener.__lid__;
    },
    
    /**
     *  Supprimer un écouteur d'événement.
     *
     *  @param {String} id Identifiant de l'écouteur d'événement à supprimer,
     *  retourné par la méthode [[#rnb.dom.addEventListener]].
     */
    removeEventListener: function (id)
    {
        rnb.dom.disableEventListener(id);
        rnb.dom.listeners[id].listener = null;
        rnb.dom.listeners[id].targets = null;
        delete rnb.dom.listeners[id];
    },
    
    /**
     *  Activer un écouteur d'événement.
     *
     *  L'activation consiste attacher l'écouteur à l'élément pour écouter
     *  l'émission d'un événement DOM. Elle est effectuée par défaut quand on 
     *  ajoute un écouteur ([[#rnb.dom.addEventListener]]). On doit donc appeler 
     *  cette méthode uniquement si l'on a déconnecté l'écouteur auparavant
     *  ([[#rnb.dom.disableEventListener]]).
     *
     *  @param {String} id Identifiant de l'écouteur.
     *  @param {Boolean} True si l'écouteur a été connecté, false si l'écouteur
     *  n'existe pas ou est déjà connecté.
     */
    enableEventListener: function (id)
    {
        var o = rnb.dom.listeners[id], i, n, t;
        if (o && !o.enabled) {
            n = o.targets.length;
            for (i = 0; i < n; i++) {
                t = o.targets[i];
                t.element.addEventListener(t.event, o.listener, t.capture);
                o.enabled = true;
            }
            return true;
        }
        return false;
    },
    
    /**
     *  Désactiver un écouteur d'événement.
     *
     *  L'écouteur n'est pas supprimé ; il pourra être réactiver avec la
     *  méthode [[#rnb.dom.enableEventListener]].
     *
     *  Pour supprimer complètement un écouteur d'événement, utiliser la méthode
     *  [[#rnb.dom.removeEventListener]];
     *
     *  @param {String} id Identifiant de l'écouteur
     *  @return {Boolean} True si l'écouteur à été déconnecté, false si l'écouteur
     *  n'existe pas ou est déjà déconnecté.
     */
    disableEventListener: function (id)
    {
        var o = rnb.dom.listeners[id], i, n, target;
        if (o && o.enabled) {
            n = o.targets.length;
            for (i = 0; i < n; i++) {
                target = o.targets[i];
                target.element.removeEventListener(
                    target.event, 
                    o.listener, 
                    target.capture
                );
            }
            o.enabled = false;
            return true;
        }
        return false;
    },
    
    /**
     *  Un objet possède-t-il un écouteur sur un événement particulier.
     *  La méthode ne peut analyser que les écouteurs attachés avec la méthode 
     *  [[#rnb.dom.addEventListener]].
     *
     *  @param {HTMLElement} obj L'objet sur lequel on est à l'écoute.
     *  @param {String} event L'événement devant posséder un écouteur.
     *  @return {boolean} 
     */
    hasEventListener: function (obj, event) 
    {
        var id, o, i, n, target;
        for (id in rnb.dom.listeners) {
            o = rnb.dom.listeners[id];
            if (!o.enabled) {
                continue;
            }
            n = o.targets.length;
            for (i = 0; i < n; i++) {
                target = o.targets[i];
                if (target.element === obj && target.event === event) {
                    return true;
                }
            }
        }
        return false;
    }
};
Exemple de gestion des écouteurs d'évenements DOM.

SCHEPERS, Doug ; HÖHRMANN, Björn ; LE HÉGARET, Philippe, et al.. Document Object Model (DOM) Level 3 Events Specification. W3C,

GObject Reference Manual. Gnome Dev Center. Signals