Skip to content

Commit 00fc824

Browse files
committed
Add onPanelSelectionChange and selectChildrenIfParentSelected properties
1 parent b1023ce commit 00fc824

File tree

3 files changed

+111
-14
lines changed

3 files changed

+111
-14
lines changed

src/controls/taxonomyPicker/ITaxonomyPicker.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IPickerTerms } from './ITermPicker';
1+
import { IPickerTerm, IPickerTerms } from './ITermPicker';
22
import { ITermSet, ITerm } from '../../services/ISPTermStorePickerService';
33
import { IWebPartContext } from '@microsoft/sp-webpart-base';
44
import { ITermActions } from './termActions/ITermsActions';
@@ -126,6 +126,17 @@ export interface ITaxonomyPickerProps {
126126
* Default will be true
127127
*/
128128
useSessionStorage?: boolean;
129+
130+
/**
131+
* Panel selection change handler. Can be used to interact with the control while selecting items in the panel, before Click or Cancel is clicked.
132+
*/
133+
onPanelSelectionChange?: (prevValue: IPickerTerms, newValue: IPickerTerms) => void;
134+
135+
/**
136+
* Specifies if the childrens should be selected when parent is selected.
137+
* By default this is set to false.
138+
*/
139+
selectChildrenIfParentSelected?: boolean;
129140
}
130141

131142
/**

src/controls/taxonomyPicker/TaxonomyPicker.tsx

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import { sortBy, cloneDeep, isEqual } from '@microsoft/sp-lodash-subset';
1515
import uniqBy = require('lodash/uniqBy');
1616
import TermParent from './TermParent';
1717
import FieldErrorMessage from './ErrorMessage';
18-
18+
import { initializeIcons } from '@uifabric/icons';
1919
import * as telemetry from '../../common/telemetry';
20+
import { EmptyGuid } from '../../common/Constants';
2021

2122
/**
2223
* Image URLs / Base64
@@ -27,6 +28,8 @@ export const GROUP_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQ
2728
export const TERMSET_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACaSURBVDhPrZLRCcAgDERdpZMIjuQA7uWH4CqdxMY0EQtNjKWB0A/77sxF55SKMTalk8a61lqCFqsLiwKac84ZRUUBi7MoYHVmAfjfjzE6vJqZQfie0AcwBQVW8ATi7AR7zGGGNSE6Q2cyLSPIjRswjO7qKhcPDN2hK46w05wZMcEUIG+HrzzcrRsQBIJ5hS8C9fGAPmRwu/9RFxW6L8CM4Ry8AAAAAElFTkSuQmCC'; // /_layouts/15/Images/EMMTermSet.png
2829
export const TERM_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACzSURBVDhPY2AYNKCoqIgTiOcD8X8S8F6wB4Aa1IH4akNDw+mPHz++/E8EuHTp0jmQRSDNCcXFxa/XrVt3gAh9KEpgBvx/9OjRLVI1g9TDDYBp3rlz5//Kysr/IJoYgGEASPPatWsbQDQxAMOAbdu2gZ0FookBcAOePHlyhxgN6GqQY+Hdhg0bDpJqCNgAaDrQAnJuNDY2nvr06dMbYgw6e/bsabgBUEN4yEiJ2wdNViLfIQC3sTh2vtJcswAAAABJRU5ErkJggg==';
2930

31+
initializeIcons();
32+
3033
/**
3134
* Renders the controls for PropertyFieldTermPicker component
3235
*/
@@ -89,7 +92,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
8992
};
9093
}
9194

92-
if (nextProps.errorMessage) {
95+
if (nextProps.errorMessage !== this.props.errorMessage) {
9396
if (!newState) {
9497
newState = {};
9598
}
@@ -230,11 +233,20 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
230233
*/
231234
private termsChanged(term: ITerm, checked: boolean): void {
232235

233-
let activeNodes = this.state.activeNodes;
236+
let activeNodes = this.state.activeNodes.slice();
234237
if (typeof term === 'undefined' || term === null) {
235238
return;
236239
}
237240

241+
const {
242+
allowMultipleSelections,
243+
selectChildrenIfParentSelected
244+
} = this.props;
245+
246+
const {
247+
termSetAndTerms
248+
} = this.state;
249+
238250
// Term item to add to the active nodes array
239251
const termItem = {
240252
name: term.Name,
@@ -243,24 +255,58 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
243255
termSet: term.TermSet.Id
244256
};
245257

258+
// Check if we need to process child terms
259+
let children: ITerm[] = [];
260+
if (allowMultipleSelections && selectChildrenIfParentSelected) {
261+
if (term.Id === term.TermSet.Id) {
262+
children = termSetAndTerms.Terms || [];
263+
} else {
264+
children = termSetAndTerms.Terms ? termSetAndTerms.Terms.filter(t => {
265+
return t.PathOfTerm.indexOf(`${term.PathOfTerm}`) !== -1;
266+
}) : [];
267+
}
268+
}
269+
246270
// Check if the term is checked or unchecked
247271
if (checked) {
248272
// Check if it is allowed to select multiple terms
249-
if (this.props.allowMultipleSelections) {
273+
if (allowMultipleSelections) {
250274
// Add the checked term
251275
activeNodes.push(termItem);
252-
// Filter out the duplicate terms
253-
activeNodes = uniqBy(activeNodes, 'key');
254276
} else {
255277
// Only store the current selected item
256278
activeNodes = [termItem];
257279
}
280+
281+
if (children.length) {
282+
activeNodes.push(...children.map(c => {
283+
return {
284+
name: c.Name,
285+
key: c.Id,
286+
path: c.PathOfTerm,
287+
termSet: c.TermSet.Id
288+
};
289+
}));
290+
}
291+
292+
// Filter out the duplicate terms
293+
activeNodes = uniqBy(activeNodes, 'key');
258294
} else {
259295
// Remove the term from the list of active nodes
260296
activeNodes = activeNodes.filter(item => item.key !== term.Id);
297+
298+
if (children.length) {
299+
const childIds = children.map(c => c.Id);
300+
activeNodes = activeNodes.filter(item => childIds.indexOf(item.key) === -1);
301+
}
261302
}
262303
// Sort all active nodes
263304
activeNodes = sortBy(activeNodes, 'path');
305+
306+
if (this.props.onPanelSelectionChange) {
307+
this.props.onPanelSelectionChange(this.state.activeNodes.slice(), activeNodes);
308+
}
309+
264310
// Update the current state
265311
this.setState({
266312
activeNodes: activeNodes
@@ -313,22 +359,51 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
313359
return input;
314360
}
315361

362+
private async validateOnGetErrorMessage(targetValue: string): Promise<boolean> {
363+
const errorMessage = await this.props.onGetErrorMessage(
364+
[
365+
{
366+
key: EmptyGuid,
367+
name: targetValue,
368+
path: targetValue,
369+
termSet: this.termsService.cleanGuid(this.props.termsetNameOrID)
370+
}
371+
]
372+
);
373+
374+
if (!!errorMessage) {
375+
this.setState({
376+
errorMessage: errorMessage
377+
});
378+
} else {
379+
this.setState({
380+
errorMessage: null
381+
});
382+
}
383+
384+
return !errorMessage;
385+
}
386+
316387
/**
317388
* Triggers when taxonomy picker control loses focus
318389
*/
319-
private onBlur(event: React.FocusEvent<HTMLElement | Autofill>): void {
390+
private async onBlur(event: React.FocusEvent<HTMLElement | Autofill>): Promise<void> {
320391
const { validateInput } = this.props;
321392
if (!!validateInput) {
322393
// Perform validation of input text, only if taxonomy picker is configured with validateInput={true} property.
323394
const target: HTMLInputElement = event.target as HTMLInputElement;
324395
const targetValue = !!target ? target.value : null;
325-
if (!!targetValue) {
326-
this.invalidTerm = targetValue;
327-
}
328-
else {
329-
this.invalidTerm = null;
396+
if (!!this.props.onGetErrorMessage && !!targetValue) {
397+
await this.validateOnGetErrorMessage(targetValue);
398+
} else {
399+
if (!!targetValue) {
400+
this.invalidTerm = targetValue;
401+
}
402+
else {
403+
this.invalidTerm = null;
404+
}
405+
this.validateInputText();
330406
}
331-
this.validateInputText();
332407
}
333408
}
334409

@@ -404,6 +479,9 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
404479
if (typeof result === 'string') {
405480
if (!result) {
406481
this.validated(value);
482+
this.setState({
483+
errorMessage: undefined
484+
});
407485
}
408486
else {
409487
this.setState({
@@ -417,6 +495,9 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
417495

418496
if (!resolvedResult) {
419497
this.validated(value);
498+
this.setState({
499+
errorMessage: undefined
500+
});
420501
}
421502
else {
422503
this.setState({

src/webparts/controlsTest/components/ControlsTest.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,7 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
658658
<div className="ms-font-m">Services tester:
659659
<TaxonomyPicker
660660
allowMultipleSelections={true}
661+
selectChildrenIfParentSelected={true}
661662
//termsetNameOrID="61837936-29c5-46de-982c-d1adb6664b32" // id to termset that has a custom sort
662663
termsetNameOrID="8ea5ac06-fd7c-4269-8d0d-02f541df8eb9"
663664
initialValues={[{
@@ -709,6 +710,10 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
709710
termActionsDisplayMode: TermActionsDisplayMode.buttons,
710711
termActionsDisplayStyle: TermActionsDisplayStyle.textAndIcon
711712
}}
713+
onPanelSelectionChange={(prev, next) => {
714+
console.log(prev);
715+
console.log(next);
716+
}}
712717
/>
713718

714719
<TaxonomyPicker

0 commit comments

Comments
 (0)