Skip to content

Commit 1f446b1

Browse files
author
Piotr Siatka
committed
Add TermActions unctionality.
1 parent 6bd0e13 commit 1f446b1

13 files changed

+468
-30
lines changed

src/controls/taxonomyPicker/ITaxonomyPicker.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { ApplicationCustomizerContext } from '@microsoft/sp-application-base';
22
import { IPickerTerms } from './ITermPicker';
3-
import { ITermStore, IGroup, ITermSet, ITerm } from '../../services/ISPTermStorePickerService';
4-
import SPTermStorePickerService from '../../services/SPTermStorePickerService';
3+
import { ITermSet, ITerm } from '../../services/ISPTermStorePickerService';
54
import { IWebPartContext } from '@microsoft/sp-webpart-base';
5+
import { ITermActions } from './termActions/ITermsActions';
66

77
/**
88
* PropertyFieldTermPickerHost properties interface
@@ -52,6 +52,17 @@ export interface ITaxonomyPickerProps {
5252
* Whether the property pane field is enabled or not.
5353
*/
5454
disabled?: boolean;
55+
56+
/**
57+
* Include standard term actions.
58+
*/
59+
includeDefaultTermActions?: boolean;
60+
61+
/**
62+
* Specifies the available term actions and their basic properties.
63+
*/
64+
termActions?: ITermActions;
65+
5566
/**
5667
* The method is used to get the validation error message and determine whether the input value is valid or not.
5768
*
@@ -77,12 +88,12 @@ export interface ITaxonomyPickerProps {
7788
* PropertyFieldTermPickerHost state interface
7889
*/
7990
export interface ITaxonomyPickerState {
80-
8191
termSetAndTerms? : ITermSet;
8292
errorMessage?: string;
8393
openPanel?: boolean;
8494
loaded?: boolean;
8595
activeNodes?: IPickerTerms;
96+
termActions?: ITermActions;
8697
}
8798

8899
export interface ITermChanges {
@@ -99,6 +110,7 @@ export interface ITermParentProps extends ITermChanges {
99110
anchorId? : string;
100111
isTermSetSelectable?: boolean;
101112

113+
termActions?: ITermActions;
102114
autoExpand: () => void;
103115
termSetSelectedChange?: (termSet: ITermSet, isChecked: boolean) => void;
104116
}
@@ -114,8 +126,10 @@ export interface ITermProps extends ITermChanges {
114126
term: ITerm;
115127
multiSelection: boolean;
116128
disabled: boolean;
129+
termActions?: ITermActions;
117130
}
118131

119132
export interface ITermState {
120133
selected?: boolean;
134+
termLabel: string;
121135
}

src/controls/taxonomyPicker/ITermPicker.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { ApplicationCustomizerContext } from '@microsoft/sp-application-base';
22
import { IWebPartContext } from '@microsoft/sp-webpart-base';
33

4-
5-
64
/**
75
* Selected terms
86
*/

src/controls/taxonomyPicker/TaxonomyPicker.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.contextualMenu {
2+
display: inline-block
3+
}
4+
15
.listItem {
26
height: 36px;
37
line-height: 36px;

src/controls/taxonomyPicker/TaxonomyPicker.tsx

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import TermPicker from './TermPicker';
77
import { IPickerTerms, IPickerTerm } from './ITermPicker';
88
import { ITaxonomyPickerProps, ITaxonomyPickerState } from './ITaxonomyPicker';
99
import SPTermStorePickerService from './../../services/SPTermStorePickerService';
10-
import { ITermSet, IGroup, ITerm } from './../../services/ISPTermStorePickerService';
10+
import { ITermSet, ITerm } from './../../services/ISPTermStorePickerService';
11+
import { TermLabelAction, ITermActions } from './termActions';
12+
1113
import styles from './TaxonomyPicker.module.scss';
1214
import { sortBy, uniqBy, cloneDeep, isEqual } from '@microsoft/sp-lodash-subset';
1315
import TermParent from './TermParent';
1416
import FieldErrorMessage from './ErrorMessage';
15-
import * as telemetry from '../../common/telemetry';
1617

18+
import * as telemetry from '../../common/telemetry';
1719

1820
/**
1921
* Image URLs / Base64
@@ -53,6 +55,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
5355
this.onSave = this.onSave.bind(this);
5456
this.termsChanged = this.termsChanged.bind(this);
5557
this.termsFromPickerChanged = this.termsFromPickerChanged.bind(this);
58+
this.prepareTermActions = this.prepareTermActions.bind(this);
5659
}
5760

5861
/**
@@ -76,24 +79,29 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
7679
}
7780
}
7881

82+
private prepareTermActions = (): ITermActions => {
83+
let termActions = this.props.termActions ? cloneDeep(this.props.termActions) : null;
84+
// Add default available actions
85+
if (termActions && termActions.addDefaultActions) {
86+
termActions.concreateActions.unshift(new TermLabelAction(this.termsService));
87+
}
88+
return termActions;
89+
}
90+
7991
/**
8092
* Loads the list from SharePoint current web site
8193
*/
8294
private loadTermStores(): void {
8395
this.termsService = new SPTermStorePickerService(this.props, this.props.context);
96+
let termActions = this.prepareTermActions();
8497
this.termsService.getAllTerms(this.props.termsetNameOrID).then((response: ITermSet) => {
8598
// Check if a response was retrieved
86-
if (response !== null) {
87-
this.setState({
88-
termSetAndTerms: response,
89-
loaded: true
90-
});
91-
} else {
92-
this.setState({
93-
termSetAndTerms: null,
94-
loaded: true
95-
});
96-
}
99+
let termSetAndTerms = response ? response : null;
100+
this.setState({
101+
termSetAndTerms,
102+
loaded: true,
103+
termActions
104+
});
97105
});
98106
}
99107

@@ -304,7 +312,10 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
304312
disabledTermIds={this.props.disabledTermIds}
305313
disableChildrenOfDisabledParents={this.props.disableChildrenOfDisabledParents}
306314
changedCallback={this.termsChanged}
307-
multiSelection={this.props.allowMultipleSelections} />
315+
multiSelection={this.props.allowMultipleSelections}
316+
317+
termActions={this.state.termActions}
318+
/>
308319
</div>
309320
)
310321
}

src/controls/taxonomyPicker/Term.tsx

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as React from 'react';
2-
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
2+
import { Checkbox, ICheckboxStyles } from 'office-ui-fabric-react/lib/Checkbox';
33
import { ITermProps, ITermState } from './ITaxonomyPicker';
44

55
import styles from './TaxonomyPicker.module.scss';
6+
import TermActionsControl from './termActions/TermActionsControl';
7+
import { UpdateAction, UpdateType } from './termActions';
68

79

810
/**
@@ -17,7 +19,8 @@ export default class Term extends React.Component<ITermProps, ITermState> {
1719
let active = this.props.activeNodes.filter(item => item.key === this.props.term.Id);
1820

1921
this.state = {
20-
selected: active.length > 0
22+
selected: active.length > 0,
23+
termLabel: this.props.term.Name
2124
};
2225

2326
this._handleChange = this._handleChange.bind(this);
@@ -43,7 +46,8 @@ export default class Term extends React.Component<ITermProps, ITermState> {
4346
if (!this.props.multiSelection) {
4447
let active = nextProps.activeNodes.filter(item => item.key === this.props.term.Id);
4548
this.state = {
46-
selected: active.length > 0
49+
selected: active.length > 0,
50+
termLabel: this.state.termLabel
4751
};
4852
}
4953
}
@@ -63,23 +67,49 @@ export default class Term extends React.Component<ITermProps, ITermState> {
6367
return styles.termEnabled;
6468
}
6569

70+
private termActionCallback = (updateAction: UpdateAction): void => {
71+
if (updateAction == null) {
72+
return;
73+
}
74+
75+
if (updateAction.updateActionType === UpdateType.updateTermLabel) {
76+
this.props.term.Name = updateAction.value;
77+
this.setState({
78+
termLabel: updateAction.value
79+
})
80+
}
81+
}
82+
6683
/**
6784
* Default React render
6885
*/
6986
public render(): JSX.Element {
7087
const styleProps: React.CSSProperties = {
7188
marginLeft: `${(this.props.term.PathDepth * 30)}px`
7289
};
90+
const checkBoxStyle: React.CSSProperties = {
91+
display: "inline-flex"
92+
}
7393

7494
return (
75-
<div className={`${styles.listItem} ${styles.term}`} style={styleProps}>
76-
<Checkbox
77-
checked={this.state.selected}
78-
disabled={this.props.term.IsDeprecated || !this.props.term.IsAvailableForTagging || this.props.disabled}
79-
className={this.getClassName()}
80-
label={this.props.term.Name}
81-
onChange={this._handleChange} />
95+
<div>
96+
<div className={`${styles.listItem} ${styles.term}`} style={styleProps}>
97+
<div>
98+
<Checkbox
99+
checked={this.state.selected}
100+
style={checkBoxStyle}
101+
disabled={this.props.term.IsDeprecated || !this.props.term.IsAvailableForTagging || this.props.disabled}
102+
className={this.getClassName()}
103+
label={this.state.termLabel}
104+
onChange={this._handleChange} />
105+
</div>
106+
{
107+
this.props.termActions &&
108+
<TermActionsControl term={this.props.term} termActions={this.props.termActions} termActionCallback={this.termActionCallback} />
109+
}
110+
</div>
82111
</div>
112+
83113
);
84114
}
85115
}

src/controls/taxonomyPicker/TermParent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export default class TermParent extends React.Component<ITermParentProps, ITermP
108108
disabled = parentPath && parentPath.length > 0;
109109
}
110110

111-
return <Term key={term.Id} term={term} termset={this.props.termset.Id} activeNodes={this.props.activeNodes} changedCallback={this.props.changedCallback} multiSelection={this.props.multiSelection} disabled={disabled} />;
111+
return <Term key={term.Id} term={term} termset={this.props.termset.Id} activeNodes={this.props.activeNodes} changedCallback={this.props.changedCallback} multiSelection={this.props.multiSelection} disabled={disabled} termActions={this.props.termActions} />;
112112
})
113113
}
114114
</div>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as React from 'react';
2+
import { CommandBarButton } from 'office-ui-fabric-react/lib/Button';
3+
import { ITermAction, TermActionsDisplayStyle, IConreteTermActionProps } from './ITermsActions';
4+
5+
export default class ButtonTermAction extends React.Component<IConreteTermActionProps> {
6+
public render(): React.ReactElement<IConreteTermActionProps> {
7+
const { term, termActions } = this.props;
8+
9+
return (
10+
<div style={{ display: 'flex', alignItems: 'stretch', height: '32px' }}>
11+
{
12+
termActions &&
13+
termActions.map(termAction => {
14+
const { name, text, iconName } = this._prepareCommandBarButton(termAction);
15+
return (
16+
<div>
17+
<CommandBarButton split={true}
18+
onClick={() => { this._onActionExecute(termAction); }}
19+
iconProps={{ iconName: iconName }}
20+
text={text}
21+
name={name}
22+
key={term.Id}
23+
style={this._getTermActionActionButtonStyle()}
24+
/>
25+
</div>
26+
)
27+
})
28+
}
29+
</div>
30+
);
31+
}
32+
33+
34+
private _prepareCommandBarButton = (termAction: ITermAction): { name: string, text: string, iconName: string } => {
35+
let name, text, iconName = "";
36+
37+
if (this.props.displayStyle && (this.props.displayStyle === TermActionsDisplayStyle.text || this.props.displayStyle === TermActionsDisplayStyle.textAndIcon)) {
38+
name = termAction.displayText;
39+
text = termAction.displayText;
40+
}
41+
if (this.props.displayStyle && (this.props.displayStyle === TermActionsDisplayStyle.icon || this.props.displayStyle === TermActionsDisplayStyle.textAndIcon)) {
42+
iconName = termAction.iconName;
43+
}
44+
45+
return { name, text, iconName };
46+
}
47+
48+
private _getTermActionActionButtonStyle = (): React.CSSProperties => {
49+
let result: React.CSSProperties = {
50+
backgroundColor: "transparent",
51+
width: "32px",
52+
height: "32px"
53+
}
54+
55+
return result;
56+
}
57+
58+
private _onActionExecute = async (termAction: ITermAction) => {
59+
const updateAction = await termAction.actionCallback(this.props.term);
60+
this.props.termActionCallback(updateAction);
61+
}
62+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ITerm } from "../../../services/ISPTermStorePickerService";
2+
import SPTermStorePickerService from "../../../services/SPTermStorePickerService";
3+
4+
import { ITermAction, UpdateAction, UpdateType } from ".";
5+
import { findIndex } from "@microsoft/sp-lodash-subset";
6+
7+
/**
8+
* TermAction is responsible to obtain different labels for the term.
9+
*/
10+
export class TermLabelAction implements ITermAction {
11+
public iconName: string = "LocaleLanguage";
12+
public displayText: string = "Get Labels"
13+
public id: string = "TermLabelActionId";
14+
15+
private _spTermsService: SPTermStorePickerService;
16+
private _labels: string[];
17+
private _processedTerms: ITerm[];
18+
19+
constructor(spTermService: SPTermStorePickerService) {
20+
this._spTermsService = spTermService;
21+
this._processedTerms = [];
22+
}
23+
24+
public applyToTerm = (currentTerm: ITerm): boolean => {
25+
const termIndex = findIndex(this._processedTerms, term => term.Id == currentTerm.Id);
26+
if (termIndex >= 0) {
27+
return false;
28+
}
29+
return true;
30+
}
31+
32+
public actionCallback = async (currentTerm: ITerm): Promise<UpdateAction> => {
33+
try {
34+
// Set pointer to loading
35+
let updateAction : UpdateAction = null;
36+
this._labels = await this._spTermsService.getTermLabels(currentTerm.Id);
37+
38+
if (this._labels) {
39+
let termLabel: string = this._labels.join(" ; ");
40+
updateAction = {
41+
updateActionType: UpdateType.updateTermLabel,
42+
value: termLabel
43+
};
44+
return updateAction;
45+
}
46+
return null;
47+
} catch (error) {
48+
console.log(error);
49+
return null;
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)