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

PCF Component for Adding and Managing Keywords #361

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
26 changes: 26 additions & 0 deletions KeywordTagger/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"env": {
"browser": true,
"es2020": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:promise/recommended"
],
"globals": {
"ComponentFramework": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"plugins": [
"@microsoft/power-apps",
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-unused-vars": "off"
}
}
23 changes: 23 additions & 0 deletions KeywordTagger/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules

# generated directory
**/generated

# output directory
/out

# msbuild output directories
/bin
/obj

# MSBuild Binary and Structured Log
*.binlog

# Visual Studio cache/options directory
/.vs

# macos
.DS_Store
8 changes: 8 additions & 0 deletions KeywordTagger/KeywordTaggerComponent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# msbuild output directories
/bin
/obj

# MSBuild Binary and Structured Log
*.binlog
11 changes: 11 additions & 0 deletions KeywordTagger/KeywordTaggerComponent/ControlManifest.Input.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="MultiLabelNS" constructor="KeywordTagger" version="0.0.1" display-name-key="KeywordTagger" description-key="A component for adding and managing keywords, stored as comma-separated values" control-type="standard">
<external-service-usage enabled="false">
</external-service-usage>
<property name="inputField" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
<resources>
<code path="index.ts" order="1"/>
</resources>
</control>
</manifest>
82 changes: 82 additions & 0 deletions KeywordTagger/KeywordTaggerComponent/KeywordTaggerComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as React from "react";
import { TagPicker, ITag, IBasePickerSuggestionsProps } from "@fluentui/react";

export interface IKeywordTaggerComponentProps {
name?: string;
inputField?: string;
updateValue: (value: ITag[]) => void;
}

const pickerSuggestionsProps: IBasePickerSuggestionsProps = {
suggestionsHeaderText: "",
noResultsFoundText: "",
};

export const KeywordTaggerComponent: React.FC<IKeywordTaggerComponentProps> = ({
name,
inputField,
updateValue,
}) => {
const [tags, setTags] = React.useState<ITag[]>([]);
const [inputValue, setInputValue] = React.useState<string>("");

React.useEffect(() => {
if (inputField) {
const initialTags = inputField
.split(",")
.map((tag) => ({ key: tag.trim(), name: tag.trim() }));
setTags(initialTags);
updateValue(initialTags);
}
}, [inputField, updateValue]);

const onTagChange = (items?: ITag[]) => {
setTags(items || []);
updateValue(items || []);
};

const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
const newTag: ITag = {
key: event.currentTarget.value,
name: event.currentTarget.value,
};
const updatedTags = [...tags, newTag];
setTags(updatedTags);
updateValue(updatedTags);
setInputValue(""); // Clear the input value
event.preventDefault();
}
};

return (
<div>
<TagPicker
onResolveSuggestions={() => []}
onEmptyResolveSuggestions={() => []}
selectedItems={tags}
onChange={onTagChange}
inputProps={{
onKeyDown: onInputKeyDown,
value: inputValue, // Bind the input value to the state
placeholder: "Type and press enter to add a tag",
}}
pickerSuggestionsProps={pickerSuggestionsProps}
styles={{
root: {
borderRadius: "5px",
},
text: {
borderRadius: "5px",
},
input: {
borderRadius: "5px",
},
itemsWrapper: {
borderRadius: "5px",
},
}}
/>
</div>
);
};
61 changes: 61 additions & 0 deletions KeywordTagger/KeywordTaggerComponent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import * as React from "react";
import { createRoot, Root } from "react-dom/client";
import { ITag } from "@fluentui/react/lib/Pickers";

import {
IKeywordTaggerComponentProps,
KeywordTaggerComponent,
} from "./KeywordTaggerComponent";

export class KeywordTagger
implements ComponentFramework.StandardControl<IInputs, IOutputs>
{
private container: HTMLDivElement;
private tags: ITag[] = [];
private notifyOutputChanged: () => void;
private root: Root;
private _value: string | undefined;

constructor() {}

init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary,
container: HTMLDivElement
) {
this.container = container;
this.notifyOutputChanged = notifyOutputChanged;
this.root = createRoot(this.container);
this.renderControl(context);
}

public updateView(context: ComponentFramework.Context<IInputs>): void {
this.renderControl(context);
}

private renderControl(context: ComponentFramework.Context<IInputs>) {
const props: IKeywordTaggerComponentProps = {
name: context.parameters.inputField.raw || "",
inputField: context.parameters.inputField.raw || "",
updateValue: this.updateValue.bind(this),
};
this.root.render(React.createElement(KeywordTaggerComponent, props));
}

private updateValue(value: ITag[]) {
this._value = value.map((tag) => tag.name).join(", ");
this.notifyOutputChanged();
}

public getOutputs(): IOutputs {
return {
inputField: this._value,
};
}

public destroy(): void {
this.root.unmount();
}
}
Loading