jQuery.info
Découvrir et utiliser jQuery, la librairie javascript du XXIIème siècle

Accueil du site > Trucs et astuces > Accroître les capacités des sélecteurs de jQuery

Accroître les capacités des sélecteurs de jQuery

jeudi 18 décembre 2008, par James Padolsey, Olivier G.

Traduction de l’article de James Padolsey Extending jQuery’s selector capabilities


Je suis sûr que vous savez tous qu’il est possible de créer des plugins et d’étendre divers aspects de la bibliothèque javascript jQuery, mais savez-vous que vous pouvez aussi étendre les capacités de son moteur de sélection ?

Eh bien, vous pouvez ! Par exemple, vous pourriez vouloir ajouter un nouveau sélecteur :inline, qui retournerait les éléments qui sont affichés inline. Regardez :

$.extend($.expr[':'],{
   inline: function(a) {
       return $(a).css('display') === 'inline';
   }
});

En utilisant le code ci-dessus, vous pouvez utiliser ce sélecteur lorsque vous voulez sélectionner des éléments qui sont affichés en ligne (inline) :

$(':inline'); // Sélectionne tous les éléments inline
$('a:inline'); // Sélectionne tous les liens inline

C’était un exemple assez simple, mais je suis sûr que vous voyez maintenant toutes les possibilités que ça offre ! Et créer un sélecteur jQuery sur mesure ne pouvait pas vraiment être plus simple !

Sélecteur d’images chargées

Vous pourriez vouloir ajouter un sélecteur loaded qui fonctionnerait sur les images et qui retournerait les images qui sont chargées :

// Marquer les images pendant l'upload :
$('img').load(function(){
   $(this).data('loaded',true);
});

// Expr() étendu
$.extend($.expr[':'],{
   loaded: function(a) {
       return $(a).data('loaded');
   }
});

// Exemple d'utilisation :
alert( 'Images chargées jusqu'à présent : ' + $('img:loaded').size() );

Interroger les data d’un élément

La fonction data de jQuery nous permet d’ajouter des données spéciales aux éléments sans avoir à corrompre des variables globales ou à ajouter des attributs invalides. Une des choses qui manquent à jQuery est de pouvoir interroger facilement des éléments en fonction de leurs données (data). Par exemple, quelqu’un pourrait décider de marquer tous les éléments ajoutés dynamiquement (avec jQuery) avec dom :

// Nouvel élément :
$('<img/>')
   .data('dom', true) // Marquage
   .appendTo('body'); // Ajout au DOM

Actuellement il n’y a pas de manière simple de sélectionner tous les éléments qui ont été marqués, mais que se passe-t-il si nous ajoutons un nouveau sélecteur :data qui pourrait interroger ce genre d’informations ?

Voici comment nous pourrions le faire :

// Encapsuler dans une fonction anonyme auto-appelante :
(function($){

   // Étendre le sélecteur natif ':' de jQuery
   $.extend($.expr[':'],{

       // Nouvelle méthode, 'data'
       data: function(a,i,m) {

           var e = $(a).get(0), keyVal;

           // m[3] fait référence à la valeur entre parenthèses
           // (si elle existe), i.e. :data(___)
           if(!m[3]) {

               // Boucler au travers des propriétés de l'élément, et trouver
               // toutes les références à jQuery :
               for (var x in e) { if((/jQuery\d+/).test(x)) { return true; } }

           } else {

               // Séparer en un tableau (nom, valeur) :
               keyVal = m[3].split('=');

               // Si une valeur est spécifiée :
               if (keyVal[1]) {

                   // Tester s'il s'agit d'une syntaxe d'expression
                   // régulière et l'utiliser :
                   if((/^\/.+\/([mig]+)?$/).test(keyVal[1])) {
                       return
                        (new RegExp(
                            keyVal[1].substr(1,keyVal[1].lastIndexOf('/')-1),
                            keyVal[1].substr(keyVal[1].lastIndexOf('/')+1))
                         ).test($(a).data(keyVal[0]));
                   } else {
                       // Tester pour une clé par rapport à une valeur
                       return $(a).data(keyVal[0]) == keyVal[1];
                   }

               } else {

                   // Tester si l'élément a une propriété data :
                   if($(a).data(keyVal[0])) {
                       return true;
                   } else {
                       // Si ça ne retire pas les données (data)
                       // (Cela sert à prendre en compte ce qui
                       // semble être un bug de jQuery) :
                       $(a).removeData(keyVal[0]);
                       return false;
                   }

               }
           }

           // Conformité stricte :
           return false;

       }

   });
})(jQuery);

Utilisation

Désormais, sélectionner les éléments qui ont cette marque ’dom’ est vraiment très facile :

$(':data(dom)'); // Tous les éléments avec la marque 'dom'
$('div:data(dom)'); // Tous les éléments div avec la marque 'dom'
$(':not(:data(dom))'); // Tous les éléments SANS la marque 'dom'

L’extension ’:data’ vous permet aussi de faire des sélections par comparaison, par exemple :

$(':data(ABC=123)'); // Tous les éléments ayant dans data une clé 'ABC' égale à 123

Elle vous permet aussi d’utiliser une expression régulière :

// Supposons que nous avons des <html><span lang="en">data</span></html> qui varient subtilement au travers d'un ensemble d'éléments :
$('div').each(function(i){
   $(this).data('divInfo','index:' + i);
   // Cela va entraîner les valeurs a être 'index:0', 'index:1', 'index:2', etc.
});

// Nous pouvons sélectionner toutes ces DIV comme ceci :
$('div:data(divInfo=/index:\\d+/)');

// Note : il est nécessaire d'utiliser des notations non littérales lorsque vous écrivez ces expressions régulières, ce qui fait que si vous voulez matcher un vrai caractère <span lang="en">backslash</span> vous devez utiliser '\\\\'. De la même manière, si vous voulez tester la présence de chiffres seulement, vous devez utiliser \\d à la place de \d.

De plus, vous pouvez sélectionner des éléments en fonction de s’ils ont n’importe quelle data appliquée :

$(':data');       // Tous les éléments avec des data
$(':not(:data)'); // Tous les éléments sans data

Quelques autres exemples :

:red

// Vérifier si la couleur de l'élément est rouge (red)
$.extend($.expr[':'],{
 red: function(a) {
     return $(a).css('color') === 'red';
 }
});

// Utilisation :
$('p:red'); // Sélectionne tous les paragraphes rouges.

:childOfDiv

// Vérifier si l'élément est l'enfant d'un div :
$.extend($.expr[':'],{
 childOfDiv: function(a) {
     return $(a).parents('div').size();
 }
});

// Oui, je sais, c'est exactement la même chose que $('div p'). Ce n'est qu'une démonstration ! ;-)

// Utilisation :
$('p:childOfDiv'); // Sélectionne tous les paragraphes qui sont enfants d'un div

:width()

// Vérifie la largeur des éléments :
$.extend($.expr[':'],{
 width: function(a,i,m) {
     if(!m[3]||!(/^(<|>)\d+$/).test(m[3])) {return false;}
     return m[3].substr(0,1) === '>' ?
              $(a).width() > m[3].substr(1) : $(a).width() < m[3].substr(1);
 }
});

// Utilisation :
$('div:width(>200)'); // Sélectionne tous les divs qui ont une largeur supérieure à 200px

// Utilisation alternative :
$('div:width(>200):width(<300)'); // Sélectionne tous les divs qui sont plus larges que 200px mais moins que 300px

:biggerThan()

// Vérifie si la surface d'élément est plus grande que celle d'un autre :
$.extend($.expr[':'],{
 biggerThan: function(a,i,m) {
     if(!m[3]) {return false;}
     return $(a).width() * $(a).height() > $(m[3]).width() * $(m[3]).height();
 }
});

// Utilisation :
$('div:biggerThan(div#banner))'); // Sélectionne tous les divs qui ont une surface plus grande que celle de #banner

// Utilisation alternative (quelque chose d'un petit peu plus compliqué) :
// (utiliser le sélecteur sur mesure :width())

// Sélectionne tous les divs qui ont une largeur inférieure à 600px mais une surface globale plus grande que celle du premier paragraphe qui a une surface plus grande que img#header :
$('div:width(<600):biggerThan(p:biggerThan(img#header):eq(0))');

Comme je l’ai dit, les possibilités sont sans fin…

Mise à jour

J’ai fabriqué deux exemples supplémentaires, jetez-y un œil :

:external

// Vérifie si les liens sont externes
// (Ne fonctionne que pour les éléments qui ont un href) :
$.extend($.expr[':'],{
 external: function(a,i,m) {
     if(!a.href) {return false;}
     return a.hostname && a.hostname !== window.location.hostname;
 }
});

// Utilisation :
$('a:external'); // Sélectionne toutes les ancres qui pointent vers une page ou un site externe.

:inView

// Vérifier si l'élément est actuellement dans la zone de vue (viewport) :
$.extend($.expr[':'],{
 inView: function(a) {
     var st = (document.documentElement.scrollTop || document.body.scrollTop),
         ot = $(a).offset().top,
         wh = (window.innerHeight && window.innerHeight < $(window).height()) ? window.innerHeight : $(window).height();
     return ot > st && ($(a).height() + ot) < (st + wh);
 }
});

// Utilisation :
$('div:inView'); // Sélectionne tous les éléments div qui sont dans la zone de vue actuelle

// Utilisation alternative :
if ( $('div#footer').is(':inView') ) {
 // Faire des choses...
}

Seconde mise à jour

J’ai créé un plugin qui rend l’ajout de nouveaux sélecteurs ’ :’ un peu plus facile. Néanmoins, ça n’est pas vraiment un ’plugin’, c’est juste une fonction qui réside dans l’espace de nom de jQuery :

(function($){
   $.newSelector = function() {
       if(!arguments) { return; }
       $.extend($.expr[':'],typeof(arguments[0])==='object' ? arguments[0]
         : (function(){
             var newOb = {}; no[arguments[0]] = arguments[1];
             return newOb;
         })()
       );
   }
})(jQuery);

Créer un nouveau sélecteur avec le plugin newSelector

// Première méthode :
$.newSelector('big', function(elem){
   return $(elem).width() + $(elem).height() > 1000;
});
// Seconde méthode :
$.newSelector({
   red: function(elem){
       return $(elem).css('color') === 'red';
   },
   yellow: function(elem){
       return $(elem).css('color') === 'yellow';
   },
   green: function(elem){
       return $(elem).css('color') === 'green';
   },
});

Répondre à cet article

1 Message


Derniers commentaires

Nouveautés sur le Web