Share via


Working with users in SharePoint Hosted Apps - Part I

While working with users in SharePoint Hosted App, I have found the JavaScript Object Model (JSOM) in SharePoint much more easier to work with compared to its REST counterpart.  The first limitation of REST protocol is that it returns only 100 items.  I have not found a way to increase this, maybe someone can guide me in the right direction.  On the other side, assigning values to person column in a list item is comparatively easier using REST.  

What I need:

  • Get all the users on SharePoint
  • Select user(s) using people picker control
  • Get the user id for selected user(s) in people picker control (I have learnt to make calls chunky not chatty, thanks Christopher Harrison)
  • Assign users (single or multiple values) to list item
  • Set the people picker control

There are several individual articles that discuss the use of each but I have not found any one which addresses all in one place.  This article will be a one stop shop to answer all these concerns and would help save considerable amount of time when implementing this feature.  You can review the references added below for further reading.

Let's get started:

Get all the users on SharePoint

When working with REST API, you need the Id of the user which is saved with the list item.  The people picker control allows you to select the user however it does not contain the Id of the user selected.  For this, you need to call back into SharePoint and get the Id using the user login name.  And if you have multiple users, then you would need to make that call as many times.  This makes our model a bit chatty.  To cut down on this, I have decided to be chunky and get all the users at one time.  

First we will add the jQuery library to our page.  Download the file instead of using a CDN (content delivery network) for intranet applications.

<script type="text/javascript" src="../Scripts/jquery-1.9.1.js"></script><br>

We need to create a global variable that will hold the user list and context:

var user_list = [];
var context = SP.ClientContext.get_current();

Using REST API, I can get the user information list sorted by ID.  If you are working with large list, you can use $select operator to return only the columns you want to work with.  In this case, we are returning all columns:

function getUsers() {
    var pUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/site/rootweb/lists/getByTitle('User Information List')/items?$orderby=Id";
    //var pUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/site/rootweb/lists/getByTitle('User Information List')/items?$orderby=Id&$select=Id,Title,Name,EMail";
    $.ajax(pUrl, { method: "GET", headers: { "accept":  "application/json;odata=verbose"  } }).done(storeUsers).fail(getUserError);
}
 
function storeUsers(data) {
    var responseParse = JSON.parse(data.body);
    user_list = responseParse.d.results;
}
 
function getUserError(jqXHR, textStatus) {
    alert(textStatus);
}

But this would only return 100 items.  So I will use JSOM to get the list of all users sorted by ID.  If you are working with large list, you can use 'Include' to return only the columns you want to work with.  In this case, we are returning all columns:

function getAllUsers() {
    var userInfoList = context.get_site().get_rootWeb().get_siteUserInfoList();
 
    var camlQuery = new SP.CamlQuery();<br>
    camlQuery.set_viewXml('<View><Query><OrderBy><FieldRef Name=\'ID\' /></OrderBy></Query></View>');    
    userListItemCollection = userInfoList.getItems(camlQuery);
 
    context.load(userListItemCollection);
    //context.load(userListItemCollection, 'Include(Title,ID,Name,EMail)');
 
    context.executeQueryAsync(onGetAllUsersSuccess, onGetAllUsersFail);
}
 
function onGetAllUsersSuccess() {
    var userArr = [];
    var arrNames = [];
    var listEnumerator = userListItemCollection.getEnumerator();
 
    while (listEnumerator.moveNext()) {
        var oList = listEnumerator.get_current();
 
        //avoid duplicates
        var index = $.inArray(oList.get_item('Title'), arrNames);
        if (index == -1) {
            userArr.push({
                Id: oList.get_item('ID'),
                Title: oList.get_item('Title'),
                Name: oList.get_item('Name'),
                EMail: oList.get_item('EMail')
            });
            arrNames.push(oList.get_item('Title'));
        }
    }
 
    user_list = userArr;
}
 
function onGetAllUsersFail(sender, args) {
    alert("Unable to load user information: " + args.get_message());
}

Select user(s) using people picker control

Before you can start using the people picker control, you need to add the necessary script references and create the div.  The MSDN article on How to: Use the client-side People Picker control in SharePoint-hosted apps <http://msdn.microsoft.com/en-us/library/office/jj713593(v=office.15).aspx> does a good job in explaining what is needed to use this control in your apps.  For ease, we will add the steps here and modify it to use our user list to get the user Id of the selected user.

<SharePoint:ScriptLink Name="sp.js" runat="server" OnDemand="true" LoadAfterUI="true" Localizable="false" />
<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.runtime.js" runat="server" LoadAfterUI="true" Localizable="false" />
<SharePoint:ScriptLink Name="sp.core.js" runat="server" LoadAfterUI="true" Localizable="false" />
 
<div id="peoplePickerDiv"></div>
$(document).ready(function () {
    var j = ExecuteOrDelayUntilScriptLoaded(initializePeoplePicker, "clientpeoplepicker.js");
});
 
function initializePeoplePicker() {
    var schema = {
        'PrincipalAccountType': 'User',
        'SearchPrincipalSource': 15,
        'ResolvePrincipalSource': 15,
        'AllowMultipleValues': false,
        'Required': true,
        'MaximumEntitySuggestions': 5,
        'Width': '235px'
    };
 
    var pickuser = null;
    SPClientPeoplePicker_InitStandaloneControlWrapper('peoplePickerDiv', pickuser, schema);
}

**Get the user id for selected user(s) in people picker control **

function getUsersInfo() {
   // Get the people picker object from the page.
   var peoplePicker = this.SPClientPeoplePicker.SPClientPeoplePickerDict.peoplePickerDiv_TopSpan;
   var arrayPeople = [];            
 
   // Get information about selected users.
   var users = peoplePicker.GetAllUserInfo();
   for (var i = 0; i < users.length; i++) {
       var user = users[i];
 
       var filtereduser = $.grep(user_list, function (item, i) {
           return item.Name == user['Key'];
       });
 
       if (filtereduser.length > 0) {
           arrayPeople.push(filtereduser[0].Id)
       }
   }
 
   return arrayPeople;
}

Assign users (single or multiple values) to list item

<input type="submit" id="cmdsave"  value="Save" />
document.onsubmit = function  () {
   var getSelectedUsers = getUsersInfo();
};

This is where things get a little twisted.  If the column in the list can only contain one user, then we can simply assign the Id to the column.  We will modify the document.onsubmit.

//Using REST
document.onsubmit = function  () {
   var getSelectedUsers = getUsersInfo();
   If (getSelectedUsers.length > 0){
      var data = {
         BookAuthorId: getSelectedUsers[0]
      }
    
      var dataJSON = JSON.stringify(data);
   } 
};

This article is not going to go over how to save data using REST.  For that you can reference the article, SharePoint 2013: Use a List Template Model with the REST API.

//Using JSOM
document.onsubmit = function  () {
   var getSelectedUsers = getUsersInfo();
   If (getSelectedUsers.length > 0){
      //context is created depending on if the list is on hostweb or appweb
      var oList = context.get_web().get_lists().getByTitle('Books');
      var oListItem = oList.getItemById(1);
 
      oListItem.set_item('BookAuthorId', getSelectedUsers[0]);
      oListItem.update();
      context.executeQueryAsync(function() { }, function(sender, args) { });
   } 
};

Now, if the column in the list allows multiple values, we would have to pass an array of user Ids.  Let's modify the document.onsubmit method.

//Using REST
document.onsubmit = function  () {
   var getSelectedUsers = getUsersInfo();
   If (getSelectedUsers.length > 0){
       
      var data = {
         BookAuthorId: { results: getSelectedUsers }
      }
    
      var dataJSON = JSON.stringify(data);
   } 
};

Simple isn't it?  But if you are working with JSOM, it is not as straight forward as you may want it to be.  This is where you need explore the SP.js namespace.  SP.js namespace contains a class, SP.FieldUserValue.  SharePoint does not return any helpful error which might indicate that you need to pass in this object for each selected user.  After reviewing various MSDN forum articles, I was able to successfully save multiple users.  Just to add a note, none of the implementations marked as answer worked for me but it did give me a direction.  When using JSOM, you cannot save the Id rather you need to use the login name and create SP.FieldUserValue objects.  So we will lookup the login name from our user list.

//Using JSOM
document.onsubmit = function  () {
   var getSelectedUsers = getUsersInfo();
   If (getSelectedUsers.length > 0){
      //context is created depending on if the list is on hostweb or appweb
      var oList = context.get_web().get_lists().getByTitle('Books');
      var oListItem = oList.getItemById(1);
 
      var userField = [];
 
      for (var k = 0; k < getSelectedUsers.length; k++) {
          var filtereduser = $.grep(user_list, function (item, i) {
              return item.Id == getSelectedUsers[k];
          });
          if (filtereduser.length > 0) {
              userField.push(SP.FieldUserValue.fromUser(filtereduser[0].Name));
          }
      }
 
      oListItem.set_item('BookAuthorId', userField);
      oListItem.update();
      context.executeQueryAsync(function() { }, function(sender, args) { });
   } 
};

Set the people picker control

You can set the people picker control to select only users, and/or groups, maximum number of suggestions, and source.  For our discussion, we would need to modify the initialize method to allow multiple values and pass in an array of valid users to set the control:

function initializePeoplePicker(pSelectedUsers) {
    var schema = {
        'PrincipalAccountType': 'User',
        'SearchPrincipalSource': 15,
        'ResolvePrincipalSource': 15,
        'AllowMultipleValues': true,
        'Required': true,
        'MaximumEntitySuggestions': 5,
        'Width': '235px'
    };
 
    var pickuser = [];
 
    for (var k = 0; k < pSelectedUsers.length; k++) {
        var filtereduser = $.grep(user_list, function (item, i) {
            return item.Id == pSelectedUsers[k];
        });
 
        if (filtereduser.length > 0) {
            var defaultUser = new Object();
            defaultUser.AutoFillDisplayText = filtereduser[0].Title;
            defaultUser.AutoFillKey = filtereduser[0].Name;
            defaultUser.Description = filtereduser[0].EMail;
            defaultUser.DisplayText = filtereduser[0].Title;
            defaultUser.EntityType = "User";
            defaultUser.EntityData = { Email: filtereduser[0].EMail };
            defaultUser.IsResolved = true;
            defaultUser.Key = filtereduser[0].Name;
            defaultUser.Resolved = true;
 
            pickuser.push(defaultUser)
        }
    }
 
    SPClientPeoplePicker_InitStandaloneControlWrapper('peoplePickerDiv', pickuser, schema);
}

Conclusion
I have come to an agreement that no one API can fulfil all of our requirements and there would always remain a need to mix and match the APIs available.

**References
**Working with users in SharePoint Hosted Apps - Part II
Work with users and groups using JavaScript
Use the client-side People Picker control in SharePoint Hosted Apps
Add multiple people using JSOM
SP.js JavaScript Reference