Skip to content

Commit e6edc48

Browse files
committed
Fix of #216: ability to set required property and error message for the component
1 parent 0f00975 commit e6edc48

File tree

5 files changed

+89
-14
lines changed

5 files changed

+89
-14
lines changed

docs/documentation/docs/controls/TaxonomyPicker.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ The TaxonomyPicker control can be configured with the following properties:
166166
| hideTagsNotAvailableForTagging | boolean | no | Specifies if the tags marked with 'Available for tagging' = false should be hidden |
167167
| hideDeprecatedTags | boolean | no | Specifies if deprecated tags should be hidden |
168168
| placeholder | string | no | Short text hint to display in empty picker |
169+
| errorMessage | string | no | 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). |
170+
| onGetErrorMessage | (value: IPickerTerms) => string \| Promise&lt;string&gt; | no | 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).<br />When it returns string:<ul><li>If valid, it returns empty string.</li><li>If invalid, it returns the error message string and the text field will</li><li>show a red border and show an error message below the text field.</li></ul><br />When it returns Promise&lt;string&gt;:<ul><li>The resolved value is display as error message.</li><li>The rejected, the value is thrown away.</li></ul> |
171+
| required | boolean | no | Specifies if to display an asterisk near the label. Note that error message should be specified in `onGetErrorMessage` or `errorMessage` |
169172

170173
Interface `IPickerTerm`
171174

src/controls/taxonomyPicker/ErrorMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface IFieldErrorMessageProps {
1111
*/
1212
export default class FieldErrorMessage extends React.Component<IFieldErrorMessageProps> {
1313
public render(): JSX.Element {
14-
if (this.props.errorMessage !== 'undefined' && this.props.errorMessage !== null && this.props.errorMessage !== '') {
14+
if (this.props.errorMessage !== undefined && this.props.errorMessage !== null && this.props.errorMessage !== '') {
1515
return (
1616
<div aria-live="assertive">
1717
<p className={`ms-TextField-errorMessage ${styles.errorMessage}`}>

src/controls/taxonomyPicker/ITaxonomyPicker.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export interface ITaxonomyPickerProps {
8181

8282
/**
8383
* The method is used to get the validation error message and determine whether the input value is valid or not.
84+
* Mutually exclusive with the static string errorMessage (it will take precedence over this).
8485
*
8586
* When it returns string:
8687
* - If valid, it returns empty string.
@@ -94,10 +95,21 @@ export interface ITaxonomyPickerProps {
9495
*/
9596
onGetErrorMessage?: (value: IPickerTerms) => string | Promise<string>;
9697

98+
/**
99+
* 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).
100+
*/
101+
errorMessage?: string;
102+
97103
/**
98104
* onChange Event
99105
*/
100106
onChange?: (newValue?: IPickerTerms) => void;
107+
108+
/**
109+
* Specifies if to display an asterisk near the label.
110+
* Note that error message should be specified in onGetErrorMessage
111+
*/
112+
required?: boolean;
101113
}
102114

103115
/**

src/controls/taxonomyPicker/TaxonomyPicker.tsx

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
4747
termSetAndTerms: null,
4848
loaded: false,
4949
openPanel: false,
50-
errorMessage: ''
50+
errorMessage: props.errorMessage
5151
};
5252

5353
this.onOpenPanel = this.onOpenPanel.bind(this);
@@ -69,12 +69,21 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
6969
/**
7070
* componentWillUpdate lifecycle hook
7171
*/
72-
public componentDidUpdate(prevProps: ITaxonomyPickerProps): void {
72+
public async componentDidUpdate(prevProps: ITaxonomyPickerProps): Promise<void> {
73+
let newState: ITaxonomyPickerState | undefined;
7374
// Check if the initial values objects are not equal, if that is the case, data can be refreshed
7475
if (!isEqual(this.props.initialValues, prevProps.initialValues)) {
75-
this.setState({
76+
newState = {
7677
activeNodes: this.props.initialValues || []
77-
});
78+
};
79+
}
80+
81+
if (this.props.errorMessage) {
82+
if (!newState) {
83+
newState = {};
84+
}
85+
86+
newState.errorMessage = this.props.errorMessage;
7887
}
7988
}
8089

@@ -158,8 +167,8 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
158167
private onSave(): void {
159168
this.cancel = false;
160169
this.onClosePanel();
161-
// Trigger the onChange event
162-
this.props.onChange(this.state.activeNodes);
170+
171+
this.validate(this.state.activeNodes);
163172
}
164173

165174
/**
@@ -210,10 +219,11 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
210219
* @param node
211220
*/
212221
private termsFromPickerChanged(terms: IPickerTerms) {
213-
this.props.onChange(terms);
214222
this.setState({
215223
activeNodes: terms
216224
});
225+
226+
this.validate(terms);
217227
}
218228

219229

@@ -236,7 +246,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
236246
* @param isChecked
237247
*/
238248
private termSetSelectedChange = (termSet: ITermSet, isChecked: boolean) => {
239-
const ts: ITermSet = {...termSet};
249+
const ts: ITermSet = { ...termSet };
240250
// Clean /Guid.../ from the ID
241251
ts.Id = this.termsService.cleanGuid(ts.Id);
242252
// Create a term for the termset
@@ -257,6 +267,52 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
257267
this.termsChanged(term, isChecked);
258268
}
259269

270+
private validate = async (value: IPickerTerms): Promise<void> => {
271+
if (this.props.errorMessage || !this.props.onGetErrorMessage) { // ignoring all onGetErrorMessage logic
272+
this.validated(value);
273+
return;
274+
}
275+
276+
const result: string | PromiseLike<string> = this.props.onGetErrorMessage(value || []);
277+
278+
if (!result) {
279+
this.validated(value);
280+
return;
281+
}
282+
283+
if (typeof result === 'string') {
284+
if (!result) {
285+
this.validated(value);
286+
}
287+
else {
288+
this.setState({
289+
errorMessage: result
290+
});
291+
}
292+
}
293+
else {
294+
try {
295+
const resolvedResult = await result;
296+
297+
if (!resolvedResult) {
298+
this.validated(value);
299+
}
300+
else {
301+
this.setState({
302+
errorMessage: resolvedResult
303+
});
304+
}
305+
}
306+
catch (err) {
307+
this.validated(value);
308+
}
309+
}
310+
}
311+
312+
private validated = (value: IPickerTerms): void => {
313+
this.props.onChange(value);
314+
}
315+
260316
/**
261317
* Renders the SPListpicker controls with Office UI Fabric
262318
*/
@@ -267,11 +323,12 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
267323
disabled,
268324
isTermSetSelectable,
269325
allowMultipleSelections,
270-
disabledTermIds,disableChildrenOfDisabledParents,
326+
disabledTermIds, disableChildrenOfDisabledParents,
271327
placeholder,
272328
panelTitle,
273329
anchorId,
274-
termActions
330+
termActions,
331+
required
275332
} = this.props;
276333

277334
const {
@@ -284,7 +341,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
284341

285342
return (
286343
<div>
287-
{label && <Label>{label}</Label>}
344+
{label && <Label required={required}>{label}</Label>}
288345
<div className={styles.termField}>
289346
<div className={styles.termFieldInput}>
290347
<TermPicker

src/webparts/controlsTest/components/ControlsTest.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -643,13 +643,16 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
643643

644644
<TaxonomyPicker
645645
allowMultipleSelections={true}
646-
termsetNameOrID="e813224c-bb1b-4086-b828-3d71434ddcd7" // id to termset that has a default sort
646+
termsetNameOrID="8ea5ac06-fd7c-4269-8d0d-02f541df8eb9" // id to termset that has a default sort
647647
panelTitle="Select Default Sorted Term"
648648
label="Service Picker"
649649
context={this.props.context}
650650
onChange={this.onServicePickerChange}
651651
isTermSetSelectable={false}
652652
placeholder="Select service"
653+
required={true}
654+
errorMessage='this field is required'
655+
onGetErrorMessage={(value) => { return 'comment errorMessage to see this one'; }}
653656
/>
654657

655658
<TaxonomyPicker
@@ -1250,7 +1253,7 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
12501253
//limiterIcon={"NumberedListText"}
12511254
/>
12521255
</div>
1253-
1256+
12541257
<div>
12551258
<FieldCollectionData
12561259
key={"FieldCollectionData"}

0 commit comments

Comments
 (0)