Skip to content

Commit b97fc24

Browse files
Update the PeoplePicker control:
- optimize context prop by requesting only necessary properties from BaseComponentContext - update documentation accordingly - move the state properties into the core component
1 parent e0a64ee commit b97fc24

File tree

9 files changed

+103
-49
lines changed

9 files changed

+103
-49
lines changed

docs/documentation/docs/controls/PeoplePicker.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,26 @@ This control renders a People picker field which can be used to select one or mo
1717

1818
![Selected people](../assets/Peoplepicker-multiplechoices.png)
1919

20-
2120
## How to use this control in your solutions
2221

2322
- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../../#getting-started) page for more information about installing the dependency.
2423
- Import the following modules to your component:
2524

2625
```typescript
27-
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
26+
import { IPeoplePickerContext, PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
2827
```
2928

3029
- Use the `PeoplePicker` control in your code as follows:
3130

3231
```typescript
32+
const peoplePickerContext: IPeoplePickerContext = {
33+
absoluteUrl: this.props.context.pageContext.web.absoluteUrl,
34+
msGraphClientFactory: this.props.context.msGraphClientFactory,
35+
spHttpClient: this.props.context.spHttpClient
36+
};
37+
3338
<PeoplePicker
34-
context={this.props.context}
39+
context={peoplePickerContext}
3540
titleText="People Picker"
3641
personSelectionLimit={3}
3742
groupName={"Team Site Owners"} // Leave this blank in case you want to filter from all users
@@ -59,7 +64,7 @@ The People picker control can be configured with the following properties:
5964

6065
| Property | Type | Required | Description | Default |
6166
| ---- | ---- | ---- | ---- | ---- |
62-
| context | BaseComponentContext | yes | Context of the current web part. | |
67+
| context | IPeoplePickerContext | yes | Context of the component, based on the SPFx context ([*BaseComponentContext*](https://learn.microsoft.com/javascript/api/sp-component-base/basecomponentcontext?view=sp-typescript-latest)). | |
6368
| titleText | string | no | Text to be displayed on the control | |
6469
| groupName | string | no | Group from which users are fetched. Leave it blank if need to filter all users. When both groupName and groupId specified groupName takes precedence. | _none_ |
6570
| groupId | number \| string \| (string\|number)[] | no | Group from which users are fetched. Leave it blank if need to filter all users. When both groupId and groupName specified groupName takes precedence. If string is specified, Microsoft 365 Group is used. If array is used, fetch results from multiple groups | _none_ |
@@ -82,8 +87,8 @@ The People picker control can be configured with the following properties:
8287
| allowUnvalidated | boolean | no | When true, allow email addresses that have not been validated to be entered, effectively allowing any user. | false |
8388
| suggestionsLimit | number | no | Maximum number of suggestions to show in the full suggestion list. | 5 |
8489
| resolveDelay | number | no | Add delay to resolve and search users | 200 |
85-
| placeholder | string | no | Short text hint to display in empty picker |
86-
| styles | Partial<IBasePickerStyles> | no | Styles to apply on control |
90+
| placeholder | string | no | Short text hint to display in empty picker | |
91+
| styles | Partial<IBasePickerStyles> | no | Styles to apply on control | |
8792
| searchTextLimit | number | no | Specifies the minimum character count needed to begin retrieving search results. | 2 |
8893

8994
Enum `PrincipalType`
@@ -97,13 +102,21 @@ The `PrincipalType` enum can be used to specify the types of information you wan
97102
| SecurityGroup | 4 |
98103
| SharePointGroup | 8 |
99104

105+
Interface `IPeoplePickerContext`
100106

101-
## MSGraph Permissions required
107+
Provides mandatory properties to search users on the tenant
102108

103-
This control requires the following scopes if groupId is of type String:
109+
| Value | Type | Description |
110+
| ---- | ---- | ---- |
111+
| absoluteUrl | string | Current `SPWeb` absolute URL. |
112+
| msGraphClientFactory | MSGraphClientFactory | Instance of MSGraphClientFactory used for querying Microsoft Graph REST API. |
113+
| spHttpClient | SPHttpClient | Instance of SPHttpClient used for querying SharePoint REST API. |
104114

105-
at least : GroupMember.Read.All, Directory.Read.All
115+
## MSGraph Permissions required
106116

117+
This control requires at least one the following scopes if `groupId` is of type `string`:
107118

108-
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/PeoplePicker)
119+
- *GroupMember.Read.All* + *User.ReadBasic.All*
120+
- *Directory.Read.All*
109121

122+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/PeoplePicker)

src/controls/dynamicForm/dynamicField/DynamicField.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { DateTimePicker } from '../../dateTimePicker/DateTimePicker';
1616
import { FilePicker, IFilePickerResult } from '../../filePicker';
1717
import { ListItemPicker } from '../../listItemPicker';
1818
import { LocationPicker } from '../../locationPicker';
19-
import { PeoplePicker, PrincipalType } from '../../peoplepicker';
19+
import { IPeoplePickerContext, PeoplePicker, PrincipalType } from '../../peoplepicker';
2020
import { RichText } from '../../richText';
2121
import { IPickerTerms, TaxonomyPicker } from '../../taxonomyPicker';
2222
import styles from '../DynamicForm.module.scss';
@@ -93,6 +93,12 @@ export class DynamicField extends React.Component<IDynamicFieldProps, IDynamicFi
9393
placeholder: placeholder
9494
};
9595

96+
const peoplePickerContext: IPeoplePickerContext = {
97+
absoluteUrl: context.pageContext.web.absoluteUrl,
98+
msGraphClientFactory: context.msGraphClientFactory,
99+
spHttpClient: context.spHttpClient
100+
};
101+
96102
// const defaultValue = fieldDefaultValue;
97103

98104
const labelEl = <label className={(required) ? styles.fieldRequired + ' ' + styles.fieldLabel : styles.fieldLabel}>{label}</label>;
@@ -377,7 +383,7 @@ export class DynamicField extends React.Component<IDynamicFieldProps, IDynamicFi
377383
placeholder={placeholder}
378384
defaultSelectedUsers={userValue}
379385
peoplePickerCntrlclassName={styles.fieldDisplay}
380-
context={context}
386+
context={peoplePickerContext}
381387
personSelectionLimit={1}
382388
showtooltip={false}
383389
showHiddenInUI={false}
@@ -401,7 +407,7 @@ export class DynamicField extends React.Component<IDynamicFieldProps, IDynamicFi
401407
placeholder={placeholder}
402408
defaultSelectedUsers={valueToDisplay !== undefined ? valueToDisplay : defaultValue}
403409
peoplePickerCntrlclassName={styles.fieldDisplay}
404-
context={context}
410+
context={peoplePickerContext}
405411
personSelectionLimit={30}
406412
showtooltip={false}
407413
showHiddenInUI={false}

src/controls/fieldCollectionData/collectionDataItem/CollectionDataItem.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as strings from 'ControlStrings';
1010
import { CustomCollectionFieldType, ICustomCollectionField } from '../ICustomCollectionField';
1111
import { Dropdown, IDropdownOption } from '@fluentui/react/lib/components/Dropdown';
1212
import { ComboBox, IComboBoxOption } from '@fluentui/react/lib/components/ComboBox';
13-
import { PeoplePicker, PrincipalType } from "../../peoplepicker";
13+
import { IPeoplePickerContext, PeoplePicker, PrincipalType } from "../../peoplepicker";
1414
import { Callout, DirectionalHint } from '@fluentui/react/lib/components/Callout';
1515
import { CollectionIconField } from '../collectionIconField';
1616
import { clone, findIndex, sortBy } from '@microsoft/sp-lodash-subset';
@@ -467,6 +467,11 @@ export class CollectionDataItem extends React.Component<ICollectionDataItemProps
467467
private renderField(field: ICustomCollectionField, item: any): JSX.Element { // eslint-disable-line @typescript-eslint/no-explicit-any
468468
const disableFieldOnEdit: boolean = field.disableEdit && !!this.props.fUpdateItem;
469469
const _selectedComboBoxKeys: string[] = [];
470+
const peoplePickerContext: IPeoplePickerContext = {
471+
absoluteUrl: this.props.context.pageContext.web.absoluteUrl,
472+
spHttpClient: this.props.context.spHttpClient,
473+
msGraphClientFactory: this.props.context.msGraphClientFactory
474+
};
470475
let _selectedComboBoxKey: string = null;
471476
let _comboBoxOptions: IComboBoxOption[] = null;
472477
let _selectedUsers: string[] = null;
@@ -580,7 +585,7 @@ export class CollectionDataItem extends React.Component<ICollectionDataItemProps
580585

581586
return <PeoplePicker
582587
peoplePickerCntrlclassName={styles.peoplePicker}
583-
context={this.props.context}
588+
context={peoplePickerContext}
584589
personSelectionLimit={typeof field.maximumUsers === "number" ? field.maximumUsers : typeof field.multiSelect === "boolean" && field.multiSelect === false ? 1 : 99}
585590
principalTypes={[PrincipalType.User]}
586591
ensureUser={true}

src/controls/peoplepicker/IPeoplePicker.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { BaseComponentContext } from '@microsoft/sp-component-base';
21
import { IBasePickerStyles } from "@fluentui/react";
32
import { DirectionalHint } from "@fluentui/react/lib/common/DirectionalHint";
43
import { IPersonaProps } from "@fluentui/react/lib/components/Persona/Persona.types";
5-
import { PrincipalType } from ".";
4+
import { IPeoplePickerContext, PrincipalType } from ".";
65

76
/**
87
* Used to display a placeholder in case of no or temporary content. Button is optional.
@@ -12,7 +11,7 @@ export interface IPeoplePickerProps {
1211
/**
1312
* Context of the component
1413
*/
15-
context: BaseComponentContext;
14+
context: IPeoplePickerContext;
1615
/**
1716
* Text of the Control
1817
*/
@@ -139,17 +138,6 @@ export interface IPeoplePickerProps {
139138
resultFilter?: (result: IPersonaProps[]) => IPersonaProps[];
140139
}
141140

142-
export interface IPeoplePickerState {
143-
mostRecentlyUsedPersons?: IPersonaProps[];
144-
errorMessage?: string;
145-
internalErrorMessage?: string;
146-
resolveDelay?: number;
147-
148-
selectedPersons?: IPersonaProps[];
149-
peoplePersonaMenu?: IPersonaProps[];
150-
delayResults?: boolean;
151-
}
152-
153141
export interface IPeoplePickerUserItem {
154142
/**
155143
* LoginName or Id of the principal in the site.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { MSGraphClientFactory, SPHttpClient } from "@microsoft/sp-http";
2+
3+
/**
4+
* Context for the PeoplePicker control
5+
*/
6+
export interface IPeoplePickerContext {
7+
/**
8+
* Current `SPWeb` absolute URL.
9+
*/
10+
absoluteUrl: string;
11+
/**
12+
* Instance of MSGraphClientFactory used for querying Microsoft Graph REST API.
13+
*/
14+
msGraphClientFactory: MSGraphClientFactory;
15+
/**
16+
* Instance of SPHttpClient used for querying SharePoint REST API.
17+
*/
18+
spHttpClient: SPHttpClient;
19+
}

src/controls/peoplepicker/PeoplePickerComponent.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as React from 'react';
33
import * as telemetry from '../../common/telemetry';
44
import styles from './PeoplePickerComponent.module.scss';
55
import SPPeopleSearchService from "../../services/PeopleSearchService";
6-
import { IPeoplePickerProps, IPeoplePickerState } from './IPeoplePicker';
6+
import { IPeoplePickerProps } from './IPeoplePicker';
77
import { TooltipHost } from '@fluentui/react/lib/Tooltip';
88
import { DirectionalHint } from '@fluentui/react/lib/Callout';
99
import { NormalPeoplePicker } from '@fluentui/react/lib/components/pickers/PeoplePicker/PeoplePicker';
@@ -14,6 +14,17 @@ import FieldErrorMessage from '../errorMessage/ErrorMessage';
1414
import isEqual from 'lodash/isEqual';
1515
import uniqBy from 'lodash/uniqBy';
1616

17+
interface IPeoplePickerState {
18+
mostRecentlyUsedPersons?: IPersonaProps[];
19+
errorMessage?: string;
20+
internalErrorMessage?: string;
21+
resolveDelay?: number;
22+
23+
selectedPersons?: IPersonaProps[];
24+
peoplePersonaMenu?: IPersonaProps[];
25+
delayResults?: boolean;
26+
}
27+
1728
/**
1829
* PeoplePicker component
1930
*/

src/controls/peoplepicker/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './IPeoplePicker';
2+
export * from './IPeoplePickerContext';
23
export * from './PeoplePickerComponent';
34
export * from './PrincipalType';

src/services/PeopleSearchService.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { BaseComponentContext } from '@microsoft/sp-component-base';
21
import { ISPHttpClientOptions, SPHttpClient } from '@microsoft/sp-http';
32
import { findIndex } from "@microsoft/sp-lodash-subset";
43
import { sp } from '@pnp/sp';
@@ -7,7 +6,7 @@ import "@pnp/sp/sputilities";
76
import "@pnp/sp/webs";
87
import { Web } from "@pnp/sp/webs";
98
import { IUserInfo } from "../controls/peoplepicker/IUsers";
10-
import { IPeoplePickerUserItem, PrincipalType } from "../PeoplePicker";
9+
import { IPeoplePickerContext, IPeoplePickerUserItem, PrincipalType } from "../PeoplePicker";
1110

1211
/**
1312
* Service implementation to search people in SharePoint
@@ -19,12 +18,16 @@ export default class SPPeopleSearchService {
1918
/**
2019
* Service constructor
2120
*/
22-
constructor(private context: BaseComponentContext) {
21+
constructor(private context: IPeoplePickerContext) {
2322
this.cachedPersonas = {};
2423
this.cachedLocalUsers = {};
25-
this.cachedLocalUsers[this.context.pageContext.web.absoluteUrl] = [];
24+
this.cachedLocalUsers[context.absoluteUrl] = [];
2625
// Setup PnPjs
27-
sp.setup({ pageContext: this.context.pageContext });
26+
sp.setup({ pageContext: {
27+
web: {
28+
absoluteUrl: context.absoluteUrl
29+
}
30+
}});
2831
}
2932

3033
/**
@@ -33,7 +36,7 @@ export default class SPPeopleSearchService {
3336
* @param value
3437
*/
3538
public generateUserPhotoLink(value: string): string {
36-
return `${this.context.pageContext.web.absoluteUrl}/_layouts/15/userphoto.aspx?accountname=${encodeURIComponent(value)}&size=M`;
39+
return `${this.context.absoluteUrl}/_layouts/15/userphoto.aspx?accountname=${encodeURIComponent(value)}&size=M`;
3740
}
3841

3942
/**
@@ -109,7 +112,7 @@ export default class SPPeopleSearchService {
109112
private async searchTenant(siteUrl: string, query: string, maximumSuggestions: number, principalTypes: PrincipalType[], ensureUser: boolean, allowUnvalidated: boolean, groupId: number | string): Promise<IPeoplePickerUserItem[]> {
110113
try {
111114
// If the running env is SharePoint, loads from the peoplepicker web service
112-
const userRequestUrl: string = `${siteUrl || this.context.pageContext.web.absoluteUrl}/_api/SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.clientPeoplePickerSearchUser`;
115+
const userRequestUrl: string = `${siteUrl || this.context.absoluteUrl}/_api/SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.clientPeoplePickerSearchUser`;
113116
// eslint-disable-next-line @typescript-eslint/no-explicit-any
114117
const searchBody: any = {
115118
queryParams: {
@@ -143,7 +146,7 @@ export default class SPPeopleSearchService {
143146

144147
// Get user loginName from user email
145148
const _users = [];
146-
const batch = Web(this.context.pageContext.web.absoluteUrl).createBatch();
149+
const batch = Web(this.context.absoluteUrl).createBatch();
147150
for (const value of graphUserResponse.value) {
148151
sp.web.inBatch(batch).ensureUser(value.userPrincipalName).then(u => _users.push(u.data)).catch(() => {
149152
// no-op
@@ -203,7 +206,7 @@ export default class SPPeopleSearchService {
203206
for (const value of values) {
204207
// Only ensure the user if it is not a SharePoint group
205208
if (!value.EntityData || (value.EntityData && typeof value.EntityData.SPGroupID === "undefined" && value.EntityData.PrincipalType !== "UNVALIDATED_EMAIL_ADDRESS")) {
206-
const id = await this.ensureUser(value.Key, siteUrl || this.context.pageContext.web.absoluteUrl);
209+
const id = await this.ensureUser(value.Key, siteUrl || this.context.absoluteUrl);
207210
value.LoginName = value.Key;
208211
value.Key = id;
209212
}

0 commit comments

Comments
 (0)