@@ -15,8 +15,9 @@ import { sortBy, cloneDeep, isEqual } from '@microsoft/sp-lodash-subset';
1515import uniqBy = require( 'lodash/uniqBy' ) ;
1616import TermParent from './TermParent' ;
1717import FieldErrorMessage from './ErrorMessage' ;
18-
18+ import { initializeIcons } from '@uifabric/icons' ;
1919import * 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
2728export const TERMSET_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACaSURBVDhPrZLRCcAgDERdpZMIjuQA7uWH4CqdxMY0EQtNjKWB0A/77sxF55SKMTalk8a61lqCFqsLiwKac84ZRUUBi7MoYHVmAfjfjzE6vJqZQfie0AcwBQVW8ATi7AR7zGGGNSE6Q2cyLSPIjRswjO7qKhcPDN2hK46w05wZMcEUIG+HrzzcrRsQBIJ5hS8C9fGAPmRwu/9RFxW6L8CM4Ry8AAAAAElFTkSuQmCC' ; // /_layouts/15/Images/EMMTermSet.png
2829export 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 }
@@ -111,7 +114,8 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
111114 hideTagsNotAvailableForTagging,
112115 initialValues,
113116 validateOnLoad,
114- termsetNameOrID
117+ termsetNameOrID,
118+ useSessionStorage
115119 } = this . props ;
116120
117121 let isValidateOnLoad = validateOnLoad && initialValues && initialValues . length >= 1 ;
@@ -120,7 +124,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
120124 const notFoundTerms : string [ ] = [ ] ;
121125 const notFoundTermIds : string [ ] = [ ] ;
122126
123- const termSet = await this . termsService . getAllTerms ( termsetNameOrID , hideDeprecatedTags , hideTagsNotAvailableForTagging ) ;
127+ const termSet = await this . termsService . getAllTerms ( termsetNameOrID , hideDeprecatedTags , hideTagsNotAvailableForTagging , useSessionStorage ) ;
124128 const allTerms = termSet . Terms ;
125129
126130 for ( let i = 0 , len = initialValues . length ; i < len ; i ++ ) {
@@ -152,7 +156,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
152156 // });
153157 }
154158
155- this . termsService . getAllTerms ( this . props . termsetNameOrID , this . props . hideDeprecatedTags , this . props . hideTagsNotAvailableForTagging ) . then ( ( response : ITermSet ) => {
159+ this . termsService . getAllTerms ( this . props . termsetNameOrID , this . props . hideDeprecatedTags , this . props . hideTagsNotAvailableForTagging , this . props . useSessionStorage ) . then ( ( response : ITermSet ) => {
156160 // Check if a response was retrieved
157161 let termSetAndTerms = response ? response : null ;
158162 this . setState ( {
@@ -166,7 +170,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
166170 * Force update of the taxonomy tree - required by term action in case the term has been added, deleted or moved.
167171 */
168172 private async updateTaxonomyTree ( ) : Promise < void > {
169- const termSetAndTerms = await this . termsService . getAllTerms ( this . props . termsetNameOrID , this . props . hideDeprecatedTags , this . props . hideTagsNotAvailableForTagging ) ;
173+ const termSetAndTerms = await this . termsService . getAllTerms ( this . props . termsetNameOrID , this . props . hideDeprecatedTags , this . props . hideTagsNotAvailableForTagging , this . props . useSessionStorage ) ;
170174
171175 this . setState ( {
172176 termSetAndTerms
@@ -229,11 +233,20 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
229233 */
230234 private termsChanged ( term : ITerm , checked : boolean ) : void {
231235
232- let activeNodes = this . state . activeNodes ;
236+ let activeNodes = this . state . activeNodes . slice ( ) ;
233237 if ( typeof term === 'undefined' || term === null ) {
234238 return ;
235239 }
236240
241+ const {
242+ allowMultipleSelections,
243+ selectChildrenIfParentSelected
244+ } = this . props ;
245+
246+ const {
247+ termSetAndTerms
248+ } = this . state ;
249+
237250 // Term item to add to the active nodes array
238251 const termItem = {
239252 name : term . Name ,
@@ -242,24 +255,58 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
242255 termSet : term . TermSet . Id
243256 } ;
244257
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+
245270 // Check if the term is checked or unchecked
246271 if ( checked ) {
247272 // Check if it is allowed to select multiple terms
248- if ( this . props . allowMultipleSelections ) {
273+ if ( allowMultipleSelections ) {
249274 // Add the checked term
250275 activeNodes . push ( termItem ) ;
251- // Filter out the duplicate terms
252- activeNodes = uniqBy ( activeNodes , 'key' ) ;
253276 } else {
254277 // Only store the current selected item
255278 activeNodes = [ termItem ] ;
256279 }
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' ) ;
257294 } else {
258295 // Remove the term from the list of active nodes
259296 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+ }
260302 }
261303 // Sort all active nodes
262304 activeNodes = sortBy ( activeNodes , 'path' ) ;
305+
306+ if ( this . props . onPanelSelectionChange ) {
307+ this . props . onPanelSelectionChange ( this . state . activeNodes . slice ( ) , activeNodes ) ;
308+ }
309+
263310 // Update the current state
264311 this . setState ( {
265312 activeNodes : activeNodes
@@ -312,22 +359,51 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
312359 return input ;
313360 }
314361
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+
315387 /**
316388 * Triggers when taxonomy picker control loses focus
317389 */
318- private onBlur ( event : React . FocusEvent < HTMLElement | Autofill > ) : void {
390+ private async onBlur ( event : React . FocusEvent < HTMLElement | Autofill > ) : Promise < void > {
319391 const { validateInput } = this . props ;
320392 if ( ! ! validateInput ) {
321393 // Perform validation of input text, only if taxonomy picker is configured with validateInput={true} property.
322394 const target : HTMLInputElement = event . target as HTMLInputElement ;
323395 const targetValue = ! ! target ? target . value : null ;
324- if ( ! ! targetValue ) {
325- this . invalidTerm = targetValue ;
326- }
327- else {
328- 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 ( ) ;
329406 }
330- this . validateInputText ( ) ;
331407 }
332408 }
333409
@@ -403,6 +479,9 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
403479 if ( typeof result === 'string' ) {
404480 if ( ! result ) {
405481 this . validated ( value ) ;
482+ this . setState ( {
483+ errorMessage : undefined
484+ } ) ;
406485 }
407486 else {
408487 this . setState ( {
@@ -416,6 +495,9 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
416495
417496 if ( ! resolvedResult ) {
418497 this . validated ( value ) ;
498+ this . setState ( {
499+ errorMessage : undefined
500+ } ) ;
419501 }
420502 else {
421503 this . setState ( {
0 commit comments