From 3283f093decb485ffd0f4c5531e66cb3f5f2e104 Mon Sep 17 00:00:00 2001 From: Giuliano De Luca Date: Fri, 8 Dec 2017 12:59:41 +0100 Subject: [PATCH] Add events on people picked can now be used as a standalone component, minor bug fixes. (#382) --- samples/react-peoplepicker/README.md | 6 +- .../OfficeUiFabricPeoplePickerWebPart.ts | 4 +- .../IOfficeUiFabricPeoplePickerProps.ts | 4 +- .../OfficeUiFabricPeoplePicker.module.scss | 23 --- .../components/OfficeUiFabricPeoplePicker.tsx | 138 ++++++++++-------- .../components/PeoplePickerExampleData.ts | 2 +- .../models/OfficeUiFabricPeoplePicker.ts | 79 ++++++++++ 7 files changed, 165 insertions(+), 91 deletions(-) delete mode 100644 samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/OfficeUiFabricPeoplePicker.module.scss create mode 100644 samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/models/OfficeUiFabricPeoplePicker.ts diff --git a/samples/react-peoplepicker/README.md b/samples/react-peoplepicker/README.md index f5f6b054f..8157d9528 100644 --- a/samples/react-peoplepicker/README.md +++ b/samples/react-peoplepicker/README.md @@ -3,7 +3,7 @@ ## Summary SharePoint Framework solution with the Office UI Fabric People Picker, the client web part across the SharePoint Rest API is able to retrieve people and groups. -![React-People-Picker-gif](./assets/Preview.gif) +![React-People-Picker-gif](/assets/Preview.gif) ## Used SharePoint Framework Version ![drop](https://img.shields.io/badge/version-GA-green.svg) @@ -25,7 +25,7 @@ Version|Date|Comments -------|----|-------- 1.0.0|May 21, 2017|Initial release 1.0.1|Sep 28, 2017|Updated to GA Version, New properties that allow to specify the number of items to display and which entities retrieve (User, SharePoint Groups, Distribution Lists, Security Groups). -1.0.2|Oct 25, 2017|fixed a broken link on readme file. +1.0.2|Dec 06, 2017|Minor bug fixes, Add events on people picked can now be used as a standalone component (Thanks to [@MikeMyers](https://github.com/thespooler) for contributing. ## Disclaimer **THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** @@ -51,4 +51,4 @@ https://localhost:4321/temp/workbench.html If you want to try on a real environment, open: https://your-domain.sharepoint.com/_layouts/15/workbench.aspx - \ No newline at end of file + diff --git a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/OfficeUiFabricPeoplePickerWebPart.ts b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/OfficeUiFabricPeoplePickerWebPart.ts index e555481db..7f1506228 100644 --- a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/OfficeUiFabricPeoplePickerWebPart.ts +++ b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/OfficeUiFabricPeoplePickerWebPart.ts @@ -4,11 +4,9 @@ import { Version } from '@microsoft/sp-core-library'; import { BaseClientSideWebPart, IPropertyPaneConfiguration, - PropertyPaneTextField, PropertyPaneDropdown, PropertyPaneToggle, - PropertyPaneSlider, - IWebPartContext + PropertyPaneSlider } from '@microsoft/sp-webpart-base'; import * as strings from 'officeUiFabricPeoplePickerStrings'; diff --git a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/IOfficeUiFabricPeoplePickerProps.ts b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/IOfficeUiFabricPeoplePickerProps.ts index b650089a0..ae0ed2358 100644 --- a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/IOfficeUiFabricPeoplePickerProps.ts +++ b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/IOfficeUiFabricPeoplePickerProps.ts @@ -1,4 +1,5 @@ -import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http'; +import { SPHttpClient } from '@microsoft/sp-http'; +import { SharePointUserPersona } from '../models/OfficeUiFabricPeoplePicker'; export interface IOfficeUiFabricPeoplePickerProps { description: string; @@ -10,4 +11,5 @@ export interface IOfficeUiFabricPeoplePickerProps { principalTypeSecurityGroup: boolean; principalTypeDistributionList: boolean; numberOfItems: number; + onChange?: (items: SharePointUserPersona[]) => void; } diff --git a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/OfficeUiFabricPeoplePicker.module.scss b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/OfficeUiFabricPeoplePicker.module.scss deleted file mode 100644 index cf4058daa..000000000 --- a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/OfficeUiFabricPeoplePicker.module.scss +++ /dev/null @@ -1,23 +0,0 @@ -.helloWorld { - - .container { - max-width: 700px; - margin: 0px auto; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); - } - - .row { - padding: 20px; - } - - .listItem { - max-width: 715px; - margin: 5px auto 5px auto; - box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); - } - - .button { - text-decoration: none; - } - -} \ No newline at end of file diff --git a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/OfficeUiFabricPeoplePicker.tsx b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/OfficeUiFabricPeoplePicker.tsx index a32583b13..8793255ab 100644 --- a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/OfficeUiFabricPeoplePicker.tsx +++ b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/OfficeUiFabricPeoplePicker.tsx @@ -1,40 +1,41 @@ import * as React from 'react'; -import { css } from 'office-ui-fabric-react'; -import styles from './OfficeUiFabricPeoplePicker.module.scss'; import { IOfficeUiFabricPeoplePickerProps } from './IOfficeUiFabricPeoplePickerProps'; - import { CompactPeoplePicker, IBasePickerSuggestionsProps, - ListPeoplePicker, NormalPeoplePicker } from 'office-ui-fabric-react/lib/Pickers'; import { IPersonaProps } from 'office-ui-fabric-react/lib/Persona'; +import { + assign, + autobind +} from 'office-ui-fabric-react/lib/Utilities'; +import { people } from './PeoplePickerExampleData'; +import { IContextualMenuItem } from 'office-ui-fabric-react/lib/ContextualMenu'; +import { + SPHttpClient, + SPHttpClientBatch, + SPHttpClientResponse } from '@microsoft/sp-http'; +import { + Environment, + EnvironmentType +} from '@microsoft/sp-core-library'; +import { Promise } from 'es6-promise'; +import * as lodash from 'lodash'; +import { + IClientPeoplePickerSearchUser, + IEnsurableSharePointUser, + IEnsureUser, + IOfficeUiFabricPeoplePickerState, + SharePointUserPersona } from '../models/OfficeUiFabricPeoplePicker'; +import { IPersonaWithMenu } from 'office-ui-fabric-react/lib/components/pickers/PeoplePicker/PeoplePickerItems/PeoplePickerItem.Props'; + const suggestionProps: IBasePickerSuggestionsProps = { suggestionsHeaderText: 'Suggested People', noResultsFoundText: 'No results found', loadingText: 'Loading' }; -import { - BaseComponent, - assign, - autobind -} from 'office-ui-fabric-react/lib//Utilities'; -import { people } from './PeoplePickerExampleData'; -import { Label } from 'office-ui-fabric-react/lib/Label'; -import { IPersonaWithMenu } from 'office-ui-fabric-react/lib/components/pickers/PeoplePicker/PeoplePickerItems/PeoplePickerItem.Props'; -import { IContextualMenuItem } from 'office-ui-fabric-react/lib/ContextualMenu'; -import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http'; -export interface IOfficeUiFabricPeoplePickerState { - currentPicker?: number | string; - delayResults?: boolean; -} -export interface IPeopleSearchProps { - JobTitle: string; - PictureURL: string; - PreferredName: string; -} export default class OfficeUiFabricPeoplePicker extends React.Component { private _peopleList; private contextualMenuItems: IContextualMenuItem[] = [ @@ -78,16 +79,18 @@ export default class OfficeUiFabricPeoplePicker extends React.Component { if (this.props.typePicker == "Normal") { return ( persona.primaryText} pickerSuggestionsProps={suggestionProps} className={'ms-PeoplePicker'} @@ -97,7 +100,8 @@ export default class OfficeUiFabricPeoplePicker extends React.Component persona.primaryText} pickerSuggestionsProps={suggestionProps} className={'ms-PeoplePicker'} @@ -107,11 +111,21 @@ export default class OfficeUiFabricPeoplePicker extends React.Component 2) { - return this._searchPeople(filterText, this._peopleList); + return this._searchPeople(filterText, this._peopleList); } } else { return []; @@ -164,8 +178,8 @@ export default class OfficeUiFabricPeoplePicker extends React.Component { - //return new Promise((resolve, reject) => setTimeout(() => resolve(results), 2000)); - if (this.props.siteUrl.toLowerCase().indexOf("wwww.contoso.com") >= 0) { + + if (DEBUG && Environment.type === EnvironmentType.Local) { // If the running environment is local, load the data from the mock return this.searchPeopleFromMock(); } else { @@ -183,7 +197,7 @@ export default class OfficeUiFabricPeoplePicker extends React.Component((resolve, reject) => + return new Promise((resolve, reject) => this.props.spHttpClient.post(userRequestUrl, - SPHttpClient.configurations.v1, - { - headers: { - 'Accept': 'application/json', - "content-type": "application/json" - }, - body: JSON.stringify(data) - }) + SPHttpClient.configurations.v1, { body: JSON.stringify(userQueryParams) }) .then((response: SPHttpClientResponse) => { return response.json(); }) - .then((response: any): void => { - let relevantResults: any = JSON.parse(response.value); - let resultCount: number = relevantResults.length; - let people = []; - let persona: IPersonaProps = {}; - if (resultCount > 0) { - for (var index = 0; index < resultCount; index++) { - var p = relevantResults[index]; - let account = p.Key.substr(p.Key.lastIndexOf('|') + 1); + .then((response: {value: string}) => { + let userQueryResults: IClientPeoplePickerSearchUser[] = JSON.parse(response.value); + let persons = userQueryResults.map(p => new SharePointUserPersona(p as IEnsurableSharePointUser)); + return persons; + }) + .then((persons) => { + const batch = this.props.spHttpClient.beginBatch(); + const ensureUserUrl = `${this.props.siteUrl}/_api/web/ensureUser`; + const batchPromises: Promise[] = persons.map(p => { + var userQuery = JSON.stringify({logonName: p.User.Key}); + return batch.post(ensureUserUrl, SPHttpClientBatch.configurations.v1, { + body: userQuery + }) + .then((response: SPHttpClientResponse) => response.json()) + .then((json: IEnsureUser) => json); + }); - persona.primaryText = p.DisplayText; - persona.imageUrl = `/_layouts/15/userphoto.aspx?size=S&accountname=${account}`; - persona.imageShouldFadeIn = true; - persona.secondaryText = p.EntityData.Title; - people.push(persona); - } - } - resolve(people); + var users = batch.execute().then(() => Promise.all(batchPromises).then(values => { + values.forEach(v => { + let userPersona = lodash.find(persons, o => o.User.Key == v.LoginName); + if (userPersona && userPersona.User) + { + let user = userPersona.User; + lodash.assign(user, v); + userPersona.User = user; + } + }); + + resolve(persons); + })); }, (error: any): void => { reject(this._peopleList = []); - }) - ); - }; + })); + } } private _filterPersonasByText(filterText: string): IPersonaProps[] { diff --git a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/PeoplePickerExampleData.ts b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/PeoplePickerExampleData.ts index 10f144f93..bad9eb901 100644 --- a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/PeoplePickerExampleData.ts +++ b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/components/PeoplePickerExampleData.ts @@ -33,4 +33,4 @@ export const people: IPersonaProps[] = [ tertiaryText: 'In a meeting', optionalText: 'Available at 4:00pm' }, -]; \ No newline at end of file +]; diff --git a/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/models/OfficeUiFabricPeoplePicker.ts b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/models/OfficeUiFabricPeoplePicker.ts new file mode 100644 index 000000000..568c7480d --- /dev/null +++ b/samples/react-peoplepicker/src/webparts/officeUiFabricPeoplePicker/models/OfficeUiFabricPeoplePicker.ts @@ -0,0 +1,79 @@ +import { IPersonaProps, IPersona } from "office-ui-fabric-react"; + +export interface IOfficeUiFabricPeoplePickerState { + currentPicker?: number | string; + delayResults?: boolean; + selectedItems: any[]; +} +export interface IPeopleSearchProps { + JobTitle: string; + PictureURL: string; + PreferredName: string; +} + +export interface IUserEntityData { + IsAltSecIdPresent: string; + ObjectId: string; + Title: string; + Email: string; + MobilePhone: string; + OtherMails: string; + Department: string; +} + +export interface IClientPeoplePickerSearchUser { + Key: string; + Description: string; + DisplayText: string; + EntityType: string; + ProviderDisplayName: string; + ProviderName: string; + IsResolved: boolean; + EntityData: IUserEntityData; + MultipleMatches: any[]; +} + +export interface IEnsureUser { + Email: string; + Id: number; + IsEmailAuthenticationGuestUser: boolean; + IsHiddenInUI: boolean; + IsShareByEmailGuestUser: boolean; + IsSiteAdmin: boolean; + LoginName: string; + PrincipalType: number; + Title: string; + UserId: { + NameId: string; + NameIdIssuer: string; + }; +} + +export interface IEnsurableSharePointUser + extends IClientPeoplePickerSearchUser, IEnsureUser {} + +export class SharePointUserPersona implements IPersona { + private _user:IEnsurableSharePointUser; + public get User(): IEnsurableSharePointUser { + return this._user; + } + + public set User(user: IEnsurableSharePointUser) { + this._user = user; + this.primaryText = user.Title; + this.secondaryText = user.EntityData.Title; + this.tertiaryText = user.EntityData.Department; + this.imageShouldFadeIn = true; + this.imageUrl = `/_layouts/15/userphoto.aspx?size=S&accountname=${this.User.Key.substr(this.User.Key.lastIndexOf('|') + 1)}`; + } + + constructor (user: IEnsurableSharePointUser) { + this.User = user; + } + + public primaryText: string; + public secondaryText: string; + public tertiaryText: string; + public imageUrl: string; + public imageShouldFadeIn: boolean; +}