Friday, 4 December 2015

(Updated) Using the Microsoft Client-side People Picker as an AngularJS Directive

I wrote about wrapping the Microsoft Client Side People Picker into an AngularJS Directive a few months back (eight months to be precise).

The original post is here: Using the Microsoft Client-side People Picker as an AngularJS Directive

I've made a few improvements to it recently, which I want to highlight in this post. The source code's been updated on GitHub, and can be downloaded here: AngularJS-Directive-for-SharePoint-People-Picker.

Summary of Improvements

  • Wrapped a parent DIV around the peoplepicker 
  • Works with SharePoint groups
  • Listens to model changes and updates the control appropriately
  • Re-factored code (better structure)
  • Raises events on the root scope when starting and finishing resolving users

Designed for REST

The other thing I want to draw attention to, is that this directive is built with REST in mind. It's designed to work with models received from a REST query, and it formats the model (when new users are added) in a format that can be used to submit a REST.

Before we get started, let's look at what SharePoint sends us when we query a list that has people fields, using REST.

I've used the following REST query. The query requests the people fields, and expands the Id, Name and Title properties.'peoplePickerTestList')/items(1)?$select=multiUserColumnId,singleUserColumnId,multiUserColumn/Name,singleUserColumn/Name,multiUserColumn/Title,singleUserColumn/Title,multiUserColumn/Id,singleUserColumn/Id&$expand=multiUserColumn,singleUserColumn

The following screen shot is from Fiddler, and shows the response from SharePoint (Office 365).

There are a few things worth noting here. The first is the field names that are appended with "Id".

For example, the two people fields, multiUserColumn and singleUserColumn, both have corresponding fields multiUserColumnId and singleUserColumnId. These corresponding fields don't actually exist on the list, but are returned by REST for lookup fields.

The fields appended with "Id" only every contain the Id of the lookup item. When you update a SharePoint list using REST, it's easiest to update the list using these fields, passing in just the look Id(s).

The next thing to notice is the difference in the object returned for a single user field verse a multi-user field.

The single user field contains direct properties for the user values. In the example above, these values include Name, Id, and Title.

The multi user field is slightly different. It returns an object with a single property, "results". "results" is an array of user objects. Each user object in the array has the expanded fields we specified in the REST request; Name, Id and Title.

The people picker directive is designed to work with these two different models. It will read and update a model using this format.

Using the People Picker Directive

Added a reference to the people picker directive

<!-- Load third party scripts required by the people picker -->
<script type="text/ecmascript" src="/_layouts/15/SP.UI.Controls.js"></script>
<script type="text/ecmascript" src="/_layouts/15/clienttemplates.js"></script>
<script type="text/ecmascript" src="/_layouts/15/clientforms.js"></script>
<script type="text/ecmascript" src="/_layouts/15/clientpeoplepicker.js"></script>
<script type="text/ecmascript" src="/_layouts/15/autofill.js"></script>
<script type="text/ecmascript" src="/_layouts/15/sp.RequestExecutor.js"></script>
<!-- AngularJS-->
<script type="text/ecmascript" src="../angularjs-peoplepicker/scripts/angular.min.js"></script>
<script type="text/ecmascript" src="../angularjs-peoplepicker/scripts/angular-sanitize.min.js"></script>
<script type="text/ecmascript" src="../angularjs-peoplepicker/scripts/angular-resource.min.js"></script>
<script type="text/ecmascript" src="../angularjs-peoplepicker/scripts/angular-route.min.js"></script>
<!-- All of this scripts are used to create the app. -->
<script type="text/ecmascript" src="../angularjs-peoplepicker/app.js"></script>
<!-- ****** The following script contains the people picker directive. ****** -->
<script type="text/ecmascript" src="../angularjs-peoplepicker/config/config.peoplepicker.js"></script>
<!-- ****** The following script contains the controller. ****** -->
<script type="text/ecmascript" src="../angularjs-peoplepicker/controllers/controllers.js"></script>

Add the people picker directive to the app

(function () {
    'use strict';        
    var app = angular.module('app', [

Add one or more people pickers to the HTML (ensure you use a different id for each picker)

<div data-ng-disabled="false" ui-people ng-model="" pp-is-multiuser="{{false}}" pp-width="220px" pp-account-type="User,DL,SecGroup,SPGroup" id="peoplePickerDivAT"></div>

Conditionally, add some code to the controller to pre-populate the people picker (this would normally be a REST query to SharePoint to get some list data).

//App Controller
(function () {
 'use strict';
 var controllerId = 'appCtrlr';
 angular.module('app').controller(controllerId, ['$scope', '$timeout', appCtrlr]);

 function appCtrlr($scope, $timeout) {
  var vm = this;
  vm.webUrl = null; = {
  vm.getPresence = getPresence;
  var service = function () {  
   this.webAbsoluteUrl = _spPageContextInfo.webAbsoluteUrl;
   this.webRelativeUrl = _spPageContextInfo.webServerRelativeUrl;
   this.currentUserId = _spPageContextInfo.userId;
   this.webAppUrl = document.location.origin || document.location.href.substring(0, document.location.href.indexOf(document.location.pathname));  

  function init() {
   //Normally you would get this information from a REST call to Office 365 / SharePoint
   //Update the weburl property
   //This is to demo passing in a Web URL to the people picker via the pp-web-url attribute.
    var s = new service;
    vm.webUrl = s.webAppUrl;
    if (!$scope.$root.$$phase) {
  function getData(){
   $timeout(function(){ = {
     //You'll need to add a valid claims based identity, and the SPUSER ID here
     Title:'Matthew Yarlett'
    //Add a user to the mulitple-user model = {results:[]};{
     //You'll need to add a valid claims based identity, and the SPUSER ID here
     Title:'Matthew Yarlett'});
    //Add a SharePoint group to the mulitple-user model{
    //You'll need to add a valid claims based identity, and the SPUSER ID here
     Name:'App Center Owners',
     Title:'App Center Owners'});
    if (!$scope.$root.$$phase) {
  function getPresence(userId, userTitle) {
   if (userId && userTitle) {
    return '<span class="ms-noWrap"><span class="ms-spimn-presenceLink"><span class="ms-spimn-presenceWrapper ms-imnImg ms-spimn-imgSize-10x10"><img class="ms-spimn-img ms-spimn-presence-disconnected-10x10x32" src="'+service.webAbsoluteUrl+'/_layouts/15/images/spimn.png?rev=23"  alt="" /></span></span><span class="ms-noWrap ms-imnSpan"><span class="ms-spimn-presenceLink"><img class="ms-hide" src="'+service.webAbsoluteUrl+'/_layouts/15/images/blank.gif?rev=23"  alt="" /></span><a class="ms-subtleLink" onclick="GoToLinkOrDialogNewWindow(this);return false;" href="'+service.webAbsoluteUrl+'/_layouts/15/userdisp.aspx?ID=' + userId + '">' + userTitle + '</a></span></span>';
   return '<span></span>';

Possible attributes and they're values

Note: The data-pp-ready-to-load attribute has been removed from this version (the directive listens to model changes and updates the people picker accordingly).

AttributePossible valuesDefault valueRequired?
data-ng-modelFor single-user fields, an object:

model = { 'Name': object.Name, 'Title': object.Title, 'Id': object.Id }

For multi-user fields, the model needs the "results" object (which is an array) of people object.

model.results = [{ 'Name': object.Name, 'Title': object.Title, 'Id': object.Id }]

Where, name is a claims based user principal, Title is the display name of the principal, and Id is the lookup Id of the person or group
nullYes, but can be null
data-pp-is-multiusertrue | falsefalseNo
data-pp-widthAny numerical value, followed by 'px'220pxNo
data-pp-account-typeAny combination of the following values, separated by a comma;

Screen shot