Créer des sélecteurs jQuery personnalisés

Didier Mouronval

Dans cet article, nous allons voir comment adapter jQuery à nos besoins en créant nos propres sélecteurs.

Commentez

- Titre : Créer des sélecteurs jQuery personnalisés

- Auteur : Didier Mouronval

- Parution : 9 avril 2013

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Didier Mouronval. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Les sélecteurs

- II. Créer un sélecteur simple

- III. Ajouter des paramètres à votre sélecteur

- IV. Aller plus loin : utilisation des sélecteurs personnalisés

- V. Conclusion et remerciements

I - Les sélecteurs

Le nom de jQuery vient de « JavaScript Query », ce qui signifie que l'objectif premier de la bibliothèque est la possibilité de récupérer facilement et efficacement des éléments du DOM pour les manipuler. Pour cela, jQuery propose un nombre important de sélecteurs reprenant la syntaxe CSS. Nous ne détaillerons pas ces sélecteurs mais vous pouvez les retrouver dans l'article Introduction à la bibliothèque JavaScript jQuery.

La syntaxe CSS permet de cibler des éléments ou des groupes d'éléments de façon très précise et satisfait la plupart des besoins. Cependant, certains manques spécifiques au développement JavaScript ont été décelés par les créateurs de jQuery qui ont donc rajouté des sélecteurs particuliers, notamment pour la gestion des éléments de formulaires. Mais là encore, ils n'ont pas pu penser à tous les cas possibles et vous aurez peut-être des développements spécifiques pour lesquels les sélecteurs existants ne seront pas disponibles.

Vous serez donc parfois amenés à avoir besoin de créer des sélecteurs personnalisés et vous pourrez constater que les développeurs de jQuery ont tout prévu pour vous faciliter la tâche.

II - Créer un sélecteur simple

Créer son propre sélecteur est relativement simple comme vous allez le voir, mais cela implique d'utiliser des fonctions internes de jQuery qui ne sont donc pas renseignées dans la documentation officielle.

Il va falloir rajouter une valeur à l'objet interne expr et plus précisément à sa propriété ':'.

Faites bien attention à ce que vous faites. Dans la mesure où vous allez surcharger un objet interne de la bibliothèque, vous devez au préalable vous assurer que vous n'écraserez pas des données existantes ce qui risquerait d'empêcher le bon fonctionnement de jQuery.

La première chose est donc de créer une nouvelle propriété dont le nom sera celui du sélecteur et de lui affecter une fonction :

$.expr[':'].monSelecteur = function(){
// ...
};

La valeur « : » est tout à fait correcte comme nom de propriété d'un objet, mais elle impose l'utilisation de la notation à crochets car la notation pointée (expr.:) provoquerait une erreur.

Cette fonction va itérer parmi tous les éléments possibles et devra retourner pour chacun d'eux true si l'élément doit être conservé, false sinon.

Elle prend quatre paramètres automatiques :

- elem : l'élément DOM en cours de traitement ;

- rang : le compteur pour l'itération en cours ;

- donnees : un ensemble d'informations que nous détaillerons plus tard ;

- collection : la collection que nous sommes en train de parcourir.

Pour l'instant, seuls les deux premiers paramètres peuvent nous être utiles. Voici comment les utiliser avec deux exemples simples.

// Exemple 1 : récupérer tous les éléments positionnés (propriété CSS position différente de static)
$.expr[':'].positionned = function(elem){
return $(elem).css('position') != 'static';
};

// Exemple 2 : récupérer les éléments de rang pair
$.expr[':'].pair = function(elem, index){
return index%2;
};

Maintenant que vous avez créé votre sélecteur, il ne vous reste plus qu'à l'utiliser dans vos scripts :

$(':positionned').css('box-shadow', '0 0 10px 2px gold');
$('#footer :pair').css('box-shadow', '0 0 10px 2px red');

Attention aux performances lorsque vous combinez des sélecteurs de type CSS avec des sélecteurs personnalisés comme dans le second exemple, nous y reviendrons plus tard.

III - Ajouter des paramètres à votre sélecteur

Vous avez probablement déjà rencontré des sélecteurs jQuery prenant des paramètres (par exemple :eq() ou :has()). Là encore, il vous est tout à fait possible de reproduire cela avec vos paramètres personnalisés.

Pour gérer les paramètres passés au sélecteur, nous allons utiliser le paramètre donnees de la fonction de rappel.
Ce paramètre correspond à un tableau contenant quatre données :

- le sélecteur complet ;

- le nom du sélecteur ;

- le type de quotes utilisé ;

- les paramètres du sélecteur.

L'exemple suivant va vous montrer à quoi correspondent concrètement ces éléments.

$.expr[':'].testParams = function(elem, index, donnees){
alert(donnes.join(' / '));
};

Que nous allons appliquer à l'élément HTML suivant :

<div id="testSelecteur" style="background-color: silver; height: 2em; margin: 5px 0;">Élément créé pour tester les paramètres du sélecteur.</div>

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Nous avons rajouté join(' / ') dans le message d'alerte afin de ne pas confondre la chaine "parametre1, paramètre2" du second exemple avec deux valeurs distinctes du tableau.

Le sélecteur peut être appelé de trois façons différentes : $(':testParams'), $(':testParams()') et $(':testParams(paramètres)'). Les valeurs respectives du dernier paramètre seront : undefined, '' (chaine vide) et paramètres. Attention à bien prendre en compte que les deux premières possibilités sont supposées être équivalentes.

Nous allons donc pouvoir utiliser l'indice 3 de ce tableau pour gérer nos paramètres.

Nous allons créer un sélecteur qui récupérera les éléments possédant un attribut HTML5 data-*.

Pour rappel, HTML5 a introduit ce type d'attribut afin de pouvoir y stocker des données personnalisées manipulables en JavaScript via l'objet dataset.
Les développeurs de jQuery ont rendu l'objet data() compatible avec cette API tout en facilitant son utilisation et nous allons nous en servir pour notre sélecteur. Si aucun paramètre n'est passé, on recherchera tous les éléments ayant un attribut de type data-*, sinon, nous ne rechercherons que ceux ayant au moins un attribut correspondant à ceux passés en paramètre. Notre sélecteur se nommera hasData.

$.expr[':'].hasData = function(elem, index, noms){
// On commence par stocker l'objet data de l'élément en cours dans une variable
$dataElem = $(elem).data();
// Si l'objet est vide, on n'accepte pas l'élément
if($.isEmptyObject($dataElem)){
return false;
}
// Sinon, si aucun paramètre n'est présent, on peut accepter l'élément
if(!noms[3]){
return true;
}
// Sinon, il faut séparer les paramètres et vérifier la présence pour chacun d'eux
var tabNoms = noms[3].replace(/\s/g, '').split(',');
var retour = false;
$.each(tabNoms, function(i, nom){
if($dataElem[nom] !== undefined){
// Si l'attribut existe bien, on peut accepter l'élément
retour = true;
// Inutile de continuer la boucle
return false;
}
});
return retour;
};

Et voilà, nous avons désormais un sélecteur personnalisé acceptant des paramètres.

IV - Aller plus loin : utilisation des sélecteurs personnalisés

Il existe quelques précautions à prendre pour optimiser le code utilisant des sélecteurs personnalisés (ou des sélecteurs non standard de jQuery).

En effet, pour améliorer les performances liées à la sélection d'éléments, jQuery utilise en interne des méthodes du DOM pour récupérer les éléments, notamment, des méthodes performantes comme getElementById() et querySelectorAll(). Or si vous utilisez des sélecteurs combinés contenant une syntaxe non compatible avec ces méthodes du DOM, jQuery va être obligé de parcourir tous les éléments concernés par la sélection pour déterminer lesquels garder.

Cela ne signifie pas qu'il ne faut pas utiliser les sélecteurs personnalisés, mais juste qu'il faut essayer autant que possible de séparer leur utilisation de celles des sélecteurs compatibles CSS.

À titre d'exemple, si vous souhaitez récupérer les éléments ayant un attribut data-* enfants d'un élément possédant un identifiant donné, évitez la syntaxe

$('#element :hasData');

mais préférez séparer la recherche de l'élément possédant l'identifiant de celle des éléments possédant l'attribut :

$('#element').find(':hasData')

Plus la page contiendra d'éléments, plus le gain de performances sera important.

Cette règle s'applique bien entendu à vos sélecteurs personnalisés mais aussi aux sélecteurs natifs de jQuery ne correspondant pas aux standards CSS (globalement, tous ceux commençant par ':').

Notez que cette règle s'applique aussi dans d'autres cas. Si votre sélecteur doit d'abord rechercher un identifiant (quelle que soit la suite de la recherche), il est recommandé de séparer la récupération de cet élément du reste du sélecteur.

Dans cette optique, il est important de se souvenir qu'un second paramètre peut être passé à la fonction $() qui correspond au contexte auquel se réfère la recherche. Ainsi, des sélecteurs tels que $('#element :hasData') ou $('#element span') seraient avantageusement remplacés par $(':hasData', '#element') ou $('span', '#element').

V - Conclusion et remerciements

La création de sélecteurs personnalisés est particulièrement simple si l'on connaît les mécanismes internes de jQuery. Néanmoins, retenez bien ces deux points importants lorsque vous créez vos nouveaux sélecteurs :

- attention à ne pas écraser des sélecteurs existants, pour cela, vous pouvez commencer votre code par une condition telle que if(!('selecteur' in $.expr[':'])){...} ;

- souvenez-vous que l'utilisation d'un sélecteur personnalisé implique de parcourir tous les éléments pour déterminer lesquels correspondent, ciblez donc convenablement votre recherche avant d'utiliser vos propres sélecteurs.

Nous tenons à remercier ClaudeLELOUP et Max pour leur relecture attentive de cet article.