Skip to content

Commit 340a86b

Browse files
committed
Implemented FieldPicker
1 parent 49998e6 commit 340a86b

File tree

14 files changed

+440
-6
lines changed

14 files changed

+440
-6
lines changed
1.48 KB
Loading
3.89 KB
Loading
3.85 KB
Loading
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# FieldPicker control
2+
3+
This control allows you to select one or multiple available site fields or list fields.
4+
5+
Here is an example of the control:
6+
7+
![FieldPicker initial](../assets/FieldPicker-initial.png)
8+
9+
`FieldPicker` single selection mode:
10+
11+
![FieldPicker single selection](../assets/FieldPicker-single.png)
12+
13+
`FieldPicker` multi selection mode:
14+
15+
![FieldPicker multi selection](../assets/FieldPicker-multi.png)
16+
17+
## How to use this control in your solutions
18+
19+
- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../../#getting-started) page for more information about installing the dependency.
20+
- Import the control into your component:
21+
22+
```TypeScript
23+
import { FieldPicker } from "@pnp/spfx-controls-react/lib/FieldPicker";
24+
```
25+
26+
- Use the `FieldPicker` control in your code as follows:
27+
28+
```TypeScript
29+
<FieldPicker
30+
context={this.props.context}
31+
group="Content Feedback"
32+
includeHidden={false}
33+
includeReadOnly={false}
34+
label="Select your field(s)"
35+
multiSelect={false}
36+
orderBy={FieldsOrderBy.Title}
37+
listId="00000000-0000-0000-0000-000000000000"
38+
onSelectionChanged={this.onFieldPickerChanged}
39+
showBlankOption={true}
40+
/>
41+
```
42+
43+
- The `onSelectionChanged` change event returns the field(s) and can be implemented as follows:
44+
45+
```TypeScript
46+
private onFieldPickerChanged (fields: ISPField | ISPField[]) {
47+
console.log("Fields:", fields);
48+
}
49+
```
50+
51+
## Implementation
52+
53+
The `FieldPicker` control can be configured with the following properties:
54+
55+
| Property | Type | Required | Description |
56+
| --- | --- | --- | --- |
57+
| context | BaseComponentContext | yes | The context object of the SPFx loaded webpart or customizer. |
58+
| listId | string | no | The ID of the list or library you wish to select a column(s) from. When not specified, picker will be populated with site columns.|
59+
| className | string | no | If provided, additional class name to provide on the dropdown element. |
60+
disabled | boolean | no | Whether or not the control is disabled. |
61+
includeHidden | boolean | no | Whether or not to include hidden fields. Default is true. |
62+
includeReadOnly | boolean | no | Whether or not to include read-only fields. Default is true. |
63+
group | string | no | Only show fields of a certain group. |
64+
filter | string | no | Filter fields from OData query (takes the upperhand of Hidden, ReadOnly and Group Filters). |
65+
orderBy | FieldsOrderBy | no | How to order the fields. |
66+
selectedFields | string \| string[] | no | Internal names of the selected item(s). If you provide this, you must maintain selection state by observing onSelectionChanged events and passing a new value in when changed.
67+
multiSelect | boolean | no | Indicates if multi-choice selections is allowed. Default to false. |
68+
label | string | no | The label to display. |
69+
placeholder | string | no | Input placeholder text. Displayed until option is selected. |
70+
onSelectionChanged | (newValue: ISPField \| ISPField[]): void | no | Callback issued when the selected option changes. |
71+
filterItems | (fields: ISPField[]): ISPField[] | no | This function is invoked after the filtering has been done. This allows you to add additional custom filtering.
72+
webAbsoluteUrl | string | no | Absolute Web Url of target site (user requires permissions). |
73+
showBlankOption | boolean | no | Whether or not to show a blank option. Default false. Works only when multiSelect is false. |
74+
75+
Enum `FieldsOrderBy`
76+
77+
| Value |
78+
| ---- |
79+
| Title |
80+
| InternalName |
81+
82+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/FieldPicker)

docs/documentation/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ nav:
3030
- DragDropFiles: 'controls/DragDropFiles.md'
3131
- DynamicForm: 'controls/DynamicForm.md'
3232
- FieldCollectionData: 'controls/FieldCollectionData.md'
33+
- FieldPicker: 'controls/FieldPicker.md'
3334
- FilePicker: 'controls/FilePicker.md'
3435
- FileTypeIcon: 'controls/FileTypeIcon.md'
3536
- FolderExplorer: 'controls/FolderExplorer.md'

src/FieldPicker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './controls/fieldPicker/index';

src/common/SPEntities.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
2-
3-
41
/**
52
* Represents SP ContentType Id
63
*/
@@ -35,6 +32,11 @@ export interface ISPLists {
3532
*/
3633
export interface ISPField {
3734
Id: string;
35+
Title?: string;
36+
InternalName?: string;
37+
Hidden?: boolean;
38+
ReadOnlyField?: boolean;
39+
Group?: string;
3840
Format?: string;
3941
RichText?: boolean;
4042
SchemaXml?: string;
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import { cloneDeep } from "lodash";
2+
import { Dropdown, IDropdownOption, IDropdownProps, Spinner, SpinnerSize } from "office-ui-fabric-react";
3+
import React from "react";
4+
import { ISPField } from "../../common/SPEntities";
5+
import * as telemetry from '../../common/telemetry';
6+
import { ISPService } from "../../services/ISPService";
7+
import { SPServiceFactory } from "../../services/SPServiceFactory";
8+
import { IFieldPickerProps, IFieldPickerState } from "./IFieldPicker";
9+
10+
const EMPTY_FIELD_KEY = 'NO_FIELD_SELECTED';
11+
12+
export class FieldPicker extends React.Component<IFieldPickerProps, IFieldPickerState> {
13+
14+
private _selectedFields: string | string[] = null;
15+
16+
constructor(props: IFieldPickerProps) {
17+
super(props);
18+
19+
telemetry.track('ReactFieldPicker');
20+
21+
this.state = {
22+
fields: [],
23+
loading: false,
24+
};
25+
}
26+
27+
public componentDidMount(): void {
28+
this.loadFields();
29+
}
30+
31+
/**
32+
* Loads the fields from the provided SharePoint site and updates the options state.
33+
*/
34+
private async loadFields(): Promise<void> {
35+
const {
36+
context,
37+
listId,
38+
includeHidden,
39+
includeReadOnly,
40+
orderBy,
41+
filter,
42+
group,
43+
webAbsoluteUrl,
44+
filterItems,
45+
} = this.props;
46+
47+
// Show the loading indicator and disable the dropdown
48+
this.setState({ loading: true });
49+
50+
const service: ISPService = SPServiceFactory.createService(context, true, 5000, webAbsoluteUrl);
51+
let results = await service.getFields(
52+
{
53+
listId,
54+
filter,
55+
includeHidden,
56+
includeReadOnly,
57+
orderBy,
58+
group
59+
}
60+
);
61+
62+
// Check if custom filter is specified
63+
if (filterItems) {
64+
results = filterItems(results);
65+
}
66+
67+
// Hide loading indicator and set the dropdown options.
68+
this.setState({
69+
loading: false,
70+
fields: results,
71+
});
72+
73+
this.setSelectedFields();
74+
}
75+
76+
/**
77+
* Set the currently selected field(s);
78+
*/
79+
private setSelectedFields() {
80+
this._selectedFields = cloneDeep(this.props.selectedFields);
81+
82+
this.setState({
83+
selectedFields: this._selectedFields,
84+
});
85+
}
86+
87+
public componentDidUpdate(prevProps: Readonly<IFieldPickerProps>, prevState: Readonly<IFieldPickerState>): void {
88+
const {
89+
includeHidden,
90+
includeReadOnly,
91+
orderBy,
92+
webAbsoluteUrl,
93+
selectedFields,
94+
listId,
95+
} = this.props;
96+
97+
if (
98+
prevProps.includeHidden !== includeHidden ||
99+
prevProps.includeReadOnly !== includeReadOnly ||
100+
prevProps.orderBy !== orderBy ||
101+
prevProps.webAbsoluteUrl !== webAbsoluteUrl ||
102+
prevProps.listId !== listId
103+
) {
104+
this.loadFields();
105+
}
106+
107+
if (prevProps.selectedFields !== selectedFields) {
108+
this.setSelectedFields();
109+
}
110+
}
111+
112+
/**
113+
* Fires when a field has been selected from the dropdown.
114+
* @param option The new selection.
115+
* @param index Index of the selection.
116+
*/
117+
private onChange = (event: React.FormEvent<HTMLDivElement>, option: IDropdownOption, index?: number): void => {
118+
const { multiSelect, onSelectionChanged } = this.props;
119+
const { fields } = this.state;
120+
121+
if (multiSelect) {
122+
let selectedFields = this._selectedFields ? cloneDeep(this._selectedFields) as string[] : [];
123+
124+
if (option.selected) {
125+
selectedFields.push(option.key.toString());
126+
}
127+
else {
128+
selectedFields = selectedFields.filter(field => field !== option.key);
129+
}
130+
this._selectedFields = selectedFields;
131+
}
132+
else {
133+
this._selectedFields = option.key.toString();
134+
}
135+
136+
if (onSelectionChanged) {
137+
if (multiSelect) {
138+
onSelectionChanged(cloneDeep(fields.filter(f => (this._selectedFields as string[]).some(sf => f.InternalName === sf))));
139+
}
140+
else {
141+
onSelectionChanged(cloneDeep(fields.find(f => f.InternalName === this._selectedFields as string)));
142+
}
143+
}
144+
}
145+
146+
public render(): React.ReactElement<IFieldPickerProps> {
147+
const { loading, fields, selectedFields } = this.state;
148+
const { className, disabled, multiSelect, label, placeholder, showBlankOption } = this.props;
149+
150+
const options: IDropdownOption[] = fields.map(f => ({
151+
key: f.InternalName,
152+
text: f.Title
153+
}));
154+
155+
if (showBlankOption && !multiSelect) {
156+
// Provide empty option
157+
options.unshift({
158+
key: EMPTY_FIELD_KEY,
159+
text: '',
160+
});
161+
}
162+
163+
const dropdownProps: IDropdownProps = {
164+
className,
165+
options,
166+
disabled: loading || disabled,
167+
label,
168+
placeholder,
169+
onChange: this.onChange,
170+
};
171+
172+
if (multiSelect) {
173+
dropdownProps.multiSelect = true;
174+
dropdownProps.selectedKeys = selectedFields as string[];
175+
}
176+
else {
177+
dropdownProps.selectedKey = selectedFields as string;
178+
}
179+
180+
return (
181+
<>
182+
{ loading && <Spinner size={SpinnerSize.xSmall} /> }
183+
<Dropdown {...dropdownProps} />
184+
</>
185+
);
186+
}
187+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
2+
import { BaseComponentContext } from "@microsoft/sp-component-base";
3+
import { FieldsOrderBy } from "../../services/ISPService";
4+
import { ISPField } from "../../common/SPEntities";
5+
6+
export interface IFieldPickerProps {
7+
/**
8+
* The web part context
9+
*/
10+
context: BaseComponentContext;
11+
/**
12+
* The ID of the list or library you wish to select a column(s) from.
13+
* When not specified, picker will be populated with site columns.
14+
*/
15+
listId?: string;
16+
/**
17+
* If provided, additional class name to provide on the dropdown element.
18+
*/
19+
className?: string;
20+
/**
21+
* Whether or not the control is disabled.
22+
*/
23+
disabled?: boolean;
24+
/**
25+
* Whether or not to include hidden fields. Default is true.
26+
*/
27+
includeHidden?: boolean;
28+
/**
29+
* Whether or not to include read-only fields. Default is true.
30+
*/
31+
includeReadOnly?: boolean;
32+
/**
33+
* Only show fields of a certain group.
34+
*/
35+
group?: string;
36+
/**
37+
* Filter fields from OData query (takes the upperhand of Hidden, ReadOnly and Group Filters).
38+
*/
39+
filter?: string;
40+
/**
41+
* How to order the fields.
42+
*/
43+
orderBy?: FieldsOrderBy;
44+
/**
45+
* Internal names of the selected item(s). If you provide this, you must maintain selection
46+
* state by observing onSelectionChanged events and passing a new value in when changed.
47+
*/
48+
selectedFields?: string | string[];
49+
/**
50+
* Indicates if multi-choice selections is allowed. Default to false.
51+
*/
52+
multiSelect?: boolean;
53+
/**
54+
* The label to display.
55+
*/
56+
label?: string;
57+
/**
58+
* Input placeholder text. Displayed until option is selected.
59+
*/
60+
placeholder?: string;
61+
/**
62+
* Callback issued when the selected option changes.
63+
*/
64+
onSelectionChanged?: (newValue: ISPField | ISPField[]) => void;
65+
/**
66+
* This function is invoked after the filtering has been done.
67+
* This allows you to add additional custom filtering.
68+
*/
69+
filterItems?: (fields: ISPField[]) => ISPField[];
70+
/**
71+
* Absolute Web Url of target site (user requires permissions).
72+
*/
73+
webAbsoluteUrl?: string;
74+
/**
75+
* Whether or not to show a blank option. Default false. Works only when multiSelect is false.
76+
*/
77+
showBlankOption?: boolean;
78+
}
79+
80+
export interface IFieldPickerState {
81+
/**
82+
* Whether or not the fieldPicker options are loading.
83+
*/
84+
loading: boolean;
85+
/**
86+
* The fields available to the listPicker.
87+
*/
88+
fields: ISPField[];
89+
/**
90+
* Keys of the currently selected item(s).
91+
*/
92+
selectedFields?: string | string[];
93+
}

src/controls/fieldPicker/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './IFieldPicker';
2+
export * from './FieldPicker';

0 commit comments

Comments
 (0)