Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added onNewTerm called when enter is pressed #967

Merged
merged 3 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/documentation/docs/controls/TaxonomyPicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ private onTaxPickerChange(terms : IPickerTerms) {
}
```

- With the `onNewTerm` property you can capture the event, when text is in the input field and
enter/return is pressed. With a controlled TaxonomyPicker, this enables you to create the term
and have the same flow as in SharePoint Keywords fields.

```typescript
const onNewTerm = (value: IPickerTerm): void => {
if(value?.name && EmptyGuid === value.key ){
console.log(`TaxonmyPicker.onNewTerm name=${value.name}`, value );
// Create keyword
} else {
console.error(`TaxonmyPicker.onNewTerm name=${value?.name}`, value );
}
};
```

## Term actions

Since version `1.12.0`, you can apply term actions to all terms or specific ones. Term actions could for instance be used to retrieve the labels of the term, or retrieve other information. These term actions can be implemented as follows:
Expand Down Expand Up @@ -158,6 +173,7 @@ The TaxonomyPicker control can be configured with the following properties:
| allowMultipleSelections | boolean | no | Defines if the user can select only one or many term sets. Default value is false. |
| termsetNameOrID | string | yes | The name or Id of your TermSet that you would like the Taxonomy Picker to chose terms from. |
| onChange | function | no | captures the event of when the terms in the picker has changed. |
| onNewTerm | function | no | captures the event when text is in the input field and the user presses return |
| isTermSetSelectable | boolean | no | Specify if the TermSet itself is selectable in the tree view. |
| disabledTermIds | string[] | no | Specify which terms should be disabled in the term set so that they cannot be selected. |
| disableChildrenOfDisabledParents | boolean | no | Specify if you want to disable the child terms when their parent is disabled. |
Expand Down
7 changes: 6 additions & 1 deletion src/controls/taxonomyPicker/ITaxonomyPicker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IPickerTerms } from './ITermPicker';
import { IPickerTerm, IPickerTerms } from './ITermPicker';
import { ITermSet, ITerm } from '../../services/ISPTermStorePickerService';
import { ITermActions } from './termActions/ITermsActions';
import SPTermStorePickerService from '../../services/SPTermStorePickerService';
Expand Down Expand Up @@ -104,6 +104,11 @@ export interface ITaxonomyPickerProps {
*/
onGetErrorMessage?: (value: IPickerTerms) => string | Promise<string>;

/**
* Called when text is in the input field and the enter key is pressed.
*/
onNewTerm?: (value: IPickerTerm) => void;

/**
* Static error message displayed below the text field. Use onGetErrorMessage to dynamically change the error message displayed (if any) based on the current value. errorMessage and onGetErrorMessage are mutually exclusive (errorMessage takes precedence).
*/
Expand Down
57 changes: 50 additions & 7 deletions src/controls/taxonomyPicker/TaxonomyPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import TermParent from './TermParent';
import FieldErrorMessage from '../errorMessage/ErrorMessage';
import { initializeIcons } from '@uifabric/icons';
import * as telemetry from '../../common/telemetry';
import { EmptyGuid } from '../../common/Constants';

/**
* Image URLs / Base64
Expand Down Expand Up @@ -363,22 +364,63 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
return input;
}

private async validateOnGetErrorMessage(targetValue: string): Promise<boolean> {
const errorMessage = await this.props.onGetErrorMessage(
[
{
key: EmptyGuid,
name: targetValue,
path: targetValue,
termSet: this.termsService.cleanGuid(this.props.termsetNameOrID)
}
]
);

if (!!errorMessage) {
this.setState({
errorMessage: errorMessage
});
}
else {
this.setState({
errorMessage: null
});
}
return !errorMessage;
}

private onNewTerm = (newLabel: string) => {
this.props.onNewTerm(
{
key: EmptyGuid,
name: newLabel,
path: newLabel,
termSet: this.termsService.cleanGuid(this.props.termsetNameOrID)
}
);
}

/**
* Triggers when taxonomy picker control loses focus
*/
private onBlur(event: React.FocusEvent<HTMLElement | Autofill>): void {
private async onBlur(event: React.FocusEvent<HTMLElement | Autofill>): Promise<void> {
const { validateInput } = this.props;
if (!!validateInput) {
// Perform validation of input text, only if taxonomy picker is configured with validateInput={true} property.
const target: HTMLInputElement = event.target as HTMLInputElement;
const targetValue = !!target ? target.value : null;
if (!!targetValue) {
this.invalidTerm = targetValue;
}
else {
this.invalidTerm = null;

if(!!this.props.onGetErrorMessage && !!targetValue) {
await this.validateOnGetErrorMessage(targetValue);
} else {
if (!!targetValue) {
this.invalidTerm = targetValue;
}
else {
this.invalidTerm = null;
}
this.validateInputText();
}
this.validateInputText();
}
}

Expand Down Expand Up @@ -534,6 +576,7 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
onChanged={this.termsFromPickerChanged}
onInputChange={this.onInputChange}
onBlur={this.onBlur}
onNewTerm={this.props.onNewTerm ? this.onNewTerm : undefined}
allowMultipleSelections={allowMultipleSelections}
disabledTermIds={disabledTermIds}
disableChildrenOfDisabledParents={disableChildrenOfDisabledParents}
Expand Down
38 changes: 34 additions & 4 deletions src/controls/taxonomyPicker/TermPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { BasePicker, IBasePickerProps, IPickerItemProps } from 'office-ui-fabric-react/lib/Pickers';
import { BasePicker, IBasePickerProps, IInputProps, IPickerItemProps } from 'office-ui-fabric-react/lib/Pickers';
import { IPickerTerm, IPickerTerms } from './ITermPicker';
import SPTermStorePickerService from './../../services/SPTermStorePickerService';
import styles from './TaxonomyPicker.module.scss';
Expand All @@ -9,6 +9,7 @@ import * as strings from 'ControlStrings';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { ITermSet } from "../../services/ISPTermStorePickerService";
import { Autofill } from 'office-ui-fabric-react/lib/components/Autofill/Autofill';
import { LegacyRef, KeyboardEvent } from 'react';

export class TermBasePicker extends BasePicker<IPickerTerm, IBasePickerProps<IPickerTerm>>
{
Expand All @@ -17,6 +18,7 @@ export class TermBasePicker extends BasePicker<IPickerTerm, IBasePickerProps<IPi

export interface ITermPickerState {
terms: IPickerTerms;
elRef?: LegacyRef<TermBasePicker> & LegacyRef<any>;
}

export interface ITermPickerProps {
Expand All @@ -30,6 +32,8 @@ export interface ITermPickerProps {
disableChildrenOfDisabledParents?: boolean;
placeholder?: string;

/** Called when text is in the input field and the enter key is pressed. */
onNewTerm?: (newLabel: string) => void;
onChanged: (items: IPickerTerm[]) => void;
onInputChange: (input: string) => string;
onBlur: (ev: React.FocusEvent<HTMLElement | Autofill>) => void;
Expand Down Expand Up @@ -201,6 +205,7 @@ export default class TermPicker extends React.Component<ITermPickerProps, ITermP
onChanged,
onInputChange,
onBlur,
onNewTerm,
allowMultipleSelections,
placeholder
} = this.props;
Expand All @@ -209,9 +214,36 @@ export default class TermPicker extends React.Component<ITermPickerProps, ITermP
terms
} = this.state;

const clearDisplayValue = () => {
const picker = this.state.elRef as unknown as TermBasePicker;
const autoFill = picker?.['input']?.current as Autofill;
if (autoFill) {
autoFill['_value'] = '';
autoFill.setState({ displayValue: '' });
} else {
throw new Error(`TermPicker.TermBasePicker.render.clearDisplayValue no autoFill to reset displayValue`);
}
};

const inputProps: IInputProps = { placeholder: placeholder };

if(onNewTerm) {
inputProps.onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e && e.key === 'Enter' && (! (e.ctrlKey || e.altKey || e.shiftKey)) && e.target?.['value'] ) {
onNewTerm(e.target['value']);
clearDisplayValue();
}
};
}

return (
<div>
<TermBasePicker
ref={(elRef) => {
if (!this.state.elRef) {
this.setState({ elRef });
}
}}
disabled={disabled}
onResolveSuggestions={this.onFilterChanged}
onRenderSuggestionsItem={this.onRenderSuggestionsItem}
Expand All @@ -224,9 +256,7 @@ export default class TermPicker extends React.Component<ITermPickerProps, ITermP
onBlur={onBlur}
itemLimit={!allowMultipleSelections ? 1 : undefined}
className={styles.termBasePicker}
inputProps={{
placeholder: placeholder
}}
inputProps={inputProps}
/>
</div>
);
Expand Down