dimanche 21 septembre 2014

Q.lib.js : Création d'une bibliothèque jQuery like, plus efficiente - Part.04 - Events simples et délégués

jQuery et les events

jQuery possède selon la doc de l'API (http://api.jquery.com/category/events/) 58 fonctions permettant de gérer les évènements... alors moi je dit WTF!

Alors oui, c'est bien joli de permettre à ceux qui ont une entorse du poignet de faire des trucs du genre :

$('li').click(function() { alert("I'm clicked!')}

jQuery propose ainsi un certain nombre de raccourcis (shortcuts) tels que click(), change(), blur(), etc... Mais, et cela est bien précisé dans la documentation de l'API, ce sont des raccourcis vers la méthode :

.on(event, handler) ou vers

.trigger(event) si les arguments du raccourcit sont absents...

Code interne de jQuery

jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
 "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
 "change select submit keydown keypress keyup error contextmenu").split(" "),
    function( i, name ) {
 // Handle event binding
 jQuery.fn[ name ] = function( data, fn ) {
  return arguments.length > 0 ?
   this.on( name, null, data, fn ) :
   this.trigger( name );
 };
});

Visiblement les développeurs frontend qui utilisent les shortcuts n'ont aucune notion sur la délégation d'events, du coût CPU que cela entraîne lorsque le nombre d’événements écoutés est important, ou bien encore du risque d'erreur lorsque qu'une liste d'éléments est modifiée dynamiquement.
Or, .on() permet non seulement de faire du traitement d'event élément par élément, mais aussi de faire de la délégation d'event et du custom events.

Autres méthodes obsoletes de jQuery

  • bind()
  • unbind()
  • delegate()
  • undelegate()

Bref! En moins de 20 minutes d'étude de l'API et du code, on peut éliminer une trentaine de méthodes obsolètes! Ce qui se traduit à nouveau par un gain en taille et en vitesse pour Q.lib.js

Q.lib.js et les events

Q.lib.js gère les events classiques avec la méthode .on() et .off() et les custom events avec .pub() et .sub() qui seront le sujet d'un prochain article.

Méthode .on()


  • .on(eventtype, handler(){})
  • Associe un handler à un évènement.
    • jQuery
    • $("#test" ).on("click", function() {
          $(this).toggleClass("active");
      });
    • Q.lib.js
    • $_.id("test").on("click", function() {
          this.toggleClass("active");
      });
  • .on(eventtype, data, handler)
  • Associe un handler à un évènement. L'objet data est accessible dans event.data.
    • jQuery
    • $( "#photo1" ).on("click", {"album":"Hollydays 2014"}, displayLegend);
    • Q.lib.js
    • $_.id( "photo1" ).on("click", {"album":"Hollydays 2014"}, displayLegend);
  • .on(eventtype, selector, handler)
  • Délègue la gestion de l'event à l'élément sélectionné. Le handler n'est appelé que si l'élément qui l'a produit appartient aux éléments renvoyés par selector.
    • jQuery
    • $( "#menu1" ).on("click", ".menu1item", changePage);
    • Q.lib.js
    • $_.id( "menu1" ).on("click", ".menu1item", changePage);
  • .on(eventtype, selector, data, handler)
  • Délègue la gestion de l'event à l'élément sélectionné. Le handler n'est appelé que si l'élément qui l'a produit appartient aux éléments renvoyés par selector. L'objet data est accessible dans event.data.
    • jQuery
    • $( "#menu1" ).on("click", ".menu1item", {"message":"changePage"}, function(e) {
        console.log(e.data);
      );
    • Q.lib.js
    • $_.id("menu1").on("click", ".menu1item", {"message":"changePage"}, function(e) {
        console.log(e.data);
      );

Méthode .off()


  • .off(eventtype, handler)
  • Supprime le handler associé à un évènement. Si le handler est une fonction anonyme on ne peut pas supprimer le listener.
    • jQuery
    • $( "#menu1" ).off("click", changePage);
    • Q.lib.js
    • $_.id( "menu1" ).off("click", changePage);

Code

event : {
 listen : function(node, eventname, fn, F) {
  var fname = $_.fname(fn);
  if (fname !== "") {
   node.attr("data-"+eventname+"-"+fname, $_.guid); 
   $_.eventQ[$_.guid] = $_.renameFunction(fname, F);
   node.addEventListener(eventname, $_.eventQ[$_.guid], false);
   ++$_.guid;
  }
  else {
   node.addEventListener(eventname, F, false);
  }
 },
   
 add : function(node, eventname, fn) {
  function F(e) {
   fn.apply(e.target, arguments);
  };
  F.bind(node);
  $_.event.listen(node, eventname, fn, F);
 },

 addWithData : function(node, eventname, data, fn) {
  function F(e) {
   e.data = data;
   fn.apply(e.target, arguments);
   e.stopPropagation();
  };
  F.bind(node);
  $_.event.listen(node, eventname, fn, F);
 },
   
 delegate : function(parent, eventname, sel, data, fn) {
  function F(e) {
   e.data = data;
   if (sel !== null) {
    var ValidTargets = parent.qa(sel);
    if (ValidTargets.contains(e.target) === false) { 
     return;
    }
    fn.apply(e.target, arguments);
    e.stopPropagation();
   }
  };
  F.bind(parent);
  $_.event.listen(parent, eventname, fn, F);
 }
}

Node.prototype.on = function(arg1, arg2, arg3, arg4) {

 if (arguments.length == 2) {
  $_.event.add(this, arg1, arg2);
  return this;
 }
 
 if (arguments.length == 3) { 
  if ($_.isString(arg2)) { // on(event, selector, function)
   $_.event.delegate(this, arg1, arg2, null, arg3);
   return this;
  }
  else { // on(event, data, function)
   $_.event.addWithData(this, arg1, arg2, arg3);
   return this;
  }
 }
 $_.event.delegate(this, arg1, arg2, arg3, arg4);
 return this; 
};

Node.prototype.off = function(eventname, fn) {
 var fname = $_.fname(fn);
 if (fname !== "") {
  var id = this.attr("data-"+eventname+"-"+fname); 
  this.removeEventListener(eventname, $_.eventQ[id], false);
  $_.eventQ[id] = null;
  this.removeAttribute("data-"+eventname+"-"+fname); 
 }
 return this;
};


Résumé


Q.lib.js sait gérer les events à la manière de jQuery. Certaines formes compactes n'ont pas été codées (passer un objet en premier argument de on() par exemple), je ne sais pas si elles sont vraiment utiles/utilisées, mais dans tous les cas on peut les transposer sous une autre forme puisque les méthodes sont chainables.

Les références vers les fonctions callback nommées sont stockées en interne dans la queue eventQ l'indice du tableau étant stocké dans un attribut data-eventname-fonctioname du node produisant l'event.

Dans le prochain article nous verrons les méthode agissant sur le DOM