Archives de catégorie : Sites collaboratif

Comment copier des affichages SharePoint d’une liste/bibliothèque vers une autre ?

Introduction

Si vous travaillez avec SharePoint depuis un bon moment, vous vous êtes surement retrouvé dans une situation dont vous aviez créé une liste/bibliothèque avec bon nombre d’affichages et qu’à un bon moment, vous aviez besoin de copier ces affichages vers une autre liste ayant les mêmes colonnes. Malheureusement, il n’y a pas de mécanisme permettant de tout sélectionner vos affichages et de les copier vers une autre liste. Dans ce billet je vous présenterai quelque façons pour y arriver en utilisant en premier un outil tiers et en second un script PowerShell.

Les outils tiers

Bien évidemment, si les outils tiers peuvent copier des listes et des bibliothèques, ils peuvent également être utilisé pour copier des affichages dans des listes existantes.

Dans l’exemple ci-dessous j’utilise ShareGate pour copier des affichages :

Affichage1

Affichage2

Puisque c’est seulement une case à cocher, c’est tout ou rien alors ce n’est pas possible de sélectionner les affichages que l’on veut copier ou pas. Heureusement, ça ne nécessite pas un doctorat en génie nucléaire pour arriver à copier des affichages avec un outils tiers.

Le PowerShell à la rescousse

Si vous effectuer une recherche sur google avec les termes « Copy sharepoint view », les premiers résultats que vous allez trouver utilise majoritairement un script PowerShell afin d’effectuer la tâche. En effet, puisqu’il y a peu de chance qu’on effectue cela à tous les jours, il est préférable de faire un script PowerShell qu’une solution utilisant le modèle objet serveur (SSOM) ou client (CSOM).

Ce script permet d’effectuer la copie des affichages d’une liste source vers une liste de destination.

On peut l’appeler facilement ainsi :

.\Copy-SPListViews.ps1 -SourceWebUrl « https://portal.contoso.com/sites/teamsite/ » -DestinationWebUrl « https://portal.contoso.com/hr » -SourceListTitle « Team Documents » -DestinationListTitle « HR Documents »

Celui-ci peut vous offrir d’avantage de flexibilité si de sélectionner les affichages que l’on veut copier ou pas

 

Conclusion

La copie d’affichage est possible dans la mesure ou les deux listes/bibliothèques ont les mêmes colonnes et dans ce billet je vous ai présenté deux méthodes ayant chacun leur niveau de convivialité et de flexibilité.

Advertisements

Facilitez la recherche de sites, d’applications et de ressources pertinentes au travail de vos utilisateurs grâce aux vignettes personnalisées dans SharePoint Server 2016

Introduction

Le lanceur d’applications est une fonctionnalité native d’Office 365 très utile permettant de redimensionner, positionner, épingler, rechercher vos applications en plus de permettre d’ajouter des vignettes personnalisés. Cette fonctionnalité est maintenant disponible dans SharePoint 2016 Server mais pour l’instant, il est seulement possible d’ajouter des vignettes personnalisés.

Prérequis

Il faut installer au minimum la mise à jour publique de novembre 2016 pour SharePoint Server 2016 (Feature Pack 1).

Configuration

La configuration pour activer les vignettes personnalisées est très bien décrite sur le site MSDN alors je ne répéterai pas ces étapes.

Ajouter des vignettes personnalisées

Tout d’abord accéder à la liste de vignette créé lors de l’étape précédente :

http://VotreSite/lists/custom%20tiles

Ensuite ajouter un nouvelle élément dans cette liste :

CustomTile.PNG

 

Note : Le champ Publics peut être laissé vide si on veut que la vignette s’affiche pour tous les utilisateurs.

 

Idéalement l’icône devrait être blanc avec un fond transparent afin d’être similaire aux autres icônes.

Exemple d’icône

Une fois que vous avez ajouté un nouvel élément à la liste de vignettes personnalisées, l’affichage dans le lanceur d’application peut prendre jusqu’à 24 heures en raison de la mise en cache. Cependant, on peut forcer le rafraîchissement de cette cache en exécutant avec la fonction JavaScript ClearSuiteLinksCache() dans la console via F12 :

Affiche les résultats dans la console du navigateur du développeur

Résultat :

Application Web Intranet

HomeIntranet

Application Web autre

TilesWebApp

On peut voir que la vignette personnalisé s’ajoute uniquement dans la Web Application Intranet.

Si jamais vous avez spécifié un URL d’icône non-accessible ou invalide vous aurez droit à magnifique hexagone :

TileHExagone.PNG

Activer les vignettes personnalisées dans plusieurs applications web

Pour utiliser la même liste de vignettes personnalisées dans plusieurs applications, activez la fonctionnalité sur chaque application web, puis mettez à jour la propriété CustomTilesListHostUrl dans l’application web qui contient la liste de vignettes personnalisées souhaitée.

Ex :

Enable-SPFeature -Identity CustomTiles -Url http://web_app -Force

$w = Get-SPWebApplication http://web_app
$w.Properties.CustomTilesListHostUrl = « http://web_app url/ »
$w.Update()

Attention de bien spécifier uniquement l’URL de l’application Web et non pas le lien vers la liste de tuile.

Il faut quand même forcer un ClearSuiteLinksCache() et ensuite les tuiles provenant de la première Application Web s’afficheront :

TileWebAppAutre

Dans ce cas, j’ai utilisé deux applications Web de Publication en HTTP et ça a fonctionné correctement. Je n’ai pas réussi à partager de vignettes entre deux applications Web HTTPS (Certificat SSL non vérifié) dont une est de type Publication et l’autre de type Hôte de site Mon Site.

 

Conclusion

C’est une fonctionnalité très intéressante pour permettre la recherche de sites, d’applications et de ressources pertinentes au travail de vos utilisateurs. Ça ne permet pas aux utilisateurs d’ajouter et de redimensionner leur propres tuiles comme dans O365 mais c’est déjà un bon premier pas.

Référence

Vignettes personnalisées dans SharePoint Server 2016 (MSDN)

 

 

 

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

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.

Techniques pour récupérer les éléments HTML d’une WebPart en JavaScript

Introduction

Dans ce billet je vous propose quelque techniques de JavaScript pour pouvoir récupérer les éléments HTML d’une WebPart spécifique. Chez un de mes clients je me suis vite rendu compte qu’il n’était pas évident d’avoir une « poigné » fixe pour appliquer une personnalisation sur une seule WebPart. Le Client Side Rendering (CSR) est à la mode c’est temps-ci mais il comporte une certaine complexité en comparaison aux quelques lignes de JavaScript qui suivront. Pour ce qui est du CSS, il n’est pas évident de l’appliquer seulement à une WebPart.

Examinons le code généré d’une WebPart

En appuyant sur F12 dans une page contenant des WebParts, vous pourrez examiner le code HTML généré par celle-ci.

Voici un exemple :

WebPartByTitle_HTMLElement

Vous remarquerez qu’il y a un ID unique sur chacune des WebParts de la page.

L’élément DIV avec le ID « MSOZoneCell_WebPartWPQ6 » correspond à l’ensemble de la WebPart.

L’élément DIV avec le ID « WebPartWPQ6_ChromeTitle » correspond à l’entête de la WebPart.

L’élément SPAN avec le ID « WebPartTitleWPQ6 » correspond au titre de la WebPart.

Voyons maintenant quelque techniques pour les récupérer.

Technique 1 – Récupérer les éléments HTML d’une WebPart par son titre

Cette technique consiste à utiliser le titre de la WebPart comme poigné pour récupérer les éléments HTML d’une WebPart précise.

Voici le code pour y arriver :

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function()
{
     //Parcours tous les éléments SPAN ayant un ID qui commencent par WebPartTitleWPQ
     $("span[id^=WebPartTitleWPQ]").each(function()
     {
          //Si le titre de la WebPart commence par "Site Collection Documents"
          if($(this).attr('title').indexOf("Site Collection Documents") == 0){
          //Applique une personnalisation sur l'ensemble de la WebPart (Couleur rouge)
               $(this).parent().parent().css('background-color', 'red');
          }
    });
});
</script>

Résultat

Le background-color rouge est appliqué uniquement sur la WebPart avec le titre « Site Collection Documents » :

WebPartByTitle_After

Points à faire attention avec cette technique

  1. Assurez-vous d’avoir un titre unique pour vos WebParts
  2. Assurez-vous d’utiliser un type de chrome incluant le « Titre »

Au besoin, vous pouvez modifier le titre de votre WebPart ou le type de chrome :

WebPartByTitle_MenuWP

Technique 2 – Récupérer les éléments HTML d’une WebPart par son ID

Cette technique consiste à utiliser l’ID comme poigné pour récupérer les éléments HTML d’une WebPart précise.

Voici un exemple de code pour modifier une WebPart par sont ID :

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function()
{
 //Applique une personnalisation à l'ensemble de la WebPart (Couleur bleu) par son ID
 $("#MSOZoneCell_WebPartWPQ6").css('background-color', 'blue');
});
</script>

Résultat

Le background-color bleu est appliqué uniquement sur la WebPart avec le ID MSOZoneCell_WebPartWPQ6 :

WebPartByID_After

Points à faire attention avec cette technique

  1. Assurez-vous d’ajuster votre code si une WebPart est déplacé,  ajouté ou supprimé

Que se passe-t’il si on déplace une WebPart ou qu’on en ajoute une nouvelle?

Le ID de la WebPart va tout simplement changer.

Avant la suppression d’une WebPart (2 WebParts) :

WebPartByTitle_HTMLElement_AvantDelete

WebPart 1 : WebPartWPQ4_ChromeTitle

 

Après la suppression d’une WebPart (1 WebPart) :

WebPartByTitle_HTMLElement_ApresDelete

WebPart 1 : WebPartWPQ2_ChromeTitle 

Voici un autre exemple qui utilise cette technique.

Conclusion

Les deux techniques présentés dans cet article peuvent vous permettre facilement d’appliquer des personnalisations sur un élément HTML d’une WebPart. La première technique utilisant le titre de la WebPart n’est pas sans faille si deux WebParts se retrouvent avec le même nom. Pour ce qui en est d’utiliser le ID de l’élément HTML de la WebPart, il faut faire attention si on déplace, supprime ou ajoute une WebPart dans la page car les ID sont susceptible de changer et ainsi de rendre votre code non fonctionnel. Ces techniques peuvent vous permettre d’appliquer des modifications rapidement lorsque ce n’est pas possible de le faire avec du CSS ou que c’est trop complexe avec du Client Side Rendering.

Utiliser les Patterns and Practices et le modèle des Apps pour déployer vos sites dans SharePoint Online ou on-premise

Introduction

Créer efficacement des sites personnalisées pour répondre aux besoins de nos clients c’est une des raisons de l’existence de SharePoint. Microsoft se concentre de plus en plus vers des déploiements dans le cloud, alors nous aurons inévitablement besoin d’avoir un modèle de déploiement qui est viable et facilement réalisable à la fois sur O365 et on-premise.

Dans cette optique, je me suis penché sérieusement sur la question afin de mieux comprendre les différentes techniques de déploiement de sites ainsi que leurs avantages et inconvénients.

Le déploiement de sites personnalisés dans SharePoint a évolué au cours des dernières versions et avec l’arrivé du modèle des Apps, il est évident qu’une façon de faire uniformisé s’impose. Une des techniques consiste à utiliser le modèle de Apps pour déployer vos sites.

Différentes techniques de déploiement de sites personnalisés

Avant d’entrer dans le vif du sujet, voyons un bref résumé des techniques qui s’offrent à nous pour effectuer le déploiement :

  • Modèles Web (Web Templates)
    • Seulement disponible au niveau de la collection de site dans O365
    • Difficile à maintenir à jour
  • Définitions de site (Site Definitions)
    • Non supporté dans O365
    • Beaucoup d’impact lors des mises à jours
  • Solutions de ferme personnalisées
    • Non supporté dans O365
    • Complexe à mettre en place et à maintenir
  • Création de structure de site avec Powershell
    • Complexe à mettre en place et à maintenir
  • Agrafage de fonctionnalités (Feature stappling)
    • Seulement disponible au niveau de la collection de site dans O365

Changement d’orientation de la part de Microsoft

Les modèles web (Web Templates) ont été, jusqu’à dernièrement, un excellent mécanisme fournis par Microsoft pour créer des centaines de sites d’équipes avec leurs propres personnalisations (bibliothèques, vues, WebPart, etc…) en addition à un modèle de site « out of the box ».

Cependant, lors d’une conférence au TechEd Europe 2014 (30 Octobre) Microsoft a annoncé ceci :

  • Éviter d’utiliser des définitions de site et des modèles Web
  • Éviter les pages maîtres personnalisées
  • Éviter d’utiliser le code XML déclaratif

siteProvisioning_NoSiteDef

Lien vers le vidéo

La raison derrière ce changement est, à mon avis, que Microsoft tente de faire d’Office 365 une plate-forme de développement qui n’est pas onéreuse à faire fonctionner et en même temps de fournir un service qui n’est pas trop complexe.

Maintenant, si on utilise des modèles Web nous ne bénéficieront pas des derniers changements pour chaque mise à jour de Microsoft. On parle ici d’une « taxe de personnalisation » que vous devez payer si vous utilisez des modèles Web.

Réaction face à ce changement d’orientation

J’évite volontairement de vous parler de l’orientation qui consiste à éviter les pages maître personnalisés et le code XML déclaratif car je ne suis pas complètement en accord avec ce changement. Je partage l’opinion de Chris O’Brien, il y aura toujours place à la personnalisation dans un site Intranet et le code XML déclaratif est encore supporté à 100% dans O365!

Tout comme nous n’avons pas immédiatement abandonné les solutions de fermes lors de l’arrivé du modèle des Apps, les modèles Web continueront sans doute d’être utilisé pour plusieurs scénarios pour où vous ne voudriez PAS de ces derniers changements provenant des mises à jour de Microsoft.

Cependant, je considère que pour les site d’équipe ainsi que pour les sites de projets, il n’est plus favorable d’utiliser un modèle Web et il serait plus approprié d’utiliser le modèle des Apps pour déployer vos sites afin d’éviter cette taxe de personnalisation!

Le modèle des Apps pour déployer vos sites

Le diagramme suivant illustre le déploiement de collections de site en utilisant le modèle des Apps ainsi qu’une approche asynchrone :

 

siteProvisioning_Remote

  1. Lorsqu’un utilisateur a besoin d’une nouvelle collection de site, il remplis les métadonnées requises dans le formulaire de création
  2. L’information est stockée dans un endroit centralisé
  3. Si requis, on peut y associer des flux d’approbation
  4. Les requêtes approuvées sont traitées par une tâche planifié distante. Ex : Une tâche planifié hébergé dans un serveur applicatif qui lance un script PowerShell ou une application console
  5. Le déploiement du site est réalisé en fonction des métadonnées fournis en utilisant le CSOM
  6. Une notification est envoyé à l’utilisateur pour lui informer de la création de la collection de site.

Le principe de cette technique consiste à sortir le code permettant d’effectuer le déploiement à l’extérieur de SharePoint afin d’avoir un faible couplage avec la plateforme. Cette technique fonctionne dans O365 ainsi qu’on-premise. Un des avantages est qu’il n’est pas nécessaire de déployer de fonctionnalités dans la ferme pour effectuer le déploiement. En plus de cela, il n’y a plus de dépendances avec des packages de solutions à l’intérieur des bases de données de contenu alors on pourrait uniquement restaurer celles-ci en cas de disaster recovery.

Fonctionnement technique

  1. Aller sur le site GitHub de OfficeDevPnp à l’adresse https://github.com/OfficeDev/PnP
  2. Cliquer sur le bouton « Download zip » et choisir un emplacement
  3. Extraire le Zip et aller dans le répertoire PnP-master\PnP-master\Samples\Provisioning.OnPrem.Async
  4. Ouvrir Provisioning.OnPrem.Async.Console.sln avec Visual Studio
  5. Vous retrouverez la méthode principale ProcessSiteCreationRequest
[...]

private static string ProcessSiteCreationRequest(ClientContext ctx, ListItem listItem)
{

 //get the base tenant admin urls
 string tenantStr = ConfigurationManager.AppSettings["SiteCollectionRequests_SiteUrl"];

 // Resolve root site collection URL from host web. We assume that this has been set as the "TenantAdminSite"
 string rootSiteUrl = tenantStr.Substring(0, 8 + tenantStr.Substring(8).IndexOf("/"));

 //Resolve URL for the new site collection
 var webUrl = string.Format("{0}/sites/{1}", rootSiteUrl, listItem["SiteUrl"].ToString());
 var tenantAdminUri = new Uri(rootSiteUrl);
 string realm = TokenHelper.GetRealmFromTargetUrl(tenantAdminUri);
 var token = TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, tenantAdminUri.Authority, realm).AccessToken;
 using (var adminContext = TokenHelper.GetClientContextWithAccessToken(tenantAdminUri.ToString(), token))
 {
 // Set the time out as high as possible
 adminContext.RequestTimeout = int.MaxValue;

 var tenant = new Tenant(adminContext);
 var properties = new SiteCreationProperties()
 {
 Url = webUrl,
 Owner = listItem["AdminAccount"].ToString(),
 Title = listItem["Title"].ToString(),
 Template = listItem["Template"].ToString(),
 };

 //start the SPO operation to create the site
 SpoOperation op = tenant.CreateSite(properties);
 adminContext.Load(op, i => i.IsComplete);
 adminContext.RequestTimeout = int.MaxValue;
 adminContext.ExecuteQuery();
 }

 // Do some branding for the new site
 SetThemeToNewSite(webUrl);

 return webUrl;
}

[...]

Afin d’uniformiser la manière de faire entre O365 et on-premise, vous remarquerez qu’on utilise la même notion de « Tenant« .

Une fois le code déployé, vous aurez un interface comme celui-ci pour saisir l’information concernant la collection de site à créer :

siteProvisioning_UI

Cette information sera ensuite sauvegardée dans une liste :

siteProvisioning_List

Finalement, l’application console que vous aurez lancé manuellement ou via une tâche planifié se chargera de créer la collection de site à partir de cette liste.

Pour plus d’information consulter ce vidéo qui présente en détail le fonctionnement du code et du processus.

Office Development Patterns & Practices (PnP)

En plus de fournir le code pour déployer des sites, le site d’Office PnP présente une série d’exemples et de scénarios sur les bonnes pratiques qui utilise le modèle des Apps.

Les exemples et scénarios utilisent les méthodes qui sont recommandées par l’équipe Office 365 et la majorité s’applique à :

  • Office 365 Multi Tenant (MT)
  • Office 365 Dedicated (D)
  • SharePoint 2013 on-premises

Voici quelque références pour les différents sites relié à Office PnP :

Conclusion

Le modèle des Apps pour déployer vos sites peut être la bonne solution dans une situation et peut ne pas l’être dans une autre. Le plus important est d’avoir une bonne compréhension des diverses techniques pour ensuite prendre une décision éclairé selon vos besoins. Le choix de votre technique de déploiement de site aura un impacte à long terme sur vos coûts de mise en place et de maintenance. J’aime bien la technique asynchrone proposé par Office PnP car il est possible de l’utiliser dans O365 ainsi que on-premise et de plus, l’utilisateur n’a pas besoin d’attendre dans son navigateur car il reçoit une notification à la fin du traitement.

Qu’est-ce que vous pensez de cette technique de déploiement de sites ?

Références

Building a async provisioning model par Office PnP

Office365 Developer Patterns and Practices par Vesa Juvonen

Provisioning site collections using SP App model in on-premises with just CSOM par Vesa Juvonen

Site provisioning techniques with SharePoint apps par Bert Jansen et Vesa Juvonen

Transforming Your SharePoint Full Trust Code to the SharePoint App Model  par Vesa Juvonen et Steve Walker

Developing for Office 365 – thoughts on use of custom master pages and web templates par Vesa Juvonen

Site provisioning techniques and remote provisioning in SharePoint 2013 par Chris O’Brien

L’effort de Microsoft au sujet de l’extensibilité d’Office 365 par Sébastien Levert

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