|
| 1 | +import * as React from 'react'; |
| 2 | +import { BaseComponentContext } from '@microsoft/sp-component-base'; |
| 3 | +import { Guid } from '@microsoft/sp-core-library'; |
| 4 | +import { IIconProps } from 'office-ui-fabric-react/lib/components/Icon'; |
| 5 | +import { PrimaryButton, DefaultButton, IconButton } from 'office-ui-fabric-react/lib/Button'; |
| 6 | +import { Label } from 'office-ui-fabric-react/lib/Label'; |
| 7 | +import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel'; |
| 8 | +import { ITag, TagPicker } from 'office-ui-fabric-react/lib/Pickers'; |
| 9 | +import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner'; |
| 10 | +import { IStackTokens, Stack } from 'office-ui-fabric-react/lib/Stack'; |
| 11 | +import { sp } from '@pnp/sp'; |
| 12 | +import { ITermInfo } from '@pnp/sp/taxonomy'; |
| 13 | +import { SPTaxonomyService } from '../../services/SPTaxonomyService'; |
| 14 | +import FieldErrorMessage from '../errorMessage/ErrorMessage'; |
| 15 | +import { TaxonomyForm } from './taxonomyForm'; |
| 16 | +import styles from './ModernTaxonomyPicker.module.scss'; |
| 17 | +import * as strings from 'ControlStrings'; |
| 18 | + |
| 19 | +// TODO: remove/replace interface IPickerTerm |
| 20 | +export interface IPickerTerm { |
| 21 | + name: string; |
| 22 | + key: string; |
| 23 | + path: string; |
| 24 | + termSet: string; |
| 25 | + termSetName?: string; |
| 26 | +} |
| 27 | + |
| 28 | +// TODO: remove/replace interface IPickerTerms |
| 29 | +export interface IPickerTerms extends Array<IPickerTerm> { } |
| 30 | + |
| 31 | +export interface IModernTaxonomyPickerProps { |
| 32 | + allowMultipleSelections: boolean; |
| 33 | + termSetId: string; |
| 34 | + anchorTermId?: string; |
| 35 | + panelTitle: string; |
| 36 | + label: string; |
| 37 | + context: BaseComponentContext; |
| 38 | + initialValues?: ITag[]; |
| 39 | + errorMessage?: string; // TODO: is this needed? |
| 40 | + disabled?: boolean; |
| 41 | + required?: boolean; |
| 42 | +} |
| 43 | + |
| 44 | +export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps) { |
| 45 | + const [termsService] = React.useState(() => new SPTaxonomyService(props.context)); |
| 46 | + const [terms, setTerms] = React.useState<ITermInfo[]>([]); |
| 47 | + const [errorMessage, setErrorMessage] = React.useState(props.errorMessage); |
| 48 | + const [internalErrorMessage, setInternalErrorMessage] = React.useState<string>(); |
| 49 | + const [panelIsOpen, setPanelIsOpen] = React.useState(false); |
| 50 | + const [loading, setLoading] = React.useState(false); // was called loaded |
| 51 | + const [selectedOptions, setSelectedOptions] = React.useState<ITag[]>([]); |
| 52 | + const [selectedPanelOptions, setSelectedPanelOptions] = React.useState<ITag[]>([]); |
| 53 | + |
| 54 | + const invalidTerm = React.useRef<string>(null); |
| 55 | + |
| 56 | + React.useEffect(() => { |
| 57 | + sp.setup(props.context); |
| 58 | + }, []); |
| 59 | + |
| 60 | + React.useEffect(() => { |
| 61 | + setSelectedOptions(props.initialValues || []); |
| 62 | + }, [props.initialValues]); |
| 63 | + |
| 64 | + React.useEffect(() => { |
| 65 | + setErrorMessage(props.errorMessage); |
| 66 | + }, [props.errorMessage]); |
| 67 | + |
| 68 | + async function onOpenPanel(): Promise<void> { |
| 69 | + if (props.disabled === true) { |
| 70 | + return; |
| 71 | + } |
| 72 | + setLoading(true); |
| 73 | + const siteUrl = props.context.pageContext.site.absoluteUrl; |
| 74 | + const newTerms = await termsService.getTerms(Guid.parse(props.termSetId), Guid.empty, '', true, 50); |
| 75 | + setTerms(newTerms.value); |
| 76 | + setLoading(false); |
| 77 | + setPanelIsOpen(true); |
| 78 | + } |
| 79 | + |
| 80 | + function onClosePanel(): void { |
| 81 | + setLoading(false); |
| 82 | + setPanelIsOpen(false); |
| 83 | + } |
| 84 | + |
| 85 | + function onSave(): void { |
| 86 | + setSelectedOptions([...selectedPanelOptions]); |
| 87 | + onClosePanel(); |
| 88 | + } |
| 89 | + |
| 90 | + async function onResolveSuggestions(filter: string, selectedItems?: ITag[]): Promise<ITag[]> { |
| 91 | + const languageTag = props.context.pageContext.cultureInfo.currentUICultureName !== '' ? props.context.pageContext.cultureInfo.currentUICultureName : props.context.pageContext.web.languageName; |
| 92 | + if (filter === '') { |
| 93 | + return []; |
| 94 | + } |
| 95 | + const filteredTerms = await termsService.searchTerm(Guid.parse(props.termSetId), filter, languageTag, props.anchorTermId ? Guid.parse(props.anchorTermId) : undefined); |
| 96 | + const filteredTermsWithoutSelectedItems = filteredTerms.filter((term) => { |
| 97 | + if (!selectedItems || selectedItems.length === 0) { |
| 98 | + return true; |
| 99 | + } |
| 100 | + for (const selectedItem of selectedItems) { |
| 101 | + return selectedItem.key !== term.id; |
| 102 | + } |
| 103 | + }); |
| 104 | + const filteredTermsAndAvailable = filteredTermsWithoutSelectedItems.filter((term) => term.isAvailableForTagging.filter((t) => t.setId === props.termSetId)[0].isAvailable); |
| 105 | + const filteredTags = filteredTermsAndAvailable.map((term) => { |
| 106 | + const key = term.id; |
| 107 | + const name = term.labels.filter((termLabel) => (languageTag === '' || termLabel.languageTag === languageTag) && |
| 108 | + termLabel.name.toLowerCase().indexOf(filter.toLowerCase()) === 0)[0]?.name; |
| 109 | + return { key: key, name: name }; |
| 110 | + }); |
| 111 | + return filteredTags; |
| 112 | + } |
| 113 | + |
| 114 | + const { label, disabled, allowMultipleSelections, panelTitle, required } = props; |
| 115 | + return ( |
| 116 | + <div className={styles.modernTaxonomyPicker}> |
| 117 | + {label && <Label required={required}>{label}</Label>} |
| 118 | + <div className={styles.termField}> |
| 119 | + <div className={styles.termFieldInput}> |
| 120 | + <TagPicker |
| 121 | + removeButtonAriaLabel="Remove" |
| 122 | + onResolveSuggestions={onResolveSuggestions} |
| 123 | + itemLimit={allowMultipleSelections ? undefined : 1} |
| 124 | + selectedItems={selectedOptions} |
| 125 | + onChange={(itms?: ITag[]) => { |
| 126 | + setSelectedOptions(itms || []); |
| 127 | + setSelectedPanelOptions(itms || []); |
| 128 | + }} |
| 129 | + getTextFromItem={(tag: ITag, currentValue?: string) => tag.name} |
| 130 | + inputProps={{ |
| 131 | + 'aria-label': 'Tag Picker', |
| 132 | + placeholder: 'Ange en term som du vill tagga' |
| 133 | + }} |
| 134 | + /> |
| 135 | + </div> |
| 136 | + <div className={styles.termFieldButton}> |
| 137 | + <IconButton disabled={disabled} iconProps={{ iconName: 'Tag' } as IIconProps} onClick={onOpenPanel} /> |
| 138 | + </div> |
| 139 | + </div> |
| 140 | + |
| 141 | + <FieldErrorMessage errorMessage={errorMessage || internalErrorMessage} /> |
| 142 | + |
| 143 | + <Panel |
| 144 | + isOpen={panelIsOpen} |
| 145 | + hasCloseButton={true} |
| 146 | + onDismiss={onClosePanel} |
| 147 | + isLightDismiss={true} |
| 148 | + type={PanelType.medium} |
| 149 | + headerText={panelTitle} |
| 150 | + onRenderFooterContent={() => { |
| 151 | + const horizontalGapStackTokens: IStackTokens = { |
| 152 | + childrenGap: 10, |
| 153 | + }; |
| 154 | + return ( |
| 155 | + <Stack horizontal disableShrink tokens={horizontalGapStackTokens}> |
| 156 | + <PrimaryButton text={strings.SaveButtonLabel} value="Save" onClick={onSave} /> |
| 157 | + <DefaultButton text={strings.CancelButtonLabel} value="Cancel" onClick={onClosePanel} /> |
| 158 | + </Stack> |
| 159 | + ); |
| 160 | + }}> |
| 161 | + |
| 162 | + { |
| 163 | + /* Show spinner in the panel while retrieving terms */ |
| 164 | + loading === true ? <Spinner size={SpinnerSize.medium} /> : '' |
| 165 | + } |
| 166 | + { |
| 167 | + loading === false && props.termSetId && ( |
| 168 | + <div key={props.termSetId} > |
| 169 | + <TaxonomyForm |
| 170 | + allowMultipleSelections={allowMultipleSelections} |
| 171 | + terms={terms} |
| 172 | + onResolveSuggestions={onResolveSuggestions} |
| 173 | + onLoadMoreData={termsService.getTerms} |
| 174 | + getTermSetInfo={termsService.getTermSetInfo} |
| 175 | + context={props.context} |
| 176 | + termSetId={Guid.parse(props.termSetId)} |
| 177 | + pageSize={50} |
| 178 | + selectedPanelOptions={selectedPanelOptions} |
| 179 | + setSelectedPanelOptions={setSelectedPanelOptions} |
| 180 | + /> |
| 181 | + </div> |
| 182 | + ) |
| 183 | + } |
| 184 | + </Panel> |
| 185 | + </div > |
| 186 | + ); |
| 187 | +} |
0 commit comments