Skip to content

Commit bbbe36f

Browse files
committed
fixed people picker
1 parent 41370c5 commit bbbe36f

File tree

7 files changed

+91
-82
lines changed

7 files changed

+91
-82
lines changed

docs/documentation/docs/guides/migrate-from-v1.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Migrating from v1 to v2
2-
Most of the controls have no breaking changes when moving from v1 to v2. However, some APIs were changed to make the libary more stable and controls behavior more even.
2+
Most of the controls have no breaking changes when moving from v1 to v2. However, some APIs were changed to make the library more stable and controls behavior more even.
33
Also, we've bumped up React and Fluent UI versions used in the library. It means you will need to update `package.json` file in your SPFx projects.
44
The below guide is an overview of what it takes to migrate from v1 to v2. If we missed something, please let us know in the issues list so we can update the guide. Thanks!
55

66
## v2 Supports SharePoint Online Only
7-
v2 of Reusable Controls is based on SharePoint Framework 1.11 and, as a result, does not support SharePoint On-Premise. **Please, use v1 if you plan to deploy your soluition on-prem.**
7+
v2 of Reusable Controls is based on SharePoint Framework 1.11 and, as a result, does not support SharePoint On-Premise. **Please, use v1 if you plan to deploy your solution on-prem.**
88

99
## React and Fluent UI versions
1010
v2 of Reusable Controls uses React.js v16.8.5 and Fluent UI (Office UI Fabric React) v6.189.2.
@@ -28,9 +28,9 @@ Please, update the `package.json` accordingly:
2828
`isRequred` is renamed to `required`.<br/>
2929
The property has been renamed to use the common approach for mandatory field naming.
3030

31-
`errorMessage` represents static error message to be displayed in the control. <br />
31+
`errorMessage` represents a static error message to be displayed in the control. <br />
3232
In v1 `errorMessage` is used to provide the text that will be displayed if the field is set as `required` and no value is selected.
33-
In v2 you can use this property to statically display error message for whatever reason.
33+
In v2 you can use this property to display an error message for whatever reason statically.
3434
```typescript
3535
/**
3636
* Static error message displayed below the text field. Use onGetErrorMessage to dynamically change the error message displayed (if any) based on the current value. errorMessage and onGetErrorMessage are mutually exclusive (errorMessage takes precedence).
@@ -42,7 +42,7 @@ In v2 you can use this property to statically display error message for whatever
4242
The method is used to get the validation error message and determine whether the input value is valid or not. Mutually exclusive with the static string errorMessage (it will take precedence over this).
4343
```typescript
4444
/**
45-
* The method is used to get the validation error message and determine whether the picker value is valid or not.
45+
* The method is used to get the validation error message and determine whether the picker value is valid.
4646
* Mutually exclusive with the static string errorMessage (it will take precedence over this).
4747
*
4848
* When it returns string:

src/controls/errorMessage/ErrorMessage.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,24 @@ import { Icon } from 'office-ui-fabric-react/lib/Icon';
44

55
export interface IFieldErrorMessageProps {
66
errorMessage: string;
7+
className?: string;
78
}
89

910
/**
1011
* Component that shows an error message when something went wront with the property control
1112
*/
1213
export default class FieldErrorMessage extends React.Component<IFieldErrorMessageProps> {
1314
public render(): JSX.Element {
14-
if (this.props.errorMessage !== 'undefined' && this.props.errorMessage !== null && this.props.errorMessage !== '') {
15+
const {
16+
errorMessage,
17+
className
18+
} = this.props;
19+
if (errorMessage !== undefined && errorMessage !== null && errorMessage !== '') {
1520
return (
1621
<div aria-live="assertive">
17-
<p className={`ms-TextField-errorMessage ${styles.errorMessage}`}>
22+
<p className={`ms-TextField-errorMessage ${styles.errorMessage} ${className || ''}`}>
1823
<Icon iconName='Error' className={styles.errorIcon} />
19-
<span data-automation-id="error-message">{this.props.errorMessage}</span>
24+
<span data-automation-id="error-message">{errorMessage}</span>
2025
</p>
2126
</div>
2227
);

src/controls/peoplepicker/IPeoplePicker.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ export interface IPeoplePickerProps {
116116
}
117117

118118
export interface IPeoplePickerState {
119-
mostRecentlyUsedPersons: IPersonaProps[];
120-
showRequiredError: boolean;
121-
errorMessage: string;
122-
resolveDelay : number;
119+
mostRecentlyUsedPersons?: IPersonaProps[];
120+
errorMessage?: string;
121+
internalErrorMessage?: string;
122+
resolveDelay?: number;
123123

124124
selectedPersons?: IPersonaProps[];
125125
peoplePersonaMenu?: IPersonaProps[];

src/controls/peoplepicker/PeoplePickerComponent.tsx

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Label } from 'office-ui-fabric-react/lib/components/Label';
1010
import { IBasePickerSuggestionsProps } from "office-ui-fabric-react/lib/components/pickers/BasePicker.types";
1111
import { IPersonaProps } from "office-ui-fabric-react/lib/components/Persona/Persona.types";
1212
import { Icon } from "office-ui-fabric-react/lib/components/Icon";
13+
import FieldErrorMessage from '../errorMessage/ErrorMessage';
1314
import isEqual from 'lodash/isEqual';
1415
import uniqBy from 'lodash/uniqBy';
1516

@@ -36,9 +37,8 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
3637
this.state = {
3738
selectedPersons: [],
3839
mostRecentlyUsedPersons: [],
39-
showRequiredError: false,
4040
resolveDelay: this.props.resolveDelay || 200,
41-
errorMessage: null
41+
errorMessage: props.errorMessage
4242
};
4343
}
4444

@@ -64,9 +64,9 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
6464
}
6565

6666
public componentWillReceiveProps(nextProps: IPeoplePickerProps) {
67-
if (this.props.showRequiredError !== nextProps.showRequiredError && nextProps.showRequiredError) {
67+
if (nextProps.errorMessage) {
6868
this.setState({
69-
showRequiredError: !this.state.selectedPersons || !this.state.selectedPersons.length
69+
errorMessage: nextProps.errorMessage
7070
});
7171
}
7272
}
@@ -82,7 +82,7 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
8282
this.groupId = await this.peopleSearchService.getGroupId(groupName, webAbsoluteUrl);
8383
if (!this.groupId) {
8484
this.setState({
85-
errorMessage: strings.PeoplePickerGroupNotFound
85+
internalErrorMessage: strings.PeoplePickerGroupNotFound
8686
});
8787
return;
8888
}
@@ -101,7 +101,8 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
101101
}
102102

103103
this.setState({
104-
selectedPersons
104+
selectedPersons,
105+
internalErrorMessage: undefined
105106
});
106107
}
107108
}
@@ -132,16 +133,12 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
132133
* On item selection change event
133134
*/
134135
private onChange = (items: IPersonaProps[]): void => {
135-
const { selectedItems: triggerUpdate } = this.props;
136136

137137
this.setState({
138-
selectedPersons: items,
139-
showRequiredError: items.length > 0 ? false : true
138+
selectedPersons: items
140139
});
141140

142-
if (triggerUpdate) {
143-
triggerUpdate(items);
144-
}
141+
this.validate(items);
145142
}
146143

147144

@@ -166,6 +163,55 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
166163
return personas.filter(persona => !this.listContainsPersona(persona, possibleDupes));
167164
}
168165

166+
private validate = async (items: IPersonaProps[]): Promise<void> => {
167+
168+
if (this.props.errorMessage || !this.props.onGetErrorMessage) { // ignoring all onGetErrorMessage logic
169+
this.validated(items);
170+
return;
171+
}
172+
173+
const result: string | PromiseLike<string> = this.props.onGetErrorMessage(items || []);
174+
175+
if (!result) {
176+
this.validated(items);
177+
return;
178+
}
179+
180+
if (typeof result === 'string') {
181+
if (!result) {
182+
this.validated(items);
183+
}
184+
else {
185+
this.setState({
186+
errorMessage: result
187+
});
188+
}
189+
}
190+
else {
191+
try {
192+
const resolvedResult = await result;
193+
194+
if (!resolvedResult) {
195+
this.validated(items);
196+
}
197+
else {
198+
this.setState({
199+
errorMessage: resolvedResult
200+
});
201+
}
202+
}
203+
catch (err) {
204+
this.validated(items);
205+
}
206+
}
207+
}
208+
209+
private validated = (value: IPersonaProps[]): void => {
210+
if (this.props.onChange) {
211+
this.props.onChange(value);
212+
}
213+
}
214+
169215

170216
/**
171217
* Checks if list contains the person
@@ -189,7 +235,7 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
189235
const {
190236
peoplePickerCntrlclassName,
191237
peoplePickerWPclassName,
192-
isRequired,
238+
required,
193239
titleText,
194240
suggestionsLimit,
195241
placeholder,
@@ -198,15 +244,14 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
198244
showtooltip,
199245
tooltipMessage,
200246
tooltipDirectional,
201-
errorMessageClassName,
202-
errorMessage
247+
errorMessageClassName
203248
} = this.props;
204249

205250
const {
206251
selectedPersons,
207252
resolveDelay,
208-
errorMessage: stateErrorMessage,
209-
showRequiredError
253+
errorMessage,
254+
internalErrorMessage
210255
} = this.state;
211256

212257
const suggestionProps: IBasePickerSuggestionsProps = {
@@ -220,7 +265,7 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
220265

221266
const peoplepicker = (
222267
<div id="people" className={`${styles.defaultClass} ${peoplePickerWPclassName ? peoplePickerWPclassName : ''}`}>
223-
{titleText && <Label required={isRequired}>{titleText}</Label>}
268+
{titleText && <Label required={required}>{titleText}</Label>}
224269

225270
<NormalPeoplePicker pickerSuggestionsProps={suggestionProps}
226271
onResolveSuggestions={this.onSearchFieldChanged}
@@ -235,7 +280,7 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
235280
}}
236281
selectedItems={selectedPersons}
237282
itemLimit={personSelectionLimit || 1}
238-
disabled={disabled || !!stateErrorMessage}
283+
disabled={disabled || !!internalErrorMessage}
239284
onChange={this.onChange}
240285
resolveDelay={resolveDelay} />
241286
</div>
@@ -257,21 +302,7 @@ export class PeoplePicker extends React.Component<IPeoplePickerProps, IPeoplePic
257302
</div>
258303
)
259304
}
260-
261-
{
262-
((isRequired && showRequiredError) || (stateErrorMessage)) && (
263-
<p className={`ms-TextField-errorMessage ${styles.errorMessage} ${errorMessageClassName ? errorMessageClassName : ''}`}>
264-
<Icon iconName='Error' className={styles.errorIcon} />
265-
{
266-
stateErrorMessage && <span data-automation-id="error-message">{stateErrorMessage}</span>
267-
}
268-
269-
{
270-
(isRequired && showRequiredError) && <span data-automation-id="error-message">{errorMessage ? errorMessage : strings.peoplePickerComponentErrorMessage}</span>
271-
}
272-
</p>
273-
)
274-
}
305+
<FieldErrorMessage errorMessage={errorMessage || internalErrorMessage} className={errorMessageClassName} />
275306
</div>
276307
);
277308
}

src/controls/taxonomyPicker/ErrorMessage.tsx

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/controls/taxonomyPicker/TaxonomyPicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import styles from './TaxonomyPicker.module.scss';
1313
import { sortBy, cloneDeep, isEqual } from '@microsoft/sp-lodash-subset';
1414
import uniqBy from 'lodash/uniqBy';
1515
import TermParent from './TermParent';
16-
import FieldErrorMessage from './ErrorMessage';
16+
import FieldErrorMessage from '../errorMessage/ErrorMessage';
1717

1818
import * as telemetry from '../../common/telemetry';
1919

src/webparts/controlsTest/components/ControlsTest.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -802,20 +802,20 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
802802
ensureUser={true}
803803
principalTypes={[PrincipalType.User, PrincipalType.SharePointGroup, PrincipalType.SecurityGroup, PrincipalType.DistributionList]}
804804
defaultSelectedUsers={["[email protected]", "[email protected]"]}
805-
selectedItems={this._getPeoplePickerItems} />
805+
onChange={this._getPeoplePickerItems} />
806806

807807
<PeoplePicker context={this.props.context}
808808
titleText="People Picker (search for group)"
809809
groupName="Team Site Visitors"
810810
principalTypes={[PrincipalType.User, PrincipalType.SharePointGroup, PrincipalType.SecurityGroup, PrincipalType.DistributionList]}
811811
defaultSelectedUsers={["[email protected]", "[email protected]"]}
812-
selectedItems={this._getPeoplePickerItems} />
812+
onChange={this._getPeoplePickerItems} />
813813

814814
<PeoplePicker context={this.props.context}
815815
titleText="People Picker (pre-set global users)"
816816
principalTypes={[PrincipalType.User, PrincipalType.SharePointGroup, PrincipalType.SecurityGroup, PrincipalType.DistributionList]}
817817
defaultSelectedUsers={["[email protected]", "[email protected]"]}
818-
selectedItems={this._getPeoplePickerItems}
818+
onChange={this._getPeoplePickerItems}
819819
personSelectionLimit={2}
820820
ensureUser={true} />
821821

@@ -824,17 +824,17 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
824824
webAbsoluteUrl={this.props.context.pageContext.site.absoluteUrl}
825825
principalTypes={[PrincipalType.User, PrincipalType.SharePointGroup, PrincipalType.SecurityGroup, PrincipalType.DistributionList]}
826826
defaultSelectedUsers={["[email protected]", "[email protected]"]}
827-
selectedItems={this._getPeoplePickerItems} />
827+
onChange={this._getPeoplePickerItems} />
828828

829829
<PeoplePicker context={this.props.context}
830830
titleText="People Picker (tenant scoped)"
831831
personSelectionLimit={5}
832832
// groupName={"Team Site Owners"}
833833
showtooltip={true}
834-
isRequired={true}
834+
required={true}
835835
//defaultSelectedUsers={["tenantUser@domain.onmicrosoft.com", "[email protected]"]}
836836
//defaultSelectedUsers={this.state.authorEmails}
837-
selectedItems={this._getPeoplePickerItems}
837+
onChange={this._getPeoplePickerItems}
838838
showHiddenInUI={false}
839839
principalTypes={[PrincipalType.User, PrincipalType.SharePointGroup, PrincipalType.SecurityGroup, PrincipalType.DistributionList]}
840840
suggestionsLimit={2}
@@ -847,10 +847,10 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
847847
personSelectionLimit={5}
848848
// groupName={"Team Site Owners"}
849849
showtooltip={true}
850-
isRequired={true}
850+
required={true}
851851
//defaultSelectedUsers={["tenantUser@domain.onmicrosoft.com", "[email protected]"]}
852852
//defaultSelectedUsers={this.state.authorEmails}
853-
selectedItems={this._getPeoplePickerItems}
853+
onChange={this._getPeoplePickerItems}
854854
showHiddenInUI={false}
855855
principalTypes={[PrincipalType.User, PrincipalType.SharePointGroup, PrincipalType.SecurityGroup, PrincipalType.DistributionList]}
856856
suggestionsLimit={2}

0 commit comments

Comments
 (0)