Sharepoint People Picker – AngularJs Directive [Updated]

I’ve recently updated the code for my angularJs directive that I wrote a year ago.

I won’t go into detail like the previous post, this is more of a change log.
The updated code is on Github (https://github.com/drohreo/spPeoplePicker-ng-directive), a few words first on what I’m planning.
At the moment the code on Github is within a demo project, a visual studio solution of a SharePoint hosted add-in.
It would make more sense to have just the relevant code there, so that’s something I’m planning to do.
But I want to make it more easy to grab, both for myself and others, so I’m hoping to make it into a bower package.
I’m also planning to give the same treatment to a taxonomy picker, and probably some other good SharePoint utilities for angular, like some standard services for common tasks and what not.

Now for the changes.
The whole directive has been given a bit of an overhaul, basically I’ve streamlined it. I’ve decided that the model needs only be an array the key and the display name. Deciding on one model rather than a configuration that gives you different outputs makes it harder to reason about how to implement the flow within the directive.
It has a drawback, that was actually there in the previous incarnation, but I did not realize it. Which is that when you place anything other than a primitive type object on the model, the built-in change detection of the $compile service does not do a deep check of properties, so I had to implement a watch to facilitate a two-way binding mechanism of the control and model.
Previously this did not work, now if you want a different model, reading my previous post, should give you enough information to make the changes needed.

Also I’ve removed the extra html for the twitter bootstrap styles and the label, allowing the directive to be purely what it is, without forcing any styling conventions, If you want twitter bootstrap styles you can place the directive within the needed html and classes yourself.

I’ve also built a service for the directive which is very simple, and probably needs some more work. What it does is use the SharePoint script on demand services, to ask for the js files from SharePoint, so that you don’t need to place the script tags on your page, it also insures that we don’t load files twice.
I noticed that If you used a aspx page in your app, with the app.master file being used, in a SharePoint online environment, many of the files were being loaded from SharePoint online CDN’s and loading them again could cause conflicts and problems. Not to mention the waste of bandwidth.

The service exposes a promise object which the directive calls the then function and initializes the people picker first after all the scripts are loaded.
Lastly I’ve placed both the service and the directive in their own module, which can then be added as a dependency of your app.

So to use it, get the files here (until I make a bower package)
https://github.com/drohreo/spPeoplePicker-ng-directive/blob/master/app/sfSpUtils/sp-ngUtils.js
https://github.com/drohreo/spPeoplePicker-ng-directive/blob/master/app/sfSpUtils/sp-ngUtils.min.js
https://github.com/drohreo/spPeoplePicker-ng-directive/blob/master/app/sfSpUtils/sp-ngUtils.min.js.map

add “spUtils” as a dependency of your app in your module declaration like this.
angular.module(“yourAppName”, [“spUtils”, “otherDeps..”])

and place this tag in your html

And you should be good to go, and if something doesn’t work, write an issue on github.

Advertisements

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