@@ -15,8 +15,9 @@ import { sortBy, cloneDeep, isEqual } from '@microsoft/sp-lodash-subset';
15
15
import uniqBy = require( 'lodash/uniqBy' ) ;
16
16
import TermParent from './TermParent' ;
17
17
import FieldErrorMessage from './ErrorMessage' ;
18
-
18
+ import { initializeIcons } from '@uifabric/icons' ;
19
19
import * as telemetry from '../../common/telemetry' ;
20
+ import { EmptyGuid } from '../../common/Constants' ;
20
21
21
22
/**
22
23
* Image URLs / Base64
@@ -27,6 +28,8 @@ export const GROUP_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQ
27
28
export const TERMSET_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACaSURBVDhPrZLRCcAgDERdpZMIjuQA7uWH4CqdxMY0EQtNjKWB0A/77sxF55SKMTalk8a61lqCFqsLiwKac84ZRUUBi7MoYHVmAfjfjzE6vJqZQfie0AcwBQVW8ATi7AR7zGGGNSE6Q2cyLSPIjRswjO7qKhcPDN2hK46w05wZMcEUIG+HrzzcrRsQBIJ5hS8C9fGAPmRwu/9RFxW6L8CM4Ry8AAAAAElFTkSuQmCC' ; // /_layouts/15/Images/EMMTermSet.png
28
29
export const TERM_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACzSURBVDhPY2AYNKCoqIgTiOcD8X8S8F6wB4Aa1IH4akNDw+mPHz++/E8EuHTp0jmQRSDNCcXFxa/XrVt3gAh9KEpgBvx/9OjRLVI1g9TDDYBp3rlz5//Kysr/IJoYgGEASPPatWsbQDQxAMOAbdu2gZ0FookBcAOePHlyhxgN6GqQY+Hdhg0bDpJqCNgAaDrQAnJuNDY2nvr06dMbYgw6e/bsabgBUEN4yEiJ2wdNViLfIQC3sTh2vtJcswAAAABJRU5ErkJggg==' ;
29
30
31
+ initializeIcons ( ) ;
32
+
30
33
/**
31
34
* Renders the controls for PropertyFieldTermPicker component
32
35
*/
@@ -89,7 +92,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
89
92
} ;
90
93
}
91
94
92
- if ( nextProps . errorMessage ) {
95
+ if ( nextProps . errorMessage !== this . props . errorMessage ) {
93
96
if ( ! newState ) {
94
97
newState = { } ;
95
98
}
@@ -111,7 +114,8 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
111
114
hideTagsNotAvailableForTagging,
112
115
initialValues,
113
116
validateOnLoad,
114
- termsetNameOrID
117
+ termsetNameOrID,
118
+ useSessionStorage
115
119
} = this . props ;
116
120
117
121
let isValidateOnLoad = validateOnLoad && initialValues && initialValues . length >= 1 ;
@@ -120,7 +124,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
120
124
const notFoundTerms : string [ ] = [ ] ;
121
125
const notFoundTermIds : string [ ] = [ ] ;
122
126
123
- const termSet = await this . termsService . getAllTerms ( termsetNameOrID , hideDeprecatedTags , hideTagsNotAvailableForTagging ) ;
127
+ const termSet = await this . termsService . getAllTerms ( termsetNameOrID , hideDeprecatedTags , hideTagsNotAvailableForTagging , useSessionStorage ) ;
124
128
const allTerms = termSet . Terms ;
125
129
126
130
for ( let i = 0 , len = initialValues . length ; i < len ; i ++ ) {
@@ -152,7 +156,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
152
156
// });
153
157
}
154
158
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 ) => {
156
160
// Check if a response was retrieved
157
161
let termSetAndTerms = response ? response : null ;
158
162
this . setState ( {
@@ -166,7 +170,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
166
170
* Force update of the taxonomy tree - required by term action in case the term has been added, deleted or moved.
167
171
*/
168
172
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 ) ;
170
174
171
175
this . setState ( {
172
176
termSetAndTerms
@@ -229,11 +233,20 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
229
233
*/
230
234
private termsChanged ( term : ITerm , checked : boolean ) : void {
231
235
232
- let activeNodes = this . state . activeNodes ;
236
+ let activeNodes = this . state . activeNodes . slice ( ) ;
233
237
if ( typeof term === 'undefined' || term === null ) {
234
238
return ;
235
239
}
236
240
241
+ const {
242
+ allowMultipleSelections,
243
+ selectChildrenIfParentSelected
244
+ } = this . props ;
245
+
246
+ const {
247
+ termSetAndTerms
248
+ } = this . state ;
249
+
237
250
// Term item to add to the active nodes array
238
251
const termItem = {
239
252
name : term . Name ,
@@ -242,24 +255,58 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
242
255
termSet : term . TermSet . Id
243
256
} ;
244
257
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
+
245
270
// Check if the term is checked or unchecked
246
271
if ( checked ) {
247
272
// Check if it is allowed to select multiple terms
248
- if ( this . props . allowMultipleSelections ) {
273
+ if ( allowMultipleSelections ) {
249
274
// Add the checked term
250
275
activeNodes . push ( termItem ) ;
251
- // Filter out the duplicate terms
252
- activeNodes = uniqBy ( activeNodes , 'key' ) ;
253
276
} else {
254
277
// Only store the current selected item
255
278
activeNodes = [ termItem ] ;
256
279
}
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' ) ;
257
294
} else {
258
295
// Remove the term from the list of active nodes
259
296
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
+ }
260
302
}
261
303
// Sort all active nodes
262
304
activeNodes = sortBy ( activeNodes , 'path' ) ;
305
+
306
+ if ( this . props . onPanelSelectionChange ) {
307
+ this . props . onPanelSelectionChange ( this . state . activeNodes . slice ( ) , activeNodes ) ;
308
+ }
309
+
263
310
// Update the current state
264
311
this . setState ( {
265
312
activeNodes : activeNodes
@@ -312,22 +359,51 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
312
359
return input ;
313
360
}
314
361
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
+
315
387
/**
316
388
* Triggers when taxonomy picker control loses focus
317
389
*/
318
- private onBlur ( event : React . FocusEvent < HTMLElement | Autofill > ) : void {
390
+ private async onBlur ( event : React . FocusEvent < HTMLElement | Autofill > ) : Promise < void > {
319
391
const { validateInput } = this . props ;
320
392
if ( ! ! validateInput ) {
321
393
// Perform validation of input text, only if taxonomy picker is configured with validateInput={true} property.
322
394
const target : HTMLInputElement = event . target as HTMLInputElement ;
323
395
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 ( ) ;
329
406
}
330
- this . validateInputText ( ) ;
331
407
}
332
408
}
333
409
@@ -403,6 +479,9 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
403
479
if ( typeof result === 'string' ) {
404
480
if ( ! result ) {
405
481
this . validated ( value ) ;
482
+ this . setState ( {
483
+ errorMessage : undefined
484
+ } ) ;
406
485
}
407
486
else {
408
487
this . setState ( {
@@ -416,6 +495,9 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
416
495
417
496
if ( ! resolvedResult ) {
418
497
this . validated ( value ) ;
498
+ this . setState ( {
499
+ errorMessage : undefined
500
+ } ) ;
419
501
}
420
502
else {
421
503
this . setState ( {
0 commit comments