Affichage des articles dont le libellé est mobile. Afficher tous les articles
Affichage des articles dont le libellé est mobile. Afficher tous les articles

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

mardi 26 août 2014

Q.lib.js : Création d'une bibliothèque jQuery like, plus efficiente - Part.03 - Attributs, Properties et CSS.

Le sélecteur $_.na


Q.lib.js possède un sélecteur de nodes possédant la propriété name.
  • DOM
  • document.getElementsByName("nameValue");
  • jQuery
  • $("[name='test1']");
  • Q.lib.js
  • $_.na("test1");

Q.lib.js est entre 200 et 300 fois plus rapide que jQuery sur ce selecteur

Code

$_.na = function(n) {
 return document.getElementsByName(n);
};

Méthode .attr()


  • .attr(attributeName)
  • Lit la valeur du premier élément dans l'ensemble des éléments sélectionnés.
    • jQuery
    • var title = $( "em" ).attr( "title" );
    • Q.lib.js
    • var title = $_.tn( "em" ).attr( "title" );
  • .attr(attributeName, value)
  • Définit la valeur d'un attribut de l'ensemble des éléments sélectionnés.
    • jQuery
    • $( "#photo1" ).attr("title", "My first picture" );
    • Q.lib.js
    • $_.id( "photo1" ).attr( "title", "My first picture" );
  • .attr(attributes)
  • définit la valeur de plusieurs attributs de l'ensemble des éléments sélectionnés (passage d'un objet clé/valeur).
    • jQuery
    • $( "img" ).attr({title:"A picture of me", alt:"Album N°3"});
    • Q.lib.js
    • $_.tn( "img" ).attr({title:"A picture of me", alt:"Album N°3"});

Code

// ====================================================
// .attr()
// ----------------------------------------------------
// Get the value of an attribute for the first element
// in the set of matched elements or set one or more 
// attributes for every matched element.
// ----------------------------------------------------
Node.prototype.attr = function(a, v) {
 if (arguments.length == 2) {
  this.setAttribute(a,v);
  return this;
 }
 
 if (typeof a === 'string') {
  return this.getAttribute(a);
 }
 
 if (typeof a === 'object') {
  for (var k in a) {
   this.setAttribute(k,a[k]);
  }
  return this;
 }
};

NodeList.prototype.attr = function() {
 var args = [].slice.apply(arguments);

 for (var i=0, L=this.length; i < L; i++) {
  this[i].attr.apply(this[i], args);
 }
 
 return this;
};

HTMLCollection.prototype.attr = NodeList.prototype.attr;

Méthode .prop()


  • .prop(propertyName)
  • Lit la valeur de la propriété du premier élément dans l'ensemble des éléments sélectionnés.
    • jQuery
    • var title = $( "#cb1" ).prop( "checked" );
    • Q.lib.js
    • var title = $_.id( "cb1" ).prop( "checked" );
  • .prop(propertyName, value)
  • Définit la valeur d'une propriété de l'ensemble des éléments sélectionnés.
    • jQuery
    • $( "[name='cb']" ).prop( "checked", true );
    • Q.lib.js
    • $_.na( "cb" ).prop( "checked", true );
  • .prop(properties)
  • définit la valeur de plusieurs propriétés de l'ensemble des éléments sélectionnés (passage d'un objet clé/valeur).
    • jQuery
    • $( "[name='cb']" ).prop({checked:true, disabled:true});
    • Q.lib.js
    • $_.na( "cb" ).prop({checked:true, disabled:true});

Code

// ====================================================
// .prop()
// ----------------------------------------------------
// Get the value of a property for the first element in
// the set of matched elements or set one or more
// properties for every matched element.
// ----------------------------------------------------
Node.prototype.prop = function(p, v) {
 if (arguments.length == 2) {
  this[p] = v;
  return this;
 }

 if (typeof p === 'string') {
  return this[p];
 }
 
 if (typeof p === 'object') {
  for (var k in p) {
   this[k] = p[k];
  }
  return this;
 }
};

NodeList.prototype.prop = function() {
 var args = [].slice.apply(arguments);

 for (var i=0, L=this.length; i < L; i++) {
  this[i].prop.apply(this[i], args);
 }
 return this;
};

HTMLCollection.prototype.prop = NodeList.prototype.prop;

Méthode .css()


  • .css(propertyName)
  • Lit la valeur réelle de la propriété CSS (getComputedStyle) du premier élément dans l'ensemble des éléments sélectionnés.
    • jQuery
    • var color = $( "#mydiv" ).css( "background-color" );
    • Q.lib.js
    • var color = $_.id( "mydiv" ).css( "background-color" );
  • .css(propertyName, value)
  • Définit la valeur de la propriété CSS de l'ensemble des éléments sélectionnés.
    • jQuery
    • $( "p" ).css( "color", "red" );
    • Q.lib.js
    • $_.tn( "p" ).css( "color", "red");
  • .css(properties)
  • définit la valeur de plusieurs propriétés CSS de l'ensemble des éléments sélectionnés (passage d'un objet clé/valeur).
    • jQuery
    • $( "div" ).css({"color":red, "background-color":"yellow"});
    • Q.lib.js
    • $_.tn( "div" ).css({"color":red, "background-color":"yellow"});

Code

// ====================================================
// .css()
// ----------------------------------------------------
// Get the value of a style property for the first 
// element in the set of matched elements or set one or
// more CSS properties for every matched element.
// ----------------------------------------------------
Node.prototype.css = function(c, v) {
   if (arguments.length == 2) {
      this.style[$_.cC(c)] = v;
      return this;
   }

   if (typeof c === 'string') {
      return window.getComputedStyle(this).getPropertyValue(c);
   }
 
   if (typeof c === 'object') {
      for (var k in c) {
         this.style[$_.cC(k)] = c[k];
      }
      return this;
   }
};

NodeList.prototype.css = function() {
   var args = [].slice.apply(arguments);

   for (var i=0, L=this.length; i < L; i++) {
      this[i].css.apply(this[i], args);
   }
   return this;
};

HTMLCollection.prototype.css = NodeList.prototype.css;

Résumé


Q.lib.js sait travailler avec toutes les propriétés associées à un élément : attributs, classes CSS, propriétés booléenne (checked, disabled, ...) et enfin les propriétés CSS classiques.
Nous avons introduit un nouveau sélecteur ($_.na) très rapide permettant de sélectionner en général des éléments de formulaires par leur nom.

Nous verrons dans le prochain article comment la librairie prend en charge les évènements.

jeudi 14 août 2014

Q.lib.js : Création d'une bibliothèque jQuery like, plus efficiente - Part.01


Pourquoi créer une nouvelle lib?

Comme nous l'avons vu dans les précédents articles, jQuery souffre d'un handicap majeur dans le cadre des applications mobiles/sites mobiles, à savoir une vitesse d’exécution très faible en regard des performances natives de Javascript, associée à sa lourdeur.

Pour autant, jQuery est largement utilisé par les web-designers, il est incontournable dans beaucoup de frameworks. L'idée est donc de concevoir une librairie dont l'utilisation mime au mieux dans sa syntaxe celle de jQuery tout en étant conçue pour être entre 4 et 20 fois plus rapide et beaucoup moins lourde (jQuery 2.1.1: 247Ko et 84Ko minimifiée).

Entrons dans les entrailles de Q.lib.js

A propos des sélecteurs

jQuery utilise uniformément la syntaxe $("selector") pour sélectionner n'importe quel type d'éléments du DOM. Et c'est principalement à cause de cette syntaxe uniforme qu'il se trouve pénalisé en temps d'exécution. Mais par ailleurs cette syntaxe est beaucoup plus compacte par exemple qu'un document.getElementById classique.

L'idée dans Q.lib.js est donc d'encapsuler ces méthodes natives par une fonction.

La syntaxe des sélecteurs de Q.lib.js est de la forme: $_.selectorfunction("string")

Q.lib.js propose 5 fonctions de sélection :
  • $_.id("myid") équivalent à
    • $("#myid")
    • ou
    • document.getElementById("myid")
  • $_.tn("p") équivalent à
    • $("p")
    • ou
    • document.getElementsByTagName("p")
  • $_.cn("myclass") équivalent à
    • $(".myclass")
    • ou
    • document.getElementsByClassName("myclass")
  • $_.qs("p:first-child") équivalent à
    • $("p:first")
    • ou
    • document.querySelector("p:first-child")
  • $_.qa("[data-sample]") équivalent à
    • $("[data-sample]")
    • ou
    • document.querySelectorAll("[data-sample]")


Code

$_ = new Object;

// ====================================================
// SELECTORS WRAPPERS
// ====================================================

$_.id = function(id) {
 return document.getElementById(id);
};

$_.tn = function(tn) {
 return document.getElementsByTagName(tn);
};

$_.cn = function(cn) {
 return document.getElementsByClassName(cn);
};

$_.qs = function(q) {
 return document.querySelector(q);
};

$_.qa = function(q) {
 return document.querySelectorAll(q);
};

Les cas de querySelector et querySelectorAll

querySelector peut ètre utilisé à partir d'un élément (ce qui réduit le champ de recherche). Un prototype .qs a donc été ajouté aux objets de type Node.
Il en va de même pour querySelectorAll. Un prototype .qa a donc été ajouté aux objets de type Node.

Code

Node.prototype.qs = function(q) {
 return this.querySelector(q);
}

Node.prototype.qa = function(q) {
 return document.querySelectorAll(q, this);
}

Résumé

Dans cette première partie nous avons vu la syntaxe des sélecteurs optimisés de Q.lib.js. Les développeurs étant censés savoir quel est la sélection à effectuer, mémoriser ces sélecteurs là ne devraient pas être difficile, pas plus que de les écrire vu leur longueur.

Dans la 2ème partie nous verrons comment Q.lib.js gère les classes CSS.