Introduction à jQuery - 1 Un peu de JavaScript - Ressources pour IPI/CGI promo 2017
←
→
Transcription du contenu de la page
Si votre navigateur ne rend pas la page correctement, lisez s'il vous plaît le contenu de la page ci-dessous
Cours jQuery Introduction à jQuery 1 Un peu de JavaScript 1.1 Bref historique Pour plus de détails, voir la présentation en PDF. JavaScript a été introduit dans Netscape Navigator en 1995. Malgré le nom, JavaScript n’a rien à voir avec Java. Simplement, Java était très en vogue à cette époque. Netscape a voulu exploiter la popularité de Java et a baptisé son langage JavaScript pour des raisons de marketing. L’apparition de JavaScript coïncide avec d’autres évolutions notables du web : • l’apparition de langages tels que Perl 5, Python, PHP, permettant de générer des pages web dynamiquement, par exemple à partir d’une base de données. • l’introduction d’élements de présentation dans le HTML (tables) et des feuilles de style. 1.2 À quoi sert JavaScript JavaScript permet d’ajouter de l’interaction dans une page web. Par exemple, le bouton HTML ci-dessous permet de revenir à la page précédente : Précédent Dans le navigateur web, JavaScript peut manipuler des éléments de la page en cours, accéder à l’historique de navigation de la fenêtre courante, programmer des actions à effectuer à l’issue d’un intervalle de temps défini (setTimeout()) ou à intervalles réguliers (setInterval()). Il est important de distinguer : • d’une part, le langage lui-même: on peut très bien imaginer que JavaScript fonctionne ailleurs que dans un navigateur web. Ce n’était pas possible à l’origine, mais depuis quelques années Node.js permet d’exécuter du JavaScript en dehors d’un navigateur. • d’autre part, les “fonctions” accessibles par ce langage, dans un certain environnement. Par exemple, l’API DOM, intégrée par défaut dans les nav- igateurs, est un ensemble de fonctions JavaScript, permettant de manipuler le document HTML. Mais elle ne fait pas partie du langage proprement dit. Ainsi elle n’est pas disponible sous Node.js. Inversement Node.js dispose de fonctions pour manipuler des fichiers ou des bases de données comme 1
MySQL, qu’on ne pourrait jamais avoir dans un navigateur. La raison pour ce dernier point est que le navigateur doit protéger l’utilisateur contre des programmes malicieux. Imaginez : vous visitez un site apparemment inoffensif. Celui-ci contient un script JavaScript, qui se télecharge de façon invisible pour vous. S’il pouvait accéder aux fichiers de votre ordinateur, il pourrait (au hasard) essayer de trouver votre RIB, votre carnet d’adresses, les mots de passe stockés dans votre navigateur, etc. C’est pourquoi c’est strictement impossible. On dit que le JavaScript dans le navigateur est “sandboxé” (en franglais dans le texte). 1.3 (In)compatibilité entre les navigateurs Beaucoup de choses en informatique sont standardisées, normalisées, et c’est heureux. Par exemple le protocole HTTP (permettant l’échange entre un serveur web et un navigateur) est strictement décrit dans une spécification (à vos risques et périls si vous vous attaquez à une lecture de ce calibre !). De même les langages sont standardisés. HTML et CSS le sont, JavaScript aussi, pour chacune de leurs versions (ici la spécification d’une des premières versions de JavaScript). Là où le bât blesse, c’est que certaines spécifications, faute d’être assez précises, peuvent laisser une petite part d’interprétation à ceux qui s’en servent. JavaScript a connu ce problème, ou plus précisément, les API associées. Cela s’est en partie arrangé depuis, heureusement. Mais par exemple, certaines fonctions de manipulation du DOM n’ont jamais été disponibles sous Internet Explorer. Ce dernier avait aussi un système différent des autres pour effectuer des requêtes AJAX vers un serveur. 1.4 Apparition de librairies pour “lisser” les incompatibilités Les problèmes de compatibilité entre navigateurs ont encouragé des développeurs indépendants à créer des librairies pour y pallier, et ainsi faciliter le travail des programmeurs d’applications JavaScript. Parmi elles, jQuery, créée initialement par un certain John Resig. Outre le fait de lisser les différences entre les navigateurs, jQuery fournit des fonctions permettant de facilement : • manipuler la page web : agir sur les élements HTML et leurs styles, ajouter ou retirer facilement des éléments, etc. • animer des éléments : par exemple pour construire un carousel d’images, avec différentes transitions possibles entre chaque (par fade in / fade out, par défilement, etc.) • effectuer des requêtes AJAX 2
De plus jQuery est extensible : il permet facilement d’ajouter d’autres fonctions à celles fournies en standard. Enfin, suivant le leitmotiv affiché sur sa page d’accueil, jQuery permet d’écrire moins, faire plus. Les programmes écrits avec jQuery que les équivalents écrits avec l’API DOM native. 2 Bases de jQuery - Les sélecteurs 2.1 L’objet jQuery Note pour les étudiants IPI/CGI : lors de notre 1ère session, j’ai commencé par les sélecteurs sans employer explicitement le terme “objet jQuery”. jQuery permet de manipuler des ensembles d’éléments HTML, via des fonctions à la syntaxe simple telles que : • .html() pour lire ou modifier le contenu des éléments ciblés • .css() pour leur attribuer des styles ou les consulter • .show(), .hide(), .toggle() pour respectivement les rendre visibles, invisibles, ou inverser leur visibilité par rapport à l’état actuel (en modifiant leur propriété CSS display et non visibility). Pour pouvoir appeler ces fonctions sur un élément ou un ensemble d’éléments, jQuery les enveloppe (wrap) dans une structure de données qui lui est propre : l’objet jQuery. Pour obtenir un objet jQuery, on fait appel à un sélecteur de la forme $(...). L’objet jQuery contient des références aux éléments “natifs” qu’on obtiendrait normalement avec l’API DOM. La facilité et la puissance de jQuery pour manipuler le DOM viennent de sa conception : • les sélecteurs ont une syntaxe largement calquée sur les sélecteurs CSS. • les fonctions appelées sur l’objet jQuery ont la même syntaxe, que celui-ci enveloppe un seul ou plusieurs éléments du DOM (contrairement à l’API DOM qui oblige à parcourir soi-même des ensembles d’éléments par des boucles). • on peut ajouter des éléments au document par différentes méthodes. Un premier exemple de création d’un objet jQuery via un sélecteur. Le sélecteur suivant enveloppe tous les liens de la page où s’exécute le script : $('a') Il est intéressant de se pencher sur ce que contient cet objet, ici dans l’onglet “Console” des outils de développement de Google Chrome. Vous pouvez le faire vous-même, en ouvrant n’importe quelle page comportant des liens, et intégrant jQuery. . . comme celle-ci ! (pour ceux qui consultent le cours en HTML). Cliquez avec le bouton droit dans cette page, et choisissez 3
“Inspecter”, ou ouvrez simplement les outils développeur. Puis allez dans l’onglet “console” et faites les manipulations ci-dessous. Figure 1: On constate la présence de propriétés : 0, 1, 2, etc. qui ressemblent à des indices dans un tableau (vous en aurez plus sur cette page, mon exemple en comportait 3). À chacune est associée un a qui est un objet puisqu’il est précédé d’une flèche (ou de Object dans Firefox). On peut donc l’examiner en cliquant sur la flèche (ci-dessous un extrait). Sans forcément s’attarder sur toutes les propriétes de l’objet, on peut constater la présence de propriétés qui nous indiquent que c’est un élément du DOM, telles que : • attributes : la liste des attributs qui ont été mis sur la balise HTML. • childNodes: les éventuels noeuds enfants de l’élément (ici un noeud de type text, le contenu textuel du lien, situé entre les balises ouvrante et fermante). • id: l’id de l’élément, sous forme d’une chaîne de caractères qui peut être vide. En plus des éléments, l’objet jQuery contient une propriété length qui contient le nombre d’éléments. Petite astuce, quand vous concevez un programme avec jQuery, et que “bizarrement” rien ne marche comme vous voulez, vous pouvez faire un console.log() de l’objet que vous avez créé avec un sélecteur. Si vous trouvez length valant 0 et aucun élément, vous avez pu faire une erreur dans votre sélecteur. . . On peut comparer le contenu de l’objet jQuery avec l’équivalent renvoyé par le DOM : À première vue, cet objet (qui est une collection d’éléments) ressemble beaucoup à l’objet jQuery, exception faite de la propriété prevObject présente dans l’objet jQuery. Si on examine les contenus des éléments dans cette structure, ce sont les mêmes que dans l’objet jQuery. Mais attention, souvenez-vous qu’en JavaScript, comme dans de nombreux 4
Figure 2: 5
Figure 3: langages dits “objet”, un objet possède des propriétés et des méthodes (fonctions). Pour voir les fonctions disponibles sur l’objet jQuery et sur l’objet DOM, toujours dans la console, vous pouvez cliquer sur la propriété __proto__ que chacun d’eux possède. Cela affiche la liste des fonctions que vous pouvez appeler sur l’objet, et on peut voir que ce ne sont pas les mêmes. Dernier point pour conclure cette section relativement complexe : malgré leurs propriétés numérotées 0, 1, 2, etc., ni l’objet jQuery, ni le résultat d’une fonction DOM getElementsBy***Name, ne sont des tableaux à proprement parler ! Ce sont des structures proches des tableaux, mais on ne peut pas utiliser des méthodes de Array dessus. Par contre, dans les deux cas, il reste possible de faire des boucles for ou while pour parcourir leurs éléments, comme on le ferait avec un tableau. Note pour les CGI : ce dernier point sera probablement plus clair quand vous aurez étudié plus en détail le JavaScript et ses différents types de données, dont les tableaux. 2.2 Les sélecteurs de base jQuery fournit d’abord trois types de sélecteurs équivalents aux fonctions du DOM permettant de récupérer un élément dans le document. Plus un autre pour rendre accessibles à un élément DOM les fonctions de jQuery. La documentation officielle de jQuery fournit une page dédiée aux sélecteurs. 2.2.1 Par ID Le sélecteur commençant par # suivi d’un id permet de cibler l’élément avec cet id. Il cible donc forcément un élément dans la page, l’id étant unique (sauf erreur de l’auteur du HTML). Voici un paragraphe HTML : J'aime les pains au chocolat. 6
Et ci-dessous, comment remplacer son contenu en JavaScript (DOM vs. jQuery). // DOM var element = document.getElementById('texte-polemique'); element.innerHTML = "Je n'aime pas les pains au chocolat."; // jQuery $('#texte-polemique').html("J'aime les chocolatines !"); À noter : • ce simple exemple (pour la partie DOM) suffit pour constater des différences entre les (vieux) navigateurs : les anciennes versions de Internet Explorer ne supportaient pas la modification directe du contenu d’un élément via innerHTML. • comme indiqué en fin de section 2.1 “l’objet jQuery”, si l’id indiqué dans le sélecteur ne correspond à rien d’existant dans la page, l’objet jQuery retourné sera vide. C’est évidemment valable aussi pour les sélecteurs que nous allons voir ensuite. • jQuery se sert, en interne, des fonctions du DOM pour aller chercher des éléments. 2.2.2 Par classe Un sélecteur commençant par . suivi d’un nom de classe CSS permet de cibler tous les éléments comportant cette classe. Apple iPhone X 1329,00 Enlever Samsung Galaxy S8 799,00 Enlever Et comment remplacer le texte des liens (par exemple pour traduire les liens en anglais): // DOM: noter le pluriel var links = document.getElementByClassName('link-remove'); for(var i = 0 ; i < links.length ; i++) { links[i].innerHTML = 'Remove'; } 7
// jQuery $('.link-remove').html('Remove'); 2.2.3 Par nom de balise (tag) Un sélecteur contenant un nom de balise HTML englobe tous les éléments de ce type. Apple iPhone X Samsung Galaxy S8 Sony Xperia XZ a.link-product { color: #55f; } // DOM var links = document.getElementByTagName('a'); for(var i = 0 ; i < links.length ; i++) { links[i].classList = 'link-product'; } $('a').addClass('link-product'); 2.3 Autres façons de construire un objet jQuery 2.3.1 À partir d’un élement ou d’une collection d’élements On peut passer en paramètre à $(...), au lieu d’un sélecteur CSS, un élément ou une collection. // Un élément var pageTitleElem = document.getElementById('title'); var pageTitleJquery = $(pageTitleElem); // Une collection var sectionTitleElems = document.getElementsByClassName('section-title'); var sectionTitlesjQuery = $(sectionTitleElems); À partir de code HTML On peut passer une chaîne de caractères contenant du HTML en paramètre : var numberedList = $('OneTwo'); 8
2.4 Les sélecteurs - suite 2.4.1 Sélecteur par attribut Le sélecteur suivant sélectionne tous les input de type text (excluant donc les autres types comme hidden, submit, etc.). Dans cet exemple on leur donne une bordure grise avec des coins arrondis : $('input[type="text"]').css({ border: '1px solid none', 'border-radius': '4px' }); Note : vous pouvez vous demander pourquoi, dans l’objet passé à .css(), la propriété border-radius est indiquée avec des guillemets, et pas border. C’est lié au langage JavaScript : le tiret - n’est pas valide dans un nom de propriété (contrairement au _ qui l’est). Sauf si on prend la précaution d’entourer la propriété avec des guillemets (simples ou doubles). 2.4.2 Filtres ou pseudo-sélecteurs Les filtres ou pseudo-sélecteurs, contrairement à tous ceux vus jusqu’ici (et à ceux des deux sections suivantes), sont particuliers, dans le sens où ils diffèrent de la syntaxe CSS. 2.4.2.1 Filtres par position Ces filtres permettent de restreindre une sélection, en fonction de la position des éléments de la sélection. • :first et :last permettent de sélectionner respectivement le premier et le dernier élément d’un ensemble • :even et :odd permettent de sélectionner respectivement les éléments d’indices pairs et impairs d’un emsemble. Attention, comme on compte à partir de 0, :even va matcher les éléments 0, 2, etc. (donc le premier, le troisième, etc.), ce qui n’est pas forcément intuitif. • :eq(n) permet de sélectionner l’élément se trouvant à la position n dans un ensemble (en comptant à partir de zéro comme très souvent en pro- grammation) A revoir 1ère ligne, 1ère cellule 1ère ligne, 2ème cellule 9
2ème ligne, 1ère cellule 2ème ligne, 2ème cellule 3ème ligne, 1ère cellule 3ème ligne, 2ème cellule 4ème ligne, 1ère cellule 4ème ligne, 2ème cellule 2.4.2.2 Pseudo-sélecteurs par type Ces pseudo-sélecteurs permettent de cibler des champs de saisie dans des for- mulaires. Ce sont parfois de simples raccourcis par rapport aux sélecteurs par attributs vus précédemment. • :checkbox sélectionne les input de type checkbox • :radio sélectionne les input de type radio • :text sélectionne les input de type text Voir l’exemple et la documentation jQuery pour la liste complète 2.4.2.3 Pseudo-sélecteurs par état Si les sélecteurs CSS permettent de cibler des éléments selon leur balise ou leurs attributs, ils ne permettent pas de le faire suivant un certain état, tel que “coché” (checked) pour un input[type="checkbox"] ou input[type="radio"], ou encore “sélectionné” (selected) pour un select. D’où ces filtres dans jQuery : • :checked pour cibler des checkboxes ou boutons radio cochés. • :selected pour cibler la ou les option(s) sélectionnée(s) dans un select. 2.4.2.4 Filtres de contenu Voir la référence Le filtre :contains() permet de cibler des éléments contenant un certain texte. Comme d’habitude on peut chaîner ce sélecteur avec d’autres. Dans cet exemple, seuls les items de la liste de films de science-fiction sont ciblés par le sélecteur. Blade Runner Interstellar Star Trek Star Wars 10
War of the Worlds Forrest Gump Lone Star The Star $("#liste-films-sf li:contains('Star')") .css('text-decoration', 'underline'); Le filtre :empty() permet de cibler des éléments vides (sans contenu d’aucune sorte, que ce soit du texte ou des éléments enfants). Enfant non vide $("div:empty").css('background', 'red'); Le filtre :has(selecteur) permet de cibler des éléments contenant un élément matchant le sélecteur donné en paramètre. C’est bel et bien différent d’utiliser seulement le sélecteur. Souligné Souligné + gras Italique + gras Italique + souligné $("#demo-filtre-has div:has('.gras')").css('background', '#ccc'); 2.4.3 Sélections “cumulées” L’utilisation de la virgule dans les sélecteurs permet de sélectionner plusieurs choses en même temps. On peut inclure des sélecteurs de types différents (par balise, id, classe, attribut, pseudo-sélecteurs. . . ). $('a,button') // tous les liens ET boutons $('#titre-page,.titre-section') // l'élément avec id titre-page + // ceux avec classe titre-section $('input[type="email"],:text') // les input de type email ET texte 11
2.4.4 Sélecteurs CSS composés L’utilisation de sélecteurs séparés par des espaces permet de restreindre de plus en plus la sélection $('div h2') // les h2 se trouvant à l'intérieur // d'une div, pas les autres $('#main .item') // les éléments avec la classe item, // à l'intérieur de #main $('#main div.item') // proche du précédent, mais restreint // aux div uniquement $('#main div .item') // DIFFERENT : éléments .item situés // DANS une div DANS #main Note pour les CGI Les deux dernières lignes de l’exemple précédent me permettent de reprendre une question posée pendant une de nos sessions : l’espace est-il important ? La réponse est définitivement oui. Quand vous voyez un sélecteur avec des espaces, lisez-le de droite à gauche: ici pour la 4ème ligne, on prend les .item à l’intérieur d’une div à l’intérieur de #main. Pour préciser la différence entre les 3ème et 4ème ligne : dans la 3ème, on cible les div avec classe item. Dans la 4ème, on cible les éléments avec classe item dans une div. Comme il est indiqué dans la documentation jQuery, il est préférable d’utiliser “le filtre le plus court qui fasse le job” : par exemple préférez #myTable th.special à #myTable thead tr th.special si le premier vous permet d’obtenir l’ensemble d’éléments que vous ciblez. 2.4.5 Plus de précision : cibler un élément enfant Il arrive qu’on ait besoin de plus de précision dans les sélecteurs. Par exemple, les pages web comportent souvent un grand nombre de div imbriquées. Le caractère > permet de choisir un élément qui est un enfant direct d’un autre. Paragraphe 1 Paragraphe 2 Paragraphe 3 Div très imbriquée Paragraphe 4 Paragraphe 5 12
$('#main > div') // div DIRECTEMENT sous #main .addClass('gray') $('.section > p') // paragraphes 2 et 4 seulement .addClass('blue') $('.section > div') // div contenant les paragraphes 2 et 4 .addClass('red') 2.4.6 Plus de précision : cibler un élément adjacent Un autre type de sélecteur permet de cibler un élément adjacent à un autre : Paragraphe 1 Paragraphe 2 Paragraphe 3 Div Paragraphe 4 $('p + p') // paragraphes précédés directement par un autre .addClass('red') 2.4.6 Conclusion sur les sélecteurs jQuery et CSS La plupart des sélecteurs jQuery - exceptés les pseudo-sélecteurs - utilise la syntaxe CSS. C’est le cas pour le suivant par exemple : $('.tabs a.active,nav a.active').css('font-weight', 'bold'); Dans le cas précédent cela ne saute pas forcément aux yeux, probablement parce qu’on n’y voit pas les accolades auxquelles on est habitué en CSS: .tabs a.active, nav a.active { color: #5db; } Pourtant c’est bien la même syntaxe. Que ce soit le chaînage de sélecteurs “inclusif” - en les séparant par des virgules, ou “restrictif” - en les séparant par des espaces, les deux syntaxes existent en jQuery comme en CSS. L’opérateur ‘>’ permettant de spécifier un enfant direct existe de même dans les deux cas. 13
3 La manipulation du DOM Pour rappel, le DOM ou Document Object Model, est l’arborescence de noeuds (éléments) que le navigateur construit à partir du HTML qu’il a interprété. jQuery permet de manipuler les éléments et leurs attributs avec une grande facilité. Il permet d’en ajouter, d’en supprimer, d’en déplacer, etc. De nombreuses fonctions sont fournies, certaines ayant des fonctionnalités similaires entre elles, avec todut de même des différences subtiles. 3.1 Insertion et manipulation à l’intérieur d’un élément La référence pour les fonctions décrites ci-dessous est disponible ici. Une des premières fonctions que nous avons vues (exemple de la section 2.2.1 sélection par ID) est .html(), permettant de lire ou d’écrire le contenu HTML d’un élément. Très important car la façon d’utiliser cette fonction est assez représentative d’une façon de faire omniprésente dans jQuery. Cette fonction fait partie des nombreuses qui font office à la fois de “getter” (terme savant pour désigner une fonction qui lit une propriété d’un objet) et de “setter” (fonction qui écrit une propriété). L’usage dépend du nombre d’arguments : // la variable contenuHtml récupère le contenu HTML de l'élément: var contenuHtml = $('#un-element').html(); // le contenu de l'élément est complètement remplacé: $('#un-element').html('Un titre de niveau 1'); Une fonction semblable est .text(), qui récupère seulement le contenu textuel d’un élément, oubliant donc les balises. // Suite de l'exemple précédent // contenuTexte contiendra la chaîne "Un titre de niveau 1" var contenuTexte = $('#un-element').text() Les fonctions .append() et .appendTo permettent d’ajouter du contenu à la fin de l’élément ciblé, soit après le dernier enfant actuel de cet élément. Si on prend le HTML suivant : 1er paragraphe Et qu’on effectue les appels suivants : $('#cible').append('2ème paragraphe'); $('3ème paragraphe').appendTo('#cible'); On aura après ces deux appels : 14
1er paragraphe 2ème paragraphe 3ème paragraphe Les fonctions ont quasiment la même fonctionnalité, mais notez que dans le JavaScript, les deux syntaxes diffèrent par l’ordre de la cible et du contenu à y ajouter. Personnellement la 1ère syntaxe me paraît plus intuitive, mais la seconde à un gros avantage, du moins si on souhaite aussitôt manipuler l’élément ajouté : on peut assigner son résultat à une variable, ou même directement chaîner des appels de fonctions jQuery à sa suite : var p = $('4ème paragraphe').appendTo('#cible'); p.css({ color: '#eae', backgroundColor: '#ddd' }); $('5ème paragraphe') .appendTo('#cible').css('color', '#aee'); Les fonctions .prepend() et .prependTo permettent d’ajouter du contenu au début de l’élément ciblé, soit avant le premier enfant actuel de cet élément. Comme leur nom l’indique, elles ont une fonctionnalité semblable, avec la même différence qu’entre .append() et .appendTo : .prependTo renvoie l’objet jQuery enveloppant le(s) élément(s) HTML qu’on a ajouté. 3.2 Insertion autour d’un élément Référence sur ce sujet ici Plusieurs fonctions permettent d’envelopper un ou plusieurs élément(s) avec du contenu HTML (attention, pas au sens d’envelopper où on l’entendait avec l’objet jQuery qui “enveloppe” un ou plusieurs élément(s) du DOM). La fonction .wrap() permet d’envelopper chaque élément d’un objet jQuery avec du HTML. Avec le HTML suivant : Hello Goodbye Et l’appel suivant : $('.niveau-2').wrap(''); On obtiendra ceci : Hello 15
Goodbye La fonction .unwrap() fait l’inverse : elle supprime le parent de chaque élément de l’objet jQuery sur lequel elle est appelée. Elle prend, en argument optionnel, un sélecteur qui permet de ne supprimer le parent que s’il correspond (match) au sélecteur. Voir l’exemple dans la “JS Sandbox” pour .wrap() et les deux façons d’appeler .unwrap(). La fonction .wrapAll() permet d’envelopper, contrairement à .wrap(), l’ensemble des éléments d’un objet jQuery, et non pas chaque élément individuellement. Avec le HTML suivant : Hello Goodbye Et l’appel suivant : $('.niveau-2').wrapAll(''); On obtiendra ceci : Hello Goodbye Enfin, .wrapInner() fonctionne d’une façon relativement similaire à ‘.wrap’, mais au lieu d’envelopper chaque élément d’un objet jQuery, elle enveloppe le contenu de chaque élément. Si on prend le même HTML que pour le 1er exemple, celui de ‘.wrap()’, et qu’on effectue l’appel suivant : $('.niveau-2').wrapInner(''); Alors il deviendra : Hello Goodbye 16
3.3 Insertion avant ou après un élément Référence sur cette section ici Des fonctions permettent d’ajouter du contenu avant ou après un élément, en dehors de celui-co contrairement aux fonctions vues en 3.1. On a notamment .after() et .before(), ainsi que .insertAfter() et .insertBefore(). Les différences entre .after() et .insertAfter(), ou entre .before() et .insertBefore(), sont les mêmes qu’entre par exemple .append() et .appendTo() vues précédemment. Milieu $('#milieu') .before('Presque au début'); $('Tout au début') .insertBefore('#presque-debut'); $('#milieu') .after('Presque à la fin'); $('Tout à la fin') .insertAfter('#presque-fin'); 3.4 Suppression d’éléments Référence sur cette section ici On va s’intéresser à .empty() (à ne pas confondre avec le filtre :empty vu précédemment) et à .remove(). .empty() supprime tous les noeuds enfants de chaque élément de l’objet jQuery : donc tout le contenu. .empty() supprime non pas le contenu des éléments matchés par le sélecteur, mais les éléments eux-mêmes. Un titre Un paragraphe Un titre Un paragraphe // enlève le h1 et le p de la div #des-trucs: $('#des-trucs').empty(); // enlève la div #dautres-trucs elle-même: $('#dautres-trucs').remove(); 17
À noter, car cela s’applique à jQuery, mais on trouve des notions similaires dans les autres librairies et frameworks front-end : quand on supprime des éléments auxquels on a attaché des listeners ou gestionnaires d’évènements (ou encore handlers), que deviennent-ils ? Eh bien, jQuery s’occupe de les supprimer pour nous, dès lors qu’on utilise ses fonctions de manipulation telles que .html(), .remove(), ou encore .replaceWith(). Et heureusement : car chaque listener prend des ressources (au moins de la mémoire). Imaginons qu’on manipule une page avec beaucoup d’éléments (par exemple une longue liste d’images à la Instagram), et que chacun de ces éléments ait un listener. Dans le cadre d’une Single-Page App, où le navigateur ne recharge jamais la page, si on est amené à quitter fréquemment cette page (entraînant la destruction de ses éléments) et à y revenir, on pourrait se retrouver rapidement avec des tas de listeners “fantômes”, qui ne sont plus rattachés à aucun élément. Si on n’utilise pas jQuery mais l’API DOM native du navigateur, celui-ci s’occupe normalement de supprimer les listeners devenus inutiles : à la condition toutefois qu’il n’y ait plus aucune référence nulle part (par exemple dans une variable) à l’élément auxquel ils sont attachés. C’est le cas des navigateurs modernes du moins, mais pendant longtemps, des fuites de mémoire dûes notamment à des gestionnaires d’évènements non supprimés, affectaient certains navigateurs. 3.5 Remplacement d’élément La fonction .replaceWith() remplace non pas le contenu d’un élément, mais l’élément lui-même. Pour reprendre l’exemple de la doc jQuery : Hello And Goodbye $( "div.second" ).replaceWith( "New heading" ); Après l’appel à .replaceWith(), on obtient : Hello New heading Goodbye 3.6 Manipulation des attributs et propriétés classiques On a vu pour l’instant la manipulation des tags eux-mêmes. jQuery offre aussi des fonctions pour manipuler les attributs et propriétés d’un élément. Les 18
fonctions principales pour cela sont .attr() et .prop(). Comme pour d’autres fonctions, elles sont à la fois “getter” et “setter” : • elles renvoient la valeur de l’attribut ou propriété demandé, si appelées avec le seul nom de l’attribut en paramètre • elles remplacent la valeur de l’attribut ou propriété, si on leur passe le nom de l’attribut suivi de sa nouvelle valeur Il y a d’autres façon de les appeler, et une nouvelle fois, pour plus de détails, référez-vous à la section correspondante de la doc jQuery. Ces fonctions sont assez similaires mais ont des différences notables. Les deux vont faire la même chose si vous les utilisez, par exemple, pour récupérer un attribut id ou href. Alors, quand utiliser l’un ou l’autre ? En gros, il faut utiliser .prop() si on manipule des attributs d’état tels que checked, selected, disabled, etc. Là où .attr('checked') sur une checkbox renverra "checked"`` si elle est cochée, etundefinedsinon,.prop(‘checked’)‘ vous renverra un état clair et précis sous forme d’un booléen. Un autre exemple que vous pouvez tester dans la “Sandbox” : var theChoice = $('#the-choice'); var the2ndChoice = $('#the-2nd-choice'); // Similaire console.log(theChoice.attr('id')); // renvoie "the-choice" console.log(theChoice.prop('id')); // renvoie "the-choice" // Différent console.log(theChoice.attr('checked')); // renvoie "checked" console.log(theChoice.prop('checked')); // renvoie true // Différent console.log(the2ndChoice.attr('checked')); // renvoie undefined console.log(the2ndChoice.prop('checked')); // renvoie false var the3rdChoice = $('input[type="checkbox"]:eq(2)'); the3rdChoice.attr('id', 'the-3rd-choice'); the3rdChoice.prop('checked', true); the3rdChoice.after( ' J\'aime les pâtes' ); console.log(the3rdChoice.attr('id')); // renvoie "the-3rd-choice" Une autre différence entre .attr() et .prop() peut s’observer en prenant le href d’un lien. .attr() va renvoyer la valeur “brute” telle qu’indiquée dans 19
l’attribut, alors que .prop() va renvoyer l’URL construite en combinant l’URL de la page courante et la valeur de href. Si celle-ci est un lien relatif (sans / au début), .prop() va construire l’URL absolue. C’est un cas où on peut préférer utiliser .attr() : être sûr que ce qu’on récupère est exactement ce qu’on avait indiqué. 3.7 Manipulation des attributs data Outre les attributs “standard” et habituels tels que id, src, href, la norme HTML5 permet d’associer aux balises des attributs nommés arbitrairement, en étant toutefois précédés de data-. On peut y stocker tout ce qu’on veut, pourvu que ce soit une string (si ce que vous souhaitez stocker dans un attribut data est par exemple un objet, vous pouvez le “sérialiser” dans une chaîne JSON grâce à JSON. stringify()). jQuery offre la fonction .data() qui, comme précédemment, fait office de getter (si appelée avec un argument) ou de setter (avec deux). Questions essentielles Des questions que tout le monde se pose // Cela pourrait être récupéré d'un fichier JSON externe. var translations = { "fr": { "the_title": "Questions essentielles", "the_subtitle": "Des questions que tout le monde se pose" }, "en": { "the_title": "Essential questions", "the_subtitle": "Questions everyone's asking oneself" } }; $('#lang-selector a').click(function(e) { e.preventDefault(); // Supprimer le # du début. Notez qu'ici on utise .attr(), // car on ne veut pas l'URL absolue qui aurait été // calculée avec .prop(). var lang = $(this).attr('href').substr(1); // Démontre une autre fonction: .each() $('[data-translate]').each(function(index, elem) { var jqElem = $(this); var translateKey = jqElem.data('translate'); jqElem.html( translations[lang][translateKey] ); 20
}); }); Quelques explications : • le sélecteur '[data-translate]' permet de choisir tous les éléments ayant un attribut data-translate (quelle que soit la valeur associée). • la fonction .each() fait une boucle : fonction passée en paramètre est ap- pelée pour chaque élément de l’objet jQuery, avec index et elem changeant à chaque itération • à noter, dans le corps de cette fonction, item et this ont la même valeur. • à partir de la valeur lang (en ou fr), et de la valeur de l’attribut data-translate, on sait quelle traduction aller chercher dans l’objet translations. 4 Propriétés de style Voir la documentation sur cette partie. 4.1 Récupérer ou modifier les propriétés CSS La fonction .css() est à nouveau un exemple de getter/setter. Cependant, son fonctionnement n’est pas aussi systématique que pour les autres fonctions getter/setter vues jusqu’ici. • Appelée avec un argument de type string, elle renvoie la propriété CSS correspondante • Appelée avec deux arguments de type string, elle remplace la propriété CSS dont le nom est passé comme premier argument, par la valeur passée comme deuxième argument. • Appelée avec un argument de type object, elle parcourt les paires “clé- valeur” de l’objet, et attribue à la propriété “clé” la “valeur” associée. Comme getter : renvoie la valeur de la propriété CSS Un paragraphe en bleu Valeur de la propriété color: Comme setter : modifie la valeur de la propriété CSS Un paragraphe sans style de base Valeur de la propriété color après modif. : Comme setter : modifie les valeurs de plusieurs propriétés CSS Un paragraphe sans style de base 21
var colorFirst = $('p:first').css('color'); $('p:first + div > span').html(colorFirst); $('p:eq(1)').css('color', 'green'); var colorSecond = $('p:eq(1)').css('color'); $('p:eq(1) + div > span').html(colorSecond); var properties = { color: 'red', backgroundColor: '#bbb', fontSize: '130%', padding: '10px', 'font-weight': 'bold' }; var thirdParagraph = $('p:eq(2)'); thirdParagraph.css(properties); for( var key in properties ) { var value = thirdParagraph.css(key); console.log( key, value ); var texte = 'Valeur de la propriété ' + key + ' après modification: '; var div = $( texte ).insertAfter( thirdParagraph ); div.find('span').html( value ); } Remarquez qu’on peut utiliser indifféremment, pour les noms des propriétés, la syntaxe CSS native (avec tirets -) ou la syntaxe “camelCase”. jQuery supporte les deux. L’intérêt d’utiliser la syntaxe camelCase se révèle pour la 3ème utilisation de la fonction, où on passe un objet en argument. JavaScript génère une erreur si on met un - dans une clé d’objet, sauf si on prend la précaution d’entourer la clé par des quotes simples ' ou doubles ". Cela peut alourdir un peu le code et le rendre moins lisible, ce qui est résolu par l’utilisation de camelCase. 4.2 Propriétés CSS dynamiques Par là, on entend des propriétés qui sont susceptibles d’être modifiées pendant la durée d’exécution du programme. Et modifiées, pas seulement par le programme lui-même, mais du fait de l’interaction de l’utilisateur. Par exemple, des éléments dont les propriétés CSS width et height n’ont pas été indiquées “en dur” avec une valeur en pixels, peuvent voir cette propriété varier si l’utilisateur redimensionne la fenêtre en cours. jQuery fournit des raccourcis pratiques pour lire ou modifier ces propriétés. 22
.width() et .height() sont encore des getter/setter simples (pas d’argument : lecture, un argument : modification), qui comme leur nom l’indique retournent ou modifient la largeur et la hauteur du ou des élément(s) ciblé(s). .innerWidth() et .innerHeight() sont des variantes, qui prennent en compte non seulement la largeur ou hauteur intrinsèque de l’élément, mais aussi sa propriété padding, en omettant border. .outerWidth() et .outerHeight() incluent les propriétés padding et border de l’élément, ainsi que margin si elle est spécifiée. Container Inner 1 Inner 2 On va attribuer des styles différents aux deux div.inner, pour voir les différences entre les résultats des différentes fonctions. div { padding: 10px; } .wrapper { background: #999; } .inner { margin-bottom: 20px; } .wrapper div:nth-child(2) { background: #bef; margin-left: 40px; } .wrapper div:nth-child(3) { background: #efb; border: 10px solid #bbb; } Le JavaScript se corse un peu. . . Ici on utilise des constructions un peu complexes, mais courantes. L’explication est juste après le code ! var divsInner = $('.inner'); divsInner.each(function() { var div = $(this); var functions = [ 'width', 'innerWidth', 'outerWidth', 'height', 'innerHeight', 'outerHeight' ]; 23
// On va construire une liste précédée d'un titre var divTitle = div.html(); // Tag d'ouverture de la liste var resultHtml = 'Div: ' + divTitle + ''; functions.forEach( function( funcName ) { // A la 1ère itération, funcName vaut width, // puis innerWidth à la 2nde, etc. // On va se servir de ce funcName pour // savoir quelle fonction appeler ! var result = div[funcName](); resultHtml += '.' + funcName + '() renvoie: '+ result + ''; }); // Ne pas oublier les tags de fermeture resultHtml += ''; // On ajoute le tout à la div de résultats. $('#results').append(resultHtml); }); Parfois on veut exécuter un certain nombre de fonctions (ou récupérer un certain nombre de propriétés) sur des objets. Plutôt que d’écrire individuellement chaque appel “en dur”, ici on itère sur les noms des fonctions à appeler (tableau functions). Pour chaque nom de fonction dans ce tableau, on va exécuter cette fonction sur l’objet jQuery div. C’est cette ligne qui fait ce tour de magie : var result = div[funcName](); Ici on prend la propriété de div indiquée par funcName (qui vaut successivement width, innerWidth, etc.). Cette propriété est ici une fonction, et pour l’appeler il faut indiquer les parenthèses, comme on le ferait pour un appel de fonction “normal”. Ensuite on sauve le résultat de l’appel dans la variable result. 24
Vous pouvez aussi lire