SharePoint : Réaliser simplement l’interaction du People Picker avec Knockout
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 :
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 :
Commentaires