
🔍 JQuery : comment trouver un élément avec un texte spécifique
Un développeur crée une fiche produit dans WooCommerce. Tout se déroule comme prévu jusqu’à ce qu’une demande arrive depuis le panneau d’administration: «supprimer le badge Featured et la virgule qui le suit, mais uniquement pour les produits appartenant à la catégorie holder». Un traitement manuel n’est pas envisageable, il y a des centaines de produits. Il nous faut un script qui trouve le bloc contenant le texte «featured» de manière autonome, remonte jusqu’au parent, récupère le span voisin et supprime l’ensemble de la construction.
C’est là que jQuery entre en scène avec ses chaînes: .each() pour l’itération, .find() pour la recherche, :contains() pour le filtrage par texte, .closest() pour remonter au parent, .next() pour passer au voisin et .remove() pour le nettoyage. Cinq méthodes, et la tâche est résolue en quatre lignes. Je détaille ci-dessous chaque étape: du sélecteur jusqu’au nettoyage final, en expliquant la logique et les pièges.
💡 Aperçu rapide:
- D’abord, nous trouvons le bloc cible contenant le texte via le sélecteur contains et nous remontons à son parent via closest
- Ensuite, nous passons à l’élément voisin via next et nous supprimons précisément les deux blocs via remove
- Enfin, nous déboguons le script et analysons les erreurs typiques liées à la perte de contexte et à un next trop gourmand
La tâche: ce que nous cherchons et ce que nous supprimons
La structure HTML d’origine ressemble approximativement à ceci:
1 <span class="tg-cats-holder"> 2 <a class="product_visibility" href="..."> 3 <span class="tg-item-term">featured</span> 4 </a> 5 <span>, </span> 6 <a class="product_visibility" href="..."> 7 <span class="tg-item-term">sale</span> 8 </a> 9 <span>, </span> 10 </span>
L’objectif principal est de trouver le <span class="tg-item-term"> qui contient le texte featured, de remonter au parent <a class="product_visibility">, puis de passer au <span> suivant (celui avec la virgule) et de le supprimer. Ensuite, supprimer le <a class="product_visibility"> avec le badge lui-même. Les autres étiquettes (sale, etc.) restent intactes.
Script prêt à l’emploi
1 jQuery(document).ready(function($) { 2 $('span.tg-cats-holder').each(function() { 3 $(this) 4 .find('span.tg-item-term:contains("featured")') 5 .closest('a.product_visibility') 6 .next('span:contains(",")') 7 .remove(); 8 9 $(this) 10 .find('span.tg-item-term:contains("featured")') 11 .closest('a.product_visibility') 12 .remove(); 13 }); 14 });
Le code est volontairement scindé en deux chaînes plutôt que d’être fusionné en une seule. La raison est simple: après un .remove() sur un élément, la chaîne se casse, il n’y a plus d’endroit où aller. Nous nettoyons donc d’abord le span voisin avec la virgule (première chaîne), puis nous supprimons le bloc lui-même (seconde chaîne).
Décomposition étape par étape
Étape 1: entrer dans chaque holder
1 $('span.tg-cats-holder').each(function() {
La méthode .each() parcourt chaque élément de la collection, dans notre cas tous les <span class="tg-cats-holder"> de la page. S’il y a plusieurs holders (différentes catégories de produits), le script fonctionnera dans chacun d’eux de manière indépendante. À l’intérieur de la fonction de rappel, this pointe vers le holder courant, toutes les recherches ultérieures partent de lui.
Étape 2: rechercher le bloc avec le texte via:contains()
1 .find('span.tg-item-term:contains("featured")')
Le sélecteur :contains("featured") trouve un élément à l’intérieur duquel (nœuds enfants inclus) se trouve la sous-chaîne «featured». Trois nuances importantes:
Sensibilité à la casse. Les chaînes «Featured», «FEATURED» et «featured» sont différentes. Si le badge est rédigé différemment dans le panneau d’administration, ajoutez une vérification via
.filter()avec.toLowerCase(), un exemple se trouve dans la section FAQ.Recherche dans tous les descendants.
:contains()vérifie le contenu textuel de l’élément lui-même et de tous ses enfants. S’il y a un autre<strong>à l’intérieur de<span class="tg-item-term">, le texte qu’il contient est également pris en compte.Ce n’est pas un sélecteur de performance.
:contains()n’est pas accéléré par lequerySelectorAllnatif, jQuery parcourt tous les candidats et lit.textContent. Pour les pages comportant des centaines d’éléments, il est préférable de restreindre la sélection avec une classe ou un attribut supplémentaire.
La méthode .find() limite la recherche aux descendants du holder courant, et non à la page entière, ce qui apporte à la fois un gain de vitesse et une protection contre les faux positifs.
Étape 3: remonter au parent via.closest()
1 .closest('a.product_visibility')
.closest() remonte vers le haut dans l’arborescence depuis l’élément trouvé et retourne l’ancêtre le plus proche correspondant au sélecteur. Contrairement à .parent(), elle ne se limite pas à un seul niveau, elle remontera jusqu’à <html> jusqu’à trouver une correspondance ou atteindre la racine.
Si nous avions utilisé .parent() au lieu de .closest(), le script se casserait à la moindre imbrication: <span class="tg-item-term"> → <strong> → <a class="product_visibility">, .parent() retournerait <strong>, pas le lien recherché. .closest() prémunit contre ce genre de surprises.
Étape 4: sauter vers le voisin via.next()
1 .next('span:contains(",")')
.next() prend l’élément frère immédiatement suivant au sein du même parent. Le filtre span:contains(",") garantit que nous prenons exactement le span avec la virgule, et non n’importe quel élément suivant. Sans le filtre, .next() attraperait le premier nœud frère disponible, par exemple un autre <a class="product_visibility"> avec l’étiquette suivante.
La différence entre .next() et .nextAll(): la première prend exactement un élément, la seconde prend tous les frères suivants. Ici, nous avons besoin d’une frappe précise, donc .next().
Étape 5: supprimer
1 .remove();
.remove() retire complètement l’élément du DOM, ainsi que les gestionnaires d’événements et les données jQuery. Si vous avez seulement besoin de masquer (avec la perspective de le faire réapparaître), utilisez .hide() ou .detach(). Mais pour la tâche «supprimer définitivement le badge», seul .remove() convient.
L’ordre est important: nous supprimons d’abord le span voisin (première chaîne), puis seulement le bloc lui-même (seconde). Si nous supprimons le bloc en premier, le span voisin perd son contexte, .next() s’exécutera depuis un élément différent ou retournera une collection vide.
Exemple avancé: recherche insensible à la casse
Dans un projet réel, le texte de l’étiquette peut apparaître avec une casse différente: featured, Featured, FEATURED. Le :contains() standard ne gérera pas cela. La solution est un sélecteur personnalisé basé sur .filter():
1 $('span.tg-cats-holder').each(function() { 2 $(this).find('span.tg-item-term').filter(function() { 3 return $(this).text().toLowerCase().indexOf('featured') !== -1; 4 }).closest('a.product_visibility').each(function() { 5 $(this).next('span:contains(",")').remove(); 6 $(this).remove(); 7 }); 8 });
Ici, .filter() avec une fonction de rappel vérifie le texte en minuscules de chaque candidat. Il y a un peu plus de lignes, mais le script ne dépend plus de la manière exacte dont le texte a été saisi dans le panneau d’administration.
Alternative en JavaScript pur
Si jQuery n’est pas du tout utilisé sur le projet, la même logique s’écrit en vanilla JS:
1 document.querySelectorAll('span.tg-cats-holder').forEach(function(holder) { 2 holder.querySelectorAll('span.tg-item-term').forEach(function(term) { 3 if (term.textContent.includes('featured')) { 4 const link = term.closest('a.product_visibility'); 5 const nextSpan = link.nextElementSibling; 6 if (nextSpan && nextSpan.matches('span') && nextSpan.textContent.includes(',')) { 7 nextSpan.remove(); 8 } 9 link.remove(); 10 } 11 }); 12 });
Le même algorithme: itération des holders → recherche par texte via .textContent.includes() → remontée au parent via le .closest() natif → vérification et suppression du voisin → suppression du bloc. L’implémentation native est plus longue mais n’entraîne pas de dépendance à jQuery.
Courte vidéo sur le sujet, décorticage de :contains() et des chaînes de navigation dans jQuery:
⁉️🤔 Foire aux questions
Pourquoi :contains() ne trouve-t-il pas un texte que je vois sur la page?
Le problème vient probablement de la casse ou des espaces.
:contains("Featured")ne trouvera pas «featured», c’est un sélecteur sensible à la casse. De plus, le navigateur peut normaliser les espaces à l’intérieur du HTML d’une manière que vous n’attendez pas. Vérifiez le contenu exact viaconsole.log($('selector').text())et copiez la chaîne à l’identique dans:contains().
Peut-on se passer de .closest() et utiliser une chaîne fixe .parent().parent()?
Techniquement, oui. En pratique, c’est déconseillé. Une chaîne rigide
.parent().parent()se cassera au moindre changement d’imbrication: ajout d’un wrapper, passage à un thème différent, le script s’arrête..closest('a.product_visibility')est plus flexible: elle cherche l’élément correspondant le plus proche à n’importe quel niveau supérieur. Écrite une fois, elle fonctionne quelle que soit la structure.
Le script supprime le mauvais span. Comment déboguer?
Première étape, insérez
console.log()avant chaque.remove()et vérifiez ce qui arrive exactement dans la sélection. Deuxièmement, affinez le sélecteur pour.next(). Sans filtre,.next()prend n’importe quel élément suivant au même niveau, même s’il s’agit d’un autre<a>ou<div>. Ajoutezspan:contains(",")ou une classe plus spécifique, et vérifiez que le voisin est bien le bon.
Faut-il envelopper le script dans jQuery(document).ready()?
Oui, si le script se trouve dans le
<head>ou se connecte avant le rendu du DOM. La constructionjQuery(document).ready(function($){ ... })garantit que le code s’exécute seulement après le chargement complet de l’arborescence DOM. De plus,$est passé explicitement dans la fonction de rappel, ce qui prémunit contre les conflits avec d’autres bibliothèques qui pourraient également revendiquer$. Si le script est dans le pied de page après tous les éléments,.ready()n’est formellement pas requis, mais la discipline de «toujours envelopper» évite des bugs difficiles à traquer lors d’un refactoring.
Qu’est-ce qui est le plus rapide: jQuery ou vanilla JS pour une telle tâche?
Le JavaScript natif l’emporte au démarrage à froid: pas besoin de charger, parser et compiler jQuery, ce qui économise des dizaines de kilo-octets de trafic et plusieurs millisecondes de parsing. Sur les opérations DOM, l’écart est plus modeste, on parle de fractions de milliseconde par appel. Pour un site WordPress typique où jQuery est déjà connecté par le thème ou les plugins, la différence est imperceptible, votre script de quatre lignes ne deviendra pas un goulot d’étranglement. Si jQuery est déjà dans le projet, écrivez en jQuery, le code est plus compact et lisible. Sinon, utilisez le vanilla JS, n’introduisez pas une bibliothèque pour un seul gestionnaire.
Est-ce que ça vaut le coup d’utiliser jQuery pour la recherche textuelle en 2026?
jQuery n’a pas disparu. Selon les statistiques W3Techs de juin 2026, la bibliothèque est installée sur 87,3% des sites utilisant une bibliothèque JavaScript connue (68,1% de tous les sites), principalement grâce à WordPress, où jQuery fait partie du cœur et est automatiquement connecté par les thèmes. Pour un développeur WordPress, jQuery est un outil natif déjà chargé et qui ne génère pas de requêtes supplémentaires.
Les alternatives directes comme Alpine.js ou HTMX résolvent des problèmes différents (réactivité, mises à jour partielles) et sont excessives pour un script DOM ponctuel. Le querySelectorAll + closest() + remove() natif est une option fonctionnelle sans dépendance, mais les chaînes d’appel deviennent plus longues. Le choix se résume à une règle simple: jQuery est déjà sur le projet, prenez jQuery; projet sans jQuery, prenez vanilla JS.
L’essentiel est que vous connaissez désormais la mécanique exacte de la combinaison :contains() + .closest() + .next() + .remove(), et c’est le projet spécifique qui décide dans quel dialecte l’écrire.



