For those of you using Backbone.js and Backbone.Stickit for client-side SharePoint development, this is a quick article describing how to add a global Stickit handler for SharePoint client People Picker controls.
In this example, we have a SharePoint Person or Group field with an internal name of WorkFor (and a corresponding attribute in our Backbone model) that we want to render using the client People Picker control through our Backbone view.
The following screenshot shows the end result:

Though the client People Picker control is often helpful and has become a de-facto standard for SharePoint users, it does present some challenges when used with backbone and the SharePoint REST API:
- A SharePoint Person or Group field stores the numeric SharePoint ID of the selected user (not the user's account name).
- The client People Picker API doesn't return the SharePoint ID of the selected user.
- In order to set the appropriate SharePoint list item property or backbone model attribute, the selected user's ID must be fetched from SharePoint whenever the selected user is changed.
- The SharePoint REST API returns the Person or Group field with 'Id' appended to the end of the field's actual internal name (see User, Person or Group Fields with REST in SharePoint 2013 for more details). So for this example, we must use 'WorkForId' whenever we need to reference the WorkFor field.
In Stickit, we can create a global handler for specific elements (identified using standard selectors) that eliminates the need to manually create and attach 3rd-party controls outside of the normal Backbone and Backbone.Stickit flow.
Basic Steps
- Define the HTML in your view template.
- Add the global Stickit handler in your view.
- Define your Stickit bindings as you normally would. When stickit(this.model, this.bindings) is called in your view's render() function, the control is automatically created and attached to the appropriate element.
// This is an example of using the SharePoint client people picker control in a Stickit handler with Backbone, | |
// Backbone.Stickit and Backbone.SharePoint | |
// In your HTML template... | |
<label for="WorkForTitle" class="control-label">Requested For</label> | |
<div class="workfor peoplepicker" id="WorkForTitle"></div> | |
// In your view... | |
Backbone.Stickit.addHandler({ | |
selector: "div.peoplepicker", | |
initialize: function ($el, model, options) { | |
$el.spPeoplePicker({ | |
PrincipalAccountType: "User", // User,DL,SecGroup,SPGroup | |
SearchPrincipalSource: 15, | |
ResolvePrincipalSource: 15, | |
AllowMultipleValues: false, | |
MaximumEntitySuggestions: 50, | |
Width: "504px" | |
}); | |
var changed = function (elementId, userInfo) { | |
if (userInfo.length === 1 && userInfo[0].IsResolved && userInfo[0].DisplayText !== model.attributes[options.observe]) { | |
model.set(options.observe, userInfo[0].DisplayText); | |
} | |
}; | |
// Set the initial value and add event handler | |
var peoplePicker = $el.spPickerAddUser(model.attributes[options.observe]); | |
peoplePicker.OnValueChangedClientScript = changed; | |
} | |
}); | |
// People picker helpers (these functions are included for the sake of this example, but would | |
// typically be included in a utility library). | |
$.fn.spPeoplePicker = function (options) { | |
var eltId = $(this).attr("id"); | |
ExecuteOrDelayUntilScriptLoaded(function () { | |
initializePeoplePicker(eltId, options); | |
}, "clientpeoplepicker.js"); | |
}; | |
$.fn.spGetPeoplePicker = function () { | |
var pickerKey = $(this).attr("id") + "_TopSpan"; | |
return SPClientPeoplePicker.SPClientPeoplePickerDict[pickerKey]; | |
}; | |
$.fn.spPickerAddUser = function (user) { | |
var eltId = $(this).attr("id"); | |
return setPeoplePickerValue(eltId, user); | |
}; | |
$.fn.spGetUserFromPicker = function () { | |
var eltId = $(this).attr("id"); | |
var spUsersInfo = getPeoplePickerValues(eltId); | |
return spUsersInfo.slice(0, -2); | |
}; | |
setPeoplePickerValue = function (eltId, value) { | |
var topSpan = eltId + "_TopSpan", | |
ppInput = $("#" + topSpan + "_EditorInput"), | |
peoplePicker = SPClientPeoplePicker.SPClientPeoplePickerDict[topSpan]; | |
if (typeof peoplePicker !== "undefined" && typeof ppInput !== "undefined") { | |
ppInput.val(value); | |
peoplePicker.AddUnresolvedUserFromEditor(true); | |
} | |
return peoplePicker; | |
} | |
getPeoplePickerValues = function (eltId) { | |
var toSpanKey = eltId + "_TopSpan", | |
peoplePicker = SPClientPeoplePicker.SPClientPeoplePickerDict[toSpanKey]; | |
if (typeof peoplePicker !== "undefined") { | |
var users = peoplePicker.GetAllUserInfo(), | |
userInfo = ""; | |
for (var i = 0; i < users.length; i++) { | |
var user = users[i]; | |
userInfo += user["DisplayText"] + ";#"; | |
} | |
return userInfo; | |
} | |
else { | |
return ""; | |
} | |
} | |
initializePeoplePicker = function (eltId, options) { | |
var schema = {}; | |
schema["PrincipalAccountType"] = options.PrincipalAccountType; | |
schema["SearchPrincipalSource"] = options.SearchPrincipalSource; | |
schema["ResolvePrincipalSource"] = options.ResolvePrincipalSource; | |
schema["AllowMultipleValues"] = options.AllowMultipleValues; | |
schema["MaximumEntitySuggestions"] = options.MaximumEntitySuggestions; | |
schema["Width"] = options.Width; | |
SPClientPeoplePicker_InitStandaloneControlWrapper(eltId, null, schema); | |
} | |
// SharePoint REST helpers (these functions are included for the sake of this example, but would | |
// typically be included in a utility library). | |
spGet = function (url, headers) { | |
var deferred = $.Deferred(); | |
$.ajax({ | |
url: url, | |
method: "GET", | |
headers: _.extend({ | |
"Accept": "application/json;odata=verbose" | |
}, headers) | |
}) | |
.done(function (data) { | |
deferred.resolve(data); | |
}) | |
.fail(function (data) { | |
deferred.reject(data); | |
}); | |
return deferred.promise(); | |
} | |
getUserByAccount = function (account) { | |
var deferred = $.Deferred(); | |
spGet(_spPageContextInfo.webServerRelativeUrl + "/_api/web/siteusers(@v)?@v='" + account + "'") | |
.done(function (data) { | |
deferred.resolve(data.d); | |
}) | |
.fail(function (data) { | |
deferred.reject(data); | |
}); | |
return deferred.promise(); | |
} | |
// In your view's stickit bindings | |
".workfor": { | |
observe: "WorkForTitle", | |
setOptions: { | |
validate: true | |
}, | |
// Override to set the actual Person or Group ID field in the model | |
// that gets written back to SharePoint via backbone. | |
update: function ($el, val, model, options) { | |
var peoplePicker = $el.spGetPeoplePicker(); | |
if (!peoplePicker) return; | |
var userInfo = peoplePicker.GetAllUserInfo()[0]; | |
if (typeof userInfo !== "undefined" && userInfo.IsResolved) { | |
spUtils.getUserByAccount(encodeURIComponent(userInfo.Key)) | |
.done(function (data) { | |
model.set("WorkRequestedForId", data.Id); | |
}); | |
} | |
} | |
} | |
// For more details, see: | |
// http://sharepoint.softwaredevelopmentoutpost.com/2016/01/backbonestickit-handler-for-sharepoint_18.html |
The same technique can be used for a variety of client-side controls and plugins to simplify and add consistency to your SharePoint CSOM/JSOM code.
No comments:
Post a Comment