Exporter des données avec jsPDF dans SharePoint 2013

Récemment, le client me demandait de faire un filtre avec deux critères sur des éléments de liste, puis de pouvoir sélectionner une partie des éléments afin d’en exporter les informations au format PDF.

Comme les données sont de taille raisonnable, je me suis intéressé à jsPDF qui se définit comme une solution côté client en HTML5 pour générer des PDF. A la date d’écriture de l’article, la version 0.9.0rc2 est disponible et propose des options sympathiques :

  • Intégration de texte (font, taille, type, couleur)
  • Intégration des images
  • Intégration de HTML
  • Saut de page
  • Formes géométrique (cercle, ellipse, line, rectangle, triangle)

Je vais repartir des webparts connectées décrites dans l’article précédent pour ajouter la fonctionnalité d’export PDF.

Pour la préparation, il faut récupérer les fichiers javascript de jsPDF et de CAMLjs. Une fois les fichiers récupérés, les rendre disponibles dans SharePoint. Au même emplacement, on ajoute le code Javascript ci-dessous (exportPdf.js dans mon exemple). Comme vous le voyez, au clic sur le bouton Export permet de récupérer l’ID des éléments sélectionnés. Avec un appel JSOM, on récupère les informations des éléments – dont l’ID est passé en paramètre – que l’on met dans un tableau d’objet de transport. Finalement on boucle sur ce tableau pour construire le PDF. jsPDF ne gère pas le templating donc il faut faire de l’itératif avec des calculs pour les offsets et les sauts de page.

Astuce 1 : pour récupérer la liste des éléments sélectionnés, le code SP.ListOperation.Selection.getSelectedItems() ne fonctionne que dans le ruban. Quand on est dans la page, il faut utiliser un sélecteur avec le nom de la webpart cible (Recettes) :

var selectedItemIDs = new Array();
$("table[summary='Recettes'] tr.s4-itm-selected").each(function(){
   selectedItemIDs.push($( this ).attr("iid").split(",")[1]);
});

Astuce 2 : dans les pré-requis j’ai mis CAMLjs. Il s’agit d’une classe utilitaire JavaScript qui permet de faciliter la création de requête CAML. En l’occurrence, je remplace une imbrication de pour récupérer tous les élements dont l’ID est sélectionné par ces quelques lignes :

var camlBuilder = new CamlBuilder();
var caml = camlBuilder.Where().IntegerField("ID").In(itemIds).ToString();
camlQuery.set_viewXml("<View><Query>" + caml + "</Query></View>");
function Recette(data){
   this.Title = data.get_item("Title");
   this.Recette = data.get_item("Recette");
}

function retrieveListItemsInclude(itemIds) {
   var clientContext = new SP.ClientContext(_spPageContextInfo.webAbsoluteUrl);
   var oList = clientContext.get_web().get_lists().getByTitle('recettes');
   var camlQuery = new SP.CamlQuery();
   var camlBuilder = new CamlBuilder();
   var caml = camlBuilder.Where().IntegerField("ID").In(itemIds).ToString();
   camlQuery.set_viewXml("<View><Query>" + caml + "</Query></View>");
   this.collListItem = oList.getItems(camlQuery);
   clientContext.load(collListItem, 'Include(Id, Title, Recette)');
   clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
}

function onQuerySucceeded(sender, args) {
   var listItemEnumerator = collListItem.getEnumerator();
   var recettes = new Array();
   while (listItemEnumerator.moveNext()) {
      var oListItem = listItemEnumerator.get_current();
      recettes.push(new Recette(oListItem));
   }

   createPDF(recettes);
}

function onQueryFailed(sender, args) {
   alert('Request failed. ' + args.get_message() + 'n' + args.get_stackTrace());
}

function createPDF(recettes){
   var index, offset = 0;
   var dataLen = recettes.length;
   var doc = new jsPDF("portrait", "mm", "a4");
   for (index = 0; index < dataLen; ++index) {
      currRecette = recettes[index];
      offset+=20;
      doc.setFontSize(22);
      doc.text(20, offset, currRecette.Title);
      lines = doc.setFont('Times','Roman')
         .setFontSize(16)
         .splitTextToSize(currRecette.Recette, 390);
      offset+=20;
      doc.text(20, offset, lines);
      offset += lines.length * 5;
   }

   doc.save('exportRecettes.pdf');
}

jQuery(document).ready(function () {
   //add event on button
   $( "#BtnPdfExport" ).click(function() {
      try{
         // retrieve selected items' ID
         var selectedItemIDs = new Array();
         $("table[summary='Recettes'] tr.s4-itm-selected").each(function(){
            selectedItemIDs.push($( this ).attr("iid").split(",")[1]);
         });
         if(selectedItemIDs.length > 0) retrieveListItemsInclude(selectedItemIDs);
      }
      catch(exception)
      {
         alert("Exception " + exception);
      }
      finally{
         return false;
      }
   });
});

Dans la page comportant les webparts connectées, ajouter une webpart de contenu et éditer son HTML afin d’ajouter le code suivant :

<script type='text/javascript' src='/_layouts/15/CAA.RSE.Communities/js/camljs.js'></script>
<script type='text/javascript' src='/_layouts/15/CAA.RSE.Communities/js/FileSaver.js'></script>
<script type='text/javascript' src='/_layouts/15/CAA.RSE.Communities/js/jspdf.js'></script>
<script type='text/javascript' src='/_layouts/15/CAA.RSE.Communities/js/exportPdf.js'></script>
<script type='text/javascript' src='/_layouts/15/CAA.RSE.Communities/js/jspdf.plugin.split_text_to_size.js'></script>

<button id="BtnPdfExport">Export PDF</button>

Et voila le résultat (gif de 500Ko) :

Etonnant, non ?

Références:

Site officiel de jsPDF

jsPDF sur Github

CAMLjs sur codeplex