Skip to content

Commit ee42842

Browse files
authored
Merge pull request #1058 from SherpasGroup/modern-taxonomy-picker-2
Added feature to add action button to terms in ModernTaxonomyPicker
2 parents 6010cad + e722a0b commit ee42842

File tree

14 files changed

+946
-445
lines changed

14 files changed

+946
-445
lines changed

docs/documentation/docs/controls/ModernTaxonomyPicker.md

Lines changed: 158 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@ import { ModernTaxonomyPicker } from "@pnp/spfx-controls-react/lib/ModernTaxonom
3535

3636
```TypeScript
3737
<ModernTaxonomyPicker allowMultipleSelections={true}
38-
termSetId="f233d4b7-68fb-41ef-8b58-2af0bafc0d38"
39-
panelTitle="Select Term"
40-
label="Taxonomy Picker"
41-
context={this.props.context}
42-
onChange={this.onTaxPickerChange} />
38+
termSetId="f233d4b7-68fb-41ef-8b58-2af0bafc0d38"
39+
panelTitle="Select Term"
40+
label="Taxonomy Picker"
41+
context={this.props.context}
42+
onChange={this.onTaxPickerChange}
43+
/>
4344
```
4445

4546
- With the `onChange` property you can capture the event of when the terms in the picker has changed:
@@ -50,6 +51,108 @@ private onTaxPickerChange(terms : ITermInfo[]) {
5051
}
5152
```
5253

54+
## Advanced example
55+
Custom rendering of a More actions button that displays a context menu for each term in the term set and the term set itself and with different options for the terms and the term set. This could for example be used to add terms to an open term set. It also shows how to set the initialsValues property when just knowing the name and the id of the term.
56+
57+
```TypeScript
58+
const termSetId = "36d21c3f-b83b-4acc-a223-4df6fa8e946d";
59+
const [clickedActionTerm, setClickedActionTerm] = React.useState<ITermInfo>();
60+
61+
const addChildTerm = (parentTermId, updateTaxonomyTreeViewCallback): void => {
62+
spPost(sp.termStore.sets.getById(termSetId).terms.getById(parentTermId).children, {
63+
body: JSON.stringify({
64+
"labels": [
65+
{
66+
"languageTag": "en-US",
67+
"name": "Test",
68+
"isDefault": true
69+
}
70+
]
71+
}),
72+
})
73+
.then(addedTerm => {
74+
return sp.termStore.sets.getById(termSetId).terms.getById(addedTerm.id).expand("parent")();
75+
})
76+
.then(term => {
77+
updateTaxonomyTreeViewCallback([term], null, null);
78+
});
79+
}
80+
81+
...
82+
83+
<ModernTaxonomyPicker
84+
allowMultipleSelections={true}
85+
termSetId={termSetId}
86+
panelTitle="Panel title"
87+
label={"Field title"}
88+
context={this.props.context}
89+
required={false}
90+
initialValues={[{labels: [{name: "Subprocess A1", isDefault: true, languageTag: "en-US"}], id: "29eced8f-cf08-454b-bd9e-6443bc0a0f5e"}]}
91+
onChange={this.onTaxPickerChange}
92+
disabled={false}
93+
customPanelWidth={700}
94+
isLightDismiss={false}
95+
isBlocking={false}
96+
onRenderActionButton={(
97+
termStoreInfo: ITermStoreInfo,
98+
termSetInfo: ITermSetInfo,
99+
termInfo: ITermInfo
100+
updateTaxonomyTreeViewCallback?: (newTermItems?: ITermInfo[], updatedTermItems?: ITermInfo[], deletedTermItems?: ITermInfo[]) => void
101+
): JSX.Element => {
102+
const menuIcon: IIconProps = { iconName: 'MoreVertical', "aria-label": "More actions", style: { fontSize: "medium" } };
103+
if (termInfo) {
104+
const menuProps: IContextualMenuProps = {
105+
items: [
106+
{
107+
key: 'addTerm',
108+
text: 'Add Term',
109+
iconProps: { iconName: 'Tag' },
110+
onClick: () => addChildTerm(termInfo.id, updateTaxonomyTreeViewCallback)
111+
},
112+
{
113+
key: 'deleteTerm',
114+
text: 'Delete term',
115+
iconProps: { iconName: 'Untag' },
116+
onClick: () => deleteTerm(termInfo.id, updateTaxonomyTreeViewCallback)
117+
},
118+
],
119+
};
120+
121+
return (
122+
<IconButton
123+
menuProps={menuProps}
124+
menuIconProps={menuIcon}
125+
style={clickedActionTerm && clickedActionTerm.id === termInfo.id ? {opacity: 1} : null}
126+
onMenuClick={(ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>, button?: IButtonProps) => {
127+
setClickedActionTerm(termInfo));
128+
}}
129+
onAfterMenuDismiss={() => setClickedActionTerm(null)}
130+
/>
131+
);
132+
}
133+
else {
134+
const menuProps: IContextualMenuProps = {
135+
items: [
136+
{
137+
key: 'addTerm',
138+
text: 'Add term',
139+
iconProps: { iconName: 'Tag' },
140+
onClick: () => addTerm(termInfo.id, updateTaxonomyTreeViewCallback)
141+
},
142+
],
143+
};
144+
return (
145+
<IconButton
146+
menuProps={menuProps}
147+
menuIconProps={menuIcon}
148+
style={{opacity: 1}}
149+
/>
150+
);
151+
}
152+
}}
153+
/>
154+
```
155+
53156
## Implementation
54157

55158
The ModernTaxonomyPicker control can be configured with the following properties:
@@ -70,5 +173,54 @@ The ModernTaxonomyPicker control can be configured with the following properties
70173
| customPanelWidth | number | no | Custom panel width in pixels. |
71174
| termPickerProps | IModernTermPickerProps | no | Custom properties for the term picker (More info: [IBasePickerProps interface](https://developer.microsoft.com/en-us/fluentui#/controls/web/pickers#IBasePickerProps)). |
72175
| themeVariant | IReadonlyTheme | no | The current loaded SharePoint theme/section background (More info: [Supporting section backgrounds](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/supporting-section-backgrounds)). |
176+
| isLightDismiss | boolean | no | Whether the panel can be light dismissed. |
177+
| isBlocking | boolean | no | Whether the panel uses a modal overlay or not. |
178+
| onRenderActionButton | function | no | Optional custom renderer for adding e.g. a button with additional actions to the terms in the tree view. |
179+
180+
## Standalone TaxonomyTree control
181+
182+
You can also use the `TaxonomyTree` control separately to just render a stand-alone tree-view of a term set with action buttons.
183+
184+
- Use the `TaxonomyTree` control in your code as follows:
185+
Initialize the taxonomy service and state, load basic info from term store and display the `TaxonomyTree` component.
186+
187+
```TypeScript
188+
const taxonomyService = new SPTaxonomyService(props.context);
189+
const [terms, setTerms] = React.useState<ITermInfo[]>([]);
190+
const [currentTermStoreInfo, setCurrentTermStoreInfo] = React.useState<ITermStoreInfo>();
191+
const [currentTermSetInfo, setCurrentTermSetInfo] = React.useState<ITermSetInfo>();
192+
const [currentLanguageTag, setCurrentLanguageTag] = React.useState<string>("");
193+
194+
React.useEffect(() => {
195+
sp.setup(props.context);
196+
taxonomyService.getTermStoreInfo()
197+
.then((termStoreInfo) => {
198+
setCurrentTermStoreInfo(termStoreInfo);
199+
setCurrentLanguageTag(props.context.pageContext.cultureInfo.currentUICultureName !== '' ?
200+
props.context.pageContext.cultureInfo.currentUICultureName :
201+
currentTermStoreInfo.defaultLanguageTag);
202+
});
203+
taxonomyService.getTermSetInfo(Guid.parse(props.termSetId))
204+
.then((termSetInfo) => {
205+
setCurrentTermSetInfo(termSetInfo);
206+
});
207+
}, []);
208+
209+
return (
210+
{currentTermStoreInfo && currentTermSetInfo && currentLanguageTag && (
211+
<TaxonomyTree
212+
languageTag={currentLanguageTag}
213+
onLoadMoreData={taxonomyService.getTerms}
214+
pageSize={50}
215+
setTerms={setTerms}
216+
termSetInfo={currentTermSetInfo}
217+
termStoreInfo={currentTermStoreInfo}
218+
terms={terms}
219+
onRenderActionButton={onRenderActionButton}
220+
hideDeprecatedTerms={false}
221+
showIcons={true}
222+
/>
223+
)}
224+
```
73225
74-
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/TaxonomyPicker)
226+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/ModernTaxonomyPicker)

src/controls/modernTaxonomyPicker/ModernTaxonomyPicker.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export interface IModernTaxonomyPickerProps {
5050
panelTitle: string;
5151
label: string;
5252
context: BaseComponentContext;
53-
initialValues?: ITermInfo[];
53+
initialValues?: Optional<ITermInfo, "childrenCount" | "createdDateTime" | "lastModifiedDateTime" | "descriptions" | "customSortOrder" | "properties" | "localProperties" | "isDeprecated" | "isAvailableForTagging" | "topicRequested">[];
5454
disabled?: boolean;
5555
required?: boolean;
5656
onChange?: (newValue?: ITermInfo[]) => void;
@@ -60,10 +60,13 @@ export interface IModernTaxonomyPickerProps {
6060
customPanelWidth?: number;
6161
themeVariant?: IReadonlyTheme;
6262
termPickerProps?: Optional<IModernTermPickerProps, 'onResolveSuggestions'>;
63+
isLightDismiss?: boolean;
64+
isBlocking?: boolean;
65+
onRenderActionButton?: (termStoreInfo: ITermStoreInfo, termSetInfo: ITermSetInfo, termInfo?: ITermInfo) => JSX.Element;
6366
}
6467

6568
export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps) {
66-
const [taxonomyService] = React.useState(() => new SPTaxonomyService(props.context));
69+
const taxonomyService = new SPTaxonomyService(props.context);
6770
const [panelIsOpen, setPanelIsOpen] = React.useState(false);
6871
const [selectedOptions, setSelectedOptions] = React.useState<ITermInfo[]>([]);
6972
const [selectedPanelOptions, setSelectedPanelOptions] = React.useState<ITermInfo[]>([]);
@@ -235,7 +238,8 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps) {
235238
hasCloseButton={true}
236239
closeButtonAriaLabel={strings.ModernTaxonomyPickerPanelCloseButtonText}
237240
onDismiss={onClosePanel}
238-
isLightDismiss={true}
241+
isLightDismiss={props.isLightDismiss}
242+
isBlocking={props.isBlocking}
239243
type={props.customPanelWidth ? PanelType.custom : PanelType.medium}
240244
customWidth={props.customPanelWidth ? `${props.customPanelWidth}px` : undefined}
241245
headerText={props.panelTitle}
@@ -261,8 +265,6 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps) {
261265
anchorTermInfo={currentAnchorTermInfo}
262266
termSetInfo={currentTermSetInfo}
263267
termStoreInfo={currentTermStoreInfo}
264-
context={props.context}
265-
termSetId={Guid.parse(props.termSetId)}
266268
pageSize={50}
267269
selectedPanelOptions={selectedPanelOptions}
268270
setSelectedPanelOptions={setSelectedPanelOptions}
@@ -273,6 +275,7 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps) {
273275
languageTag={currentLanguageTag}
274276
themeVariant={props.themeVariant}
275277
termPickerProps={props.termPickerProps}
278+
onRenderActionButton={props.onRenderActionButton}
276279
/>
277280
</div>
278281
)
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
export * from './ModernTaxonomyPicker';
2-
export * from './termItem/TermItem';
2+
export * from './modernTermPicker/index';
3+
export * from './taxonomyPanelContents/index';
4+
export * from './taxonomyTree/index';
5+
export * from './termItem/index';
6+
export * from '../../services/SPTaxonomyService';

src/controls/modernTaxonomyPicker/modernTermPicker/ModernTermPicker.types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface ITermItemStyles {
3838
close: IStyle;
3939
}
4040

41-
export interface ITermItemSuggestionProps extends React.AllHTMLAttributes<HTMLElement> {
41+
export interface ITermItemSuggestionElementProps extends React.AllHTMLAttributes<HTMLElement> {
4242
/** Additional CSS class(es) to apply to the TermItemSuggestion div element */
4343
className?: string;
4444

@@ -49,8 +49,8 @@ export interface ITermItemSuggestionProps extends React.AllHTMLAttributes<HTMLEl
4949
theme?: ITheme;
5050
}
5151

52-
export type ITermItemSuggestionStyleProps = Required<Pick<ITermItemSuggestionProps, 'theme'>> &
53-
Pick<ITermItemSuggestionProps, 'className'> & {};
52+
export type ITermItemSuggestionStyleProps = Required<Pick<ITermItemSuggestionElementProps, 'theme'>> &
53+
Pick<ITermItemSuggestionElementProps, 'className'> & {};
5454

5555
export interface ITermItemSuggestionStyles {
5656
/** Refers to the text element of the TermItemSuggestion */
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './ModernTermPicker';
2+
export * from './ModernTermPicker.types';
Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,6 @@
11
@import '~office-ui-fabric-react/dist/sass/References.scss';
22

33
.taxonomyPanelContents {
4-
.choiceOption {
5-
color: "[theme: bodyText, default: #323130]";
6-
display: inline-block;
7-
padding-inline-start: 26px;
8-
}
9-
10-
.disabledChoiceOption {
11-
color: "[theme: disabledBodyText, default: #323130]";
12-
display: inline-block;
13-
padding-inline-start: 26px;
14-
}
15-
16-
.selectedChoiceOption {
17-
font-weight: bold;
18-
}
19-
20-
.checkbox {
21-
color: "[theme: bodyText, default: #323130]";
22-
margin-inline-start: 4px;
23-
}
24-
25-
.disabledCheckbox {
26-
color: "[theme: disabledBodyText, default: #323130]";
27-
margin-inline-start: 4px;
28-
}
29-
30-
.selectedCheckbox {
31-
font-weight: bold;
32-
}
33-
344
.taxonomyTreeSelector {
355
border-bottom-color: blue;
366
border-bottom-style: solid;
@@ -42,17 +12,4 @@
4212
font-size: 18px;
4313
font-weight: 100;
4414
}
45-
46-
.spinnerContainer {
47-
height: 48px;
48-
line-height: 48px;
49-
display: flex;
50-
justify-content: center;
51-
align-items: center;
52-
}
53-
54-
.loadMoreContainer {
55-
height: 48px;
56-
line-height: 48px;
57-
}
5815
}

0 commit comments

Comments
 (0)