gd-sprest-bs
Version:
SharePoint JavaScript, TypeScript and Web Components designed using the Bootstrap framework.
458 lines (402 loc) • 18.3 kB
text/typescript
import { ContextInfo, Helper, PeoplePicker as Search, SPTypes, Types, Web } from "gd-sprest";
import { Components } from "../core";
import { IFormControlPropsPeoplePicker, IPeoplePicker, IPeoplePickerProps } from "./types";
/**
* People Picker
*/
export const PeoplePicker = (props: IPeoplePickerProps): IPeoplePicker => {
let _filterText: string = null;
let _menu: Components.IPopover = null;
let _minCharSearch: number = props.minCharSearch > 0 ? props.minCharSearch : 3;
let _users = [];
// Method to add a user
let addUser = (userInfo: Types.IPeoplePickerUser | string) => {
let user: Types.IPeoplePickerUser = typeof (userInfo) === "string" ? JSON.parse(userInfo) : userInfo;
// Adds the button
let addButton = (userInfo?: Types.SP.User | Types.SP.Group) => {
// See if the picker is disabled or read only
if (props.disabled || props.readOnly) {
// Render a tooltip
let tooltip = Components.Tooltip({
el: elSelectedUsers,
content: [
'<div class="text-white text-wrap text-break">',
'<small>' + (userInfo["Email"] || userInfo["UserPrincipalName"]) + '</small>',
'<br />',
'<small>' + userInfo.LoginName + '</small>',
'</div>'
].join('\n'),
placement: Components.TooltipPlacements.Top,
type: Components.TooltipTypes.Primary,
btnProps: {
className: "me-1 mb-1",
isSmall: true,
text: userInfo.Title,
type: Components.ButtonTypes.Primary
}
});
} else {
// Render a tooltip
let tooltip = Components.Tooltip({
el: elSelectedUsers,
content: [
'<div class="text-white text-wrap text-break">',
'<small>' + (userInfo["Email"] || userInfo["UserPrincipalName"]) + '</small>',
'<br />',
'<small>' + userInfo.LoginName + '</small>',
'</div>'
].join('\n'),
placement: Components.TooltipPlacements.Top,
type: Components.TooltipTypes.Primary,
btnProps: {
data: userInfo,
className: "me-1 mb-1",
isSmall: true,
text: userInfo.Title,
type: Components.ButtonTypes.Primary,
badge: {
className: "people-picker-x ms-2",
content: "×",
isPill: true,
type: Components.BadgeTypes.Light,
onClick: () => {
// Remove the button
elSelectedUsers.removeChild(tooltip.button.el);
// Call the event
props.onChange ? props.onChange(obj.getValue()) : null;
}
}
}
});
// Set the data attribute
tooltip.button.el.setAttribute("data-user", JSON.stringify(userInfo.stringify()));
}
// Call the event
props.onChange ? props.onChange(obj.getValue()) : null;
}
// See if we are allowing multiple users
let allowMultple = typeof (props.multi) == "boolean" ? props.multi : false;
if (!allowMultple) {
// Remove existing users
while (elSelectedUsers.firstChild) { elSelectedUsers.removeChild(elSelectedUsers.firstChild); }
}
// Ensure this is a user object
if (user.EntityData) {
// Ensure the group or user id is set
if (user.EntityData.SPGroupID) {
// Find the user by id
Web().SiteGroups(parseInt(user.EntityData.SPGroupID)).execute(group => {
// Add the button
addButton(group);
});
} else if (user.EntityData.SPUserID) {
// Find the user by id
Web().getUserById(parseInt(user.EntityData.SPUserID)).execute(userInfo => {
// Add the button
addButton(userInfo);
});
} else {
// Find the user
Web().ensureUser(user.Key).execute(userInfo => {
// Add the button
addButton(userInfo);
}, addButton);
}
} else if (parseInt(user as any) > 0) {
// Find the user by id
Web().getUserById(user as any).execute(userInfo => {
// Add the button
addButton(userInfo);
});
} else if (typeof (user) === "string") {
// Find the user by email
Web().ensureUser(user).execute(userInfo => {
// Add the button
addButton(userInfo);
});
}
}
// Method to get the context of the site to search
let getContext = (): PromiseLike<string> => {
// Return a promise
return new Promise(resolve => {
// See if a url was provided
if (props.searchUrl) {
// Get the context of the site
ContextInfo.getWeb(props.searchUrl).execute(context => {
// Resolve the request
resolve(context.GetContextWebInformation.FormDigestValue);
}, () => {
// Do nothing on error
resolve(null);
});
} else {
// Do nothing
resolve(null);
}
});
}
// Method to search for the users
let searchUsers = (el: HTMLElement, searchText: string, searchAll: boolean = true, spGroupId?: number) => {
// Ensure 3 characters exist
if (_filterText.length >= _minCharSearch) {
// Get the context of the target site, if we are targeting one
getContext().then(requestDigest => {
// Search for the user
Search({ requestDigest, url: props.searchUrl }).clientPeoplePickerSearchUser({
MaximumEntitySuggestions: props.maxResults || 25,
PrincipalSource: searchAll ? SPTypes.PrincipalSources.All : SPTypes.PrincipalSources.UserInfoList,
PrincipalType: props.allowGroups ? SPTypes.PrincipalTypes.All : SPTypes.PrincipalTypes.User,
QueryString: _filterText,
SharePointGroupID: spGroupId
}).execute((search) => {
// Ensure the search text matches
if (_filterText != searchText) { return; }
// Clear the users results
_users = [];
// Set the menu header
el.innerHTML = '<h6 class="dropdown-header">Search Results for "' + searchText + '"</h6>';
el.innerHTML += '<div class="dropdown-divider"></div>';
// See if no users were found
if (search.ClientPeoplePickerSearchUser.length == 0) {
// Add a message
el.innerHTML += '<h6 class="dropdown-header">No results were found...</h6>';
} else {
// Parse the users
for (let i = 0; i < search.ClientPeoplePickerSearchUser.length; i++) {
let exists = false;
let user = search.ClientPeoplePickerSearchUser[i];
// Save the user
_users.push(user);
// Parse the selected users
for (let j = 0; j < elSelectedUsers.children.length; j++) {
let userInfo = JSON.parse(elSelectedUsers.children[j].getAttribute("data-user")) as Types.IPeoplePickerUser;
// See if this user is already selected
if (exists = user.Key == userInfo.Key) { break; }
}
// Ensure the user isn't already selected
if (exists) { continue; }
// Create the item
let elItem = document.createElement("a");
elItem.className = "dropdown-item";
elItem.href = "#";
elItem.innerHTML = user.DisplayText;
elItem.setAttribute("data-user", JSON.stringify(user));
el.appendChild(elItem);
// Create a tooltip for this item
Components.Tooltip({
target: elItem,
content: [
'<div class="text-white text-wrap text-break">',
'<small>' + user.EntityData.Email + '</small>',
'<br />',
'<small>' + user.Key + '</small>',
'</div>'
].join('\n'),
placement: Components.TooltipPlacements.Left,
type: Components.TooltipTypes.Primary
});
// Set the click event
elItem.addEventListener("click", ev => {
let userInfo = (ev.currentTarget as HTMLAnchorElement).getAttribute("data-user");
// Add the user
addUser(userInfo)
// Hide the menu
_menu.hide();
// Clear the search text
elTextbox.querySelector("input").value = "";
});
}
}
// Refresh the popover
_menu.hide();
_menu.show();
});
});
}
}
// Method to set the value
let setValue = (selectedUsers: Array<string | Types.IPeoplePickerUser> = []) => {
// Clear the selected users
elSelectedUsers.innerHTML = "";
// Parse the selected users
for (let i = 0; i < selectedUsers.length; i++) {
// Add the user
addUser(selectedUsers[i]);
}
}
// Create the people picker
let elPeoplePicker = document.createElement("div");
elPeoplePicker.className = "people-picker";
// Create the menu
let elMenu = document.createElement("div");
elMenu.className = "dropdown-menu border-0 mw-fit";
elMenu.innerHTML = '<h6 class="dropdown-header">Search requires 3+ characters</h6>';
// Add the selected users
let elSelectedUsers = document.createElement("div");
elSelectedUsers.style.position = "relative";
elPeoplePicker.appendChild(elSelectedUsers);
// Create the textbox
let elTextbox = Components.InputGroup({
placeholder: props.placeholder == null ? "Search" : props.placeholder,
onChange: searchText => {
let currentHTML = elMenu.innerHTML;
// See if a value exists
if (searchText) {
// Set the filter text
_filterText = searchText;
// Set the header
elMenu.innerHTML = ['<h6 class="dropdown-header">',
_filterText.length >= _minCharSearch ? 'Searching for "' + _filterText + '"' : `Search requires ${_minCharSearch}+ characters`,
'</h6>'
].join('\n');
// Wait 500ms before searching
setTimeout(() => {
// Ensure the filters match
if (searchText == _filterText) {
// Search for the users
searchUsers(elMenu, searchText, props.searchLocal ? false : true, props.groupId);
}
}, 500);
} else {
// Set the header
elMenu.innerHTML = '<h6 class="dropdown-header">Search requires 3+ characters</h6>';
}
// See if a refresh is required
if (currentHTML != elMenu.innerHTML) {
// Refresh the popover
_menu.hide();
_menu.show();
}
}
}).el;
props.disabled || props.readOnly ? elTextbox.classList.add("d-none") : null;
elPeoplePicker.appendChild(elTextbox);
// Create the popover menu
_menu = Components.Popover({
target: elTextbox.querySelector("input"),
placement: Components.PopoverPlacements.BottomStart,
options: {
hideOnClick: false,
maxWidth: "none",
trigger: "focus"
}
});
// Set the content
_menu.setContent(elMenu);
// Set the value and ensure it's a
let value: any = props.value || [];
if (typeof (props.value) != "object") {
// Set the default selected users
setValue([value]);
} else {
// See if this is a user object
let userValue = value as Types.IPeoplePickerUser;
if (userValue.EntityData) {
// Set the value
value = userValue.EntityData.SPGroupID || userValue.EntityData.SPUserID;
// Set the default selected users
setValue([value]);
}
// Else, see if the results exist
else if (value.results) {
let userIds = [];
// Parse the results
for (let i = 0; i < value.results.length; i++) {
// Add the user id
userIds.push(value.results[i].Id);
}
// Set the default selected users
setValue(userIds);
}
else {
// Set the default selected users
setValue(value);
}
}
// Create the element
let el = document.createElement("div");
el.appendChild(elPeoplePicker);
// See if we are rendering it to an element
if (props.el) {
// Ensure the parent element exists
if (props.el.parentElement && props.el.parentElement.classList) {
// Set the bootstrap class
props.el.parentElement.classList.contains("bs") ? null : props.el.parentElement.classList.add("bs");
}
// Append the elements
while (el.children.length > 0) {
props.el.appendChild(el.children[0]);
}
// Update the element
el = props.el as any;
} else {
// Set the bootstrap class
el.classList.add("bs");
}
// Create the object
let obj = {
el: elPeoplePicker,
getValue: () => {
let selectedUsers = [];
// Parse the selected users
for (let i = 0; i < elSelectedUsers.children.length; i++) {
let userInfo = JSON.parse(elSelectedUsers.children[i].getAttribute("data-user"));
let user = Helper.parse(userInfo) as Types.SP.User | Types.SP.Group;
// Add this user
selectedUsers.push(user);
}
// Return the value
return selectedUsers;
},
setValue
};
// Execute the assign to event
props.assignTo ? props.assignTo(obj) : null;
// Return the people picker object
return obj;
}
// Extend the form controls
export const PeoplePickerControlType = 101;
Components.FormControlTypes["PeoplePicker"] = PeoplePickerControlType;
Components.CustomControls.registerType(PeoplePickerControlType, (props: IFormControlPropsPeoplePicker) => {
let picker: IPeoplePicker = null;
// Set the created method
let onRendered = props.onControlRendered;
props.onControlRendered = ctrl => {
// Render a people picker
picker = PeoplePicker({
allowGroups: props.allowGroups,
assignTo: props.assignTo,
className: props.className,
disabled: props.isDisabled,
el: ctrl.el,
groupId: props.groupId,
label: props.label,
maxResults: props.maxResults,
multi: props.multi,
onChange: props.onChange,
placeholder: props.placeholder,
readOnly: props.isReadonly,
searchLocal: props.searchLocal,
searchUrl: props.searchUrl,
value: props.value
});
// See if the label exists
let elLabel: HTMLElement = ctrl["_elLabel"];
if (elLabel) {
// Set the id and aria properties
elLabel ? elLabel.id = (props.id || props.name) + "_label" : null;
picker.el.querySelector("input").setAttribute("aria-labelledby", elLabel.id);
}
// Set the control
ctrl.setControl(picker);
// Call the custom render event
onRendered ? onRendered(ctrl) : null;
}
// Register a people picker
props.onGetValue = (ctrl) => {
// Return the value
return picker ? picker.getValue() : ctrl.value;
};
});