Ça fait déjà un bon bout de temps que je veux faire ce tutoriel, ou plutôt ce document de référence. Quoi de plus insolite (voire inutile pour les vieux webmestres comme moi, qui ont vu le web d’avant jQuery) que de tomber sur des codes de rien du tout qui requièrent néanmoins une vaste librairie pour s’exécuter! J’ai bien compris le besoin et même abdiqué au besoin pour passer moi-même à jQuery. jQuery a permis de populariser moult bidules, menu déroulant, accordéon, boîte de dialogue et son fameux slider. Que dire des ses nombreux effets.
Mais c’est surtout pour la portabilité et la compatibilité que jQuery était dur à battre. Il a aussi pour un temps palier à un manque de flexibilité du CSS3. J’irais jusqu’à dire que jQuery est derrière la motivation de plusieurs nouveautés du CSS3. Il faut aussi dire pour sa défense qu’il est utilisé à peu près partout. Et c’est quant à moi un défaut. Si les standards sont indispensables en programmation, les monopoles sont quant à eux dangereux.
C’était avant le duo CSS3 / HTML5!
Le paysage du Web script est en train de prendre un nouveau virage. Ou plutôt de revenir au chemin d’origine. On n’est pas encore à remplacer la librairie des anciens projets mais on peut recommencer à créer des sites Web en pure Javascript en utilisant uniquement les outils dit natif du navigateur, sans devoir charger d’immense librairie quasi inutile dans la grande majorité des cas... Non seulement le CSS3 à rendu accessible un grand nombre d’effets et de fonctionnalités qui compense largement jQuery. Plusieurs méthodes ou raccourcies historiquement associées à jQuery sont maintenant possibles avec le nouveau Javascript du HTML5. Le ECMAScript 5. Par exemple le sélecteur querySelectorAll
. Personnellement j’adore l’objet classList
pour manipuler les classes...
Compatibilité
Navigateurs avec le support ECMAScript 5 (ES5) : Firefox 4, Chrome 19, Safari 6, Opera 12.10 et Internet Explorer 10.
// IE9 est aussi compatible mais seulement en « mode strict ». // JavaScript "use strict"; // Restrictions au niveaux des variables globales // des nombres octaux, de la méthode eval()... // À éviter avec des librairies. Sinon, vaut mieux s’y préparer! // Exemple pour s’assurer qu’une méthode est compatible : <script> if (document.querySelector){ // Compatible } else { // Incompatible } </script>
Simple, puissant et gratuit. Mais :
- Grosse librairie, lourde;
- Trop souvent inutile quand on n’a besoin d’un seul livre;
- À la merci d’un tierce partie, jQuery Foundation;
- Plusieurs équivalents en pure CSS3 alors que le Javascript se désactive;
- Et surtout, à la merci de moteurs externes au navigateur. Question performance, je préfère de loin les outils natifs du navigateur.
Comparaison
Vous n’êtes pas encore convaincu? Jetez un œil sur cette comparaison des plus éloquentes par Craig Buckler. Notamment la méthode document.getElementsByClassName
et son score tout à fait spectaculaire jusqu’à 60 fois plus rapide!
Code | Duré |
---|---|
// jQuery 2.0 var c = $("#comments .comment"); |
4,649 ms |
// jQuery 2.0 var c = $(".comment"); |
3,437 ms |
// native querySelectorAll var c = document.querySelectorAll("#comments .comment"); |
1,362 ms |
// native querySelectorAll var c = document.querySelectorAll(".comment"); |
1,168 ms |
// native getElementById / getElementsByClassName var n = document.getElementById("comments"); var c = n.getElementsByClassName("comment"); |
107 ms |
// native getElementsByClassName var c = document.getElementsByClassName("comment"); |
75 ms |
Voilà donc une liste d’équivalent Javascript fort pratique, près à l’emploi :
Un des exemples les plus rependu est le sélecteur jQuery qui a l’avantage d’être simple, et de régler plusieurs détails. Contrairement au nœud ou la liste de nœuds du DOM retourné par le Javascript. Via l’ID, la classe ou la balise. Notez que jQuery utilise un moteur de sélecteur (Sizzle) qui double le travail déjà effectué par le navigateur! On connait déjà :
// jQuery $("#monID").get(0); // Retourne seulement le premier nœud // JavaScript document.getElementById('monID'); // Retourne seulement le premier nœud // jQuery $('.maClasse'); // JavaScript document.getElementsByClassName('maClasse'); // Retourne une liste de nœuds // jQuery $('div'); // JavaScript document.getElementsByTagName('div'); // Retourne une liste de nœuds
On peut d’ailleurs simuler le populaire sélecteur jQuery « $ » : (voir l’exemple de Jeffrey Way pour une solution complète)
// JavaScript var $ = function(el) { return document.querySelectorAll(el); }; // Usage $('.maClasse');
Sélecteur querySelector / querySelectorAll
Liste de nœuds statiques
Un staticNodeList est une collection statique des éléments du DOM. Statique puisqu’elle n’est pas affecté par les changements dynamiques apportés sur l’arbre du document, comme la suppression, l’ajout et la modification d’un de ces éléments.
Entièrement natif, plus robuste et beaucoup plus rapide, les méthodes querySelector()
et querySelectorAll()
permet de saisir un sélecteur CSS via un paramètre et retourner les éléments sélectionnés comme des nœuds du DOM. La méthode querySelector
cherche dans l’ensemble ou dans une partie du document et retourne seulement le premier nœud de la staticNodeList (ou null).
La puissante méthode querySelectorAll() se comporte exactement comme querySelector() à l’exception qu’elle ne retourne pas seulement le premier élément qui correspond au sélecteur CSS spécifié par le paramètre, mais la liste des nœuds au complet comme un « staticNodeList » avec la propriété length
pour obtenir le nombre d’éléments sélectionnés.
Compatible Firefox 3.1+, IE8+ (seulement en mode standard) et Safari 3.1+.
querySelector
// JavaScript 5 // Retourne la première instance du ID « monID » document.querySelector('#monID'); // Retourne la première instance de la classe « maClass » document.querySelector('.maClass'); // Retourne la première instance de la balise P avec la classe class="petit" document.querySelector('p.petit'); // Retourne la première instance de le premier élément de type img (image) à l’intérieur du "#conteneur" document.querySelector('#conteneur img:nth-of-type(1)'); // Retourne la première instance coché des boîtes à cocher à l’intérieur du "#conteneur" document.querySelector('#conteneur input[type="checkbox"]:checked'); // Retourne le formulaire dont l’action est action="form.asp" document.querySelector('form[action="feedback.php"]'); // Retourne la première image dont l’adresse (src) débute par "http" document.querySelector('img[src^="http"]'); // Retourne la première image dont l’adresse (src) se termine par ".gif" document.querySelector('img[src$=".gif"]'); // Retourne la dernière instance d’une liste à puce. document.querySelector('ul.maListe li:last-child'); // Retourne la première instance de l’élément #evenement ou #galerie // selon celui trouvé en premier dans l’arbre du document document.querySelector('#evenement, #galerie');
querySelectorAll
// Retourne la liste de nœuds des instances de la classe « maClass » document.querySelectorAll('.maClass'); // Retourne la liste de nœuds des instances de la balise « div » document.querySelectorAll('div'); // Retourne la liste de nœuds « sélectionnés » des options de chaque élément SELECT document.querySelectorAll('option[selected="selected"]'); // Retourne la liste de nœuds des premières cellules du tableau #monTableau document.querySelectorAll('#monTableau tr>td:nth-of-type(1)'); // Retourne la liste de nœuds de tous les hyperliens qui débute par "http" document.querySelectorAll('a[src^="http"]'); // Retourne la liste de nœuds de tous les hyperliens qui contient la chaine "trucsweb" document.querySelectorAll('a[href*="trucsweb"]'); // Retourne la liste de nœuds des éléments #evenement et #galerie (inclusivement) document.querySelectorAll('#evenement, #galerie'); // jQuery « find » $(’#conteneur’).find('li'); // JavaScript document.querySelectorAll('#conteneur li'); // JavaScript beaucoup plus rapide et surtout compatible CSS2.1 (IE8) document.getElementById('conteneur').getElementsByTagName('li');
Todd Motto suggère même une petite fonction _ pour conforter les accros au jQuery. Je passe mon tour ;-)
var _ = function ( elem ) { return document.querySelectorAll( elem ); } // Utilisation var oMaClasse = _('.maClasse');
La méthode getElementsByClassName
Il y a aussi une nouvelle méthode à signaler quand il s’agit de recueillir facilement des éléments du document HTML. La méthode getElementsByClassName()
, native du navigateur, est encore plus rapide. Comme son nom l’indique, cette méthode renvoie une collection d’élément(s) en fonction de leur nom de classe partagée. Il est préférable d’utiliser cette méthode au lieu des méthodes document.querySelectorAll()
/ document.querySelectorAll()
lorsque vous désirez obtenir seulement la liste d’éléments identifiés par une classe. Compatible FF3, Opéra 9.5 et Safari 3. Malheureusement, IE8 n’est pas compatible.
// Retourne les éléments de classe "maClasse" document.getElementsByClassName("maClasse"); // Retourne les éléments de classe "maClasse" et "taClasse" (inclusivement) document.getElementsByClassName("maClasse taClasse");
Manipulation du « Document object model » (DOM)
Rien de nouveau à ce niveau, d’ailleurs jQuery utilise les méthodes natives du JavaScript se contentant de simplifier les instructions à la manière de la petite fonction de Todd Motto (voir plus haut).
// Ajout de contenu HTML au DOM // jQuery utilise la méthode innerHTML $("#conteneur").append("<p>Paragraphe ajouté</p>"); // Javascript document.getElementById("conteneur").innerHTML += "<p>Paragraphe ajouté</p>"; // Ou mieux encore pour l’amateur du XML en moi, quoique plus complexe. Travailler directement sur les nœuds de l’arbre du document. var p = document.createElement("p"); p.appendChild(document.createTextNode("Paragraphe ajouté"); document.getElementById("conteneur").appendChild(p); // Ajout de contenu HTML à un élément précis // jQuery $("<div id=boite></div>").appendTo('body'); // Rien de nouveau en JavaScript var div = document.createElement("div"); div.id = "boite"; document.body.appendChild(div); // Suprimer un nœud du DOM avec jQuery $("#conteneur").empty(); // En JavaScript document.getElementById("conteneur").innerHTML = null; // ou var c = document.getElementById("conteneur"); while (c.lastChild) c.removeChild(c.lastChild); // ou (pour un seul nœud. document.getElementById("conteneur").innerHTML = ""; // Supprimer tous les élément d’un conteneur. // Avec jQuery $("#conteneur").remove(); // En JavaScript: var c = document.getElementById("conteneur"); c.parentNode.removeChild(c); // Ciblage // jQuery $("#conteneur").siblings(); // En JavaScript var c = document.getElementById("conteneur"); Array.prototype.filter.call(c.parentNode.children, function(child){ return child !== c; }); // Ajouter du texte // Avec jQuery $("#conteneur").text("Chaine de caractère"); // En JavaScript var c = document.getElementById("conteneur"); c.textContent = "Chaine de caractère"; // Ajouter du HTML // Avec jQuery $("#conteneur").html("Chaine de caractère"); // En JavaScript c.innerHTML = "Chaine de caractère"; // Remplace du HTML // Avec jQuery $("#conteneur").replaceWith("Chaine de caractère"); // En JavaScript var c = document.getElementById("conteneur"); c.outerHTML = "Chaine de caractère"; // Nœud précédent // Avec jQuery $("#conteneur").prev(); // En JavaScript var c = document.getElementById("conteneur"); c.previousElementSibling; // Nœud suivant // Avec jQuery $("#conteneur").next(); // En JavaScript var c = document.getElementById("conteneur"); c.nextElementSibling; // Nœud parent // Avec jQuery $("#conteneur").parent(); // En JavaScript var c = document.getElementById("conteneur"); c.parentNode; // Position x/y // Avec jQuery $("#conteneur").position(); // En JavaScript var c = document.getElementById("conteneur"); {left: c.offsetLeft, top: c.offsetTop} // Vider // Avec jQuery $("#conteneur").empty(); // En JavaScript var c = document.getElementById("conteneur"); c.innerHTML = "";
Manipulation de classes avec le nouvel objet HTML5 « classList »
Compatible Firefox 3.6, Opera 11.50, Chrome 8, IE10 et Safari 5.1. Sinon, il existe deux scripts pour étendre la compatibilité à IE8 et Android 2.1
- classList.js by Eli Grey
- Minimal "classList" shim by Devon Govett
Petite fonction pour vérifier le support « classList » par le navigateur :
if("classList" in document.createElement("a")) { // compatible classList }
Ajouter une classe
// jQuery
$('div').addClass('maClasse');
// JavaScript 5
var div = document.querySelector('div');
div.classList.add('maClasse');
Supprimer une classe
// jQuery
$("div").removeClass("maClasse");
// JavaScript 5
var div = document.querySelector("div");
div.classList.remove("maClasse");
Contient une classe ou non
// jQuery
$("div").contains("maClasse");
// JavaScript 5
var div = document.querySelector("div");
div.classList.contains("maClasse");
// Exemple
if(div.classList.contains("maClasse") {
div.classList.remove("maClasse");
} else {
div.classList.add("maClasse");
}
Interchanger une classes (toggle)
// jQuery
$('div').toggleClass('maClasse');
// JavaScript 5
// Ajoute ou supprime la classe si elle est présente ou non.
var div = document.querySelector('div');
div.classList.toggle('maClasse', false);
// Le deuxième paramètre, pas toujours supporté
// Force ou non l’ajout de la classe, si elle existe déjà.
Interchanger deux classes (switch)
// jQuery $('div').switchClass( "maClasse1", "maClasse2" ); // JavaScript // Il n'existe malheureusement pas d'équivalent de la méthode .switchClass en Javascript. // Premier exemple var div = document.querySelector("div"); div.classList.remove("maClasse1"); div.classList.add("maClasse2");
Vous n’avez qu’une seule classe à interchangeable? rien ne vaut la bonne vielle méthode .className
, un doux plaisir Script. Pourquoi s’en passer!
// JavaScript
var div = document.querySelector("div");
div.className = (div.className=="maClasse1") ? "maClasse1" : "maClasse1";
Nombre de classe
// jQuery $('div').length(); // JavaScript 5 var div = document.querySelector('div'); div.classList.length; // Pour récupérer directement une classe // du classList il y a la méthone « item ». var div = document.querySelector('div'); div.classList.item(x);
Modifier une propriété d’une classe.
// jQuery $('.maClasse').css('color', 'red'); // Javascript [].forEach.call( document.querySelectorAll('.maClasse'), function(el) { el.style.color = 'red'; });
On utilise [].forEach.call()
pour vraiment simuler la méthode jQuery qui boucle tous les éléments de la classe « maclasse ».
// La même chose plus compatible (sans forEach)
var oMaClasse = document.getElementsByClassName('maClasse'),
i = oMaClasse.length;
while ( i-- > 0 && (oMaClasse[i].style.color = 'red') );
// Ou
for(var i=0, len = oMaClasse.length; i<len; i++) {
oMaClasse[i].style.color = 'red';
}
Avant jQuery on procédait en modifiant le nom de la classe en manipulant la chaîne de caractère. Ce qui devenait vite complexe lorsqu’un élément avait plus d’une classe. Voir les fonctions « addClass, removeClass, toggleClass » de Jeffrey Way pour normaliser la manipulation de classe avec les vieux navigateurs et ce avec des expressions régulière. Notez qu’il est préférable d’utiliser une bonne vielle boucle JavaScript, plus performant que les expressions régulière...
Justement, jQuery ne fait aucune différence entre une seule instance ou plusieurs. Alors que les méthodes natives en Javascript n’affectent que la première instance. Par exemple pour modifier l’ensemble des éléments d’une même classe, c’est la même instruction en jQuery :
// jQuery, affecte toutes les instances. var oMonObjet = $('.maClasse'); oMonObjet.addClass('autreClasse'); // JavaScript, affecte seulement la première instance. var oMonObjet = document.querySelector('.maClasse'); oMonObjet.classList.add('autreClasse'); // JavaScript, affecte toutes les instances du « NodeList ». var oMonObjet = document.querySelectorAll('.maClasse'); for (var i = 0; i < oMonObjet.length; i++) { oMonObjet[i].classList.add('autreClasse'); }
Manipulation des attributs
Fouillez moi, pourquoi on passe ici directement pas la méthode sans passer au préalable par un classList
ou plutôt un AttrList
? Mais aussi simple que le jQuery et d’ailleurs plus sémantique. À faire rougir jQuery :
Modifier un attribut
// jQuery $('.maClasse').attr('disabled', true); // JavaScript document.querySelector('.maClasse').setAttribute('disabled', true);
Supprime un attribut
// jQuery $('.maClasse').removeAttr('disabled'); // JavaScript document.querySelector('.maClasse').removeAttribute('disabled');
Récupère un attribut
// jQuery $('.maClasse').attr('title') // JavaScript document.querySelector('.maClasse').getAttribute('title') // Ou // jQuery $(’[monAttribue="valeur"]’); // JavaScript document.querySelectorAll(’[monAttribue="valeur"]’);
Attributs personnalisés « Data-* »
Une nouveauté HTML5 très pratique. Vous avez peut-être été frustré à répétition par le validateur de la W3C en utilisant des attributs en XHTML strict à des fins de manipulation interne en JavaScript. C’est terminé avec le nouvel attribut personnalisable Date-*
. Exemple :
<div class="edimestre" data-nom="Oznog" data-fonction="Analyste-programmeur"> Contenu visible </div> <script> // jQuery alert($('.edimestre').data('nom')); // JavaScript alert(document.querySelector('.edimestre').getAttribute('data-nom')); </script>
Par Pseudo-classe
Sélectionner chaque champ de saisie d’un formulaire qui est invalide.
// jQuery
$('#monFormulaire :invalid');
// JavaScript (IE 10+)
document.querySelectorAll('#monFormulaire :invalid');
Enfants
Sélectionner tous les enfants d’un élément.
// jQuery $('#monParent').children(); // JavaScript (avec « comment » et nœud texte) document.getElementById('myParent').childNodes; // ou // JavaScript (IE 9+) (sans « comment » & nœud texte) document.getElementById('monParent').children;
Sélectionner un enfant à partir de l’attribut « ng-click ».
// jQuery
$('#monParent').children('[ng-click]');
// ou
$('#monParent > [ng-click]');
// JavaScript
document.querySelector('#monParent > [ng-click]');
Descendants
Trouver toutes les ancres sous #monParent.
// jQuery
$('#monParent A');
// JavaScript
document.querySelectorAll('#monParent A');
Éléments exclus
Sélectionner tous les éléments <div>
sauf ceux avec la classe CSS « maClasse ».
// jQuery
$('DIV').not('.maClasse');
// ou
$('DIV:not(.maClasse)');
//JavaScript (IE 9+)
document.querySelectorAll('DIV:not(.maClasse)');
Ray Nicholus propose ce petit protptype pour trouver les sélecteurs.
window.$ = function(selector) { var selectorType = 'querySelectorAll'; if (selector.indexOf('#') === 0) { selectorType = 'getElementById'; selector = selector.substr(1, selector.length); } return document[selectorType](selector); };
Le nouveau module « dataset API » du HTML5 est assez bien supporté. Combiné aux nouveaux sélecteurs que dire maintenant de la manipulation JSON!
Manipulation JSON (JavaScript Object Notation)
Wow, je suis épaté ici, faut dire que le gros de la job est fait par l’objet JSON
et sa méthode parse
. Je ne suis pas un fan du JSON que j’attribue au manque de support XML historique du PHP (avant PHP5). C’est en fait une technique du siècle dernier, dérivé du format CSV. Le seul et unique avantage du JSON c’est la simplicité de son poids plume, à part les accolades et les double grouillement c’est des données, que des données. Sinon personnellement ma drogue à moi c’est le XML mur à mur. Bon, le PHP mais surtout à cause du AJAX (tout comme l’ActionScript) qui l’utilise abondamment, c’est quand même du « JavaScript Object Notation ». Mais cette fois à cause de jQuery et probablement de l’équipe Google qui le préfère visiblement au XML largement négligé justement à cause du JSON. Je serais curieux de savoir ce que Tim Berners-Lee en pense, ou encore ce que l’équipe Netscape en dirait. Dire que le XML (c’est à dire extensible!) est bien supporté par les navigateurs modernes.
Enfin, ce n’est pas toujours facile d’extraire des données du JSON. Voilà donc une méthode fort utile pour manipuler du JSON. Pour reprendre l’exemple de l’attribut personnalisé « Data-* » :
//Exemple de chaine JSON { 'nom' : 'Oznog', 'fonction' : 'Analyste-programmeur' } <div class="oJSON" data-edimestre="{ 'nom' : 'Oznog', 'fonction' : 'Analyste-programmeur' }"> Contenu visible </div> <script> // jQuery var monElement = $('.oJSON').data('edimestre'); var monJSON = $.parseJSON(monElement); // Ici le miracle! alert(monJSON.nom); // Retourne : Oznog alert(monJSON.fonction); // Retourne : Analyste-programmeur // JavaScript var monElement = document.querySelector('.oJSON').getAttribute('data-edimestre'); var monJSON = JSON.parse(monElement); // Ici le miracle sans jQuery! alert(monJSON.nom); // Retourne : Oznog alert(monJSON.fonction); // Retourne : Analyste-programmeur </script>
Que dire de plus, fantastique!
Manipulation des événements
Une fonctionnalité jQuery compacte, très pratique, notamment au niveau de la compatibilité. Mais en JavaScript il suffit aussi d’utiliser la méthode addEventListener
. Voilà un exemple en bouclant tous les hyperliens du document.
// Le très simple jQuery $('a').on('click', maFonction); // Ou $('a').on('click', function () {...}); // Le simple JavaScript document.querySelector('a').onclick = function () {...}; // jQuery $('#monFormulaire').on('submit', function () {...}); // JavaScript document.querySelector('#monFormulaire').onsubmit = function () {...}; // jQuery $('#monChamp').on('change', function () {...}); // JavaScript document.querySelector('#monChamp').onchange = function () {...};
Malheureusement le JavaScript n’affecte que la première instance. Contrairement au jQuery qui affecte toutes les occurrences du document. Pour ce faire il faut donc utiliser le sélecteur querySelectorAll
et boucler le résultat avec forEach
.
// Javascript
[].forEach.call( document.querySelectorAll('a'), function(el) {
el.addEventListener('click', function() {
// Clic sur un hyperlien
// NOTE : Référence de l’objet avec « this »
}, false);
});
// Exemple simplifié avec la fonction « addEvent » de
// Jeffrey Way
var oLiens = document.getElementsbyTagName('a');
addEvent(oLiens, 'click', maFonction);
// Plus tordu, ajouter un événement aux items d’une liste à puces mais uniquement sur les hyperlien.
// jQuery
$('ul').on('click', 'a', maFonction);
// JavaScript
// Cette fois avec la nouvelle méthode matchesSelector
// pour déterminer la cible (target)
var oListeApuces = document.getElementsByTagName('ul');
oListeApuces.addEventListener('click', function(e) {
if ( e.target.matchesSelector('ul a') ) {
// Clic sur un hyperlien de la liste à puce.
}
}, false);
// Exemple simplifié avec la fonction « matches » de
// Jeffrey Way
var oListeApuces = document.getElementsByTagName('ul');
document.addEventListener('click', function(e) {
if ( matches.call( e.target, 'ul a') ) {
// Clic sur un hyperlien de la liste à puce.
}
}, false);
// Exemple sans « match » avec simplifié avec la fonction
// « addEvent » de Jeffrey Way
var oListeApuces = document.getElementsByTagName('ul');
addEvent(oListeApuces, 'click', function() {
var target = e.target || e.srcElement;
if ( target && target.nodeName === 'A' ) {
// Clic sur un hyperlien de la liste à puce.
}
});
Ce dernier exemple démontre bien la différence entre les deux méthodes. jQuery ajoute l’événement sur la liste à puces et vérifie ensuite que le clic est fait sur un hyperlien. Alors que la méthode JavaScript doit au préalable sélectionner les listes à puces pour ensuite vérifier si le clic est fait sur une hyperlien de l’objet.
Le standard addEventListener/removeEventListener
et l’exception attachEvent/detachEvent
Incompatible Internet Explorer, addEventListener
est néanmoins le nouveau standard. Voilà les deux méthodes en exemple suivit de la parfaite fonction compatible de John Resig :
// Ajouter un événement W3C document.addEventListener('click', maFunction() { // ... }, false); // Ajouter un événement IE document.attachEvent('click', maFunction() { // ... }, false); // Supprimer un événement W3C document.removeEventListener('click', maFunction() { // ... }, false); // Supprimer un événement IE document.detachEvent('click', maFunction() { // ... }, false);
Encore la faute à Microsoft
Enfin Microsoft a tardé à être compatible et encore. Microsoft avait créé sa propre méthode alors que addEventListener
existait déjà là. Plus simple que l’exemple de Jeffrey Way, il y a les fonctions de John Resig. Il suffit d’utiliser le attachEvent
pour Internet Explorer et addEventListener
pour les autres navigateur. Point intéressant, on a deux fonctions, une pour ajouter un événement et une autre pour supprimer un événement.
// Attacher et supprimer un événement par John Resig function addEvent( obj, type, fn ) { if ( obj.attachEvent ) { obj['e'+type+fn] = fn; obj[type+fn] = function(){obj['e'+type+fn]( window.event );} obj.attachEvent( 'on'+type, obj[type+fn] ); } else obj.addEventListener( type, fn, false ); } function removeEvent( obj, type, fn ) { if ( obj.detachEvent ) { obj.detachEvent( 'on'+type, obj[type+fn] ); obj[type+fn] = null; } else obj.removeEventListener( type, fn, false ); } // Usage addEvent(objet, 'click', mafonction ); removeEvent( objet, , mafonction ); // Exemple d’utilisation addEvent( document.getElementsByClassName('maClasse'), 'click', function(){ alert(this.innerHTML); });
Source : Flexible Javascript Events.
ready et DOMContentLoaded
Une autre bonne pratique instaurée par jQuery est de s’assurer que l’ensemble de l’arbre du DOM est chargé avant de le manipuler. Vous avez sans doute déjà exécute une fonction JavaScrip qui manipule le DOM avant que la page soit chargée. Soit parce que l’instruction est dans l’entête (Head) c’est-à-dire exécuté avant même que la construction du DOM soit commencée. Ou encore directement dans le document, toujours avant que le DOM soit tout à fait chargée. Il suffit de placer votre code en bas de page. Ou encore utiliser l’événement on load
dans la balise body
. Noter que la version jQuery est beaucoup plus précise puisqu’elle attend non seulement que la page, mais que toutes les images et les documents externes soient chargés... Pour reproduire exactement ce résultat en JavaScript il faut utiliser une autre librairie(le domready de Google) alors aussi bien utiliser jquery si c’est obligatoire. Sinon DOMContentLoaded
fait très bien l’affaire.
// Avant le HTML5 <body onload="maFonction"> // jQuery, exécute la fonction aussitôt le DOM chargé $(document).ready(maFonction) // JavaScript document.addEventListener('DOMContentLoaded', function() { // Le DOM est chargé }); // Internet Explorer document.readyState != 'loading'; // Fonction combinée. function documentPret(maFonction) { if (document.readyState != 'loading'){ maFonction(); } else { document.addEventListener('DOMContentLoaded', maFonction); } }
Naviguer parmi les nœuds
// jQuery cible le nœud suivant $('#list').next(); // JavaScript var oSuivant = document.querySelector('#oListe').nextElementSibling; // À partir IE9 // Encore mieux var oListe = document.getElementById('oListe'), oSuivant = oListe.nextSibling; // Seulement un vrai nœud, et non pas un nœud texte... // nodeType > 1 égal sans doute un nœud de type texte while ( next.nodeType > 1 ) next = next.nextSibling;
Boucler une matrice (array) avec forEach
Le JavaScript n’avait pas de méthode native pour boucler une matrice, alors que le jQuery le permet :
// jQuery var maMatrice = ['item1', 'item2', 'item3', 'item4'] $.each( maMatrice, function ( index, value ) { alert(value); }); // JavaScript var maMatrice = ['item1', 'item2', 'item3', 'item4'] for ( var i = 0; i < maMatrice.length; i++ ) { alert(maMatrice[i]); } // Nouvelle méthode forEach HTML5 ['item1', 'item2', 'item3', 'item4'].forEach(function(){ // ... }); // Ou var maMatrice = ['item1', 'item2', 'item3', 'item4']; maMatrice.forEach(function(){ // ... });
Fonctions de Jeffrey Way
Ces premières fonctions permettent une compatibilité beaucoup plus large de la manipulation de classes. Il s’agit essentiellement de modifier, ajouter ou supprimer une classe en tenant compte si l’élément a plus d’une classes.
// Fonctions « addClass, removeClass, toggleClass » de Jeffrey Way pour normaliser la manipulation de classe. var box = document.getElementById('box'), hasClass = function (el, cl) { var regex = new RegExp('(?:\\s|^)' + cl + '(?:\\s|$)'); return !!el.className.match(regex); }, addClass = function (el, cl) { el.className += ' ' + cl; }, removeClass = function (el, cl) { var regex = new RegExp('(?:\\s|^)' + cl + '(?:\\s|$)'); el.className = el.className.replace(regex, ' '); }, toggleClass = function (el, cl) { hasClass(el, cl) ? removeClass(el, cl) : addClass(el, cl); }; // Usage addClass(box, 'drago'); removeClass(box, 'drago'); toggleClass(box, 'drago');
Pour normaliser le modèle addEventListener
de la W3C avec le attachEvent
d’Internet Explorer tout en simplifiant le code à la manière jQuery.
// Fonction « addEvent » de Jeffrey Way pour simuler le jQuery var addEvent = (function () { var filter = function(el, type, fn) { for ( var i = 0, len = el.length; i < len; i++ ) { addEvent(el[i], type, fn); } }; if ( document.addEventListener ) { return function (el, type, fn) { if ( el && el.nodeName || el === window ) { el.addEventListener(type, fn, false); } else if (el && el.length) { filter(el, type, fn); } }; } return function (el, type, fn) { if ( el && el.nodeName || el === window ) { el.attachEvent('on' + type, function () { return fn.call(el, window.event); }); } else if ( el && el.length ) { filter(el, type, fn); } }; })(); // usage addEvent( document.getElementsByTagName('a'), 'click', fn);
La fonction « matches » permet de normaliser cette fois le préfixe pour chaque navigateur (!) Bien que le matchesSelector
soit de plus en plus supporté, plusieurs navigateurs utilisent malheureusement son propre préfixe.
// Fonction « matches » de Jeffrey Way pour normaliser les préfixes var matches; (function(doc) { matches = doc.matchesSelector || doc.webkitMatchesSelector || doc.mozMatchesSelector || doc.oMatchesSelector || doc.msMatchesSelector; })(document.documentElement); // Usage document.addEventListener('click', function(e) { if ( matches.call( e.target, 'ul a') ) { // Clic } }, false);
Ce dernier exemple permet de simuler le sélecteur jQuery « $ ». Ça fonctionne mais ce n’est certes pas conseillé puisqu’elle ne supporte pas les sélecteurs CSS complexes, des vieux navigateurs notamment. Il faut dire que la librairie jQuery est beaucoup plus optimisée à ce niveau et prend en charge la compatibilité au sens large. Mais c’est un bon exemple du fonctionnement du moteur de jQuery.
// Fonction pour simuler le sélecteur jQuery « $ ». if ( !document.getElementsByClassName ) { document.getElementsByClassName = function(cl, tag) { var els, matches = [], i = 0, len, regex = new RegExp('(?:\\s|^)' + cl + '(?:\\s|$)'); // Si aucune balise n’est sécifiée // capturer l’ensemble du DOM els = document.getElementsByTagName(tag || "*"); if ( !els[0] ) return false; for ( len = els.length; i < len; i++ ) { if ( els[i].className.match(regex) ) { matches.push( els[i]); } } return matches; // retourne une matrice si un élément a la bonne classe }; } // Valide seulement le id, la classe, ou la balise. var $ = function(el, tag) { var firstChar = el.charAt(0); if ( document.querySelectorAll ) return document.querySelectorAll(el); switch ( firstChar ) { case "#": return document.getElementById( el.slice(1) ); case ".": return document.getElementsByClassName( el.slice(1), tag ); default : return document.getElementsByTagName(el); } }; // Usage $('#conteneur'); $('.maClasse'); // Retourne un élément de classe « maClasse » $('.maClasse', 'div'); // Retourne un élément DIV de classe « maClasse » $('p'); // Retourne tous les éléments P
Chainage d’instructions
Le chainage jQuery est aussi populaire pour combiner plusieurs instructions en une seule. Et pourtant c’est une des forces du Javascript, notamment avec les opérateur, les expressions régulière... Mais le Javascript ne permet pas de chainage d’instruction à la manière jQuery, ni le « with » du VBScript. Par exemple, la combinaison suivante tout à fait valide en jQuery :
// Chainage jQuery $('#momDiv').height(100).fadeIn(200);
Ce n’est pas vraiment une nouvelle fonctionnalité qu’une habitude à prendre. En programmation orientée objet Javascript il faut simplement assigner les valeurs mais pour pouvoir les manipuler de manière chainé, il faut aussi retourner les valeurs, question de ne pas devoir assigner à une variable à chaque fois. Et ainsi pouvoir chainer les manipulations dans une seule instruction. Pour la forme, un exemple. Personnellement j’éviterais cette façon de faire.
// Exemple Javascript de chainage var monObjet = function() { this.id = 'monID'; this.nom = 'nomNom'; }; monObjet.prototype.setId = function(id) { this.id = id; return this; // Important d’avoir un retour. }; monObjet.prototype.setNom = function(nom) { this.nom = nom; return this; // Important d’avoir un retour. }; monObjet.prototype.maMethode = function() { console.log( 'Exemple : ' + this.id + ', ' +this.nom; ); return this; // Important d’avoir un retour. }; // Sans chainage var unObjet = new monObjet(); unObjet.setId('mon ID'); unObjet.setNom('mon nom'); unObjet.maMethode(); // Sortie // « Exemple : mon ID, mon nom » // Avec chainage new monObjet() .setId('mon ID') .setNom('mon nom') .maMethode(); // Sortie // « Exemple : mon ID, mon nom »
<ul> <li id="foo">foo</li> <li id="bar">bar</li> <li id="baz">baz</li> </ul> jQuery alert('Index: ' + $('#bar').index(); Javascript function indexInParent(node) { var children = node.parentNode.childNodes; var num = 0; for (var i=0; i<children.length; i++) { if (children[i]==node) return num; if (children[i].nodeType==1) num++; } return -1; }
Conclusion
Quelle lourdeur, que j’espère même bonifier au fil des nouveautés. À consulter au scalpel. Mais c’est justement de poids dont il est question. Et même de surpoids quand on réalise le nombre d’opérations dupliquées par les moteurs jQuery. Cette page de références permet déjà de convertir rapidement un bout de code, un exemple trouvé à la volée, afin d’éviter de charger la librairie jQuery au complet pour une simple instruction JavaScript. Et un peu pour dire aux nouveaux webmestres que derrière le jQuery, il y a toujours le Javascript! On est toutefois très loin de voir disparaitre le jQuery car il s’adresse aussi aux « gabarits industriels » (Framework), à l’open source jusqu’aux derniers designs gratuits qui pullulent sur le Web! Car faut bien le dire, c’est surtout ça le jQuery, une communauté, une technologie ouverte, libre et accessible (comme un navigateur en passant! Merci à Mozilla).
D’ailleurs on peut lire sur la page d’accueil de jQuery « CSS3 Compliant » ou Conforme CSS3, prise en charge des sélecteurs CSS3! Au-delà de la syntaxe, le jQuery c’est surtout ces « widjets » et ses effets de transition, largement dépassé par Scriptaculous’ Effects.js en passant! C’est davantage au niveau du CSS3, tout particulièrement des transitions et des animations et « keyframe », des transparences, ou encore du SVG que l’on doit orienter tout nouveau développement.
Références
- You might not need jQuery
- HTML5 Doctor : The classList API par Derek Johnson
- Alsacréations - DOM : querySelector et querySelectorAll
- Why I STILL prefer Prototype over jQuery
- jQuery’s browser bug workarounds
- Flexible Javascript Events
- Is it time to drop jQuery? Essentials to learning JavaScript from a jQuery background par Todd Motto
- You Don’t Need jQuery! par Ray Nicholus (sélecteurs)
- You Don’t Need jQuery! par Ray Nicholus (événements)
- ECMAScript 5 - the current JavaScript standard
- From jQuery to JavaScript: A Reference
- jQuery - The jQuery Foundation
- Support d’ECMAScript 5 par Mozilla
- ECMAScript® Language Specification
- ECMAScript 5 compatibility table