Using the People Picker Control in Sandbox Solutions / Office 365

The SharePoint sandbox does not allow you to use the Microsoft.SharePoint.WebControls namespace in your code. This means you can’t instantiate objects from classes in that namespace or automate existing object instances of classes in that namespace. Because of this restriction, the MSDN documentation for PeopleEditor you will see the following:

Sandbox is a Server Thing

As I wrote above, this restriction is enforced by the sandbox. Available in Sandboxed Solutions: No means you can’t use this class in your code. However, you are free to use any of the Microsoft.SharePoint.WebControls in your page markup. For example, if you create a custom master page it will almost certainly contain controls like CssRegistration and CssLink; both are members of Microsoft.SharePoint.WebControls. You can’t write code that uses these classes. Therefore you can’t call CssRegistration.Register() programmatically. You can use the control in markup assuming you can get the desired server side functionality declaratively via the control’s property attributes. Markup as follows is permitted in pages deployed to the sandbox.

<SharePoint:CssRegistration name="<% $SPUrl:~sitecollection/Style Library/~language/Themable/Core Styles/controls.css %>"
								runat="server"/>

Pop Quiz!

If you want to use the PeopleEditor control in your sandbox solution can you use it in: a) an aspx page, b) a Web Part, or c) both?

The answer is a) an aspx page. However, to do anything with the selected value you will need a custom Web Part. The Web Part can’t use the PeopleFinder class, but it can read all of the page’s form fields. As it turns out, this is good enough with a little JavaScript glue to hold everything together on the client side.

Adding People Finder to a Page

First you must register the control namespace by including the following register directive (but it’s probably on your page already).

<%@ register tagprefix="SharePoint"
namespace="Microsoft.SharePoint.WebControls"
assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

 

Next include the control with the desired properties. In the example below the control allows the selection of a single user or SharePoint group. See the MSDN documentation for PeopleEditor for complete documentation.

<SharePoint:PeopleEditor
        AllowEmpty="false"
        ValidatorEnabled="false"
        id="assignedTo"
        runat="server"
        SelectionSet="User,SPGroup"
        MultiSelect="false"/>

On my custom task form the result looks like this:

Validating the Entry

Since automation of this control via the PeopleEditor class is forbidden, the page requires some JavaScript code to ensure that the entry is valid. It also must resolve the entry to make it easy for the page’s Web Part to get the value from a form field. The picker’s rendered state includes JavaScript that validates the user and the custom JavaScript takes advantage of the existing script. The only real trick is that the validation involves an asynchronous call to the server. The solution is to use a timer and observe the state of the picker’s markup. As usual I use jQuery to observe and manipulate the markup.

There are four states the markup manifests:

  1. An entry that is confirmed as invalid

  2. An entry that is confirmed as valid

  3. No entry

  4. An entry that is not confirmed as either valid or invalid

To detect the first state the script looks for descendants of the control that contain the isresolved attribute set to false. Conversely, for the second state the script looks for descendants of the control that contain the isresolved attribute set to true.

Assuming the control is inside a content placeholder named PlaceHolderMain as is usual for SharePoint content pages, the HTML ID attribute of the control with a server ID of assignedTo is ctl00_PlaceHolderMain_assignedTo. The check for the first two states is as follows:

if ($('#ctl00_PlaceHolderMain_assignedTo').find('*[isresolved="False"]').length > 0) {
return false; //Confirmed invalid
}
if ($('#ctl00_PlaceHolderMain_assignedTo').find('*[isresolved="True"]').length > 0) {
return true; //Confirmed valid
}

 

The rendered control contains a div element that is empty if the user hasn’t entered anything. To detect the third state, the script checks this div for an absence of children,

var entry = $(‘#ctl00_PlaceHolderMain_assignedTo_upLevelDiv’).html();
if (entry === "" || entry === "&nbsp;") {
return true; //Valid – no entry
}

The final state is the one that requires the picker to validate itself before form submission. This process starts by calling the WebForm_DoCallback function. While it runs the script uses a timer to check for the first or second state. The completed validation functions are shown below.

var checkingName = false;

function checkName() {
if (!checkingName) {
checkingName = true;
var arg = getUplevel('ctl00_PlaceHolderMain_assignedTo');
var ctx = 'ctl00_PlaceHolderMain_assignedTo';
EntityEditorSetWaitCursor(ctx);
WebForm_DoCallback('ctl00$PlaceHolderMain$assignedTo', arg,
EntityEditorHandleCheckNameResult, ctx, EntityEditorHandleCheckNameError, true);

}
}

function nameIsValid() {
if ($('#ctl00_PlaceHolderMain_assignedTo').find('*[isresolved="False"]').length > 0) {
checkingName = false;
return false; //Confirmed invalid
}
if ($('#ctl00_PlaceHolderMain_assignedTo').find('*[isresolved="True"]').length > 0) {
checkingName = false;
return true; //Confirmed valid
}
var entry = $('#ctl00_PlaceHolderMain_assignedTo_upLevelDiv').html();
if (entry === "" || entry === "&nbsp;") {
return true; //Valid - no entry
}
//Not confirmed and not blank, ask the server
checkName();
//Wait half a second and check the markup to determine validity
setTimeout(ValidateAndSave, 500);
return false;
}

 

Getting the Result

If you submit the form after executing the above code, you can pick apart the form fields to read the entries. However, I prefer to use a bit more script to pull the valid name from the picker markup and stuff it into another field. This is cleaner and it decouples the Web Part code completely from the PeopleFinder. The following function returns the value from the picker in a valid, not empty state.

function getName() {
var data = $('#ctl00_PlaceHolderMain_assignedTo_upLevelDiv').html();
if (data !== "" && data !== "&nbsp;") {
var ret = $('#ctl00_PlaceHolderMain_assignedTo').find('div#divEntityData').attr('key');
if (ret !== undefined) {
return ret;
}
else {
return "";
}
}
return "";
}

In the Web Part I use the value directly without additional parsing.

SPUser user = web.EnsureUser(task.assignedTo);
SPFieldUserValue at = new
SPFieldUserValue(web, user.ID, user.LoginName);

 

Happy coding!
–Doug

Author: Doug Ware