Sharepoint People Picker wrapped in a AngularJs Directive

In a project I was involved in during my Internship, we used AngularJs to create a single-page-application Sharepoint app, that allowed for creation and filtered display of subsites as individual projects.

As such in our form to create a subsite, we used two Sharepoint Client-side people pickers, there are several blog posts out there showing how to use them, and even a MSDN article(links to these resources will be posted at the end of this article), and even though there were posts of people using the people picker with angular, their implementation was not enough for me.

I wanted to get the people picker hooked into the angular model, and I wanted to be able to use AngularJs’s validation to prevent user action.

So the solution was to wrap the people picker in a directive, and I want to share how I did that, big Thanks to a post by Christopher Nadeau on how to use the ngModel Controller in a directive (link also at the bottom).


So this is a basic go through of how I did it.

First we declare a directive on the app.

(function (){
'use strict';
angular.module('app').directive('spPeoplePicker',[spPeoplePicker]);

function spPeoplePicker(){

var uniqueNr = 1;

return {
   restrict: 'E',
   replace: true,
   require: 'ngModel',
   link: link,
   scope: {
      allowMulti: '=',
      label: '@',
      cssWidth: '@'
   },
   template: '<div class="form-group"> <div class="input-group">' +
                '<div class="input-group-addon"><label for="peoplePickerDivX">{{label}}</label></div>' +
                '<div id="peoplePickerDivX"></div>' +
                '</div></div>'
   };
}
})();

We restrict the directive to a Element tag, tell it to replace its parent tag with its content, expose three attributes from the directive tag to be passed into the scope. Those are basics.

The important bit is “require: ‘ngModel'” this allows us to put a ng-model attribute on the tag and pass a specified models controller into our link function.

also note that Ive added several div tags into the template, which allows me to style the people picker with twitter bootstrap classes. the important div is

 <div id="peoplePickerDivX"> 

This is the div that will be used by the sharepoint code to generate the people picker in.

Also I’ve declared a uniqueNr variable direct within the directive, we will get to that soon.

The link function, where essentialy everything happens.

function link(scope, element, attrs, ngModelCtrl) {
            var uniqueId = '_' + uniqueNr++;

            var pickerOuterId = 'peoplePickerDiv' + uniqueId;
            var pickerDivId = pickerOuterId + '_TopSpan';

            element.find('label').attr('for', pickerDivId);
            element.find('#peoplePickerDivX').attr('id', pickerOuterId);
...

in the linking function we pass in the scope, the jQuery lite element, the attributes of the tag and the ngModel Controller.

first off I declare a uniqueId variable, pulling in the uniqueNr variable declared outside the linking function and incrementing it. As the directive is I guess a singleton, but the linking function is called for each time a new instance of the directive is called from a page, it allows us to get a unique number for each people picker on a page.

Next I set a pickerOuterId which will be the div Id of the wrapper that generates the picker, and pickerDivId will be used to Identify the div where the picker actually ends up being.

Then I find the label and the wrapper div under the element, remember these were in our template.
Importantly, I find the wrapper by the Id I set in the template, and change the Id to the one with our unique number in it.

var schema = {};
            schema['PrincipalAccountType'] = 'User,DL,SecGroup,SPGroup';
            schema['SearchPrincipalSource'] = 15;
            schema['ResolvePrincipalSource'] = 15;
            schema['AllowMultipleValues'] = (scope.allowMulti) ? scope.allowMulti : true;
            schema['MaximumEntitySuggestions'] = 50;
            schema['Width'] = (scope.cssWidth) ? scope.cssWidth : '220px';

            schema['OnUserResolvedClientScript'] = function() {
                if (peoplePicker) {
                        ngModelCtrl.$setViewValue(peoplePicker.GetAllUserKeys());
                }
            };

            SPClientPeoplePicker_InitStandaloneControlWrapper(pickerOuterId, null, schema);

            element.find('#' + pickerDivId).addClass('form-control');

            var peoplePicker = SPClientPeoplePicker.SPClientPeoplePickerDict[pickerDivId];

Above you can see the basic schema that is needed for creating the picker, some of the settings I pull from the attributes passed in on the tag and now are on the scope, But if they are not present we set a standard value.

The last bit on the schema is the important bit. the picker raises a couple of events, and the one Im interested in is the OnUserResolved, and I’m able to set a callback function for that event, where I set the $ViewValue on the ngModel controller, with the User keys.

At the point, when the code is live, the View value will be equal to a ‘;’ separated string of userkeys.
Next the people picker is generated by calling the a function on the global scope from the sharepoint libraries (clientform.js, clienttemplate.js autofill.js)

for the sake of my bootstrap styling I find the picker div generated and put a class on it, and get the picker object.

Working with the ngModel Controller

The next thing is to handle the propagation of the view to the model and back again. A better description of these bits is on Christopher Nadeau’s blog, I will go though what I did, please check out his tutorial on this for a better explanation.

ngModelCtrl.$formatters.push(function(modelValue) {

                if (modelValue && modelValue.constructor === Array) {                                           
                            return modelValue.join(';');
                }
                return modelValue;
            });

In the formatters pipeline I check the modelValue (which I’ve decided shall be an array of user keys) and join them into a ‘;’ separated string for the View.

ngModelCtrl.$parsers.push(function(viewValue) {
                if (viewValue) {
                    var keys = viewValue.split(';');
                    return keys;                    
                }
                return viewValue;

            });

In the parsers pipeline I check the View value and split it into an array for the Model.

ngModelCtrl.$render = function() {
                if (peoplePicker) {
                    if (ngModelCtrl.$viewValue) {
                            var toAdd = ngModelCtrl.$viewValue;
                            if (peoplePicker.TotalUserCount &amp;amp;amp;amp;amp;gt; 0) {
                                for (var SPClientPeoplePickerProcessedUser in peoplePicker.ProcessedUserList)                        {
                                    var prUser = peoplePicker.ProcessedUserList[SPClientPeoplePickerProcessedUser];
                                    if (prUser) {
                                        var userKey = prUser.UserInfo.Key + ';';
                                        if (toAdd.search(userKey) != -1) {
                                            toAdd = toAdd.replace(userKey,&amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;quot;);
                                        }
                                    }                                    
                                }
                            }
                            peoplePicker.AddUserKeys(toAdd);
                        
                        peoplePicker.UpdateUnresolvedUser();
                        peoplePicker.Validate();
                    }
                }
            };

I override the ngModel controller’s $render function with my own where I check to see if the people picker allready has the any of the userkeys I’m adding, if so they are removed. This could possibly be done in a better way, any Ideas would be appreciated, all this code is available on my github.

The important bit here is the three calls to the people picker object, AdduserKeys, UpdateUnresolvedUser and Validate.

With these calls the user keys are added and resolved.. [duh]

And with that its all wired up.

A slightly more fleshed out version is available on my github.
https://github.com/drohreo/spPeoplePicker-ng-directive
Where there is a demo solution containing the directive.

Also don’t forget there are a number of js libraries in sharepoint that need to be referenced (clienttemplates.js, clientforms.js, clientpeoplepicker.js, autofill.js, sp.core.js) how you do this depends on your app. if you use a aspx page with scriptlink tags, or a html page, and direct links to the layouts folder.

I found that on a html page that does not get the app.master treatment, I got a Strings is not defined, which I solved by adding a reference to the sharepoint online CDN Strings.js.

the blog entries that helped me on the way to get this together are the following:

Using NgModelController with Custom Directives – Christopher Nadeau

Using Multiple Peoplepicker’s in Sharepoint Hosted App 2013 with AngularJs – Jeremy Thake

Using the SharePoint Client-Side People Picker with an AngularJS SPA – Matthew Yarlett

MSDN Article – How to: Use the client-side People Picker control in SharePoint-hosted apps

Hope you have use for all this. Happy Coding

6 responses to “Sharepoint People Picker wrapped in a AngularJs Directive

  1. Pingback: Office 365 Developer Podcast: Episode 025 on Solutions, Scenarios & Samples in PnP github repo » PC Portal of Wausau

  2. Pingback: Office 365 Developer Podcast: Episode 025 on Solutions, Scenarios & Samples in PnP github repo | Office 365 Deployment Autoblog

  3. james grizzle March 27, 2015 at 1:45 am

    This is fantastic! This is just what I needed, thank you so much for taking the time to blog this.

    Like

  4. sbro July 14, 2015 at 5:41 pm

    Thanks for this

    Like

Leave a comment