Distribution de données inter domaines avec JSONP, cas pratiques (AddThis...)

Introduction

Vous utilisez les services d'AddThis pour les fonctionnalités et statistiques des partages de vos pages sur les réseaux sociaux (Twitter, Facebook, Google+...).

Parmi les fonctionnalités, AddThis offre la possibilité de récupérer la liste des pages les plus partagées (trending content) à l'échelle du jour, de la semaine ou du mois.

L'ancien code était simple (API v250). Il suffisait dans une page de définir une balise div vide avec un id :

<div id="addthis_trendingcontent"></div>

d'inclure les API d'AddThis dans la page avec son compte AddThis (pubid):

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

et enfin d'appeler la méthode addthis.box en donnant notamment en paramètre l'id de la balise div qui accueille la liste des pages retournées par AddThis :

<script type="text/javascript">
  addthis.box("#addthis_trendingcontent", {
    feed_title : "Les 10 articles les plus partagés : ",
    feed_type : "trending",
    feed_period : "month",
    num_links : 10,
    height : "auto",
    width : "auto"});
</script>

Avec les nouvelles API d'AddThis (v300), cette méthode ne fonctionne plus. AddThis propose désormais d'activer la liste des articles tendance depuis la console d'administration :

Console AddThis

et d'implémenter le code HTML ci-dessous dans sa page pour charger les APIs AddThis et placer une balise div vide avec la classe addthis_recommended_vertical, conteneur de la future liste :

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

Bien que l'idée soit excellente en simplicité, le rendu n'est pas forcément ce que l'on attend :

Rendu div addthis original

Le nom du domaine www.sqlpac.com est affiché et ce n'est pas modifiable. Si un titre n'est pas défini dans la console, des défauts ergonomiques se produisent, c'est pourquoi il y a une petite flèche dans la figure ci-dessus.

Alors vous recherchez le moyen de récupérer cette liste par programmation et c'est là qu'intervient JSONP (JSON with Padding). AddThis propose effectivement de récupérer les données par des API (RSS, JSON...), toutefois la documentation n'est pas très claire pour un débutant : AddThis Academy - Content Feeds.

Échec des méthodes XMLHttpRequest lors de l'interrogation de données JSON en inter domaines (cross domains)

En survolant la documentation AddThis, tout a l'air enfantin. Pour récupérer les données au format JSON de la tendance mensuelle des articles, à priori il faut appeler l'URL : http://q.addthis.com/feeds/1.0/trending.json?pubid=<compte>&period=week&domain=www.sqlpac.com.

Son format est simple à manipuler avec Javascript :

[
  {"url":"http://www.sqlpac.com/article1.html", "title":"Article 1"}
 ,{"url":"http://www.sqlpac.com/article2.html", "title":"Article 2"}
 ...
]

Vous savez rapatrier les données d'un fichier JSON avec Javascript (Fonction générique de chargement de données JSON avec Javascript), donc on pense aussitôt à coder de la manière suivante :

  display_topten_addthis = function(data) {
    v_div=document.getElementById("topten");
    ...
  };
        
  var xmlhttp = new XMLHttpRequest();
  
  xmlhttp.onreadystatechange=function() {
    if (xmlhttp.readyState==4 && xmlhttp.status==200) {
        var dataprops = JSON.parse(xmlhttp.responseText);
        display_topten_addthis(dataprops);
    }
  };
  var url = "http://q.addthis.com/feeds/1.0/trending.json?pubid=<compte>&period=week&domain=www.sqlpac.com";
  xmlhttp.open("GET",url,true);
  xmlhttp.send();

Dans ce code, la fonction display_topten_addthis se charge d'afficher dans le bloc div id="topten" la liste une fois les données JSON reçues.

Le code est parfait mais rien ne fonctionne ! La console Javascript de votre navigateur préféré hurle un message d'erreur à la fois clair et obscur.

XMLHttpRequest cannot load http://q.addthis.com/feeds/1.0/trending.json?pubid=<compte>&period=week&domain=www.sqlpac.com.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://www.sqlpac.com' is therefore not allowed access.

Le comportement est tout à fait normal, après s'être documenté... Dans ce contexte, l'interrogation inter domaines (cross domains) addthis.com <=> www.sqlpac.com avec XMLHttpRequest n'est pas autorisée. C'est ici le territoire du Cross Origin Resource Sharing (CORS) pour la définition des autorisations intersites avec XMLHttpRequest mais ce n'est pas le sujet de cet article.

JSONP va résoudre ce point bloquant.

JSONP pour l'inter domaines

Dans un contexte inter domaines, c'est la technologie JSONP qui est mise en œuvre. Au lieu d'utiliser un objet XMLHttpRequest, le fichier JSON est chargé comme un script Javascript en ajoutant le paramètre callback dans l'URL. Le paramètre callback a pour valeur le nom d'une fonction de rappel (callback=fonction). Avec JSONP, cette fonction est automatiquement appelée à la fin du chargement et reçoit en argument les données du fichier JSON.

http://q.addthis.com/feeds/1.0/trending.json?pubid=<compte>&period=week&domain=www.sqlpac.com&callback=display_topten_addthis

Concernant l'exemple de cet article avec AddThis, la fonction display_topten_addthis est bien entendu la fonction de rappel puisqu'elle s'occupe des données reçues.

  display_topten_addthis = function(data) {
    v_div=document.getElementById("topten");
         for (i=0; i < data.length;i++) {
            v_div.appendChild(document.createTextNode(data[i].title));
         }
  };
  
  var url  = "http://q.addthis.com/feeds/1.0/trending.json?pubid=<compte>&period=week&domain=www.sqlpac.com";
      url += "&callback=display_topten_addthis";
      
  v_script = document.createElement('script');
  v_script.setAttribute('src',url);
  v_script.setAttribute('async',true);
  v_script.setAttribute('id','jsonfeed');
  
  document.getElementsByTagName('head')[0].appendChild(v_script);

Et la liste apparaît ! Rien de sorcier avec JSONP.

Un id est attribué au script inséré (jsonfeed), ce n'est pas par hasard. Lorsque les données ont été reçues, c'est une petite sécurité supplémentaire, ce script est retiré de l'arbre DOM avec la méthode removeChild.

  display_topten_addthis = function(data) {
    v_div=document.getElementById("topten");
         for (i=0; i < data.length;i++) {
            v_div.appendChild(document.createTextNode(data[i].title));
            ...
         }
         
    v_head = document.getElementsByTagName("head")[0];
    v_script = document.getElementById("jsonfeed");
    v_head.removeChild(v_script);
  };

Conclusion

C'est un peu perturbant, après avoir appris à charger et manipuler des données JSON sur son domaine avec XMLHttpRequest, lorsqu'il s'agit d'interroger des données JSON en inter domaines, la méthode est différente avec JSONP mais elle est si simple avec le paramètre callback dans l'URL du fichier JSON. Il faut juste le savoir.