SharePoint : Réaliser simplement l’interaction du People Picker avec Knockout

Le développement SharePoint s’orientant inexorablement vers le “côté client”, notamment à travers SharePoint Online, l’utilisation de bibliothèques JavaScript externes devient obligatoire. Une bibliothèque que j’utilise régulièrement dans mes développements est KnockoutJs (pour plus d’informations sur cette bibliothèque implémentant le pattern MVVM, c’est ici).

Là où ça se complique avec KnockoutJs, c’est pour faire un binding avec le People Picker SharePoint. En effet, avec les outils proposés en standard par KnockoutJs, il est impossible de réaliser un binding simplement. Dans cet article, nous allons élaborer une solution pour mettre en place le binding sur un People Picker.

 

Utilisation du Client-Side People Picker

La première chose à mettre en place dans notre code est le Client-Side People Picker (plus d’info ici). Comme son nom l’indique, le Client-Side People Picker est la variante côté client du people picker SharePoint.
Côté HTML, il suffit simplement d’ajouter un élément div vide ainsi que les références nécessaires au people picker (clientforms.js, clientpeoplepicker.js, et autofill.js).
Côté JavaScript, il faut faire un appel à la fonction globale SPClientPeoplePicker_InitStandaloneControlWrapper qui va initialiser le People Picker et réaliser son rendu. Une fois ce rendu effectué, l’élément div vide va contenir les composants suivants :

  • un élément input permettant la saisie des noms des utilisateurs ou des groupes
  • un contrôle span indiquant le nom des utilisateurs résolus
  • un élément div caché qui remplit automatiquement une liste déroulante avec les choix correspondants aux critères de recherche saisis
  • un contrôle qui fait le remplissage automatique

La multiplicité des éléments composant le People Picker et le fait qu’ils soient créés au moment du rendu (côté JS) complexifie fortement l’interaction avec KnockoutJs.

 

Création des Custom Binding Handlers

Pour parvenir à faire le binding KnockoutJs, il va donc falloir créer plusieurs Custom Binding Handlers :

  • un binding handler qui sera associé à l’élément input de saisie utilisateur
  • un binding handler qui sera associé aux entités résolues

 

La première étape est de déclarer les deux binding handlers :

interface KnockoutBindingHandlers {
    pickerEntities: KnockoutBindingHandler; // Associé aux entités du People Picker
    pickerInput: KnockoutBindingHandler; // Associé au champ de saisie du People Picker
}

NB: L’ensemble du code de cet article est écrit en TypeScript.

 

Binding Handler associé au champ de saisie du People Picker

ko.bindingHandlers.pickerInput = {
    update: function (element: any, valueAccessor: any, allBindingsAccessor: KnockoutAllBindingsAccessor): void {
        // Récupération du contrôle People Picker
        var pickerControl = SPClientPeoplePicker.SPClientPeoplePickerDict[element.id + "_TopSpan"];
        // Récupération de chaque terme à chercher dans le people picker
        var usersValue = ko.utils.unwrapObservable(valueAccessor());
        $.each(usersValue, (indexInArray, valueOfElement) => {
            // Ajout du terme dans le people picker
            jQuery("#" + pickerControl.EditorElementId).val(valueOfElement);

            // Résolution de l'utilisateur
            pickerControl.AddUnresolvedUserFromEditor(true);
        });

        // Une fois tous les termes recherchés, on vide le tableau des termes à chercher
        var observable = valueAccessor();
        observable([]);
    }
};

Le but de ce Binding Handler est de détecter l’ajout de nouveaux termes saisis par l’utilisateur et de les résoudre. Le paramètre valueAccessor doit être ici un observableArray. Ce choix a été fait pour permettre de rechercher plusieurs termes en même temps.
Ce traitement récupère chacun des éléments de l’observableArray représentant les utilisateurs à rechercher puis les ajoute dans le people picker. Les éléments sont ensuite résolus un à un.
Une fois l’ensemble des éléments traités, alors on vide le tableau des éléments à rechercher.

 

Binding Handler associé aux entités résolues

ko.bindingHandlers.pickerEntities = {
    init: function (element: any, valueAccessor: any, allBindingsAccessor: KnockoutAllBindingsAccessor): void {
        // Définition du schema permettant d'initialiser le Client-Side People Picker
        var schema: ISPClientPeoplePickerSchema = {
            PrincipalAccountType: “User”,
            SearchPrincipalSource: 15,
            ResolvePrincipalSource: 15,
            AllowEmailAddresses: true,
            AllowMultipleValues: true,
            MaximumEntitySuggestions: 30,
            InitialHelpText: “Entrez un nom ou un email valide...”,
            Width: “350px”,
            OnUserResolvedClientScript: function (elemId: string, userKeys: ISPClientPeoplePickerEntity[]): void {
                // Récupération de l'élément DIV contenant le people picker
                var pickerElement = SPClientPeoplePicker.SPClientPeoplePickerDict[elemId];
                // Récupération de l'observable
                var observable = valueAccessor();
                // Mise à jour de la valeur de l'observable
                observable(pickerElement.GetControlValueAsJSObject());
            }
        };

        // Initialisation du Client-Side People Picker
        SPClientPeoplePicker.InitializeStandalonePeoplePicker(element.id, null, schema);
    },
    update: function (element: any, valueAccessor: any, allBindingsAccessor: KnockoutAllBindingsAccessor): void {
    }
};

Le but de ce Binding Handler est d’initialiser le People Picker et de mettre à jour l’observableArray associé. L’ensemble des propriétés du People Picker sont définies dans la variable schema. Puis la fonction globale SPClientPeoplePicker.InitializeStandalonePeoplePicker est appelée pour effectuer le rendu. Cette fonction est la version TypeScript de la fonction SPClientPeoplePicker_InitStandaloneControlWrapper expliquée précédemment.

A noter la fonction très importante OnUserResolvedClientScript. Cette fonction est appelée après chaque résolution d’utilisateur. Elle est donc appelée chaque fois qu’un observableArray lié au handler pickerInput voit sa valeur mise à jour. Cette fonction récupère la valeur des entités résolues et met à jour l’observableArray en conséquence.

 

Utilisation

HTML : App.aspx

<SharePoint:ScriptLink name="clienttemplates.js" runat="server" LoadAfterUI="true" Localizable="false" />
<SharePoint:ScriptLink name="clientforms.js" runat="server" LoadAfterUI="true" Localizable="false" />
<SharePoint:ScriptLink name="clientpeoplepicker.js" runat="server" LoadAfterUI="true" Localizable="false" />
<SharePoint:ScriptLink name="autofill.js" runat="server" LoadAfterUI="true" Localizable="false" />
<SharePoint:ScriptLink name="sp.js" runat="server" LoadAfterUI="true" Localizable="false" />
<SharePoint:ScriptLink name="sp.runtime.js" runat="server" LoadAfterUI="true" Localizable="false" />
<SharePoint:ScriptLink name="sp.core.js" runat="server" LoadAfterUI="true" Localizable="false" />
<SharePoint:ScriptLink name="~site/SiteAssets/jquery-2.2.3.min.js" runat="server" LoadAfterUI="true" Localizable="false" />
<SharePoint:ScriptLink name="~site/SiteAssets/knockout-3.4.0.js" runat="server" LoadAfterUI="true" Localizable="false" />
<!-- ko.peoplepicker.js contient nos deux nouveaux binding handlers -->
<SharePoint:ScriptLink name="~site/SiteAssets/ko.peoplepicker.js" runat="server" LoadAfterUI="true" Localizable="false" />
<!-- app.js contient le view model ko –>
<SharePoint:ScriptLink name="~site/SiteAssets/app.js" runat="server" LoadAfterUI="true" Localizable="false" />
<div id="peoplePickerDiv" data-bind="pickerEntities: users, pickerInput: usersToResolve"></div> <br /> <div> <button role="button" data-bind="click: addSpecificUser">Add Specific User</button> </div> <div data-bind="foreach: users"> <br /> <!-- ko if: typeof EntityData !== 'undefined' --> <h1 data-bind="text: 'User #' + ($index() + 1)"></h1> <p>Department: <span data-bind="text: EntityData.Department"></span></p> <p>Email: <span data-bind="text: EntityData.Email"></span></p> <p>MobilePhone: <span data-bind="text: EntityData.MobilePhone"></span></p> <p>Title: <span data-bind="text: EntityData.Title"></span></p> <!-- /ko --> </div>

Dans le code ci-dessus, il faut remarquer :

  • les références aux bibliothèques clientforms.js, clientpeoplepicker.js, et autofill.js et à ko.peoplepicker.js qui contient nos deux nouveaux binding handlers.
  • l’élément div peoplePickerDiv va contenir les composants du People Picker. Cet élément est associé aux deux nouveaux binding handlers : pickerEntities (via l’observableArray users) et pickerInput (via l’observableArray usersToResolve)
  • un bouton dont l’événement click est associé à une fonction ko permettant d’ajouter un utilisateur specifique
  • plusieurs éléments affichant les informations en temps réel des utilisateurs résolus

 

TypeScript : App.ts

class MyViewModel {
    // Tableau des utilisateurs résolus dans le People Picker
    users: KnockoutObservableArray<ISPClientPeoplePickerEntity>;
    // Tableau des termes à résoudre dans le People Picker
    usersToResolve: KnockoutObservableArray<string>;

    constructor() {
        // On initialise à vide le tableau des utilisateurs résolus
        this.users = ko.observableArray([]);
        // On initialise le tableau des termes à résoudre 
        // On peut aussi bien ajouter le nom de l'utilisateur que son mail ou son identifiant
        this.usersToResolve = ko.observableArray([“Alain Démo”, “jbolliet@infinitesquare.com”]);
    };

    public addSpecificUser() {
        // Pour ajouter un terme à résoudre, il suffit d'ajouter un élément au tableau des termes à résoudre
        this.usersToResolve.push(“gbouveret@infinitesquare.com”);
    };
};

$(document).ready(function () {
    // Création du ViewModel + binding
    ko.applyBindings(new MyViewModel());
});

Dans le code ci-dessus, il faut remarquer :

  • la classe MyViewModel contient deux propriétés : users et usersToResolve. Ces deux propriétés sont des observableArray. users est le tableau des entités résolues et usersToResolve est le tableau des utilisateurs à résoudre.
  • une entité est du type ISPClientPeoplePickerEntity en TypeScript
  • usersToResolve peut avoir des éléments à résoudre dès l’initialisation : soit en écrivant en toutes lettres le nom de l’utilisateur à rechercher (ici ‘Alain Démo’) ou soit en utilisant l’email de l’utilisateur à rechercher (ici 'jbolliet@infinitesquare.com')
  • usersToResolve peut également être mis à jour après rendu (usersToResolve.push)

 

A l’utilisation, sans aucune manipulation particulière autre que renseigner les termes à rechercher, les informations des utilisateurs sont récupérées et affichées :
Capture01

 

De même, en cliquant sur le bouton ‘Add Specific User’, on met à jour l’observableArray usersToResolve en recherchant le terme ‘gbouveret@infinitesquare.com’. Les informations de cet utilisateur sont automatiquement affichées :

Capture02

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus