Thursday, 16 October 2014

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

[UPDATED]: I've written an updated post for this that include some feature enhancements and fixes. You can read that post here: (Updated) Using the Microsoft Client-side People Picker as an AngularJS Directive

If you're building a client-side SharePoint app using AngularJS (or any other JavaScript framework), and need a people picker, then using Microsoft's Client-Side People Picker is great option.

Resolving a user with the People Picker

A selected user in the people picker


The ClientSidePeoplePicker is Microsoft client side people picker control. It's built using HTML and JavaScript, is pretty easy to get working, and is generally well documented. E.g. How to: Use the client-side People Picker control in SharePoint-hosted apps

Microsoft: "The picker and its functionality are defined in the clientforms.js, clientpeoplepicker.js, and autofill.js script files, which are located in the %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\15\TEMPLATE\LAYOUTS folder on the server."

In this post I want to demonstrate how to resolve the SharePoint user ID for a selected user and use it to update the data model in an AngularJS spa using a REST call.

To update a people field in a SharePoint list using a REST call, you need the SharePoint SPUser.ID value for the selected user. Unfortunately, the clientside people picker doesn't contain the userid of a resolved user (I guess for good reason; if it did, it would have to call EnsureUser on the web for every user in the people picker that is resolved).

So the challenge is, how to get the SPUser.ID for the selected user?

When a user is resolve by the client side people picker, the claims identity of the user is returned. The people picker also fires an event, OnUserResolvedClientScript, after each user in the picker is resolved. This event is the key to getting that user ID!

Using the OnUserResolvedClientScript event, all you need to do is call a function that will take the claims identity of the user, and use it to perform an EnsureUser on the web. Calling EnsureUser on the SPWeb will add the user to the SharePoint User Information List (of the site collection), and return the SPUser object.

The sequence of actions is:

1. Client selects a user in the people picker
2. The people picker resolves each selected user, and fires the OnUserResolvedClientScript event each time a user is resolved
3. Each time the OnUserResolvedClientScript event is raised, the custom function (that you need to write) to ensure the user is called.
4. This function disables saving the model, and then does a quick REST call to EnsureUser.
5. When the REST call returns, you get the selected users ID back, and can use this to update the data model.
6. Saving the model is re-enabled.

The first step is to create a function that calls EnsureUser on the web. In this example, I'm using the AngularJS Resource module to make a REST call to EnsureUser:

// Define the ensureuser model
var ebc = ebc || {};
ebc.models = ebc.models || {};
ebc.models.ensureUser = function () {
    this.logonName = null;
}
// This example uses AngularJS resource service for iteracting with RESTful services
// This function defines the REST endpoint for EnsureUser.
function getUserIdResource() {
    return $resource('../../../_api/web/ensureuser',
        {}, {
            post: {
                method: 'POST',
                params: {
                },
                headers: {
                    'Accept': 'application/json;odata=verbose;',
                    'Content-Type': 'application/json;odata=verbose;',
                    'X-RequestDigest': service.securityValidation
                }
            }
        });
}
  
// the getUser function takes a claims formatted user string
// and makes a POST to ensureUser to check whether the specified login name
// belongs to a valid user in the site.
// If the user doesn't exist, adds the user to the site.
// The specified SPUser object is returned, containing the users SharePoint user ID.
// The user ID can then be used with the data model to update the list item
function getUser(claimsUserName) {    
    var userModel = new ebc.models.ensureUser();
    userModel.logonName = claimsUserName;
    var resource = getUserIdResource();
    var deferred = $q.defer();
    resource.post(userModel, function (data) {
        // successful callback 
        // data.d contains the JSON representation of the SPUser
        deferred.resolve(data.d);
    }, function (error) {
        // error callback
        deferred.reject(error);
    });
    return deferred.promise;
}

The second step is to create a function that can be called by the people picker each time a user is resolved. This function will be responsible for calling the function to ensure the user, and updating the data model when that function returns the selected users user id.

// This function is called by the people picker each time a user is resolved.
// The property parameter contains a string value that represents the property
// on the data model that needs to be updated with value of the SPUser ID on
function setUserIdFromPickerChoice(userKey, property, isMultiValued) {
    if (userKey && userKey[0] && property) {
        //disable saving the "rec" data model
        rec.saveDisabled = true;
        //call the getUser function
        datacontext.getUser(userKey[0].Key)
            .then(function (data) {
                if (data.Id) {
                    // "item" represents the SharePoint list item model property of the data model.
                    // check that the property "property" exists on the data model.
                    if (rec.item.hasOwnProperty(property)) {
                        // set the value of the property
                        rec.item[property] = data.Id;
                    }
                }
                // re-enable saving
                rec.saveDisabled = false;
                // log
                common.logger.logSuccess('Set user id.', null, controllerId);
            })
            .catch(function (error) {
                // log the error
                common.logger.logError('error retrieving request details data', error, controllerId);
            });
    }
    else if (property && rec.item) {
        //If the userKey doesn't contain a value, clear the value from the data model
        if (rec.item.hasOwnProperty(property)) {
            // "item" represents the SharePoint list item model property of the data model.
            // Use the the "property" value
            rec.item[property] = null;
        }
    }
}

You can see the POST and RESPONSE returned from the call to EnsureUser using Fiddler;

POST to /_api/web/ensureuser


RESPONSE from /_api/web/ensureuser


The third step is to initialise the people picker, and attach our function to the OnUserResolvedClientScript function

function initializePeoplePicker(peoplePickerElementId, displayName, userName, modelProperty, isMultiValued) {
    var schema = {};
    schema['PrincipalAccountType'] = 'User';
    schema['SearchPrincipalSource'] = 15;
    schema['ResolvePrincipalSource'] = 15;
    schema['AllowMultipleValues'] = isMultiValued;
    schema['MaximumEntitySuggestions'] = 50;
    schema['Width'] = '270px';
    schema['OnUserResolvedClientScript'] = function (elementId, userKey) {
        //Check if the people picker is intialised
        if (peoplePickerInitalised) {
            // If the picker is initialised, then call the 
            // setUserIdFromPickerChoice method.
            // This method will ensure the user on the web, 
            // and then update the data model 
            // Note that the modelProperty variable, is passed into this 
            // function. This variable is passed into the initalizePeoplePicker
            // function, and then used with the setUserIdFromPickerChoice function
            // to update the data model property with the selected users userid  
            setUserIdFromPickerChoice(userKey, modelProperty, isMultiValued);
        }
    }
    // If the userName property contains a value,
    //then populate the people picker with the user passed to the function
    var users = null;
    if (userName != null) {
        users = new Array(1);
        var user = new Object();
        user.AutoFillDisplayText = displayName;
        user.AutoFillKey = userName;
        user.AutoFillSubDisplayText = "";
        user.DisplayText = displayName;
        user.EntityType = "User";
        user.IsResolved = true;
        user.Key = userName;
        user.ProviderDisplayName = "Tenant";
        user.ProviderName = "Tenant";
        user.Resolved = true;
        users[0] = user;
    }
    // Call SPClientPeoplePicker_InitStandaloneControlWrapper to initialise the control
    SPClientPeoplePicker_InitStandaloneControlWrapper(peoplePickerElementId, users, schema);
}

Once you have this done, you're ready to go!

The screen shot below (from Fiddler) shows the people pickers request and the JSON returned, containing the users claims identity. This is followed by the call to EnsureUser, from the method we've attached to the people pickers OnUserResolvedClientScript event.



I'll be posting a full working example on the MSDN Gallery soon!