Développer une fonction générique de chargement dynamique de librairies Javascript (callback, attributs..)

Introduction

Dans vos pages, vous incorporez de plus en plus d'outils dynamiquement: Google +1, AddThis, Google Analytics, Google Custom Search Engine, HighlightJS, Mathjax et bien d'autres. Pour ajouter ces outils, des librairies Javascript sont insérées dans l'entête de la page.

Pour le bouton Google +1 :

<head>
  ...
  <script type="text/javascript" 
        src="https://apis.google.com/js/platform.js" 
        async defer >
     {lang:"fr", parsetags:"explicit"}
  </script>
  ...
</head>

Pour AddThis, le code est plus simple :

<head>
  ...
  <script type="text/javascript" 
     src="http://s7.addthis.com/js/300/addthis_widget.js#pubid=compte" 
  </script>
  ...
</head>

Les syntaxes diffèrent souvent au niveau des attributs (async=true, id, texte...).

Pour certains outils, une fonction de rappel, souvent appelée par abus de langage fonction callback, est invoquée à la fin du chargement du script Javascript. C'est le cas par exemple d'HighlightJS, librairie Javascript pour la coloration syntaxique de code (SQL, awk, PHP...) dans une page HTML, la fonction hljs.initHighlighting(); est appelée à la fin du chargement du script highlight.pack.js.

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

hljs.initHighlighting();

Le sujet n'est pas nouveau, et encore moins révolutionnaire, mais pour simplifier l'ajout dynamique en Javascript de ces scripts avec des options aussi diverses, une fonction générique en pur Javascript (sans jQuery) peut tout prendre en charge (attributs, fonctions callback, nœud texte...). Si ça peut aider quelqu'un, alors l'objectif de ce petit article est atteint. En voici une : wsys_load_js, à l'ouvrage ici même, elle est loin d'être parfaite et ne respecte peut être pas l'excellence du codage en Javascript aussi les commentaires, suggestions et remarques sont les bienvenus.

Internet Explorer 8 et ses incompatibilités avec les standards Javascript (addEventListener, appendChild d'un nœud texte sur un objet script) est encore supporté dans la fonction proposée car étrangement, malgré la fin de Windows XP, 5% des visiteurs sur ce site utilisent encore cette version en septembre 2016. En juin 2017, le support d'IE 8 devrait être retiré.

La fonction wsys_load_js

Les pages intègrent votre propre bibliothèque Javascript (lib.js) :

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

Dans cette librairie, la fonction générique wsys_load_js (w pour window, sys pour système) est définie avec le prototype ci-dessous :

wsys_load_js = function( url, callback, options ) {

};

Chargement dynamique d'un script Javascript dans l'entête avec wsys_load_js (url)

Le premier paramètre url de la fonction wsys_load_js est bien entendu l'URL du script Javascript à charger (sur le Web ou en local). Ce script est créé classiquement avec la méthode createElement('script') et chargé dans l'entête de la page avec la méthode appendChild.

wsys_load_js = function (url, callback_function, options) {
  
  v_head = document.getElementsByTagName("head")[0];
  
  v_script_js = document.createElement ("script");
  v_script_js.setAttribute("type","text/javascript");
  v_script_js.setAttribute("src",url);
  
  v_head.appendChild(v_script_js);
};

Déclenchement de la fonction de rappel callback_function à la fin du chargement

L'ajout de l'appel de la fonction callback à la fin du chargement du script est simple avec la méthode addEventListener de l'objet script créé. La fonction est appelée lorsque le script est chargé, chargement qui est détecté avec l'événement load.

wsys_load_js = function (url, callback, options) {
  
  v_head = document.getElementsByTagName("head")[0];
  
  v_script_js = document.createElement ("script");
  v_script_js.setAttribute("type","text/javascript");
  v_script_js.setAttribute("src",url);
	
  if (callback_function) {
    v_script_js.addEventListener("load",callback,false); 
  }   
  v_head.appendChild(v_script_js);
};

Ça se corse si le support d'Internet Explorer 8 et versions inférieures est maintenu. La méthode addEventListener sur les objets scripts n'est pas supporté pour ces versions. Les analyses statitiques du site Web, avec Google Analytics notamment, vont gouverner ce choix, mais grosso modo, avec la fin du support Windows XP depuis avril 2014, la proportion d'Internet Explorer 8 a énormément chuté mais reste non négligeable. Pour www.sqlpac.com, parmi les utilisateurs de Microsoft Internet Explorer, la version 8 représente encore 5% des visites en 2016, contre 23% en 2015. C'est un choix qui reste assez personnel. Ici Le support d'Internet Explorer 8 est encore maintenu pour pallier au mieux aux migrations HTML 5 / CSS3 qui sont terminées et qui doivent faire souffrir assez violemment cette version d'Internet Explorer.

Pour Internet Explorer 8, c'est la propriété readyState et la méthode onreadystatechange de l'objet script qui sont exploitées afin de détecter la fin du chargement et appeler la fonction callback.

En intégrant le support d'IE 8 :

wsys_load_js = function (url, callback, options) {
  
  v_head = document.getElementsByTagName("head")[0];
  
  v_script_js = document.createElement ("script");
  v_script_js.setAttribute("type","text/javascript");
  v_script_js.setAttribute("src",url);
	
  if (callback) {
  
    /**  IE >= 9, Chrome, FireFox, Safari */
    if(v_script_js.addEventListener) {
      v_script_js.addEventListener("load",callback,false); 
    } 
    
    /**  IE < 9  */
    else if (v_script_js.readyState){  
      v_script_js.onreadystatechange = function(){
        if (v_script_js.readyState === "loaded" ||
            v_script_js.readyState === "complete"){
            v_script_js.onreadystatechange = null;
            callback();
        }
      };
    } 	
  } 
  v_head.appendChild(v_script_js);
};

Les options (id, async, defer, text...)

Le troisième paramètre de la fonction wsys_load_js est un tableau, tableau dans lequel sont définis des attributs complémentaires (id, async, defer) ou des éléments texte enfants (vtext).

options : { "id": "valeur", "async":"true|false", "defer": "true|false", "vtext": "texte enfant" };

Pour les trois premières options, le code pour appliquer les attributs est trivial :

wsys_load_js = function (url, callback, options) {

  v_head = document.getElementsByTagName("head")[0];
  v_script_js = document.createElement ("script");
  v_script_js.setAttribute("type","text/javascript");
  v_script_js.setAttribute("src",url);

  if (typeof options != "undefined") {
    if (typeof options.id != "undefined")      { v_script_js.setAttribute("id",options.id); }
    if (typeof options.async != "undefined")   { v_script_js.async="true"; }
    if (typeof options.defer != "undefined")   { v_script_js.defer="true"; }
  }

  if (callback) { ... }

  v_head.appendChild(v_script_js);

};

La dernière option vtext, ajoute un nœud texte à la balise script, exemple :

<script type="text/javascript" 
        src="https://apis.google.com/js/platform.js" 
        async defer>
     {lang:"fr", parsetags:"explicit"}
</script>

Mais là encore si le support d'Internet Explorer 8 est encore assuré, l'ajout d'un nœud texte avec la méthode appendChild n'est pas possible pour cette version, il faut utiliser la propriété text de l'objet script. Ce contexte peut être détecté avec la propriété canHaveChildren de l'objet script. Pour plus d'informations : Ajout dynamique de code Javascript, différences entre les navigateurs (canHaveChildren). Le code reste simple pour gérer ce contexte :

wsys_load_js = function (url, callback, options) {
...

if (typeof options != "undefined") {
 ...
  if (typeof options.vtext != "undefined") {
     /**  IE >= 9, Chrome, FireFox, Safari */
    if (v_script_js.canHaveChildren === null || v_script_js.canHaveChildren) { 
        v_script_js.appendChild(document.createTextNode(options.vtext)); 
    }
    /** Internet Explorer <= 8 */
    else { v_script_js.text = options.vtext; }
...
};

Code complet

La fonction générique wsys_load_js est prête à l'emploi :

  wsys_load_js = function (url, callback, options) {

  v_head = document.getElementsByTagName("head")[0];
  v_script_js = document.createElement ("script");
  v_script_js.setAttribute("type","text/javascript");
  v_script_js.setAttribute("src",url);

  if (typeof options != "undefined") {
  
    if (typeof options.id != "undefined") { v_script_js.setAttribute("id",options.id); }
    if (typeof options.async != "undefined") { v_script_js.async="true"; }
    if (typeof options.defer != "undefined") { v_script_js.defer="true"; }
    
    if (typeof options.vtext != "undefined") {
      /**  IE >= 9, Chrome, FireFox, Safari */
      if (v_script_js.canHaveChildren === null || v_script_js.canHaveChildren) { 
              v_script_js.appendChild(document.createTextNode(options.vtext)); }
      /** Internet Explorer <= 8 */
      else { v_script_js.text = options.vtext; }
    }
    
  }

  if (callback) {
  
    /**  IE >= 9, Chrome, FireFox, Safari */
    if(v_script_js.addEventListener) {
      v_script_js.addEventListener("load",callback,false); 
    } 
    /** Internet Explorer <= 8 */
    else if (v_script_js.readyState){ 
      v_script_js.onreadystatechange = function() {
          if (v_script_js.readyState === "loaded" ||
              v_script_js.readyState === "complete") {
                 v_script_js.onreadystatechange = null;
                 callback();
          }
      };
    }
    
  }
  
  v_head.appendChild(v_script_js);
};

Exemples d'appels

Quelques exemples d'appels qui simplifient sa bibliothèque Javascript et sa boîte à outils.

wsys_load_js("../js/mathjax/MathJax.js?config=AM_HTMLorMML-full");
<script type="text/javascript"
           src="../js/mathjax/MathJax.js?config=AM_HTMLorMML-full"></script>
wsys_load_js("https://apis.google.com/js/platform.js"
              ,display_googleplus
              ,{"vtext": '{lang:"fr", parsetags:"explicit"}', "async": true, "defer": true } );
<script type="text/javascript"
           src="https://apis.google.com/js/platform.js" async defer >
         {lang:"fr", parsetags:"explicit"}
   </script>

Dans le dernier exemple, la fonction display_googleplus est déclenchée à l'issue du chargement du script https://apis.google.com/js/platform.js