Bien préparer un développement javascript avec jQuery

On me pose souvent des questions sur certaines bonnes-pratiques javascript assez récurrentes en partant de l’utilisation d’une librairie telle que jQuery. C’est pourquoi je profite d’une de ces questions posée récemment (merci Gilles) pour faire un article sur le sujet.

Donc voici les différentes étapes assez systématiques par lesquelles je passe quand je dois entamer un développement javascript dans une page.

1. Charger jQuery

Je passe rapidement sur cette étape, vous pouvez directement l’appeler depuis le serveur de jquery.com ou le copier dans le répertoire local du site.

<script type="text/javascript"
  src="http://code.jquery.com/jquery-latest.pack.js"></script>

2. Créer un fichier javascript et le charger dans la page

On ne le redira jamais assez : ne mettez pas de balises <script> dans vos pages ! Préférez un fichier à part du genre monScript.js cela vous permettra de bien séparer les choses. Ici je pars du principe que vous avez un dossier js à la racine du site dans lequel vous mettez ce genre de fichier.

<script type="text/javascript" src="/js/monScript.js"></script>

3. Préparer le fichier javascript

A la longue, mes fichiers javascripts finissent par avoir toujours la même structure… En l’occurence, je commence toujours par écrire les quelques lignes suivantes

(function($){

  $(document).ready(function(){
    // Méthodes exécutées au chargement du DOM
  });

})(jQuery);

a. Englober tout le code dans une fonction anonyme

Ce morceau de code peut déjà sembler complexe à certains, mais une fois décortiqué il se comprend très bien. Dans un premier temps, on crée ce que l’on appelle une fonction anonyme, c’est à dire une fonction qui n’a pas de nom :

function(){ /* contenu de monScript.js */ }

Le but va être de « ranger » tout le code du fichier à l’intérieur de cette fonction. L’avantage est énorme, car grâce au principe de champ d’application des variables et des objets en javascript, tout ce que nous allons faire dans cette fonction ne pourra pas venir perturber d’autres scripts éventuels chargés dans la même page. Par exemple, fini les fonctions avec des noms à rallonge pour être sûr qu’aucune autre n’aura le même nom.

Le problème à ce stade, c’est qu’une fonction (anonyme ou pas) ne s’exécute pas d’elle-même, pour l’exécuter, il faut qu’elle soit appelée. Ainsi, dans le code suivant, la première ligne sert à déclarer la fonction, la deuxième à l’appeler :

var toto = function() { alert("toto") };
// => Pour l'instant rien ne se passe...

toto();
// => Là ma page affiche l'alerte, car la fonction toto est exécutée

La subtilité avec une fonction anonyme c’est justement qu’elle n’a pas de nom ! Mais ce n’est en fait pas un problème, car en javascript vous pouvez considérer que n’importe quel bout de code entre parenthèse est un objet en soit (ou une variable pour faire simple). Du coup, on peut à la fois déclarer une fonction anonyme et l’appeler en mettant la fonction entre parenthèse, puis en juxtaposant des parenthèses vides « () » justes après, comme on le ferait avec le nom d’une fonction classique déclarée auparavant. Ce qui nous donne :

(function(){ /* contenu de monScript.js */ })()

Pour bien comprendre, il faut réaliser que les lignes de codes suivantes sont totalement équivalentes :

function tata() { alert("tata") }; tata();
// => Affiche "tata"

var titi = function() { alert("titi") }; titi();
// => Affiche "titi"

(function() { alert("toto") })();
// => Affiche "toto"

b. Garantir l’équivalence jQuery == $

Au final on profite également du fait de passer par une fonction pour lui passer en paramètre l’objet jQuery. L’intérêt est de profiter du fait que lorsque on déclare une fonction, on déclare au passage le nom des variables internes qui vont correspondre au paramètres passés à la fonction. Par exemple dans le code suivant, je passe la variable globale monMessage à la fonction showMessage à l’intérieur de laquelle je manipule cette fois la variable « m » qui à l’avantage d’être beaucoup plus courte.

function showMessage(m) {
  alert(m);
};

var monMessage = "Coucou !";
showMessage(monMessage);
// => Affiche "Coucou !"

Par défaut, jQuery propose déjà une variable « $ » qui est l’équivalent de la variable globale jQuery. Malheureusement on ne peut pas toujours s’y fier, car certaines librairies telles que Prototype ou Mootools peuvent venir surcharger elles aussi la variable $. Il est donc plus sûr de redéfinir cette variable nous-même.

Cette astuce a aussi l’avantage inverse, à savoir permettre d’utiliser jQuery en mode « no conflict ». C’est à dire en lui demandant explicitement de ne pas utiliser la variable $ pour qu’elle soit disponible pour d’autres librairies (et donc éviter les conflits). Car après tout jQuery n’a pas le monopole du dollar ! 😉

4. Répartir tout le code dans des fonctions simples avec des noms explicites

Il est souvent tentant de commencer un développement (surtout les plus petits) en écrivant le code sans essayer de le structurer. Mais avec le temps on se rend compte que faire du code propre dès le départ est toujours payant.

Pour cela j’essaie d’appliquer 2 grands principes :

  1. Je crée des fonctions différentes pour chaque unitée fonctionnelle. Notamment il arrive souvent qu’un développement consiste en deux parties : l’opération en elle même exécutée par le navigateur et le branchement de cette opération sur un évènement (par exemple, le clic sur un lien ou un bouton). Dans ce cas, je sépare ces deux parties en deux fonctions distinctes. L’intérêt principal est bien entendu de pouvoir rappeler la première fonction dans différents cas (on factorise le code). On gagne aussi en clarté et en maintenabilité
  2. Je nomme mes fonctions de manière explicite en respectant une nomenclature précise. Par exemple une fonction qui est appelée directement au chargement commence par « init », si elle consiste à brancher une fonction sur un évènement elle précise « event » ainsi que l’élément sur lequel elle s’applique, etc.

En bonus, vous pouvez aussi décrire vos fonctions avec des commentaires, ça ne mange pas de pain… 😉

Tout cela nous donne un code qui peut ressembler à celui-ci :

(function($){

  // Code exécuté au chargement du DOM
  $(document).ready(function(){
    initEventOnClickOnSaveButton();
  });

  /*
   * Action lors du clic sur le bouton "sauvegarder"
  */
  function initEventOnClickOnSaveButton() {
    $("#saveButton").click(function(){
      saveFormData($(this).closest("form"));
    });
  }

  /*
   * Sérialisation et sauvegarde des données d'un formulaire
   * formElement via une requête POST en AJAX
   */
  function saveFormData(formElement) {
    var $form = $(form), data = $form.serialize();
    $.post($form.attr("action"), data);
  }

})(jQuery);

5. Utiliser des champs cachés pour transmettre des données aux scripts

Dans l’exemple précédant, je simule une fonction qui doit faire une requête AJAX (via la méthode post de jQuery). Pour déduire l’URL à interroger pour faire la requête, elle utilise la valeur de l’attribut action d’un formulaire. C’est une technique assez pratique et élégante dans la mesure où les formulaires contiennent par nature toutes les informations nécessaires pour envoyer une requête à un serveur, y compris l’URL à interroger. Le problème c’est qu’on a bien sûr pas toujours la possibilité de rencontrer ce cas de figure idéal. Bien souvent, quand on doit faire une requête AJAX, l’URL à interroger doit être générée par une méthode côté serveur (une méthode PHP par exemple).

Pour répondre à cette problématique, un réflexe courant est de générer dynamiquement du code javascript dans la page, comme dans l’exemple suivant :

<script type="text/javascript">

// On suppose qu'il existe déjà une variable PHP $ajaxUrl...
var ajaxUrl = "<?php echo $ajaxUrl ?>";
// => On peut maintenant utiliser la variable globale ajaxUrl en javascript

</script>

Cette façon de faire a plusieurs inconvénients, dont certains vont à l’encontre de quelques unes des bonnes pratiques que nous avons vu juste avant :

  • Génération d’une balise <script> dans la page
  • Utilisation d’une variable globale qui peut entrer en conflit avec d’autres scripts

La méthode que j’utilise pour pallier à ces problèmes est de passer par des champs cachés. La mise en oeuvre est assez simple. Je commence par mettre le code suivant tout en bas de la page concernée (ou plutôt dans le footer global du site si possible, comme ça les données sont dispo partout) :

<form id="dataForJS">
  <input type="hidden" id="ajaxUrl" value="<?php echo $ajaxUrl ?>" />
</form>

Et voilà ! En javascript, il ne me reste plus qu’à aller chercher le champ caché par son identifiant et en récupérer la valeur. Ce qui, avec jQuery, est assez trivial :

var ajaxUrl = $("#ajaxUrl").val();

Vous pouvez même un peu plus automatiser les choses en récupérant directement toutes les URLs s’il y en a plusieurs. En ajouter une revient alors simplement à ajouter un champ caché :

var urls = {};
$("#dataForJs input").each(function(){
  var $input = $(this);
  urls[$input.attr("id")] = $input.val();
});

alert(urls["ajaxUrl"]);
// => Affiche l'URL stockée dans le champs caché avec l'id "ajaxUrl"

En résumé…

Voilà donc au final à quoi peut ressembler votre fichier JS prêt pour accueillir confortablement vos développements javascript avec jQuery :

(function($){

  // Code exécuté au chargement du DOM
  $(document).ready(function(){
    initUrls();
    initEventOnClickOnSaveButton();
  });

  /*
   * Récupération des URLs sérialisées dans la page
  */
  var urls = {};
  function initUrls() {
    $("#dataForJs input").each(function(){
      var $input = $(this);
      urls[$input.attr("id")] = $input.val();
    });
  }

  /*
   * Action lors du clic sur le bouton "sauvegarder"
  */
  function initEventOnClickOnSaveButton() {
    $("#saveButton").click(function(){
      saveFormData($(this).closest("form"));
    });
  }

  /*
   * Sérialisation et sauvegarde des données d'un formulaire
   * formElement via une requête POST en AJAX
   */
  function saveFormData(formElement) {
    var $form = $(form), data = $form.serialize();
    $.post(urls["ajaxUrl"], data);
  }

})(jQuery);

Et vous ? Avez-vous d’autres bonnes pratiques ou des conseils pour préparer vos développements frontend ? N’hésitez pas à les poster en commentaire, je compléterai ou corrigerai l’article au besoin…

Publicités

5 réflexions sur “Bien préparer un développement javascript avec jQuery

  1. Merci pour toutes ces astuces.
    Ca m’a permis de fixer quelques connaissances, et d’en acquérir d’autres.

    Par exemple la syntaxe :
    (function() { alert(« toto ») })();
    dont je n’avais aucune idée. C’est assez élégant.

    J’ai plein de questions et remarques 😉

    Une remarque sur l’inclusion du fichier js dans la page : si tu écris
    <script type="text/javascript" src="/js/monScript.js"></script>
    alors le script n’est pas recherché à la racine du site mais à la racine du serveur, et ce sont forcément deux choses différentes. Ici, le script ne sera pas trouvé !

    Dans le « 3. Préparer le fichier javascript », il y a quelque chose que je ne comprends pas. Tu écris :
    (function($){
    bla bla…
    })(jQuery);
    Pourquoi mettre écrire « jQuery » à la fin, dans les parenthèses ?

    Une question que je me pose souvent pour inclure la librairie : est il plus rapide de l’insérer à partir de son propre serveur, où à partir de http://code.jquery.com/jquery-latest.pack.js ? Ou encore à partir du serveur de google, qui propose aussi un paquet de lib JS [http://code.google.com/apis/ajaxlibs/documentation/index.html#jquery]
    En tout cas je viens de faire un grand nombre de fois le test à partir de code.jquery.com et la lib a mis à chaque fois entre 150 et 200ms.

    Bravo pour l’article, à bientôt

    J'aime

  2. Encore quelque chose. Au fond, puisque pour une fois quelqu’un parle de bonnes pratiques du jQuery, et en plus si c’est moi qui ait initié ce post, autant aller au fond des choses. Car j’utilise (brillamanent) jQuery depuis plus d’un an et que je ne suis ici que pour éclairer les zones d’ombre.

    Je vois dans la source de cette page :
    <script type="text/javascript">
    $(function() {
    $(‘a.lightbox’).lightBox();
    });
    </script&gt
    C’est donc une fonction anonyme qui sera executée au chargement de la page, mais je ne vois pas les parenthèses qui la jouxtent. Du coup, est on vraiment obligé de mettre « () » après une fonction anonyme pour qu’elle soit appelée ?

    J'aime

  3. Hello Gilles. Merci d’alimenter le débat et content que ces astuces te semblent utiles ! 🙂

    Alors je vais reprendre tes questions/remarques dans l’ordre :

    – Les chemins qui commencent par « / » sont relatifs à la racine du serveur et pas du site
    => Tu as raison, j’ai voulu faire un raccourci un peu rapide… En pratique, il vaut mieux prendre un peu plus de précautions sur ces histoires de chemin et ne pas considérer que le site sera toujours à la racine du domaine.

    – Pourquoi mettre « jQuery » dans les parenthèses ?
    => C’est marrant, je pensais avoir écris tout un chapitre là-dessus. Preuve que ça n’est pas encore assez clair.
    L’équivalent de notre fonction anonyme auto-exécutée peut s’écrire comme ceci :

    var globalFunc = function($) {
     // Code de la page
    }
    globalFunc(jQuery);

    L’intérêt est de ne pas utiliser $ comme une variable globale déclarée par la librairie, mais comme la traduction du paramètre passé à la fonction globalFunc.
    On pourrait aussi faire un truc encore plus tordu et écrire :

    var globalFunc = function(objetJquery) {
      // Code de la page où on peut écrire par exemple :
      objetJquery(document).ready(function(){
        // Code exécuté au chargement du DOM
      });
    }
    var coucouJeSuisJQuery = jQuery;
    globalFunc(coucouJeSuisJQuery);

    – Faut-il inclure jQuery depuis les serveurs de jQuery ou de Google, plutôt que de l’héberger sur sont site ?
    => Vaste débat ! En gros il n’y a pas beaucoup d’avantage à appeler la lib depuis un serveur tel que celui de Google ou jQuery. Ces serveurs proposent des URLs qui renvoient la dernière version, ce qui permet de profiter des corrections/optimisation automatiquement, mais ça peut aussi être dangereux à cause des problème de compatibilité entre version…. Certains aiment bien aussi se dire que du coup, au premier accès, l’utilisateur à des chances d’avoir déjà la lib dans le cache de son navigateur… Le gain n’est pas énorme (et en plus ça ne vaut que pour le premier accès).

    – Fonction anonyme appelée sans parenthèses
    => Attention, tu confonds ici deux écriture similaires, mais qui n’ont rien à voir :

    (function(){...})();
    $(function(){...});

    Ici la deuxième ligne n’est pas une fonction anonyme auto-exécutée, mais un raccourci proposé par jQuery pour déclencher une fonction au chargement du DOM. jQuery se comporte différemment si on lui passe une chaine (un sélecteur typiquement) ou une fonction. Ainsi les deux écritures suivantes sont équivalentes :

    $(document).ready(function(){...});
    $(function(){...});

    Il est peu conseillé d’utiliser la version raccourci, pour éviter justement les problèmes de confusion tel que celui que tu as eu Gilles en faisant cette remarque.

    J'aime

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s