Skip to content

Commit

Permalink
modern taxonomy picker: ability to disallow selecting children (#1279)
Browse files Browse the repository at this point in the history
* Add `.github/fabricbot.json`

* feat(modern taxonomy picker): ability to disallow selecting children

Adds new `allowSelectingChildren` prop, which is true by default. Setting to `false` means only the top-level terms appear in search results, and their tree nodes are not expandable. Respects `anchorTermId`.

Co-authored-by: msftbot[bot] <48340428+msftbot[bot]@users.noreply.github.com>
Co-authored-by: Alex Terentiev <aleksei.terentiev@gmail.com>
Co-authored-by: Jake Stanger <jake.stanger@core.co.uk>
  • Loading branch information
4 people committed Sep 7, 2022
1 parent f5a16ba commit e37f4f8
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 6 deletions.
1 change: 1 addition & 0 deletions docs/documentation/docs/controls/ModernTaxonomyPicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ The ModernTaxonomyPicker control can be configured with the following properties
| isBlocking | boolean | no | Whether the panel uses a modal overlay or not. |
| onRenderActionButton | function | no | Optional custom renderer for adding e.g. a button with additional actions to the terms in the tree view. |
| isPathRendered | boolean | no | Whether the terms will be rendered with the term label or the full path up to the root. |
| allowSelectingChildren | boolean | no | Whether child terms can be selected. Default value is true. |

## Standalone TaxonomyTree control

Expand Down
11 changes: 9 additions & 2 deletions src/controls/modernTaxonomyPicker/ModernTaxonomyPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export interface IModernTaxonomyPickerProps {
isLightDismiss?: boolean;
isBlocking?: boolean;
onRenderActionButton?: (termStoreInfo: ITermStoreInfo, termSetInfo: ITermSetInfo, termInfo?: ITermInfo) => JSX.Element;
allowSelectingChildren?: boolean;
}

export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps): JSX.Element {
Expand Down Expand Up @@ -160,15 +161,20 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps): JSX.Ele
if (filter === '') {
return [];
}
const filteredTerms = await taxonomyService.searchTerm(Guid.parse(props.termSetId), filter, currentLanguageTag, props.anchorTermId ? Guid.parse(props.anchorTermId) : Guid.empty);
const filteredTerms = await taxonomyService.searchTerm(Guid.parse(props.termSetId), filter, currentLanguageTag, props.anchorTermId ? Guid.parse(props.anchorTermId) : Guid.empty, props.allowSelectingChildren);

const filteredTermsWithParentInformation = props.isPathRendered ? await addParentInformationToTerms(filteredTerms) : filteredTerms;
const filteredTermsWithoutSelectedItems = filteredTermsWithParentInformation.filter((term) => {
if (!selectedItems || selectedItems.length === 0) {
return true;
}
return selectedItems.every((item) => item.id !== term.id);
});
const filteredTermsAndAvailable = filteredTermsWithoutSelectedItems.filter((term) => term.isAvailableForTagging.filter((t) => t.setId === props.termSetId)[0].isAvailable);

const filteredTermsAndAvailable = filteredTermsWithoutSelectedItems
.filter((term) =>
term.isAvailableForTagging
.filter((t) => t.setId === props.termSetId)[0].isAvailable);
return filteredTermsAndAvailable;
}

Expand Down Expand Up @@ -329,6 +335,7 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps): JSX.Ele
themeVariant={props.themeVariant}
termPickerProps={props.termPickerProps}
onRenderActionButton={props.onRenderActionButton}
allowSelectingChildren={props.allowSelectingChildren}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface ITaxonomyPanelContentsProps {
themeVariant?: IReadonlyTheme;
termPickerProps?: Optional<IModernTermPickerProps, 'onResolveSuggestions'>;
onRenderActionButton?: (termStoreInfo: ITermStoreInfo, termSetInfo: ITermSetInfo, termInfo: ITermInfo, updateTaxonomyTreeViewCallback?: (newTermItems?: ITermInfo[], updatedTermItems?: ITermInfo[], deletedTermItems?: ITermInfo[]) => void) => JSX.Element;
allowSelectingChildren?: boolean;
}

export function TaxonomyPanelContents(props: ITaxonomyPanelContentsProps): React.ReactElement<ITaxonomyPanelContentsProps> {
Expand Down Expand Up @@ -114,6 +115,7 @@ export function TaxonomyPanelContents(props: ITaxonomyPanelContentsProps): React
onRenderActionButton={props.onRenderActionButton}
hideDeprecatedTerms={true}
showIcons={false}
allowSelectingChildren={props.allowSelectingChildren}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface ITaxonomyTreeProps {
selection?: Selection<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
hideDeprecatedTerms?: boolean;
showIcons?: boolean;
allowSelectingChildren?: boolean;
}

export function TaxonomyTree(props: ITaxonomyTreeProps): React.ReactElement<ITaxonomyTreeProps> {
Expand Down Expand Up @@ -228,7 +229,7 @@ export function TaxonomyTree(props: ITaxonomyTreeProps): React.ReactElement<ITax
level: 1,
isCollapsed: true,
data: { skiptoken: '', term: term },
hasMoreData: term.childrenCount > 0,
hasMoreData: props.allowSelectingChildren !== false && term.childrenCount > 0,
};
if (g.hasMoreData) {
g.children = [];
Expand Down
28 changes: 25 additions & 3 deletions src/services/SPTaxonomyService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,33 @@ export class SPTaxonomyService {
}
}

public async searchTerm(termSetId: Guid, label: string, languageTag: string, parentTermId?: Guid, stringMatchId: string = '0', pageSize: number = 50): Promise<ITermInfo[]> {
public async searchTerm(termSetId: Guid, label: string, languageTag: string, parentTermId?: Guid, allowSelectingChildren = true, stringMatchId: string = '0', pageSize: number = 50): Promise<ITermInfo[]> {
try {
const searchTermUrl = sp.termStore.concat(`/searchTerm(label='${label}',setId='${termSetId}',languageTag='${languageTag}',stringMatchId='${stringMatchId}'${parentTermId && parentTermId !== Guid.empty ? `,parentTermId='${parentTermId}'` : ''})`).toUrl();
let query = [
`label='${label}'`,
`setId='${termSetId}'`,
`languageTag='${languageTag}'`,
`stringMatchId='${stringMatchId}'`

This comment has been minimized.

Copy link
@robertmacnair

robertmacnair Nov 1, 2023

stringMatchId appears to no longer be supported, with the API request failing. This needs to be stringMatchOption as per guidance from Microsoft as its the only enumerator mentioned. Changing the API call to include stringMatchOption instead is successful. @JakeStanger

];

if(parentTermId !== Guid.empty) {
query.push(`parentTermId='${parentTermId}'`);
}

const searchTermUrl = sp.termStore.concat(`/searchTerm(${query.join(',')})`).toUrl();
const searchTermQuery = SharePointQueryableCollection(searchTermUrl).top(pageSize);
const filteredTerms = await searchTermQuery();
let filteredTerms: ITermInfo[] = await searchTermQuery();

if(allowSelectingChildren === false) {
const hasParentId = parentTermId !== Guid.empty;

const set = sp.termStore.sets.getById(termSetId.toString());
const collection = hasParentId ? set.terms.getById(parentTermId.toString()).children : set.children;

const childrenIds = await collection.select("id").get().then(children => children.map(c => c.id));
filteredTerms = filteredTerms.filter(term => childrenIds.includes(term.id));
}

return filteredTerms;
} catch (error) {
return [];
Expand Down

0 comments on commit e37f4f8

Please sign in to comment.