@pnp/spfx-property-controls
Version:
Reusable property pane controls for SharePoint Framework solutions
257 lines • 13.6 kB
JavaScript
import * as React from 'react';
import { Async } from '@fluentui/react/lib/Utilities';
import { PrimaryButton, DefaultButton, IconButton } from '@fluentui/react/lib/Button';
import { Panel, PanelType } from '@fluentui/react/lib/Panel';
import { Spinner, SpinnerSize } from '@fluentui/react/lib/Spinner';
import { Label } from '@fluentui/react/lib/Label';
import TermPicker from './TermPicker';
import styles from './PropertyFieldTermPickerHost.module.scss';
import { sortBy, uniqBy, cloneDeep } from '@microsoft/sp-lodash-subset';
import TermGroup from './TermGroup';
import FieldErrorMessage from '../errorMessage/FieldErrorMessage';
import * as telemetry from '../../common/telemetry';
import * as strings from 'PropertyControlStrings';
import { setPropertyValue } from '../../helpers/GeneralHelper';
/**
* Image URLs / Base64
*/
export const COLLAPSED_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAUCAYAAABSx2cSAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjEwcrIlkgAAAIJJREFUOE/NkjEKwCAMRdu7ewZXJ/EqHkJwE9TBCwR+a6FLUQsRwYBTeD8/35wADnZVmPvY4OOYO3UNbK1FKeUWH+fRtK21hjEG3vuhQBdOKUEpBedcV6ALExFijJBSIufcFBjCVSCEACEEqpNvBmsmT+3MTnvqn/+O4+1vdtv7274APmNjtuXVz6sAAAAASUVORK5CYII='; // /_layouts/15/images/MDNCollapsed.png
export const EXPANDED_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAUCAYAAABSx2cSAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjEwcrIlkgAAAFtJREFUOE9j/P//PwPZAKSZXEy2RrCLybV1CGjetWvX/46ODqBLUQOXoJ9BGtXU1MCYJM0wjZGRkaRpRtZIkmZ0jSRpBgUOzJ8wmqwAw5eICIb2qGYSkyfNAgwAasU+UQcFvD8AAAAASUVORK5CYII='; // /_layouts/15/images/MDNExpanded.png
export const GROUP_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAC9SURBVDhPY2CgNXh1qEkdiJ8D8X90TNBuJM0V6IpBhoHFgIxebKYTIwYzAMNpxGhGdsFwNoBgNEFjAWsYgOSKiorMgPgbEP/Hgj8AxXpB0Yg1gQAldYuLix8/efLkzn8s4O7du9eAan7iM+DV/v37z546der/jx8/sJkBdhVOA5qbm08ePnwYrOjQoUOkGwDU+AFowLmjR4/idwGukAYaYAkMgxfPnj27h816kDg4DPABoAI/IP6DIxZA4l0AOd9H3QXl5+cAAAAASUVORK5CYII='; // /_layouts/15/Images/EMMGroup.png
export const TERMSET_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACaSURBVDhPrZLRCcAgDERdpZMIjuQA7uWH4CqdxMY0EQtNjKWB0A/77sxF55SKMTalk8a61lqCFqsLiwKac84ZRUUBi7MoYHVmAfjfjzE6vJqZQfie0AcwBQVW8ATi7AR7zGGGNSE6Q2cyLSPIjRswjO7qKhcPDN2hK46w05wZMcEUIG+HrzzcrRsQBIJ5hS8C9fGAPmRwu/9RFxW6L8CM4Ry8AAAAAElFTkSuQmCC'; // /_layouts/15/Images/EMMTermSet.png
export const TERM_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZBJREFUeNqkU0trwkAQnhURBBFPKghekuJN7KW9pOCj0Agl9/4uT/6BHgXpxQfxYKltKEYhV3tXFF/4RrOdXYgkxpz8YA47O983O9+whFIKt8DvddHv9wej0UjY7/dwPB55sGaJRKImSVLxXMiSl9Hr9fRWq0WvoVKpUFVVv6xaF1nX9R+LvFqteKAgrVarVNM0fi6Xy9Sq99mfjeTv2Wz2mMvlYL1en/OGYYCiKKTT6fAzG8flQbfb1ReLxX0+n4fJZOLwQxRFKJVKNJPJwHa7dZuI5I/lcsnJrHMwGHQUpdNpHp5bGI/Hr7IsA3Pc7/dcDEynU5jP5xAIBJwCpmmeN+IF9IaT0VA20p+V5yaGQqF2s9mE3W4Hh8PBFXZyLBbTstmsaAkQqyuu7nOz2UiCIEAkEoFwOMzz6A0n44YgHo8bhULBYQaxP7vRaLyjyFsqleIiDLbOAyTfXY5GLueu1+s1XNULE2Fg5Gg0qj4jrnlDrhmHIu3hcPh0Op0gmUz+IvfBy1xy62/0wY34F2AAKtctO7g/KgIAAAAASUVORK5CYII=';
/**
* Renders the controls for PropertyFieldTermPicker component
*/
export default class PropertyFieldTermPickerHost extends React.Component {
/**
* Constructor method
*/
constructor(props) {
super(props);
this.previousValues = [];
this.cancel = true;
telemetry.track('PropertyFieldTermPicker', {
allowMultipleSelections: props.allowMultipleSelections,
excludeSystemGroup: props.excludeSystemGroup,
limitByTermsetNameOrID: !!props.limitByTermsetNameOrID,
limitByGroupNameOrID: !!props.limitByGroupNameOrID,
disabled: props.disabled
});
this.termsService = props.termService;
this.state = {
activeNodes: typeof this.props.initialValues !== 'undefined' ? this.props.initialValues : [],
termStores: [],
loaded: false,
openPanel: false,
errorMessage: ''
};
this.onOpenPanel = this.onOpenPanel.bind(this);
this.onClosePanel = this.onClosePanel.bind(this);
this.onSave = this.onSave.bind(this);
this.termsChanged = this.termsChanged.bind(this);
this.async = new Async(this);
this.validate = this.validate.bind(this);
this.termsFromPickerChanged = this.termsFromPickerChanged.bind(this);
this.notifyAfterValidate = this.notifyAfterValidate.bind(this);
this.delayedValidate = this.async.debounce(this.validate, this.props.deferredValidationTime);
}
/**
* Loads the list from SharePoint current web site
*/
loadTermStores() {
this.termsService.getTermStores().then((response) => {
// Check if a response was retrieved
if (response !== null) {
this.setState({
termStores: response,
loaded: true
});
}
else {
this.setState({
termStores: [],
loaded: true
});
}
}).catch(() => { });
}
/**
* Validates the new custom field value
*/
validate(value) {
if (this.props.onGetErrorMessage === null || this.props.onGetErrorMessage === undefined) {
this.notifyAfterValidate(this.props.initialValues, value);
return;
}
const result = this.props.onGetErrorMessage(value || []);
if (typeof result !== 'undefined') {
if (typeof result === 'string') {
if (result === '') {
this.notifyAfterValidate(this.props.initialValues, value);
}
this.setState({
errorMessage: result
});
}
else {
result.then((errorMessage) => {
if (typeof errorMessage === 'undefined' || errorMessage === '') {
this.notifyAfterValidate(this.props.initialValues, value);
}
this.setState({
errorMessage: errorMessage
});
}).catch(() => { });
}
}
else {
this.notifyAfterValidate(this.props.initialValues, value);
}
}
/**
* Notifies the parent Web Part of a property value change
*/
notifyAfterValidate(oldValue, newValue) {
if (this.props.onPropertyChange && newValue !== null) {
setPropertyValue(this.props.properties, this.props.targetProperty, newValue);
this.props.onPropertyChange(this.props.targetProperty, oldValue, newValue);
// Trigger the apply button
if (typeof this.props.onChange !== 'undefined' && this.props.onChange !== null) {
this.props.onChange(this.props.targetProperty, newValue);
}
}
}
/**
* Open the right Panel
*/
onOpenPanel() {
if (this.props.disabled === true) {
return;
}
// Store the current code value
this.previousValues = cloneDeep(this.state.activeNodes);
this.cancel = true;
this.loadTermStores();
this.setState({
openPanel: true,
loaded: false
});
}
/**
* Close the panel
*/
onClosePanel() {
this.setState(() => {
const newState = {
openPanel: false,
loaded: false
};
// Check if the property has to be reset
if (this.cancel) {
newState.activeNodes = this.previousValues;
}
return newState;
});
}
/**
* On save click action
*/
onSave() {
this.cancel = false;
this.delayedValidate(this.state.activeNodes);
this.onClosePanel();
}
/**
* Clicks on a node
* @param node
*/
termsChanged(term, termGroup, checked) {
let activeNodes = this.state.activeNodes;
if (typeof term === 'undefined' || term === null) {
return;
}
// Term item to add to the active nodes array
const termItem = {
name: term.Name,
key: term.Id,
path: term.PathOfTerm,
termSet: term.TermSet.Id,
termGroup: termGroup,
labels: term.Labels
};
// Check if the term is checked or unchecked
if (checked) {
// Check if it is allowed to select multiple terms
if (this.props.allowMultipleSelections) {
// Add the checked term
activeNodes.push(termItem);
// Filter out the duplicate terms
activeNodes = uniqBy(activeNodes, 'key');
}
else {
// Only store the current selected item
activeNodes = [termItem];
}
}
else {
// Remove the term from the list of active nodes
activeNodes = activeNodes.filter(item => item.key !== term.Id);
}
// Sort all active nodes
activeNodes = sortBy(activeNodes, 'path');
// Update the current state
this.setState({
activeNodes: activeNodes
});
}
/**
* Fires When Items Changed in TermPicker
* @param node
*/
termsFromPickerChanged(terms) {
this.delayedValidate(terms);
this.setState({
activeNodes: terms
});
}
/**
* Called when the component will unmount
*/
componentWillUnmount() {
if (typeof this.async !== 'undefined') {
this.async.dispose();
}
}
/**
* Renders the SPListpicker controls with Office UI Fabric
*/
render() {
return (React.createElement("div", null,
this.props.label && React.createElement(Label, null, this.props.label),
React.createElement("table", { className: styles.termFieldTable },
React.createElement("tbody", null,
React.createElement("tr", null,
React.createElement("td", null,
React.createElement(TermPicker, { context: this.props.context, termPickerHostProps: this.props, disabled: this.props.disabled, value: this.state.activeNodes, onChanged: this.termsFromPickerChanged, allowMultipleSelections: this.props.allowMultipleSelections, areTermsSelectable: this.props.areTermsSelectable, areTermsHidden: this.props.areTermsHidden, isTermSetSelectable: this.props.isTermSetSelectable, disabledTermIds: this.props.disabledTermIds, termsService: this.termsService, resolveDelay: this.props.resolveDelay === undefined ? 500 : this.props.resolveDelay })),
React.createElement("td", { className: styles.termFieldRow },
React.createElement(IconButton, { disabled: this.props.disabled, iconProps: { iconName: 'Tag' }, onClick: this.onOpenPanel }))))),
React.createElement(FieldErrorMessage, { errorMessage: this.state.errorMessage }),
React.createElement(Panel, { isOpen: this.state.openPanel, hasCloseButton: true, onDismiss: this.onClosePanel, isLightDismiss: true, type: PanelType.medium, headerText: this.props.panelTitle, onRenderFooterContent: () => {
return (React.createElement("div", { className: styles.actions },
React.createElement(PrimaryButton, { iconProps: { iconName: 'Save' }, text: strings.SaveButtonLabel, value: strings.SaveButtonLabel, onClick: this.onSave }),
React.createElement(DefaultButton, { iconProps: { iconName: 'Cancel' }, text: strings.CancelButtonLabel, value: strings.CancelButtonLabel, onClick: this.onClosePanel })));
} },
/* Show spinner in the panel while retrieving terms */
this.state.loaded === false ? React.createElement(Spinner, { size: SpinnerSize.medium }) : '',
/* Once the state is loaded, start rendering the term store, group, term sets */
this.state.loaded === true ? this.state.termStores.map((termStore, index) => {
return (React.createElement("div", { key: termStore.Id },
!this.props.hideTermStoreName ? React.createElement("h3", null, termStore.Name) : null,
termStore.Groups && termStore.Groups._Child_Items_ && termStore.Groups._Child_Items_.map((group) => {
return React.createElement(TermGroup, { key: group.Id, group: group, termstore: termStore.Id, termsService: this.termsService, activeNodes: this.state.activeNodes, changedCallback: this.termsChanged, multiSelection: this.props.allowMultipleSelections, isTermSetSelectable: this.props.isTermSetSelectable, areTermsSelectable: this.props.areTermsSelectable, areTermsHidden: this.props.areTermsHidden, disabledTermIds: this.props.disabledTermIds, anchorId: this.props.anchorId });
})));
}) : '')));
}
}
//# sourceMappingURL=PropertyFieldTermPickerHost.js.map