3. jQuery et le DOM▲
3-1. Rappels sur le DOM▲
Nous avons déjà abordé la notion de DOM (Document Object Model) au chapitre précédent. Nous avions alors vu qu'il s'agit de la représentation du document sous forme d'objets JavaScript, nous allons compléter cette définition, car il est important d'avoir une vision claire de ce qu'est le DOM pour pouvoir bien utiliser JavaScript et jQuery.
Comme nous l'avons évoqué, le DOM est construit parallèlement à l'interprétation du code HTML de la page. À chaque balise rencontrée, JavaScript associe un objet de type HTMLElement, dont les propriétés correspondent aux attributs HTML autorisés pour cette balise. L'association est faite de façon à ce que chaque modification d'une propriété ait un impact sur la valeur de l'attribut associé et vice versa. Ceci a pour effet une confusion entre propriété JavaScript d'un objet du DOM et structure HTML. Pour bien comprendre la différence, nous allons utiliser l'exemple suivant et suivre son comportement dans Firebug, l'outil indispensable de développement web pour Firefox :
<!
DOCTYPE HTML
>
<html xmlns=
"http://www.w3.org/
1999
/xhtml"
>
<head>
<meta charset=
"iso-
8859
-
1
"
>
<title>
DOM et HTML</title>
<script
type="text/javascript">
function modifDOM
(
){
document
.getElementById
(
'test'
).
value =
'Ajouté par le DOM'
;
alert
(
document
.getElementById
(
'test'
).
getAttribute
(
'value'
));
}
function modifHTML
(
){
document
.getElementById
(
'test'
).
setAttribute
(
'value'
,
'Modifié par HTML'
);
alert
(
document
.getElementById
(
'test'
).
value);
}
</script>
</head>
<body>
<div><input id=
"test"
/></div>
<div><button onclick=
"modifDOM();"
>
Modifier le DOM</button></div>
<div><button onclick=
"modifHTML();"
>
Modifier le HTML</button></div>
</body>
</html>
Dans cet exemple, nous avons juste créé un champ texte (balise input). Vous noterez que les attributs type et value n'ont pas été renseignés, ce sont donc les valeurs par défaut qui seront affectées (type=« text » et value=""). Nous ajoutons aussi deux boutons, l'un pour modifier la valeur d'une propriété et afficher la valeur de l'attribut associé (fonction modifDOM() , l'autre pour le contraire (modifHTML() .
Les résultats sont illustrés à la figure 2-1.
Nous pouvons voir dans l'onglet HTML de Firebug que le champ est bien de type texte et qu'il ne contient pas de valeur par défaut.
Voyons maintenant ce qu'il se passe en cliquant sur Modifier le DOM (figure 2-2).
On voit bien que le champ a été rempli, en revanche, l'attribut value n'existe toujours pas dans le code HTML.
Si maintenant nous rechargeons la page et que nous cliquons sur Modifier le HTML, on obtient le résultat de la figure 2-3.
Là encore, le champ a été correctement rempli, mais la balise input a cette fois été modifiée avec l'apparition de l'attribut value, dont nous constatons qu'il a eu pour effet de modifier aussi la propriété JavaScript associée.
Si maintenant nous ajoutons la ligne de code :
script>alert(document.getElementById('test').type + ' - ' +
document.getElementById('test').getAttribute('type'));</script>
juste avant la fermeture de la balise body, on obtient l'affichage de la figure 2-4.
On se rend bien compte qu'à la création de la page, bien que les attributs n'aient pas été définis dans le code HTML (ici, l'attribut type), les propriétés associées dans l'objet JavaScript sont malgré tout créées avec comme valeur la valeur par défaut de l'attribut.
Bien sûr, les exemples précédents montrent bien que la confusion entre la manipulation du DOM et celle du HTML a finalement peu d'impact puisque les répercussions sur l'affichage sont identiques, la différenciation entre attribut et propriété peut donc sembler essentiellement théorique. Néanmoins, il existe différents cas pour lesquels elle est importante. Vous pouvez d'abord avoir besoin de récupérer le code HTML généré par les scripts, dans ce cas, il sera important de manipuler les attributs. De même, vous pourrez avoir envie ou besoin de stocker des informations dans des propriétés ou des attributs qui vous sont propres (attention toutefois que la création d'attributs personnalisés, bien que possible, empêchera d'obtenir la validation W3C de la page). Or dans ces cas, il n'existe plus de lien entre le code HTML et le DOM, il est donc alors nécessaire de savoir sur quel type de valeur on travaille. Bien entendu, jQuery permet de manipuler distinctement chaque type de valeurs.
Enfin, il est aussi important de préciser qu'il existe certains attributs particuliers communs à quasiment toutes les balises HTML.
Le premier de ceux-ci est l'attribut style. Une propriété style existe donc aussi au niveau de chaque objet JavaScript. Cette propriété est aussi un objet dont les propriétés correspondent aux différents styles possibles pour cette balise. Il est donc possible de définir n'importe quel style avec JavaScript, cependant, vous ne pourrez récupérer avec cette propriété que les valeurs des propriétés de style définies dans l'attribut et non celles définies dans les feuilles de style.
Une autre famille d'attributs HTML est celle des attributs d'événements. Tout comme pour le style, ces attributs sont particuliers dans la mesure où bien qu'étant intégrés dans du code HTML, ils attendent comme valeur du code JavaScript (ce qui, au passage, rend inepte de les faire précéder par un javascript: inapproprié comme on le voit bien trop souvent). Ces attributs vont également rendre disponibles les propriétés équivalentes pour les objets JavaScript. Il faut toutefois noter qu'il s'agit bien là de propriétés, qui n'acceptent donc qu'une valeur unique, chaque nouvelle définition écrasant la précédente, contrairement aux méthodes spécifiques à la gestion des événements qui peuvent gérer des « piles » d'événements.
Au-delà de ces attributs spécifiques, il est aussi possible de gérer grâce au DOM les styles définis dans les feuilles de styles ainsi que les gestionnaires d'événements, mais contrairement aux cas cités précédemment, leur manipulation (récupération et modification) n'est pas identique pour tous les navigateurs et jQuery vous sera très utile pour les contrôler de façon simple et uniformisée.
3-2. L'arbre DOM▲
Le langage HTML et plus encore le XHTML sont dérivés du XML. Parmi les règles d'écriture du code XML, l'imbrication des balises va être très utile pour le DOM. Cette règle stipule qu'il est interdit que des balises se chevauchent. Dit autrement, une balise ouverte à l'intérieur d'une autre balise doit aussi être fermée à l'intérieur de cette dernière. Les exemples suivants vont être plus explicites :
<balise1></balise1><balise2></balise2>
// Correct
<balise1><balise2></balise2></balise1>
// Correct
<balise1><balise2></balise1></balise2>
// Incorrect
Cette structure permet d'obtenir une représentation du DOM sous forme d'arborescence et d'imbrication de balises. Ainsi, il va être possible, une fois un élément ciblé, de naviguer dans cette arborescence pour retrouver d'autres éléments. Cet arbre DOM est facilement représenté par les navigateurs. Si l'on ouvre la page d'exemple précédemment utilisée dans Internet Explorer et que l'on ouvre la console de débogage (touche F12), on obtient (en mode d'affichage IE8) :
Cette arborescence montre bien les balises présentes dans la page, mais on constate que le DOM comprend aussi d'autres éléments, par exemple, le contenu des balises <button> est précédé de la mention Texte. On constate aussi que le navigateur a ajouté des attributs à certaines balises.
En fait, chaque élément de la page (pas uniquement les balises) est représenté par un objet appelé nœud. Il existe plusieurs types de nœuds différents pour décrire l'ensemble du contenu de la page (type balise, texte, attribut, etc.) auxquels il est possible d'accéder. En règle générale, ce sont les nœuds de type balise qui nous intéresseront.
Si nous regardons la structure de la même page avec Firefox (via l'extension DOM Inspector par exemple), on obtient le résultat de la figure 2-6 :
DOM Inspector présente l'intérêt d'avoir dans la fenêtre de droite une description détaillée de chaque nœud. Au-delà de la présentation un peu différente, on peut voir qu'apparaissent de nombreux éléments #text, beaucoup plus qu'avec Internet Explorer.
En fait, Internet Explorer, jusqu'à la version 8 (la représentation du DOM d'IE9 rendrait le même résultat que Firefox) interprète le DOM différemment que les autres principaux navigateurs qui considèrent tous les caractères de la page comme des éléments, donc y compris les retours chariots utilisés pour formater la page et la rendre plus facile à lire. À titre d'exemple, vous pouvez voir que dans le code de la page, il n'y a pas de retour chariot avant les balises input et button et effectivement, aucun retour chariot n'est visible avant ces balises, alors qu'ils sont bien présents à chaque fois qu'un retour chariot est présent dans le code source.
De ce fait, parcourir le DOM avec les différentes méthodes JavaScript s'avérera parfois incertain, car les résultats pourront dépendre du navigateur utilisé. Mais ici aussi, jQuery uniformise tout cela et facilite l'accès à tous les éléments de la page.
Pour terminer sur l'arborescence du DOM, il est important de connaitre la terminologie utilisée pour désigner les rapports des éléments entre eux. Cette terminologie est similaire à celle d'une famille, on distingue donc les nœuds :
- parent : la balise immédiatement au-dessus dans la hiérarchie (une balise parent est donc unique) ;
- child (enfant) : les nœuds immédiatement au-dessous dans la hiérarchie (les enfants peuvent être multiples et ne sont pas nécessairement des balises) ;
- ancestors (ancêtres) : les balises contenant le nœud en question ;
- descendants : l'ensemble des nœuds inclus dans une balise ;
- siblings (frères) : les nœuds situés au même niveau hiérarchique.
La majorité des méthodes permettant de naviguer dans le DOM sont destinées à « descendre » dans l'arborescence, jQuery va vous offrir de nombreuses méthodes pour le traverser de façon beaucoup plus complexe.
3-3. jQuery et le DOM▲
Comme nous l'avons déjà évoqué, la première étape dans un script est souvent la récupération dans le DOM du ou des éléments à partir desquels nous aurons envie de travailler. Bien entendu, on comprend facilement que tant que cela est possible, il est recommandé de prévoir ce comportement dans la structure HTML. On utilisera pour cela un balisage adapté, des noms de classe particuliers sur certains éléments, ainsi que des identifiants. Il existe différentes méthodes natives en JavaScript pour la récupération d'éléments, mais à part getElementById, toutes renvoient une collection d'éléments, ce qui signifie que tout traitement sur cette collection devra se faire dans le cadre d'une boucle. Enfin, les méthodes les plus précises pour cibler des éléments sont souvent non compatibles pour tous les navigateurs et souvent implémentées uniquement dans les dernières versions.
C'est donc probablement dans la récupération d'éléments et le traitement par lots que vous pourrez constater toute la force et la puissance de jQuery !
Avec jQuery, tout commence avec la fonction $(). Comme nous l'avons déjà vu, cette fonction (qui est la clé de voûte de jQuery) peut jouer plusieurs rôles. En fait, son comportement dépendra de la valeur de son (parfois ses) paramètre(s).
La fonction $() est la base de jQuery. En fait, il ne s'agit que d'un alias (un raccourci) pour la fonction jQuery(). Ce doublon laisse la possibilité de réserver la fonction $() en cas d'utilisation de plusieurs bibliothèques. Considérant que se servir de plusieurs bibliothèques sur une même page est à considérer comme une mauvaise pratique (souvent due à une méconnaissance des deux), nous n'évoquerons pas ici cette question. Vous pourrez toutefois trouver toutes les informations utiles en cas de besoin sur le site officiel de jQuery (http://jquery.com).
La méthode $() va donc permettre de récupérer ou de créer des éléments du DOM de façon ciblée et le plus souvent en une seule instruction.
Nous avons déjà vu les syntaxes les plus simples correspondant à la récupération d'éléments en fonction de leur identifiant (attribut id) ou de leur nom de classe :
$(
'#un_id'
);
$(
'.une_classe'
);
Ce sont ici des sélecteurs basiques, mais que l'on peut compléter suivant les règles avancées des sélecteurs CSS. Tous les sélecteurs disponibles en CSS sont applicables avec jQuery, y compris si le navigateur n'est pas supposé les supporter.
Sans vouloir revenir sur la syntaxe CSS, le tableau suivant en récapitule les points essentiels appliqués à jQuery.
Tableau 2-1 : Sélecteurs de type CSS3 de jQuery
$('nom') |
Sélectionne toutes les balises <nom> |
$('#nom') |
Sélectionne l'élément dont l'attribut id vaut nom (rappel : un id doit être unique dans la page) |
$('.nom') |
Sélectionne tous les éléments dont ‘nom' est présent parmi ses classes CSS (rappel : plusieurs classes peuvent être données à un même élément) |
$('*') |
Sélectionne tous les éléments de la page |
$('selecteur1 selecteur2') |
Sélectionne tous les éléments correspondant à 'selecteur2' compris dans des éléments correspondant à 'selecteur1' (selecteurN pouvant être un nom de balise, de classe ou d'identifiant) |
$('selecteur1, selecteur2') |
Sélectionne tous les éléments 'selecteur1' et tous les éléments 'selecteur2' |
$('[attribut]') |
Sélectionne tous les éléments possédant l'attribut 'attribut' (peut être précédé d'un sélecteur) |
$('[attribut="valeur"]') |
Sélectionne tous les éléments dont l'attribut 'attribut' vaut 'valeur' (peut être précédé d'un sélecteur) |
$('[attribut^="valeur"]') |
Sélectionne tous les éléments dont l'attribut 'attribut' commence par 'valeur' |
$('[attribut$="valeur"]') |
Sélectionne tous les éléments dont l'attribut 'attribut' fini par 'valeur' |
$('[attribut*="valeur"]') |
Sélectionne tous les éléments dont l'attribut 'attribut' contient 'valeur' |
$('[attribut~="valeur"]') |
Sélectionne tous les éléments dont l'attribut 'attribut' contient le mot 'valeur' |
$('[attribut!="valeur"]') |
Sélectionne tous les éléments dont l'attribut 'attribut' ne vaut pas 'valeur' (ou qui ne possède pas d'attribut 'attribut') |
$('[attribut|="valeur"]') |
Sélectionne tous les éléments dont l'attribut 'attribut' vaut 'valeur' ou commence par 'valeur' suivi d'un tiret (caractère « - » ) |
$('selecteur1 > selecteur2') |
Sélectionne tous les éléments 'selecteur2' qui sont descendants directs (enfants) de 'selecteur1' |
$('selecteur1 + selecteur2') |
Sélectionne tous les éléments 'selecteur2' qui sont frères immédiatement suivant 'selecteur1' (ce qui signifie que pour chaque 'selecteur1', il ne peut y avoir que 0 ou 1 'selecteur2') |
$('selecteur1 ~ selecteur2') |
Sélectionne tous les éléments 'selecteur2' qui sont frères suivants de 'selecteur1' |
Si l'on prend en considération que tous ces sélecteurs peuvent être combinés entre eux, on se rend facilement compte des possibilités offertes, mais ce n'est pas encore suffisant puisque la syntaxe de jQuery supporte aussi un système de pseudoéléments basés encore une fois sur la syntaxe CSS, avec certains ajouts particulièrement utiles listés dans le tableau suivant (classé par ordre alphabétique). Ces sélecteurs agissent comme des filtres, ils s'appliquent habituellement au résultat renvoyé par l'un des sélecteurs listés précédemment.
Tableau 2-2 : Sélecteurs avancés de jQuery
$(':animated')* |
Sélectionne tous les éléments qui sont en cours d'animation |
$(':button')* |
Sélectionne toutes les balises <button> et <input> de type button |
$(':checkbox')* |
Sélectionne tous les input de type checkbox |
$(':checked')* |
Sélectionne tous les éléments cochés |
$(':contains("texte")') |
Sélectionne tous les éléments contenant ‘texte' (attention : la recherche est sensible à la casse). |
$(':disabled') |
Sélectionne tous les éléments de formulaire désactivés |
$(':empty') |
Sélectionne toutes les balises vides (note : vide signifie ne possédant aucun nœud enfant, y compris les nœuds texte) |
$(':enabled') |
Sélectionne tous les éléments de formulaire non activés |
$(':eq(index)')* |
Sélectionne l'élément de rang index (rappel : les index commencent à 0) |
$(':even')* |
Sélectionne les éléments de rang pair |
$(':file')* |
Sélectionne les champs de formulaire de type file |
$(':first-child') |
Sélectionne tous les éléments qui sont les premiers enfants de leur élément parent |
$(':first')* |
Sélectionne le premier élément (équivalent de $(':eq(0)')) |
$(':focus') |
Sélectionne l'élément ayant le focus |
$(':gt(index)')* |
Sélectionne les éléments de rang supérieur à index (note : l'élément de rang index n'est donc pas compris) |
$(':has(selecteur)')* |
Sélectionne les éléments dont un descendant correspond à 'selecteur' |
$(':header')* |
Sélectionne tous les éléments de type titre (balises <hn>) |
$(':hidden')* |
Sélectionne tous les éléments non visibles. Sont considérés comme non affichés :
|
$(':image')* |
Sélectionne les champs de formulaire de type 'image' |
$(':input')* |
Sélectionne tous les champs de formulaire (balises input, textarea, select et button) |
$(':last-child') |
Sélectionne tous les éléments qui sont derniers enfants de leur parent |
$(':last')* |
Sélectionne le dernier élément |
$(':lt(index)')* |
Sélectionne les éléments de rang inférieur à index |
$(':not(selecteur)') |
Sélectionne tous les éléments qui ne correspondent pas à 'selecteur' |
$(':nth-child(index)') |
Sélectionne tous les éléments qui sont enfants de rang index de leur parent |
$(':odd')* |
Sélectionne tous les éléments de rang impair |
$(':only-child') |
Sélectionne tous les éléments qui sont les enfants uniques de leur parent |
$(':parent')* |
Sélectionne les éléments qui sont les parents d'autres nœuds, y compris les nœuds texte (l'opposé de $(':empty')) |
$(':password')* |
Sélectionne les champs de formulaire de type 'password' |
$(':radio')* |
Sélectionne les champs de formulaire de type 'radio' |
$(':reset')* |
Sélectionne les champs de formulaire de type 'reset' |
$(':selected')* |
Sélectionne les options de champs <select> sélectionnés |
$(':submit')* |
Sélectionne les champs de formulaire de type 'submit' |
$(':text')* |
Sélectionne les champs de formulaire de type 'text' |
$(':visible')* |
Sélectionne tous les éléments visibles (le contraire de $(':hidden')) |
(*) Les filtres suivis d'un astérisque ne font pas partie de la spécification CSS
L'ensemble de ces filtres va permettre d'affiner les sélections afin de cibler au mieux les éléments que l'on veut manipuler. Cependant, la syntaxe telle que nous l'avons décrite sert à récupérer les éléments avec la méthode $().
Il est à noter que la méthode $() peut prendre un second paramètre qui permettra de définir un contexte sur lequel effectuer la recherche. Ce contexte peut être une portion de HTML ou un objet jQuery. Pour autant, il est conseillé de ne pas utiliser cette option et de lui préférer la méthode filter(), qui est celle utilisée en interne par jQuery lorsque le second paramètre est défini.
On peut avoir envie d'effectuer une première sélection, puis de la filtrer ultérieurement, par exemple en réponse à une action de l'utilisateur. Pour cela, jQuery propose différentes méthodes, en particulier la méthode filter() ou find() dont le rôle est d'effectuer une recherche de sélecteur dans une collection retournée par jQuery.
Rappelez-vous que la méthode $() (comme quasiment toutes les méthodes de jQuery) renvoie non pas une collection HTML, mais la collection d'objets jQuery correspondante. La différence est importante et jQuery se distingue du fonctionnement d'autres bibliothèques JavaScript comme Prototype.
Par exemple, si l'on considère la balise HTML suivante :
<input type
=
"text"
value
=
"une valeur"
id
=
"test"
/>
on obtiendra avec les instructions de Prototype et de jQuery :
alert
(
$(
'test'
).
value);
// Prototype, affiche 'une valeur'
alert
(
$(
'#test'
).
value);
// jQuery, affiche 'undefined'
alert
(
$(
'#test'
).val
(
));
// jQuery, affiche 'une valeur
Cet exemple permet de mettre en avant que Prototype va enrichir les objets HTML des propriétés et méthodes de la bibliothèque alors que jQuery va créer des objets qui lui sont propres et pour lesquels des propriétés et des méthodes spécifiques seront attribuées. value n'étant pas une propriété créée par jQuery, sa valeur vaut donc, comme pour toute propriété non définie d'un objet JavaScript, undefined.
En fait, jQuery crée un tableau d'éléments DOM, pour retrouver un élément DOM avec jQuery, il faut préciser l'indice recherché :
alert
(
$(
'#test'
)[
0
].
value);
Notez que la méthode get() permet de récupérer depuis une sélection jQuery un tableau des éléments du DOM correspondant à cette sélection. Il est possible de passer en paramètre à cette méthode un index correspondant au rang d'un élément spécifique à récupérer (rappelez-vous que les rangs commencent à 0 en JavaScript). Enfin, si l'index passé est négatif, la recherche partira de la fin. Du coup, le code précédent est équivalent à
alert
(
$(
'#test'
).get
(
0
).
value);
Dernière précision concernant la méthode get(), si la sélection ne comporte qu'un seul élément, selsection.get() renverra malgré tout un tableau et ne sera pas équivalente à selection.get(0).
Important : La méthode get() sert aussi à lancer un appel AJAX de type GET avec jQuery, le comportement de cette méthode dépend donc du contexte d'utilisation.
Les méthodes filter() et find() vont donc permettre d'affiner le résultat renvoyé par une méthode jQuery et d'en améliorer la pertinence voire l'efficacité. Par exemple, les instructions :
$(
'selecteur1 selecteur2'
);
$(
'selecteur1:header'
);
sont équivalentes à :
$(
'selecteur1'
).find
(
'selecteur2'
);
$(
'selecteur1'
).filter
(
':header'
);
Notez la différence concernant les filtres non supportés en CSS (ceux suivis d'un astérisque dans le tableau ci-dessus). En interne, jQuery tente, quand c'est possible, d'utiliser les méthodes natives de JavaScript puisqu'elles offrent de meilleures performances (en particulier la méthode querySelectorAll() , or ces méthodes ne pourront pas être utilisées si le filtre n'existe pas en CSS. Dans ce cas, il est recommandé de séparer la requête entre la partie exécutable nativement en JavaScript, puis d'utiliser la méthode filter() pour affiner la recherche. Cette façon de procéder sera toujours meilleure en termes de performance.
La méthode find() va rechercher parmi les éléments présents dans l'objet jQuery en cours les enfants répondant au sélecteur passé en paramètre, tandis que filter() effectuera cette recherche parmi l'ensemble des éléments présents dans l'objet jQuery en cours.
Une autre utilisation de la méthode $() sera non plus de récupérer, mais de créer des portions de code HTML sous forme d'objets jQuery. Il suffit pour cela de lui passer en paramètre le code HTML que l'on souhaite générer. Il sera ensuite possible de manipuler cette portion HTML comme tout autre objet jQuery. Par exemple, le code suivant :
alert
(
$(
'<div>Portion de code générée par <span id="jquery"
style=
"font-weight:bold"
>
jQuery</
span></
div>
').find('
#jquery').html());
affichera le message de la figure 2-7.
La portion de code a donc bien été convertie en objet jQuery et peut donc être traitée comme telle (la méthode html() permet de récupérer le contenu HTML d'une balise). Bien entendu, il sera ensuite possible de l'intégrer dans le document à l'aide de méthodes que nous verrons ultérieurement.
3-4. Parcourir le document▲
Dans le chapitre précédent, nous avons vu comment récupérer ou créer un ensemble d'éléments du document à partir de la méthode $(). Nous avons aussi abordé comment filtrer cette collection avec les méthodes find() et filter(). Ces méthodes vont permettre d'affiner la recherche. Si filter() permet de restreindre la recherche parmi les éléments déjà récupérés, en revanche find() va remplacer chaque élément de l'objet en cours par les éléments répondant à la requête passée en paramètre parmi ses descendants. Par exemple, l'instruction : $('div'); renverra toutes les balises <div> présentes dans la page, mais l'instruction :
$(
'div'
).find
(
'span'
);
récupérera dans un premier temps toutes les balises <div>, puis les remplacera par toutes les balises <span> trouvées parmi leurs descendants. L'objet jQuery ne contiendra donc plus que des balises <span>.
Rappelez-vous au passage de l'importance de la syntaxe utilisée, si $('div') récupère toutes les balises <div> présentes dans le document, $('<div>') en revanche créera une balise <div>.
La méthode find() va donc permettre, à partir d'un jeu d'éléments, de parcourir le DOM pour récupérer un nouveau jeu d'éléments. Dans l'exemple précédent, on peut se demander l'intérêt puisque :
$(
'div'
).find
(
'span'
);
est équivalent à :
$(
'div span'
);
qui sera même plus optimisé. Ceci dit, on peut avoir envie d'appliquer certains traitements sur le premier résultat, puis d'autres sur les éléments qu'ils contiennent et il est possible d'insérer des instructions jQuery entre la méthode $() et la méthode find(). Par exemple, on peut vouloir changer la couleur de fond des balises <div> puis de mettre en gras les balises <span> qu'elles contiennent, comme dans l'exemple suivant :
<!
DOCTYPE HTML
>
<html xmlns=
"http://www.w3.org/
1999
/xhtml"
>
<head>
<meta charset=
"iso-
8859
-
1
"
>
<title>
Traverser le DOM</title>
<script
type="text/javascript" src="http://ajax.googleapis.com/
ajax/libs/jquery/1.6.1/jquery.min.js">
</script>
<script
type="text/javascript">
function findSpan
(
){
$(
'div'
).css
(
'background-color'
,
'silver'
).
find
(
'span'
).css
(
'font-weight'
,
'bold'
);
}
</script>
</head>
<body>
<div>
Premier élément div contenant <span>
un span</span></div>
<div>
Premier élément div sans span</div>
<div>
Deuxième élément div contenant <span>
un span</span></div>
<p>
Un paragraphe contenant <span>
un span</span></p>
<div>
Troisième élément div contenant <span>
un span</span></div>
<p><button onclick=
"findSpan();"
>
Modifier les éléments</button></p>
</body>
</html>
Ce code affichera dans le navigateur le résultat obtenu à la figure 2-8.
Puis, une fois le bouton cliqué, l'affichage sera modifié (figure 2.9).
Le code précédent va donc récupérer toutes les balises <div> puis leur appliquer un style CSS (couleur de fond grise, notez la méthode css() qui permet d'appliquer un style CSS sur l'objet jQuery courant), la méthode find() permet ensuite de récupérer toutes les balises <span> contenues dans les éléments de l'objet jQuery et met leur contenu en gras.
La méthode find() va donc permettre de parcourir l'arbre DOM à partir d'un élément donné pour récupérer d'autres éléments. D'autres méthodes similaires servent à modifier l'objet jQuery en cours.
Différentes méthodes vont donc permettre de modifier la sélection en cours, toutes n'ayant pas le même impact. Certaines, à l'image de filter() vont réduire le nombre d'éléments présents dans l'objet en cours, d'autres, comme find(), vont substituer les éléments, d'autres enfin vont en rajouter (par exemple add()).
Le tableau suivant récapitule (par ordre alphabétique) ces différentes méthodes en précisant leur effet sur l'objet jQuery en cours.
Les arguments entre crochets sont optionnels.
Tableau 2-3 : Parcourir le DOM
.add(['selecteur']) |
Ajoute des éléments à la collection selon le sélecteur passé en paramètre |
Ajout |
.children(['selecteur']) |
Remplace chaque élément par l'ensemble de ses enfants selon le sélecteur passé en paramètre. |
Substitution |
.closest(['selecteur']) |
Remplace chaque élément par l'ancêtre le plus proche correspondant au sélecteur passé en paramètre |
Substitution |
.contents() |
Remplace chaque élément par l'ensemble de ses nœuds enfants, y compris les nœuds texte |
Substitution |
.eq([-]index) |
Récupère dans la collection celui de rang index et supprime les autres (les indices commencent à 0, si index est négatif, le compte se fait à rebours depuis la fin) |
Filtre |
.filter(['selecteur']) |
Récupère dans la sélection les éléments correspondant à selecteur |
Filtre |
.find('selecteur') |
Remplace chaque élément par leurs enfants correspondant à selecteur (note : le paramètre n'est pas optionnel ici) |
Substitution |
.first() |
Récupère le premier élément de la sélection |
Filtre |
.has(['selecteur']) |
Récupère dans la sélection les éléments ayant au moins un enfant correspondant à selecteur |
Filtre |
.last() |
Récupère le dernier élément de la sélection |
Filtre |
.next(['selecteur']) |
Remplace chaque élément de la sélection par son frère immédiatement suivant s'il correspond à selecteur |
Substitution |
.nextAll(['selecteur']) |
Remplace chaque élément de la sélection par ses frères suivants s'ils correspondent à selecteur |
Substitution |
.nextUntil(['selecteur']) |
Remplace chaque élément de la sélection par tous ses frères suivants jusqu'au premier correspondant à selecteur (ce dernier n'est pas inclus) |
Substitution |
.offsetParent() |
Remplace chaque élément de la sélection par son premier ancêtre positionné s'il existe (propriété CSS position à fixed, absolute ou relative) |
Substitution |
.parent(['selecteur']) |
Remplace chaque élément de la sélection par son parent s'il correspond à selecteur |
Substitution |
.parents(['selecteur']) |
Remplace chaque élément de la sélection par ses ancêtres correspondant à selecteur |
Substitution |
.parentsUntil(['selecteur']) |
Remplace chaque élément de la sélection par tous ses ancêtres jusqu'au premier correspondant à selecteur (ce dernier n'est pas inclus) |
Substitution |
.prev(['selecteur']) |
Remplace chaque élément de la sélection par son frère précédent s'il correspond à selecteur |
Substitution |
.prevAll(['selecteur']) |
Remplace chaque élément de la sélection par tous ses frères précédents correspondant à selecteur |
Substitution |
.prevUntil(['selecteur']) |
Remplace chaque élément de la sélection par tous ses frères précédents jusqu'au premier correspondant à selecteur (ce dernier n'est pas inclus) |
Substitution |
.siblings(['selecteur']) |
Remplace chaque élément de la sélection par tous ses frères correspondant à selecteur (note : il s'agit d'une substitution, l'élément n'est donc pas inclus dans le résultat retourné, d'autre part, si deux éléments frères sont présents dans la sélection, ils seront compris dans le résultat, mais les autres frères ne seront ajoutés qu'une seule fois) |
Substitution |
.slice(debut[, fin]) |
Récupère les éléments de la sélection du rang debut inclus au rang fin exclu (note : debut et fin peuvent être négatifs, dans ce cas, le compte se fait à rebours depuis la fin ; les indices commencent à 0 ; si fin correspond à un élément se trouvant avant debut, alors aucun élément n'est retourné) |
Filtre |
Notez que pour chacune de ces méthodes, si aucun élément n'est trouvé, cela ne déclenche pas d'erreur, c'est un objet vide qui sera retourné, mais sur lequel il est malgré tout possible d'enchaîner d'autres traitements.
Vous pourrez trouver des exemples d'utilisation de ces différentes méthodes sur la documentation officielle de jQuery (http://api.jquery.com/category/traversing/ ) afin de mieux comprendre leur fonctionnement.
Il existe enfin quelques méthodes de parcours du DOM dont le comportement nécessite plus d'explications.
3-4-1. Retour sur filter() et find()▲
Nous avons déjà vu le fonctionnement de base des méthodes filter() et find() avec un paramètre optionnel de type sélecteur. Mais ces méthodes peuvent prendre d'autres types de paramètres.
Tout d'abord, il est possible de leur passer comme paramètre un autre objet jQuery, dans ce cas, la méthode filter() ne gardera que les éléments de l'objet initial aussi présent dans celui en paramètre, par exemple :
var filtre = $('.gras');
$('div').css('background-color','silver').filter(filtre).css('font-weight','bold');
aura pour effet de stocker dans une variable tous les éléments dont la classe CSS vaut gras, la seconde instruction va récupérer toutes les balises <div> du document, leur appliquer une couleur de fond grise, puis récupérer parmi ces éléments ceux qui ont aussi pour classe gras et mettre leur contenu en gras.
Appliqué à find(), le même exemple ferait la même chose, mais en mettant en gras le contenu des descendants des balises <div> ayant la classe CSS gras.
Il est aussi possible de passer comme paramètre un élément ou une collection HTML directement récupérés du DOM à partir d'une méthode native de JavaScript. Par exemple :
$('div')
.css('background-color','silver')
.find(document.getElementsByTagName('span'))
.css('font-weight','bold');
dont l'effet sera dans un premier temps de mettre un fond gris à toutes les divisions du document, puis de mettre en gras le texte de chaque élément <span> contenu dans une <div>.
Notez au passage la syntaxe utilisée lorsque les lignes de code sont un peu longues, comme cela peut arriver régulièrement avec jQuery du fait du chaînage des instructions. Il est d'usage pour faciliter la lisibilité de revenir à la ligne avant chaque point en incrémentant après l'instruction initiale.
Enfin, il est aussi possible de passer à la méthode filter() une fonction qui sera appelée pour chaque élément de la sélection en cours. L'indice de l'élément dans l'objet jQuery est passé en paramètre à cette fonction et à l'intérieur de cette fonction, deux valeurs remarquables peuvent être utilisées : $(this) représente l'élément jQuery en cours de traitement et this l'objet HTML correspondant. Enfin, la fonction doit retourner une valeur dont l'équivalent booléen déterminera si l'élément est conservé ou non dans la sélection. Rappelez-vous que toute valeur JavaScript possède un équivalent booléen. Sont considérées comme fausses, les valeurs false, undefined, null, ainsi qu'une chaîne vide ou 0.
3-4-2. Ajouter les résultats précédents▲
Nous avons vu les méthodes de filtrage et de substitution ainsi qu'une méthode (add() . Mais nous pouvons avoir envie non pas de substituer les éléments, mais d'ajouter à l'objet en cours le résultat d'une recherche effectuée à partir de la sélection en cours. Par exemple, nous avons vu que la méthode siblings() permet de substituer à chaque élément de la sélection tous ses nœuds frères. Si nous prenons la structure HTML suivante :
<div>
<p id="paragraphe">1er paragraphe</p>
<p>2e paragraphe</p>
<p>3e paragraphe</p>
<p>4e paragraphe</p>
</div>
l'instruction jQuery suivante :
$('#paragraphe').siblings();
référencera donc toutes les balises <p> sauf celle ayant l'identifiant paragraphe, puisque la méthode siblings() effectue une substitution. Or il sera fréquent que l'on veuille en réalité récupérer tous les frères y compris celui qui sert de référence afin de leur appliquer les mêmes traitements. Une solution pourrait être de sélectionner d'abord la balise de référence, lui appliquer le traitement voulu, puis récupérer les autres éléments et réitérer les traitements. Heureusement, jQuery vous permet d'éviter cette multiplication de traitements en gardant stockée la pile des différentes sélections au fur et à mesure que celles-ci évoluent. Ainsi, différentes méthodes vont permettre de retrouver la sélection précédente et de l'utiliser.
Parmi ces méthodes, andSelf() permet d'ajouter la sélection précédente à la sélection en cours. Avec cette méthode, en reprenant l'exemple précédent, pour obtenir la sélection de tous les paragraphes, il suffira d'écrire :
$('#paragraphe').siblings().andSelf();
3-4-3. Parcourir une sélection▲
Nous avons déjà vu que pour appliquer un traitement à l'ensemble des éléments sélectionnés, il suffit de chaîner les instructions jQuery. Par exemple :
$('.gras').css('font-weight', 'bold');
va mettre en gras tous les éléments de la page dont la classe CSS est gras. Bien entendu, il est possible de chaîner plusieurs instructions jQuery, ceci dit, il n'est possible de cette manière que d'effectuer un même traitement sur chacun des éléments, or vous aurez parfois besoin de différencier les opérations à réaliser en fonction de certains critères. Pour cela, jQuery vous permet de faire une boucle avec la méthode each(). Cette méthode prend en paramètre une fonction de rappel (callback) à laquelle seront passés automatiquement en paramètres l'indice de l'élément en cours dans la collection et l'élément HTML lui-même (attention, il s'agit bien de l'élément HTML et non de l'élément jQuery).
3-4-3-1. Important : rappel sur les callback▲
Certaines fonctions et méthodes de JavaScript (setTimeout, setInterval, gestionnaires d'événements… ) ou de jQuery prennent en paramètre une fonction de rappel et il est fréquent de leur affecter un appel de fonction à la place. Un callback correspond à une référence à la fonction à appeler et non à la fonction elle-même. Par exemple, si vous souhaitez appeler la fonction monTraitement lors de l'événement onclick de l'élément monElement, l'erreur serait de déclarer :
monElement.onclick = monTraitement();
Dans ce cas, vous affectez à la propriété onclick non pas l'appel de la fonction, mais le résultat de son exécution au moment où l'instruction est rencontrée dans le code, ce qui est rarement le comportement souhaité. Pour que la fonction soit exécutée au moment du clic, il faut écrire : monElement.onclick = monTraitement;
Vous constaterez en particulier qu'il n'est pas possible de passer de paramètres à cette fonction de rappel. Cependant, dans certains cas, des paramètres sont automatiquement transmis à la fonction, par exemple pour les événements, un objet correspondant à l'événement reçu.
Si vous devez passer des paramètres à votre callback, il est possible de passer en paramètre ou d'affecter une déclaration de fonction :
monElement.onclick = function(){
monTraitement(param1, param2);
};
Dans ce cas, c'est la fonction anonyme qui sera lancée lors de l'événement et qui lancera monTraitement avec ses paramètres. Mais là aussi, les erreurs sont fréquentes concernant la portée et la valeur des paramètres passés. Souvenez-vous que la fonction sera exécutée au moment de l'événement. Donc si vous affectez cet événement dans le cadre d'une fonction, au moment où votre callback sera lancé, la fonction au sein de laquelle il a été déclaré aura terminé son exécution et ses variables locales auront été détruites (sauf si elles sont passées en paramètre, dans ce cas, une référence est gardée en mémoire), vous ne pouvez donc plus vous en servir. De la même manière, si les événements sont affectés dans une boucle et que vous passez en paramètre un compteur de boucle, sa valeur sera celle définie au moment de l'exécution, donc sa valeur à la fin de la boucle. Pour parer à cela, on utilise en général une propriété intermédiaire :
for(var i = 0; i < collection.length; i++){
collection[i].indice = i; // indice vaut bien la valeur souhaitée
collection[i].onclick = function(){
traitement(i);
};
}
function traitement(param){
alert(param); // vaut collection.length
alert(this.indice); // vaut la valeur de i au moment de l'affectation
}
L'exemple suivant montre le fonctionnement de la méthode each() :
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="iso-8859-1">
<title>Méthodes each()</title>
<script type="text/javascript" src="http://ajax.googleapis.com/
ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript">
var colors = ['red', 'green', 'blue', 'navy'];
function toColor(indice, elem){
$(elem).css('color', colors[indice]);
}
function jqEach(){
$('p').each(toColor);
}
</script>
</head>
<body>
<div id="ref">
<p>Paragraphe 1</p>
<p>Paragraphe 2</p>
<p>Paragraphe 3</p>
<p>Paragraphe 4</p>
</div>
<button onclick="jqEach()">Boucle</button>
</body>
</html>
Dans cet exemple, nous créons d'abord un tableau contenant différents noms de couleurs. En cliquant sur le bouton, nous récupérons toutes les balises <p> et nous appelons la méthode each() pour leur affecter la couleur de fonte de même rang dans le tableau en utilisant les paramètres passés automatiquement à la fonction de rappel. Vous noterez que nous sommes obligés d'appeler la fonction $() à chaque élément pour pouvoir lui appliquer une méthode jQuery. Notez aussi que la fonction jqEach() pourrait aussi bien s'écrire :
$('p').each(function(indice, elem){
$(elem).css('color', colors[indice]);
});
Le résultat dans le navigateur est montré à la figure 2-10.
Rappelez-vous aussi qu'à l'intérieur de la fonction de rappel, l'élément en cours de traitement peut aussi être récupéré avec le mot-clé this (de même, l'élément jQuery est accessible via $(this) , on peut donc considérer que le second paramètre de la fonction de rappel est en général inutile.
3-4-4. Récupérer la sélection précédente▲
Il peut arriver d'avoir besoin, à partir d'une sélection jQuery, d'appliquer des traitements différents sur plusieurs catégories d'éléments. Pour cela, jQuery met à votre disposition la méthode end() qui permet de filtrer une sélection, de lui appliquer un traitement, puis de revenir à la sélection précédente. Dans l'exemple suivant, nous souhaitons appliquer aux enfants d'un élément de référence des styles spécifiques en fonction de leur nom de classe.
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="iso-8859-1">
<title>jQuery</title>
<script type="text/javascript" src="http://ajax.googleapis.com/
ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript">
function jqEnd(){
$('#ref')
.find('.souligne').css('text-decoration', 'underline')
.end().find('.gras').css('font-weight', 'bold')
.end().find('.rouge').css('color', 'red');
}
</script>
</head>
<body>
<div id="ref">
<p class="souligne">Paragraphe 1</p>
<p class="gras rouge">Paragraphe 2</p>
<p class="gras">Paragraphe 3</p>
<p class="souligne rouge">Paragraphe 4</p>
</div>
<button onclick="jqEnd()">Ajouter les styles</button><br />
</body>
</html>
Nous récupérons dans un premier temps la balise de référence (identifiant ref), puis tous ses enfants ayant pour classe souligne et nous soulignons leur contenu. La méthode end() permet de revenir à la balise de référence, nous récupérons ensuite les éléments dont la classe est gras puis ceux de classe rouge. Le résultat illustré à la figure 2-11.
3-4-5. Que suis-je ?▲
L'idéal lorsque vous écrivez une fonctionnalité avec JavaScript (et donc avec jQuery) est d'essayer de créer des fonctions réutilisables ; c'est-à-dire que le même code pourra être intégré dans n'importe quelle page pour implémenter une fonctionnalité. Ce sera particulièrement le cas pour l'écriture de plug-ins. Dans cette optique, vous aurez probablement besoin de savoir si un ensemble d'éléments auquel vous souhaitez affecter un traitement, répond à une condition précise. Ou alors, vous pouvez avoir besoin, sur une sélection, de n'appliquer le traitement pour l'élément en cours que s'il répond à une condition.
Pour cela, jQuery offre la méthode is() qui est un peu particulière dans le sens ou elle ne modifie pas la sélection en cours, mais renvoie un booléen indiquant si l'objet jQuery en cours correspond à la condition passée en paramètre.
La comparaison pourra se faire selon un sélecteur, un objet jQuery, un objet du DOM ou une fonction de rappel.
La valeur de retour sera true si l'un au moins des éléments de la sélection remplit la condition. Par exemple, si l'on écrit :
var selection = $('.classe');
if(selection.is('div')){
// traitement
}
la condition sera remplie si au moins un élément ayant la classe .classe est une balise <div>. Si vous voulez n'affecter le traitement qu'aux éléments ayant la classe donnée et étant des divisions, il faudra écrire :
$('.classe').each(function(){
if($(this).is('div')){
// traitement
}
});
Vous comprendrez que l'utilisation habituelle de is() sera dans le cadre d'une boucle puisque cette méthode ne garantit en rien que la totalité des éléments répondent à la condition. Notez aussi que si vous utilisez une fonction de rappel, l'indice de l'élément en cours sera passé automatiquement en paramètre.
3-4-6. La méthode map()▲
La méthode map() est similaire à each(). Elle va appliquer une fonction de rappel à chaque élément de la sélection. La différence essentielle est que each() permet d'appliquer un traitement sur chacun des éléments alors que map() va créer un nouvel objet jQuery à partir des résultats renvoyés par la fonction. Comme pour each(), deux paramètres seront automatiquement passés à la fonction : l'indice de l'élément en cours et l'élément DOM associé.
La méthode map() va donc construire un nouvel objet jQuery à partir des résultats renvoyés par le callback. Ceci dit, cette méthode est le plus souvent utilisée pour renvoyer des valeurs et non des éléments du DOM. Le résultat sera donc le plus souvent un ensemble de valeurs.
L'exemple suivant utilise la méthode map() pour récupérer les valeurs sélectionnées dans une liste de cases à cocher :
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="iso-8859-1">
<title>Méthode map()</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/
libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript">
function jqMap(){
$('#resultat').text($(':checkbox').map(function(){
if($(this).is(':checked')){
return $(this).val();
}
}).get().join());
}
</script>
</head>
<body>
<div><label for="un">Checkbox 1</label>
<input type="checkbox" value="un" id="un" /></div>
<div><label for="deux">Checkbox 2</label>
<input type="checkbox" value="deux" id="deux" /></div>
<div><label for="trois">Checkbox 3</label>
<input type="checkbox"value="trois" id="trois" /></div>
<div><label for="quatre">Checkbox 4</label>
<input type="checkbox"value="quatre" id="quatre" /></div>
<div><label for="cinq">Checkbox 5</label>
<input type="checkbox" value="cinq" id="cinq" /></div>
<div><label for="six">Checkbox 6</label>
<input type="checkbox" value="six" id="six" /></div>
<div><label for="sept">Checkbox 7</label>
<input type="checkbox"value="sept" id="sept" /></div>
<div><label for="huit">Checkbox 8</label>
<input type="checkbox" value="huit" id="huit" /></div>
<div><label for="neuf">Checkbox 9</label>
<input type="checkbox" value="neuf" id="neuf" /></div>
<div><label for="dix">Checkbox 10</label>
<input type="checkbox" value="dix" id="dix" /></div>
<p><button onclick="jqMap()">Récupérer les checkbox cochés
</button>
</p>
<div id="resultat" style="font-weight:bold"></div>
</body>
</html>
Si nous cochons les cases 4, 7 et 9, le résultat est le suivant :
Vous noterez dans le code que les résultats renvoyés étant récupérés dans un objet jQuery, nous devons leur appliquer la méthode get() pour retrouver les valeurs JavaScript correspondantes (ici un tableau) pour pouvoir appliquer la méthode JavaScript join().
D'autre part, l'affichage du résultat se fait à l'aide de la méthode text(), que nous reverrons bientôt.
3-4-7. Retirer des éléments▲
La dernière méthode de filtrage que nous verrons est la méthode not(). Contrairement à ce que pourrait laisser croire son nom, cette méthode n'est pas l'inverse de is(). En réalité, not() est plus exactement le contraire de filter(). Elle prend en paramètre un sélecteur, une collection d'éléments du DOM ou une fonction de rappel (pour laquelle l'indice de l'élément en cours est passé en paramètre) et supprime de la sélection tous les éléments qui correspondent. Par exemple, ce code :
$('.classe').not('div');
récupérera dans un premier temps tous les éléments ayant pour classe CSS classe, puis retirera de la sélection tous les éléments de type <div>. Si le paramètre passé est une fonction, alors seront retirés de la sélection tous les éléments pour lesquels cette fonction renverra un équivalent de true, ce qui signifie que, comme toujours avec les fonctions de rappel, il est important qu'elle renvoie un résultat avec le mot-clé return.
La facilité avec laquelle jQuery permet de manipuler et de traverser l'arbre DOM de la page va grandement faciliter l'interaction avec les actions de l'utilisateur au travers des événements.