Archives de mot-clé : SharePoint 2013

Créer des filtres dynamiques sur vos List View WebPart avec du JS + KnockoutJS

Introduction

Jusqu’à tout récemment, j’utilisait une WebPart de filtre avec une connexion afin de filtrer l’année de publication des nouvelles :

Actualites

Cependant, ça ne fonctionne pas lorsqu’on utilise la pagination ET le filtre.  Le problème c’est que la pagination ajoute les paramètres de filtre directement dans le Querystring Ex : FilterField1=PublishedDateYear&FilterValue1=2016. Par la suite, même si on change de valeur via la WebPart de filtre, la valeur qui sera appliqué sera toujours celle du Querystring.

Je n’ai malheureusement pas trouvé de solution élégante à cette problématique alors j’ai plutôt opté pour une solution en JavaScript qui m’offre plus de flexibilité.

Une solution 100 % JS

Je me suis basé sur un article que j’ai trouvé sur le blog de Phil Harding et je l’ai adapté à mon besoin. Le résultat est très intéressant et nous allons voir en détail comment mettre le tout en place dans ce billet. Voici tout d’abord un bref aperçu :

ActualitesFiltreDynamique

Le code peut être exécuté autant dans SP2010 que SP2013 mais il y a quelque subtilité au niveau du code.

Intégration du code

La solution utilise une WebPart Éditeur de contenu pointant vers un fichier « txt » qui contient le balisage HTML. Ce fichier « txt » contient des références vers un fichier JS, CSS et au Framework Knockout JS v3.1.0.

La structure des fichiers est la suivante :

  • /Style Library/txt/script_actualites_filtre_html.txt (Gist)
  • /Style Library/css/app_filtre.css (Gist)
  • /Style Library/scripts/app_filtre.js (Gist)
  • /Style Library/scripts/knockout-3.1.0.js (Source)

La première étape consiste à copier vos fichiers dans votre dossier Style Library et à ajuster les références.

Ensuite, dans une page contenant une List View WebPart ajouter une WebPart Éditeur de contenu avec un lien vers le fichier : /Style Library/txt/script_actualites_filtre_html.txt.

Puis, adapter le fichier app_filtre.js (Voir les TODO dans le code) afin que vos filtres correspondent à vos colonnes et à votre liste.

app_filtre.js


(function(module,$j) {
"use strict";

window.pd = window.pd || {};

pd.MeetingsViewModel = function() {
   /* observable state */
   this.FilterTextIm = ko.observable('');
   this.FilterText = ko.computed(this.FilterTextIm)
                        .extend({ throttle: 400 });
   this.LibraryTitle = ko.observable('');
   this.LibrarySubTitle = ko.observable('');
   this.FilterYears = new ko.observableArray([]);
   this.FilterMonths = new ko.observableArray(['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre']);
   this.SelectedYear = ko.observable('');
   this.SelectedMonth = ko.observable('');
   this.Working = ko.observable(true);

   /* populate years filter as Current Year..-=4 */
   for(var cy = parseInt(new Date().getFullYear()), y = cy; y > (cy-3); y--) { this.FilterYears.push(y.toString()); }

   /* model actions */
   this.filterByYear = function(year) {
      this.FilterTextIm('');   // zap the filter text
      year = (year === this.SelectedYear()) ? '' : year;
      this.SelectedYear(year);
   }.bind(this);
   this.filterByMonth = function(month) {
      this.FilterTextIm('');   // zap the filter text
      month = (month === this.SelectedMonth()) ? '' : month;
      this.SelectedMonth(month);
   }.bind(this);

   /* model behaviour */
   this.isSelectedYear = function (year) {
      var y = this.SelectedYear();
      return (y == year);
   }.bind(this);
   this.isSelectedMonth = function (month) {
      var m = this.SelectedMonth();
      return (m == month);
   }.bind(this);
}


pd.FilterLibraryApp = function() {
   var
      viewModel = new pd.MeetingsViewModel(),
      debug = true,
      wpCtx = null,
      wpEvent = null,
      
      filterControlId = null,
      listViewTableId = null,
      _module = {
         start: start
      };
   return _module;

   function locateListView() {
      var fn = function() {
         InitAllClvps();

         // find the CTX for the LVWP
         for(var k in g_ctxDict) {
           if (debug && window.console) console.log(g_ctxDict[k]);
           
           if (g_ctxDict[k].ListTitle === module.LibraryTitle) {
             wpCtx = window['ctx'+g_ctxDict[k].ctxId];
             break;
           }
         }
         if (debug && window.console) {
            console.log(wpCtx.clvp);
            console.log(wpCtx.clvp.ctx);
            console.log(wpCtx.clvp.ctx.view);
         }
         /* this isn't used in the 2010 version, not figured out if/how to use it yet 
         var clvp = (wpCtx && wpCtx.clvp) ? wpCtx.clvp : null;
         wpEvent = { clvp: clvp, event: { currentCtx: { clvp: clvp } } };
         */
         
         // find the LVWP and table containing the rows
         var 
            $wpnode = $j('#'+wpCtx.clvp.wpq).first(),
            $wpTableNode = $wpnode.find("table[id^='onetidDoclibViewTbl']");
         listViewTableId = $wpTableNode.attr('id');

		 //Il faut avoir préalablement coché la case "Afficher le bouton de raffraichisement" sur la LVWP

         // find the postback control id; e.g. ctl00$m$g_109629d4_d78b_4c9e_8ec0_90078b6e444e$ctl02
         var
            $wpinput = $wpnode.find('> :first-child > input:first-child'),
            ctrlid = $wpinput.attr('id');
         ctrlid = ctrlid && ctrlid.length
                     ?  ctrlid.replace(/^(ctl\d\d)_/gi,'$1$')
                               .replace(/_g_/g,'$g_')
                               .replace(/_(ctl\d\d)$/gi,'$$$1')
                     : '';
         if (debug && window.console) { console.log(">>locateListView: controlid="+ctrlid); }
         filterControlId = ctrlid;
         viewModel.LibraryTitle(wpCtx.ListTitle);
         viewModel.LibrarySubTitle('All');
         viewModel.Working(false);
      };
      EnsureScript("inplview", typeof InitAllClvps, fn);
   }

   function onFilterChange(filterType) {
      if (!filterControlId) {
         alert('Unable to filter: the list view webpart could not be located or the list view contains a Person/Group column!');
         return;
      }

      var
         filterColumn = null,
         filterValue = '';

      if (filterType.match(/year/gi)) {
         filterValue = this.SelectedYear();
         filterColumn = 'PublishedDateYear'; //TODO ajuster votre filtre
      } else if (filterType.match(/month/gi)) {
         filterValue = this.SelectedMonth();
         filterColumn = 'PublishedDateMonth'; //TODO ajuster votre filtre
      }

      if (!filterValue) filterValue = '##dvt_all##';
      var filterCall = "__doPostBack('"+filterControlId+"','NotUTF8;__filter={"+filterColumn+"="+filterValue+"}')"

      if (debug && window.console) {
         console.log(">>Filter by " + filterType + " on ["+filterColumn+"] = ["+filterValue+"]");
         console.log("  >> " + filterCall);
      }
      eval(filterCall);
   }

   function start() {
      ko.applyBindings(viewModel, $j('#cdtm').get(0));
      viewModel.SelectedYear.subscribe(onFilterChange.bind(viewModel, 'year'));
      viewModel.SelectedMonth.subscribe(onFilterChange.bind(viewModel, 'month'));

      locateListView();
   }
}();

$j(pd.FilterLibraryApp.start);

})({ Name: 'Module', LibraryTitle: 'Billets' },jQuery); //TODO Ajuster le titre de votre liste


Dernier détail

Afin de pouvoir lier les filtres à la vue, pour ma part j’ai été contraint de cocher la case suivante :

ActualitesFiltreDynamiqueLVWP

Sans cela, le controlid était toujours vide. Si vous avez une erreur vérifier tout d’abord votre console (F12) :

ActualitesFiltreDynamiqueLVWPConsole

Conclusion

Seulement avec du JS on a réussi à ajouter des filtres dynamiques sur une List View WebPart et cela fonctionne correctement avec la pagination. Donc, beaucoup de flexibilité au niveau du déploiement et de la personnalisation visuelle.

Référence

Dynamically Filtering SharePoint List Views using Javascript

Advertisements

Importer un document word avec ses images dans un Wiki SharePoint

Introduction

Cette semaine, un de mes clients m’a demandé d’importer des documents Word contenant des centaines d’images dans une page Wiki SharePoint. Au départ, je me suis dit que ça serait facile et qu’un simple copier-coller allait faire le travail! Cependant, je me suis vite rendu compte que les images ne suivaient pas. Je n’avais pas vraiment envie de faire le travail de moine consistant à enregistrer individuellement chacune des images pour ensuite les insérer dans le contenu Wiki. Je me suis donc penché sur les diverses solutions possibles pour me faciliter le travail.

Solutions possible

  1. Utiliser un outils tiers
  2. Utiliser un Plugin sur CodePlex : MS Word SharepointWiki Plugin
  3. Utiliser Word pour publier vers un billet de Blog SharePoint
Dans ce billet, je vais mettre l’emphase sur le point 3 car il ne nécessite pas de frais d’acquisition et d’installation particulière.

Utiliser Word pour publier vers un billet de Blog SharePoint

Tout d’abord, pour vous assurer que cette solution fonctionne, il vous faudra idéalement respecter les prérequis suivant :

  • Une version de Word qui correspond à votre version de SharePoint.
    • Ex  : Word 2013 et SharePoint 2013
  • Un site de type « Blog » afin de pouvoir publier votre document Word.
    • Il pourra être supprimé sans problème une fois que l’opération sera complété.

Étape 1

La première étape consiste à ouvrir votre document Word et ensuite de naviguer sous l’onglet Fichier -> Partager -> Publier sur le blog

wordToWiki.PNG

Étape 2

Ensuite, il faut enregistrer un compte de blog

wordToWiki2.PNG

Puis sélectionner « Blog SharePoint » dans la liste déroulante

wordToWiki3

Puis saisir le URL vers votre site Blog

wordToWiki4

Étape 3

Ensuite, appuyer sur le bouton « Publier » dans Word et patienter pendant la publication.

wordToWiki5

Une fois l’opération complété, naviguer vers votre Blog afin de vérifier si le contenu a bien été créé.

wordToWiki6

Accéder à l’élément en mode édition et récupérer la source HTML en utilisant le bouton « Edit source » dans le ruban.

wordToWiki7

Étape 4

Copier la source HTML récupéré précédemment dans votre page Wiki en utilisant le bouton « Edit source » dans le ruban.

Vous devriez maintenant voir le contenu de votre document Word sans les images. Les images sont importées dans une bibliothèque de document « Photos » du site Blog.

wordToWiki9

Il faut donc copier les images importées dans le site Blog vers une bibliothèque dans votre site Wiki. Le plus facile est d’utiliser la fonctionnalité « Ouvrir avec l’explorateur ».

wordToWiki8

Une fois que vos images ont été copiées, il vous faudra ajuster le chemin vers vos images dans la source HTML de votre page Wiki.

Ex : /VotreBlog/Photos -> /VotreWiki/Photos

Une fois cela terminé, vous devriez avoir le même contenu dans votre page Wiki que dans votre document Word.

Résultat :

wordToWiki10

Note

Il arrive que les liste à puces ou les listes numérotés ne fonctionnent pas correctement lorsqu’il y a des espaces entre les éléments et qu’il soit nécessaire de les refaire manuellement dans la page Wiki.

Conclusion

L’intégration entre SharePoint et la suite Office est très évolué comme on a pu le voir dans ce billet. Cette façon de faire peut vous sauver énormément de temps si vous avez des documents volumineux avec un grand nombre d’images. Je considère que c’est une meilleure alternative que d’avoir à installer un plugin ou acheter un logiciel tiers.

 

Envoyer des courriels périodiques pour SharePoint OnPrem avec du PowerShell

Introduction

SharePoint dispose d’un mécanisme d’alerte assez puissant. Celui-ci permet d’envoyer à l’abonné une alerte par courriel basé sur une action qui se produit sur l’élément (il est créé, modifié, etc…). Il comporte cependant des limitations :

  • On ne peut pas recevoir une alerte périodique (ex : tous les trimestres, tous les premiers Lundi du mois, etc…);
  • La modification du gabarit de courriel est complexe;
  • Il n’est pas possible de modifier l’adresse de réponse et le sujet pour une alerte en particulier;
  • Vous devez être que propriétaire du site afin d’envoyer des alertes à d’autres personnes. Sans cela, la seule personne à qui vous pouvez envoyer des alertes à est vous-même.

Alors que faire si on veut contourner ces limitations?

Il y a quelques solutions possibles :

  1. Créer un timer job SharePoint (Exemple)
  2. Créer un flux de travail (Exemple)
  3. Utiliser un logiciel tiers (Exemple)
  4. Créer un script PowerShell s’exécutant dans une tâche planifié (Source)

Je vais mettre l’emphase sur le point 4 dans ce billet car à mon avis c’est la meilleure alternative lorsque les alertes « out-of-the-box » ne répondent pas à votre besoin.

Comparaison des différentes solutions

Alertes SharePoint out-of the-box Timer Job personnalisé

 

Flux de travail personnalisé Outil tiers

Script PowerShell

Flexibilité Mark Mark
Permet de modifier le contenu dynamique envoyé facilement Mark Mark Mark Mark
Permet de modifier le visuel du gabarit sans avoir besoin de programmation Mark Mark Mark
Envoyer des courriels à des utilisateurs externes Mark Mark
Nécessite peu d’efforts de configuration Mark Mark
Permet d’envoyer un courriel basé sur un intervalle de temps Mark Mark Mark
Gratuit Mark Mark Mark Mark

 

Utiliser un script PowerShell

Le script pour SharePoint 2013 sur site (OnPrem) est disponible ici :

telecharger-bouton

Assurez-vous de modifier la section « Constantes » et d’y inscrire les valeurs correspondants à votre environnement.

Capture

Celui-ci va créer une liste EmailTemplate :

EmailTemplate

Cette liste permet à un utilisateur de piloter les différentes valeurs sans avoir besoin d’effectuer de la programmation lorsque vient le temps de changer le contenu, le sujet, etc… Bien évidemment, si vous ajoutez d’autres modèles, vous devrez ajuster votre code afin de les utiliser correctement.

Le script va aussi créer une liste Project :

ProjectListView

Celle-ci est uniquement présente à des fins de démonstration. C’est à partir de cette liste que nous allons envoyer du contenu dynamique par courriel. Vous pouvez donc la supprimer et vous alimenter de votre propre liste si vous le souhaitez.

Voici un exemple de courriel envoyé par le script :

EmailResult

Exécution du script PowerShell dans une tâche planifiée

Démarrer -> Exécuter -> Taskschd.msc

Créer une nouvelle tâche :

ScheduledTaskPowerShell_create

Spécifier un compte ayant les droits d’administrateur de la ferme SharePoint.

Spécifier un déclencheur :

ScheduledTaskPowerShell_declencheur

Ajouter une nouvelle action :

ScheduledTaskPowerShell_action

Programme/Script : Powershell.exe

Ajouter des arguments : -ExecutionPolicy Bypass C:\Sources\SharePointerie.OnPrem.SendTimeBasedEmail.ps1

En prenant soin d’adapter le chemin vers votre script.

Cliquer sur OK pour enregistrer la tâche.

Outils de développeur pour faciliter le travail avec les courriels

Pour ma part, j’utilise l’outil SMTP4DEV afin de travailler en mode local. Il suffit de démarrer l’application et celle-ci se chargera d’intercepter les envois de courriel sur le port défini.

smtp4Dev

Source : https://smtp4dev.codeplex.com/

Conclusion

L’utilisation d’un script PowerShell pour envoyer des courriels périodiques permet un maximum de flexibilité autant au niveau du déploiement qu’au niveau du pilotage. Il n’y a pas de période d’indisponibilité lors du déploiement comparativement au déploiement d’un Timer Job. Je crois donc que c’est la meilleure alternative lorsque les alertes out-of-the-box ne répondent pas à votre besoin.

Personnaliser vos sites SharePoint 2013 avec le Client Side Rendering (CSR) et le JS Link

Introduction

Effectuer des personnalisations dans l’interface utilisateur de SharePoint a toujours été un défi pour bien des développeurs. Dans les versions 2007 et 2010, on utilisait majoritairement le XSLT pour faire des personnalisations bien que c’était relativement complexe à s’y familiariser. D’autres utilisaient des WebParts de type Éditeur de contenu pour effectuer des modifications JavaScript directement dans une page et dans ce cas la difficulté est de trouver une « poignée » pour appliquer les modifications sur un élément précis. La fonctionnalité JSLink est arrivé dans SharePoint 2013 pour simplifier la vie aux développeurs en leur permettant de faire des personnalisations avec des langages qu’ils connaissent déjà : HTML, JavaScript et CSS. Grâce à cette fonctionnalité, on peut contrôler via un fichier JavaScript le rendu visuelle des éléments suivant :

  • Champ
  • Élément
  • Formulaire de liste
  • Vue
  • WebPart

XSL/XSLT vs Client Side Rendering

Le tableau ici-bas résume bien les différences entre les deux fonctionnalités :

XSL/XSLT Client Side Rendering
Performance
  • Plus rapide lorsqu’il y a un grand nombre d’éléments à afficher.
  • Aucune charge additionnel dans le navigateur.
  • Plus lent lorsqu’il y a un grand nombre d’éléments à afficher.
  • La vitesse du navigateur client peut avoir un impact sur la performance.
  • Réduit la charge du serveur.
Complexité
  • Langage complexe à apprendre initialement.
  • Difficile à déboguer (Souvent par essaie-erreur).
  • Temps de développement plus grand.
  • Facile de déboguer du JavaScript, HTML et CSS avec les navigateurs modernes.
  • Temps de développement réduit.
Flexibilité
  • Limité aux fonctions XSLT 1.0 pour SharePoint 2010 et 2013.
  • Limité pour faire un rendu visuel complexe.
  • Très efficace pour faire des opérations coûteuse (ex : Count dans une vue).
  • Permet de cibler les personnalisations sur une vue, un champ, une WebPart, etc…
  • Possibilité d’utiliser des plugins (ex : JQuery).
  • Très adapté pour faire un rendu visuel complexe.
  • Inefficace pour faire des opérations coûteuses (ex : Count dans une vue).
Compatibilité
  • Indépendant du navigateur.
  • Supporté dans SP2010 et SP2013.
  • Fonctionne même si le JavaScript est désactivé dans le navigateur.
  • Génère du contenu statique qui est bien interprété par les robots d’indexation.
  • Adapté pour des sites publiques.
  • Dépendant du navigateur.
  • Supporté dans SP2010 et SP2013
  • Requiert que le JavaScript soir activé dans le navigateur.
  • Génère du contenu dynamique qui n’est pas bien interprété par les robots d’indexation.
  • Non-adapté pour des sites publiques.

Comment configurer le JSLink?

Pour référencer vos fichiers dans la propriété JSLink vous pouvez spécifier un jeton de remplacement (Token) ainsi qu’un séparateur (|) si vous avez plusieurs fichiers.

Les jetons disponibles sont les suivants :

  • ~site : correspond au URL du site Web courant.
  • ~sitecollection : correspond au URL de la collection de site parent du site Web courant.
  • ~layouts : correspond au dossier « _layouts/15 » de la web application.
  • ~sitelayouts – correspond au dossier layouts du site courant (ex : site/monsite/monsoussite/_layouts/15).
  • ~sitecollectionlayouts : correspond au dossier layouts de la collection de site courante (Ex:  /sites/monsite/_layouts/15).
  • ~sitelayouts – correspond au dossier layouts du site courant (Ex: /site/monsite/monsoussite/_layouts/15).

En utilisant ces jetons avec le séparateur on peut donc référencer plusieurs fichiers de scripts dans le JSLink.

Ex : ~site/style library/­monScript1.js|~sitecollection/style library/monScript2.js.

On peut spécifier la propriété JSLink en utilisant ces méthodes :

  • PowerShell
  • Code serveur C#/VB en utilisant l’API
  • Code serveur déclaratif XML
  • Interface utilisateur

Pour simplifier ce billet, je vous propose la méthode utilisant l’interface utilisateur :

jslink_link

Pour plus de détail sur les autres méthodes permettant de spécifier la propriété JSLink, je vous recommande le billet de Tobias Zimmergren.

Fonctionnement technique

À l’intérieur de ces fichiers, le JavaScript est utilisé pour « overrider » :

  • la manière dont les données sont affichés
  • le contenu à afficher

Voici un exemple de code permettant de modifier la valeur d’un champ dans une vue :

// Créer un namespace pour la fonction afin d'éviter des collisions avec les autres fonctions
var sharepointerie = sharepointerie || {};

(sharepointerie.CustomizeFieldRendering = function () {

// Initialise les variables pour overrider les objets
 var overrideCtx = {};
 overrideCtx.Templates = {};

 /*
 * Force la valeur "SharePointerie" dans le titre de tous les éléments.
 */
 overrideCtx.Templates.Fields = {
 'LinkTitle': { 'View' : 'SharePointerie' }
 };

 /*
 * Enregistre l'override du template.
 */
 SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
})();

Techniquement, on construit un objet qui sera ensuite utilisé afin d’enregistrer l’override du template. L’objet « overrideCtx » peut ensuite interagir avec ces propriétés :

  • View
  • Header
  • Body
  • Footer
  • Group
  • Item
  • Fields
  • OnPreRender
  • OnPostRender

Dans l’exemple on utilise la propriété Fields avec ce format :

overrideCtx.Templates.Fields = {NomDuChamp: {Porté : Override}}

  • NomDuChamp : Le nom interne du champ que l’on veut overrider.
  • Porté : Les choix possibles « View », »DisplayForm », »EditForm », et « NewForm » permettent de spécifier l’endroit ou l’on veut faire l’override.
  • Override : Contient une chaîne HTML ou une fonction à exécuter à l’intérieur de tags « <#= #> ».

Résultat (avant l’ajout du CSR) :

jslink_technique_avant

Résultat (après l’ajout du CSR) :

jslink_technique_apres

Des exemples concrets

1) Limiter la taille du texte dans le champ « Corps » d’une liste d’annonces

Cet exemple de code, réalisé par Muawiyah Shannak, permet d’afficher un résumé du corps d’une annonce et d’afficher la version intégrale dans une infobulle.

Pour déployer la solution :

  1. Télécharger le fichier CSR.
  2. Ajouter le fichier sous /Style Library/.
  3. Créer une liste de type Annonces
  4. Créer ou modifier une vue afin d’afficher le champ Corps.
  5. Modifier la page contenant la List View WebPart.
  6. Aller dans les propriétés de la WebPart et ajouter le fichier JSLink (~sitecollection/Style%20Library/SubstringLongText.js) dans la propriété Lien Js (JSLink) sous l’onglet Divers
  7. Cliquer sur Appliquer et quitter l’édition de la page.

Résultat (avant l’ajout du CSR) :

jslink_limite_texte_avant

Résultat (après l’ajout du CSR) :

jslink_limite_texte_apres

2) Ajouter des onglets dans un formulaire

Cet exemple de code, réalisé par Muawiyah Shannak, permet d’utiliser JQuery Tabs pour afficher des onglets dans un formulaire.

Pour déployer la solution :

  1. Télécharger le fichier CSR.
  2. Ajouter le fichier sous /Style Library/.
  3. Créer une liste personnalisé
  4. Ajouter des colonnes à la liste
  5. Modifier l’objet tabsObj selon vos besoin.
  6. Créer une nouvelle vue contenant le champ Lien
  7. Modifier les pages « newform » et « editform » de la liste
  8. Aller dans les propriétés de la WebPart et ajouter le fichier JSLink (~sitecollection/Style%20Library/Tabs.js) dans la propriété Lien Js (JSLink) sous l’onglet Divers
  9. Cliquer sur Appliquer et quitter l’édition de la page.

Résultat (avant l’ajout du CSR) :

jslink_tabs_avant

Résultat (après l’ajout du CSR) :

jslink_tabs_apres

Points à faire attention

  • Il peut y avoir des conflits avec le CSR si vous utilisez la fonctionnalité « Minimal download strategy ». Pour corriger ce problème consulter ce billet.
  • Le code de vos fonctions CSR ne devrait pas être défini dans le Namespace globale.
  • Le nom interne des colonnes est utilisé dans le code JavaScript des fonctions CSR alors favoriser des noms de colonnes sans espaces et sans caractères accentués.
  • Le CSR, contrairement au XSLT, dépend du navigateur alors assurez-vous de tester dans plusieurs navigateurs.

Conclusion

Pour conclure, le CSR comporte plusieurs avantages au niveau de la flexibilité et de la complexité comparativement au XSLT. Cependant, il vient aussi avec certaines lacunes en lien avec la performance et la compatibilité. Il y a donc énormément de potentiel à utiliser le CSR pour vos personnalisations (Validations, AJAX, etc…) mais comme il y a des limitations, je crois que le XSLT va continuer d’exister en parallèle. Je vous invite donc à l’essayer et à me donner vos commentaires!

Références

Voici quelques liens pour approfondir vos connaissance sur le CSR / JSLink.

Exemples de codes CSR réalisé par Muawiyah Shannak

Perfomance de l’utilisation du CSR via le JSLink

5 faits concernant l’utilisation du JSLink

Comment : personnaliser un type de champ à l’aide de rendu côté client

Comment : personnaliser le mode Liste dans les apps pour SharePoint à l’aide du rendu côté client

Introduction au CSR par Ross Bradbrook

Bon point de départ sur l’utilisation de l’API CSR

Conversion de solutions de ferme SharePoint 2010 vers SharePoint 2013

De retour sur mon blog après des vacances bien mérité. On m’attendait de pied ferme pour évaluer avec d’autres collaborateurs la conversion de plusieurs solutions de ferme SharePoint 2010 vers SharePoint 2013. Je vais donc tenter, dans ce billet, de vous aider à identifier et à corriger plus rapidement d’éventuelles problématiques de conversion.

Conversion des Requête de contenu (Content Query WebPart) SharePoint 2010 vers SharePoint 2013

Dans les composantes contenant des Requêtes de contenu (Content Query WebPart), nous sommes tombés sur une SharePointerie lors de la conversion. À première vue, après avoir effectué la conversion vers SharePoint 2013 de cette composante avec Visual Studio 2013, le code compile!

conversion_sp2013_Upgrade

On est prêt pour aller en PROD ? Pas tout à fait! J’ai alors déployé la composante et lorsque j’ai essayé de la tester alors j’ai obtenu un message d’erreur lors de l’affichage de la WebPart.

Dans la plupart des Requête de contenu à convertir vers 2013, l’aspect visuel a été modifié afin de fournir une expérience utilisateur plus riche. Pour s’y prendre, le lien vers les fichiers XSL par défaut a été modifié dans le XML du fichier *.webpart.

Exemple d’un fichier *.webpart SharePoint 2010

<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
 <metaData>
   ...
 </metaData>
 <data>
 <properties>
   <...>
   <property name="MainXslLink" type="string">/Style Library/XSL Style Sheets/POC.SHP.WebPart.Main.xsl</property>
   <property name="ItemXslLink" type="string">/Style Library/XSL Style Sheets/POC.SHP.WebPart.Item.xsl</property>
   <...>
 </properties>
 </data>
 </webPart>
</webParts>

J’ai alors tenté de retirer les liens XSL afin d’utiliser l’affichage par défaut de la Requête de contenu (Content Query WebPart) et le tout s’est affiché mais sans inclure l’aspect visuel personnalisé. On s’est donc dit que le URL vers le fichier XSL n’était pas bon. Je me suis alors rappelé que dans SharePoint 2013, il faut maintenant spécifier dans le URL le dossier /15/ (Aussi appelé Hive) avant le /Style Library/.

Exemple d’un fichier *.webpart corrigé pour SharePoint 2013

<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
 <metaData>
   ...
 </metaData>
 <data>
 <properties>
   <...>
   <property name="MainXslLink" type="string">/15/Style Library/XSL Style Sheets/POC.SHP.WebPart.Main.xsl</property>
   <property name="ItemXslLink" type="string">/15/Style Library/XSL Style Sheets/POC.SHP.WebPart.Item.xsl</property>
   <...>
 </properties>
 </data>
 </webPart>
</webParts>

Pourquoi est-ce ainsi?

Dans SharePoint 2013, il est possible d’installer à la fois des solutions SharePoint 2010 et 2013. Ceci est supporté par le fait qu’il y a deux dossiers virtuel, un pour le SP2010 (14) et l’autre pour les SP2013 (15).

Si votre code SharePoint 2010 contient des références vers /_layouts/ ou /Style Library/, il vous faudra les mettre à jour respectivement pour /_layouts/15/ ou  /15/Style Library/ afin que le chemin soit résolus correctement lors de l’utilisation de la solution dans SharePoint 2013.

Le tableau ici-bas présente les différences des chemins :

SharePoint 2010 SharePoint 2013
/Style Library/scripts/jquery.min.js /15/Style Library/scripts/jquery.min.js
/_layouts/images/test.jpg /_layouts/15/images/test.jpg

Conversion des Visual WebParts SharePoint 2010

Lors de la conversion des composantes de ferme de type Visual Web Part nous sommes tombé sur une autre SharePointerie. Cependant, lorsqu’on connait la recette vue précédemment ça devient un peu plus évident.

Lorsque j’ai ajouté la Visual WebPart dans ma page, ça à tout simplement fait exploser la page et SharePoint m’a proposé la page de maintenance afin de supprimer la WebPart en erreur. C’est très gentil de sa part, mais ce n’est pas très utile pour diagnostiquer un problème. Alors, j’ai décidé de m’attacher au code et de déboguer la méthode CreateChildControls qui est lancé au tout début lors de l’affichage de la Visual WebPart.

C’est alors que j’ai eu ce message d’exception : The file ‘/_controltempates/…/…UserControl.ascx’ does not exist.

conversion_sp2013_debug_visualwebpart

Pourtant mon fichier *.ascx est bien déployé dans le dossier :

conversion_sp2013_15_visualwebpart

J’ai alors appliqué la même recette que vue précédemment en ajoutant le /15/ dans le URL tout de suite après le /_controltemplates/ et bien sûr ça a bien fonctionné :

conversion_sp2013_debug_passed_visualwebpart

Il faut comprendre que le commentaire dans le haut de la constante _ascxPath indique : Visual studio might automatically update this path when you change the Visual WebPart.

Cependant, Visual Studio ne l’a pas fait lors de la conversion de SharePoint 2010 vers SharePoint 2013 ! J’ai ouvert un billet sur le site de Visual Studio User Voice alors n’hésiter pas à aller voter pour celui-ci. Qui sais, un jour Visual Studio sera peut-être amélioré et cette SharePointerie n’existera plus!

Conclusion

En conclusion, si votre code SharePoint 2010 contient des références vers /_layouts/, /_controltemplates/ ou /Style Library/ vous aurez à modifier vos URL pour que ça fonctionne dans SharePoint 2013. Il ne s’agit pas d’une liste exhaustive des problématiques de conversion que l’on peut retrouver mais c’est un bon point de départ. De plus, lorsque l’on connait la recette pour corriger les problèmes c’est beaucoup plus simple et on perd beaucoup moins de temps.

Avez-vous effectué des conversions SharePoint 2010 vers SharePoint 2013 ? Est-ce que vous avez rencontré des SharePointeries? N’hésitez pas à commenter pour lancer la discussion!

Comment créer un service Web WCF (REST) personnalisé dans SharePoint 2013

Récemment, un collègue m’a demandé comment faire pour exécuter du code serveur dans un bouton du ruban. Je lui avait alors répondu : Le plus simple c’est de te faire une page aspx avec une WebPart contenant du code serveur. C’est comme si tu appelais un service web mais au lieu de cela c’est une page aspx avec des paramètres dans le querystring.

Ex : Lors du clique sur un bouton du ruban on appel une page avec des paramètres pour effectuer la modification d’un élément. Assumons que la page se nomme ModifierElement.aspx, alors il sera possible d’y accéder seulement à partir de sont chemin fixe : http://serveur/sites/site/web/pages/ModifierElement.aspx?Id=1

Dans le code du ruban (CustomAction), on peut seulement utiliser du code JavaScript (CSOM) et pour utiliser du code serveur on doit invoquer un événement via le bouton. Ces deux dernières méthodes comportent un certain niveau de complexité ainsi que des limitations.

L’approche de la page aspx est souvent utilisé pour sauver du temps lors du développement. Cependant, après quelques recherches je suis tombé sur une meilleure façon d’exécuter du code serveur en utilisant un Service Web WCF (REST) personnalisé hébergé dans SharePoint.

Comment faire un service Web (WCF) personnalisé ?

Les services Web natif de SharePoint peuvent être accédés à partir du chemin _vti_bin.  Lorsque l’on va développer notre service Web personnalisé il sera accessible via ce même chemin.

Ex : Assumons que le service Web personnalisé se nomme Service.svc alors il sera possible d’y accéder, entre autres, à partir des chemins suivants :

* C’est un avantage comparativement à la méthode de la page contenant une WebPart 

Étape 1 – Créer un projet Visual Studio 2012

  • Créer un nouveau projet à partir du modèle « SharePoint 2013 – Empty Project »
  • Nommer le projet « POC.SharePoint.WebServiceHelloWord »
  • Sélectionner « Deploy as a farm solution »

Étape 2 – Ajouter les références

  • Ajouter les références suivantes dans votre projet : Microsoft.SharePoint.Client.ServerRuntime 15.0.0.0 et System.ServiceModel 4.0.0.0

referenceWcf

Étape 3 – Ajouter un SharePoint Mapped Folder (ISAPI)

Le dossier ISAPI est le dossier spécial dans la « hive » SharePoint où tous les services WCF doivent être déployés. Ce dossier est associé au chemin « _vti_bin » dans IIS et ainsi les services déployés dans ce dossier peuvent être accédé par ce URL : http://serveur/_vti_bin/{NomService}.svc

Pour ajouter un nouveau Mapped Folder on sélectionne le projet et ensuitie on clique de droit -> ajouter -> SharePoint Mapped Folder :

mappedFolderWcf

Ensuite sélectionner le dossier ISAPI :

isapiWcf

C’est une bonne pratique de se créer un sous dossier spécifique à votre projet e.g.: « WebServiceHelloWord » .

Étape 4 – Définir le Service, le Contract et l’Interface

Dans le Mapped Folder « ISAPI » ajouter les fichiers suivants :

  • un fichier texte nommé « Service.svc » (Fichier du service lui même qui sera déployé dans le hive de SharePoint et qui sera appelé dans IIS)
  • un fichier Interface nommé « IService.cs » (Fichier contenant le contrat d’opération du service)
  • un fichier Classe nommé « Service.svc.cs » (Fichier contenant l’implémentation de l’interface du service)

Étape 5 – Définir le contenu du Service, du Contract et de l’Interface

Ouvrez le fichier Service.svc.cs et y insérer le code suivant :

using Microsoft.SharePoint.Client.Services;
using System.ServiceModel.Activation;

namespace POC.SharePoint.WebServiceHelloWord.ISAPI.WebServiceHelloWord
{
 [BasicHttpBindingServiceMetadataExchangeEndpoint]
 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
 public class Service : IService
 {
 public string HelloWorld()
 {
 return "Hello World de mon service WCF dans SharePoint 2013";
 }
 }
}

Ouvrir le fichier IService.cs et y insérer le code suivant :

using System.ServiceModel;

namespace POC.SharePoint.WebServiceHelloWord.ISAPI.WebServiceHelloWord
{
 [ServiceContract]
 public interface IService
 {
 [OperationContract]
 string HelloWorld();
 }
}

Ouvrir le fichier Service.svc et y insérer le code suivant :

<%@ ServiceHost Language="C#" Debug="true"
 Service="POC.SharePoint.WebServiceHelloWord.ISAPI.WebServiceHelloWord.Service, $SharePoint.Project.AssemblyFullName$"
 CodeBehind="Service.svc.cs"
 Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressBasicHttpBindingServiceHostFactory,
 Microsoft.SharePoint.Client.ServerRuntime,
 Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

Étape 6 – Permettre à VS d’effectuer le remplacement des tokens dans un fichier *.svc

Dans le code du dernier fichier Service.svc qu’on a ajouté il y a une ligne contenant un jeton de remplacement (Token) : $SharePoint.Project.AssemblyFullName$

Si on ne fait rien on aura une erreur lors de la compilation. Il y a plusieurs façon de s’y prendre mais à mon avis la façon la plus élégante est de permettre à VS d’effectuer le remplacement de ces tokens pour les fichiers « *.svc ». J’appel cette technique la passe du coyote qui tousse!

Pour s’y faire, on doit effectuer un « Unload Projet » et ensuite un « Edit {NomDuProjet}.csproj ».

Ensuite trouver la ligne suivante :

<SandboxedSolution>False</SandboxedSolution>

Après cette ligne ajouter celle-ci :

<TokenReplacementFileExtensions>svc</TokenReplacementFileExtensions>

Sauvegarder le fichier et faire un « Reload project ».

Étape 7 – Compiler, déployer et tester le service WCF (REST) personnalisé

Une fois que vous aurez compilé et déployé votre service, vous pourrez le tester directement par son URL : http://serveur/_vti_bin/WebServiceHelloWord/Service.svc

testWcf

Cliquez ici pour télécharger le code source

Autres cas d’utilisations

Bien évidemment, le message retourné par notre fonction « HelloWorld » ne nous apporte rien de concret mais voici quelques exemples d’utilisation que l’on peut faire avec un service Web WCF (REST) personnalisé dans SharePoint :

  • Modifier les métadonnées d’un document (Ex: Ajouter un préfixe)
  • Exporter un rapport personnalisé en PDF
  • Modifier la sécurité d’un élément
  • Envoyer un document en pièce jointe d’un courriel
  • etc…

Conclusion

Utiliser un service Web WCF (REST) personnalisé dans SharePoint comporte plusieurs avantages comparativement à utiliser une page « aspx » avec une WebPart. D’une part, le service est accessible plus facilement via « _vti_bin » dans tout les sites et lorsqu’on utilise un service web WCF on envoie seulement le XML nécessaire au lieu d’envoyer tout le contenu HTML d’une page « aspx ».