Champs calculés SharePoint en JavaScript

Une des nouvelles façon d’interagir avec votre site SharePoint depuis la version 2013 est ce qu’on appelle le “Client side rendering” ou encore le “JSLink”. Vous avez sans doute entendu parler de ce point d’entrée offert à plusieurs niveaux pour choisir comment sont affichés listes, formulaires, et résultats de recherche dans votre site SharePoint 2013 et/ou Office 365. C’est d’ailleurs particulièrement intéressant dans le cadre d’un site Office 365 pour permettre des “customisation” sans code serveur.

Vous trouverez tout un tas de ressources sur le net en recherchant SharePoint + JSLink. Je vous invite si vous débutez sur le sujet à lire quelques exemples, et je vous conseille en démarrage ceux dispo ici : https://code.msdn.microsoft.com/office/Client-side-rendering-JS-2ed3538a

Dans cet article je vais vous présenter un cas d’usage précis qui consiste à répondre au besoin de champs calculés dans une liste personnalisée. Quand je parle de champ calculé, j’entends une valeur qui peut être complexe à calculer car basée non seulement sur des valeurs dans d’autres champs du même item mais pourquoi pas basée sur des valeurs en provenance d’autres listes ou d’autres infos venant du site par exemple.

Quels étaient nos choix (sans JSLink) dans ce genre de cas ?

  • Champ calculé directement dans la liste, avec une formule “à la sauce” SharePoint : on atteint très vite la limitation car on est contraint à ne pouvoir utiliser que les valeurs de l’item courant
  • Workflow via SharePoint designer : ici on est moins limité en terme d’utilisation de données venant d’autres items ou autres listes mais tout de même au niveau “algo” on va vite être bloqué. De plus ce genre de solution deviens très vite difficile à maintenir avec cette usine à gaz qu’est SPD.
  • Event Receiver de list : c’est la solution ultime qui permet de tout faire. Du code côté serveur, en mode Full trust vous n’avez à peut près aucune limite. Cependant cela nécessite de déployer une solution côté serveur et de gérer les mises à jours éventuelles avec les interruptions de service que cela engendre. Et bien sûr cette solution n’est pas envisageable sur du Office 365.

C’est là que le JSLink, en mode personnalisation de champ de formulaire, est un très bon compromis:

  • vous n’avez pas besoin de déployer des solutions côté serveur,
  • vous utilisez un vrai langage de programmation, donc accessible à tous développeurs web et donc plus facile à maintenir
  • SharePoint offre une API JS très riche pour interagir avec le site, presque autant qu’avec l’API serveur.
  • D’un point de vue stratégique pour le moyen/long terme vous partez sur la bonne voie, les outils comme SharePoint designer ayant tendance à disparaitre et les API clients ayant tendance à évoluer en plus complet….

Il est temps maintenant de rentrer dans le concret du sujet et pour cela on va commencer par un exemple très simple. (Dans cet exemple les champs calculés standard feraient l’affaire, mais l’objectif est de vous amener petit à petit vers une vrai solution complexe, donc chaque chose en son temps)

Démo 1 : Calcul d’un montant TTC

J’ai une liste permettant d’enregistrer les factures avec : une référence, un montant HT, une TVA, et un montant TTC.Le montant TTC peut donc être calculé directement à partir du montant HT et de la TVA. Pour cette dernière colonne nous allons commencer par utiliser un champ de saisie type “Devise” :
imagevoilà le type d’item qui peuvent être crées avec cette liste : image

Avec un peu de JavaScript nous allons voir comment personnaliser le champs Montant TTC dans le cas du formulaire de création/mise à jour d’un item pour ne pas à avoir à le saisir manuellement et obtenir ceci :

création:
image

mise à jour: image

Le code:

Nous devons prévoir plusieurs éléments dans le code JS à fournir:

  • La déclaration de l’objet au bon format pour faire comprendre à SharePoint quel champ on personnalise, à quel moment et avec quelle fonction de rendu
  • La fonction de rendu qui va fournir le HTML d’affichage de ce champ
  • La fonction callback de récupération de la valeur, c’est à dire la fonction que SharePoint va appeler lorsque l’utilisateur va enregistrer le formulaire. Et c’est là que je vais vous donner un “trick” pour récupérer facilement la valeurs des autres champs de l’item, en l’occurrence le montant HT et la TVA

En tout première étape, déclarez un namespace dans votre fichier Js est préférable pour éviter tout conflit de fonction portant le même nom et pour organiser un peu tout ça :

//on declare un namespace pour y "ranger" tout le code lié ces champs de cette liste specifiquement
var FacturesJSComputed = {};

 

Ensuite en fonction de “démarrage” on envoi l’objet ayant la bonne structure à SharePoint pour lui dire quel champ on surcharge, dans quel cas et avec qu’elle fonction de rendu :

//start hook
(function () {

    //object to give to sharepoint to override rendering of the field
    var overrideCtx = {};
    overrideCtx.Templates = {};
    overrideCtx.Templates.Fields = {};
    //utilisez le nom interne du champ pour le customiser
    overrideCtx.Templates.Fields.MontantTTC =
         {
             //ici on fourni la fonction de rendu pour chaque cas de formulaire.
             // dans mon cas la même fonction qu'on soit en mode new ou edit
             "NewForm": FacturesJSComputed.MontantTTCFieldTemplate,
             "EditForm": FacturesJSComputed.MontantTTCFieldTemplate
         };

    //apply override
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);

})();

 

Enfin on écris la fonction de rendu qui est comme vous l’avez vu dans le code juste au dessus FactureJSComputed.MontantTTCFieldTemplate. Cette fonction contient en mode “inline” la fonction de callback pour la sauvegarde de la valeur (registerGetValueCallback):

//surcharge du rendu html du spfield
FacturesJSComputed.MontantTTCFieldTemplate = function (ctx) {

    //l'objet contexte qui va nous permettre d'interagir sur le form
    var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);

    //en cas de mode 'update' on peut avvoir beoin de la valeur actuelle pour l'utiliser dans l'affichage
    //on l'enregistre ds une variable globale
    FacturesJSComputed.actualMontantTTC = formCtx.fieldValue;

    //on surcharge le callback appelé par SharePoint lors de la sauvegarde du form
    //c'est là qu'on doit renvoyer le résultat de notre calcul
    formCtx.registerGetValueCallback(formCtx.fieldName, function (c) {

        //tout le 'trick' est là. On a besoin de la valeur des sautres champs
        // ils sont disponible sur l'objet this
        // à chaque field correspond une methode sur cet objet this
        var montantHT = parseFloat(this.MontantHT());
        var tva = parseFloat(this.TVA()) / 100;

        //on retourne la valeur à enregistrer
        return ((montantHT * tva) + montantHT);
    });

    // en sortie de la fonction principale on renvoi le html pour faire le rendu du field
    // ici on renvoi un simple texte 
    return FacturesJSComputed.actualMontantTTC + " (ce champ sera rempli automatiquement)";
};

 

Comme vous pouvez le voir, en retour de la fonction de rendu (dernière ligne) on renvoi le html qui s’affichera sur le formulaire, en l’occurrence un simple texte avec éventuellement la valeur précédente du champ qui a été récupéré au début.

Concernant la fonction registerGetValueCallback, nous devons renvoyer la valeur du champ. C’est donc là qu’il faut faire le calcul.

Et pour cela nous avons besoin des valeurs du montant HT et de la TVA. Il se trouve que nous n’avons pas ici de référence sur l’objet “item” SharePoint ce qui et logique en particulier dans le cas de la création d’un nouvel item. Par contre, et tout le trick est là, vous constaterez que l’objet “this” contient une fonction pour chacun des champs de votre item. L’appel de cette fonction renvoi la valeur !

this.MontantHT()

Il ne vous reste plus qu’à stocker ce fichier JS dans une librairie de documents de votre collection de site. Je vous conseille de créer une librairie dédiée à ces fichiers. Puis référencer ce JS sur le formulaire NewItem et EditItem de votre liste en faisant un edit page directement depuis les formulaires et en donnant l’url du JS dans la propriété JSLink du webpart de formulaire.

ATTENTION : Ne laissez pas de caractères encodés dans cette url (comme un %20, remettez l’espace) car le webpart va re encoder et ça donnera une URL non valide. De plus utilisez le token ~sitecollection plutôt que de donner une url absolue.

Démo 2 : Récupération du nom, prénom, email et login à partir d’un champ User

Pour aller un peu plus loin sur l’exploitation de ce “trick” nous allons aborder un second cas d’usage où il ne s’agit pas réellement de calculer une valeur mais plutôt d’extraire une valeur à partir d’un champ plus complexe.

Une liste avec un champ Personne c’est bien : on a un sélecteur de personne (people picker), et la certitude que l’utilisateur sélectionné existe bien dans l’annuaire du site. Seulement ce type de champ est limité sur 2 besoins très classiques :

  1. Lors de l’export des donnée via Excel, on récupère un champs avec id#;display et c’est tout. Il serait utile de récupérer son email et son login par exemple
  2. On ne peut pas faire de champ recherche (lookup) dans une autre liste en pointant sur ce champ Personne. Alors souvent on ajoute un champ texte libre et on demande aux utilisateurs de saisir dedans une seconde fois le nom de la personne (avec le risque d’erreur que cela engendre) pour avoir sur les liste “enfant” un champ recherche pointant sur le champ texte au lieu du champ Personne (oui c’est nul).

J’ai donc une liste “Responsables vente” où je veux avoir mes responsables renseignés via un champ Personne et à terme je souhaite pouvoir associer ces responsables à mes factures. Enfin les factures doivent être exportables dans Excel et on doit y retrouver l’Email et le login des responsables.

Voilà la structure de la liste “Responsables vente”

image On va donc prendre la même approche que dans le cas précédent on fournissant une fonction de rendu custom pour chacun es 3 champs pour obtenir ceci :

image

Et en résultats :

image

Exactement sur le même modèle on va écrire 3 fonctions de rendu. A chaque fois on récupère la valeur du champ utilisateur par l’appel à :

this.Responsable()
Et c’est là le 2ieme “trick” de l’article. Vous constaterez que ce champ de type ‘Personne’ est récupéré sous forme d’objet JSON sérialisé en string. En le parsant via JSON.parse() on retrouve un objet contenant pleins de choses utiles :
var userObject = JSON.parse(this.Responsable());
cas 1 : le Display
//surcharge du rendu html du spfield pour un display name
ResponsableVentesJSComputed.DisplayFieldTemplate = function (ctx) {

    //l'objet contexte qui va nous permettre d'interagir sur le form
    var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);

    //en cas de mode 'update' on peut avvoir beoin de la valeur actuelle pour l'utiliser dans l'affichage
    //on l'enregistre ds une variable globale
    ResponsableVentesJSComputed.actualDisplayValue = formCtx.fieldValue;

    //on surcharge le callback appelé par SharePoint lors de la sauvegarde du form
    //c'est là qu'on doit renvoyer le résultat de notre calcul
    formCtx.registerGetValueCallback(formCtx.fieldName, function (c) {

        //tout le 'trick' est là. On a besoin de la valeur des autres champs
        // ils sont disponible sur l'objet this
        // le second trick est de parser l'objet JSON dispo en cas de champ type SPUser
        var userObject = JSON.parse(this.Responsable());

        //si les conditions sont réunies on retourne la valeur à enregistrer
        if (userObject && userObject.length > 0) {
            var user = userObject[0];
            if (user && user.IsResolved) {
                if (user.DisplayText)
                    return user.DisplayText;
                else return ResponsableVentesJSComputed.actualDisplayValue;
            }
        }
        alert("Le responsable selectionné n'est pas valide !");

    });

    // en sortie de la fonction principale on renvoi le html pour faire le rendu du field
    // ici on renvoi un simple texte 
    return ResponsableVentesJSComputed.actualDisplayValue + " (ce champ sera rempli automatiquement)";
};
cas 2 : l’Email
//surcharge du rendu html du spfield pour un email
ResponsableVentesJSComputed.EmailFieldTemplate = function (ctx) {

    //l'objet contexte qui va nous permettre d'interagir sur le form
    var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);

    //en cas de mode 'update' on peut avvoir beoin de la valeur actuelle pour l'utiliser dans l'affichage
    //on l'enregistre ds une variable globale
    ResponsableVentesJSComputed.actualEmailValue = formCtx.fieldValue;

    //on surcharge le callback appelé par SharePoint lors de la sauvegarde du form
    //c'est là qu'on doit renvoyer le résultat de notre calcul
    formCtx.registerGetValueCallback(formCtx.fieldName, function (c) {

        //tout le 'trick' est là. On a besoin de la valeur des autres champs
        // ils sont disponible sur l'objet this
        // le second trick est de parser l'objet JSON dispo en cas de champ type SPUser
        var userObject = JSON.parse(this.Responsable());

        //si les conditions sont réunies on retourne la valeur à enregistrer
        if (userObject && userObject.length > 0) {
            var user = userObject[0];
            if (user && user.IsResolved) {
                if (user.EntityData.Email)
                    return user.EntityData.Email;
                else return ResponsableVentesJSComputed.actualEmailValue;
            }
        }
        alert("Le responsable selectionné n'est pas valide !");

    });

    // en sortie de la fonction principale on renvoi le html pour faire le rendu du field
    // ici on renvoi un simple texte 
    return ResponsableVentesJSComputed.actualEmailValue + " (ce champ sera rempli automatiquement)";
};

cas 3 : le Login

//surcharge du rendu html du spfield pour un Login
ResponsableVentesJSComputed.LoginFieldTemplate = function (ctx) {

    //l'objet contexte qui va nous permettre d'interagir sur le form
    var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);

    //en cas de mode 'update' on peut avvoir beoin de la valeur actuelle pour l'utiliser dans l'affichage
    //on l'enregistre ds une variable globale
    ResponsableVentesJSComputed.actualLoginValue = formCtx.fieldValue;

    //on surcharge le callback appelé par SharePoint lors de la sauvegarde du form
    //c'est là qu'on doit renvoyer le résultat de notre calcul
    formCtx.registerGetValueCallback(formCtx.fieldName, function (c) {

        //tout le 'trick' est là. On a besoin de la valeur des autres champs
        // ils sont disponible sur l'objet this
        // le second trick est de parser l'objet JSON dispo en cas de champ type SPUser
        var userObject = JSON.parse(this.Responsable());

        //si les conditions sont réunies on retourne la valeur à enregistrer
        if (userObject && userObject.length > 0) {
            var user = userObject[0];
            if (user && user.IsResolved) {
                if (user.Description)
                    return user.Description;
                else return ResponsableVentesJSComputed.actualLoginValue;
            }
        }
        alert("Le responsable selectionné n'est pas valide !");

    });

    // en sortie de la fonction principale on renvoi le html pour faire le rendu du field
    // ici on renvoi un simple texte 
    return ResponsableVentesJSComputed.actualLoginValue + " (ce champ sera rempli automatiquement)";
};

Enfin on “branche” le tout dans la fonction de démarrage :

//start hook
(function () {

    //object to give to sharepoint to override rendering of the field
    var overrideCtx = {};
    overrideCtx.Templates = {};
    overrideCtx.Templates.Fields = {};
    overrideCtx.Templates.Fields.Title =
         {
             "NewForm": ResponsableVentesJSComputed.DisplayFieldTemplate,
             "EditForm": ResponsableVentesJSComputed.DisplayFieldTemplate
         };
    overrideCtx.Templates.Fields.Email =
        {
            "NewForm": ResponsableVentesJSComputed.EmailFieldTemplate,
            "EditForm": ResponsableVentesJSComputed.EmailFieldTemplate
        };
    overrideCtx.Templates.Fields.Login =
        {
            "NewForm": ResponsableVentesJSComputed.LoginFieldTemplate,
            "EditForm": ResponsableVentesJSComputed.LoginFieldTemplate
        };

    //apply override
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);

})();

Vous n’avez plus qu’à mettre tout ça dans un fichier JS et le lier au formulaires via la propriété JSLink et le tour est joué.

On peut désormais faire un champ ‘Recherche’ sur ma liste de factures pour les lier à un responsable en pointant sur le champ Display. On peux en profiter aussi pour rapatrier l’email :
image
Ce qui permet de répondre au besoin d’avoir le nom du responsable et son email en cas d’export Excel de la liste des factures :
image
Et en plus on a bien le lien vers la liste responsable en cas de click sur son nom :
image

Le code complet pour ces 3 champs est disponible ici

Conclusion

Vous avez pu voir que la personnalisation d’un formulaire d’item SharePoint devient facilement envisageable via du JavaScript et qu’il est même possible d’interagir avec les valeurs des autres colonnes. Ce qui permet des scenarios du type champs calculés assez simplement.

Cependant des scenarios plus complexe ne sont pas encore envisageable en l’état. En effet vous pourrez avoir besoin de faire des recherches de données dans d’autres listes pour “composer” la valeur finale du champ.

C’est pourquoi je vous invite à revenir d’ici peu pour un second article où je vous montrerai comment aller plus loin en utilisant le modèle objet SharePoint en JavaScript avec son lots de “tricks” pour y arriver facilement et efficacement ! 😉

[EDIT] le second article est disponible ici

Publié dans SharePoint Tagués avec : , , , , , ,
11 commentaires pour “Champs calculés SharePoint en JavaScript
  1. PtiTom dit :

    Cool ! Je pense aussi que l’apport de JSLink dans les sites O365 est indéniable. Du coup j’y étais également allé de mes tests… Et en Franco-Français, le travail avec des montants devient rapidement « pas cool » 🙂
    Dans un contexte de rendu de liste, j’ai remarqué que plutôt que d’utiliser la syntaxe item.Champ(), qui donne un résultat formaté donc virgule et symbole de devise, on peut utiliser l’indexeur suffixé d’un point : par exemple item[« Champ. »] qui me renvoie la valeur brute !

    (Et mon item a alors de beaux cheveux… ahem, je sors…)

  2. Pivert dit :

    Bonjour,

    Concernant la Démo 1, je suis à la recherche de cela mais pour SP 2010
    J’explique :
    J’ai une saisie de formulaire avec des champs de type nombre et j’ai créé une colone de type valeur calculée qui fait la somme de 4 champs.
    Je souhaiterai qu’à la saisie de ces 4 champs dans le formulaire, le total apparaisse de façon automatique dans un champ de ce même formulaire.
    Avez-vous quelque chose là-dessus ?
    Merci à vous.

    • Lionel dit :

      C’est un peu le sens du second article qui est en cours de rédaction ;).
      En quelques mots, dans votre cas, le principe consiste à surcharger le rendu des 4 champs pour pouvoir se « brancher » via du jQuery par exemple sur le changement de valeur dans les input. Là vous pouvez avoir dans une variable JS la valeur du calcul. Une surcharge du 5ieme champ permettrai d’injecter cette valeur, toujours via jQuery par exemple, dans le html qui va bien….

      • Lionel dit :

        Je viens de me rendre compte que vous demandez pour SP2010. Le JSLink n’est dispo qu’en 2013 🙁
        pour 2010 j’aurais tendance à partir sur un dev d’un bloc html + JS inséré via un ContentEditor WebPart en lieu et place du formulaire « standard ». Mais cela implique de prendre en charge l’intégralité du rendu et de la logique du formulaire. Par contre vous pourrez faire tout ce que vous souhaitez puisque c’est VOTRE formulaire 🙂

        • PIVERT dit :

          Bonjour,
          Merci de votre réponse.
          Malheureusement pour moi, je n’ai pas pour l’instant, suivi de formation de dév pour Sharepoint.
          J’utilise SP Designer 2010.
          Je vais combler cette lacune dans peu de temps.
          Encore merci pour votre réponse.

  3. Très intéressant comme article Lionel

  4. Shifae dit :

    très intéressant.
    Pour le 2éme cas , je voulais juste savoir l’emplacement de la liste qui contient les informations du responsable, est c qu’elle est stocké sur sharepoint ou ailleurs.

    • Lionel dit :

      C’st un champ de type « User ». Dans mojn cas le site utilise une authent NTLM, donc les infos de profil viennent de mon Active Directory.
      Avec un site en Form Based Auth, et un custom Membership Provider, les infos de profils viendrons de ce MemberShip Provider

  5. Ghazali dit :

    Bonjour,
    Un bon article , mais j’ai des questions une fois que j’ai ajouter une application liste personnalisé et j’ai crée mes champs. le champs Montant TTC est de type devis , on le laisse vide . Mais c’est que j’ai pas compris l’insertion des programmes vous pouvez m’expliquer plus merci .

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*

Verifions que vous êtes un humain * Time limit is exhausted. Please reload CAPTCHA.

Archives

Social

  • Twitter
  • LinkedIn
  • Flux RSS
  • mvp
  • technet
  • Google+