Skip to content

Commit ef5e786

Browse files
committed
Add contentTypePicker control
1 parent 99ffea4 commit ef5e786

File tree

15 files changed

+450
-12
lines changed

15 files changed

+450
-12
lines changed
1.45 KB
Loading
4.8 KB
Loading
3.63 KB
Loading
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# ContentTypePicker control
2+
3+
This control allows you to select one or multiple available site content types or list content types.
4+
5+
Here is an example of the control:
6+
7+
![ContentTypePicker initial](../assets/ContentTypePicker-initial.png)
8+
9+
`ContentTypePicker` single selection mode:
10+
11+
![ContentTypePicker single selection](../assets/ContentTypePicker-single.png)
12+
13+
`ContentTypePicker` multi selection mode:
14+
15+
![ContentTypePicker multi selection](../assets/ContentTypePicker-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 { ContentTypePicker } from "@pnp/spfx-controls-react/lib/ContentTypePicker";
24+
```
25+
26+
- Use the `ContentTypePicker` control in your code as follows:
27+
28+
```TypeScript
29+
<ContentTypePicker
30+
context={this.props.context}
31+
group="Content Feedback"
32+
includeHidden={false}
33+
includeReadOnly={false}
34+
label="Select your content type"
35+
multiSelect={false}
36+
orderBy={ContentTypesOrderBy.Name}
37+
listId="00000000-0000-0000-0000-000000000000"
38+
onSelectionChanged={this.onContentTypePickerChanged}
39+
showBlankOption={true}
40+
/>
41+
```
42+
43+
- The `onSelectionChanged` change event returns the content type(s) and can be implemented as follows:
44+
45+
```TypeScript
46+
private onContentTypePickerChanged (contentTypes: ISPContentType | ISPContentType[]) {
47+
console.log("Content types:", contentTypes);
48+
}
49+
```
50+
51+
## Implementation
52+
53+
The `ContentTypePicker` 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 content type(s) from. When not specified, picker will be populated with site content types.|
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 content types. Default is true. |
62+
includeReadOnly | boolean | no | Whether or not to include read-only content types. Default is true. |
63+
group | string | no | Only show content types of a certain group. |
64+
filter | string | no | Filter content types from OData query (takes the upperhand of `hidden`, `readOnly` and `group` Filters). |
65+
orderBy | ContentTypesOrderBy | no | How to order the content types. |
66+
selectedContentTypes | string \| string[] | no | IDs 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 is false. |
68+
label | string | no | The label to display. |
69+
placeholder | string | no | Input placeholder text. Displayed until option is selected. |
70+
onSelectionChanged | (newValue: ISPContentType \| ISPContentType[]): void | no | Callback issued when the selected option changes. |
71+
filterItems | (contentTypes: ISPContentType[]): ISPContentType[] | 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 is false. Works only when `multiSelect` is false. |
74+
75+
Enum `ContentTypesOrderBy`
76+
77+
| Value |
78+
| ---- |
79+
| Name |
80+
| Id |
81+
82+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/ContentTypePicker)

docs/documentation/docs/controls/FieldPicker.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,16 @@ disabled | boolean | no | Whether or not the control is disabled. |
6161
includeHidden | boolean | no | Whether or not to include hidden fields. Default is true. |
6262
includeReadOnly | boolean | no | Whether or not to include read-only fields. Default is true. |
6363
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). |
64+
filter | string | no | Filter fields from OData query (takes the upperhand of `hidden`, `readOnly` and `group` Filters). |
6565
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. |
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 is false. |
6868
label | string | no | The label to display. |
6969
placeholder | string | no | Input placeholder text. Displayed until option is selected. |
7070
onSelectionChanged | (newValue: ISPField \| ISPField[]): void | no | Callback issued when the selected option changes. |
7171
filterItems | (fields: ISPField[]): ISPField[] | no | This function is invoked after the filtering has been done. This allows you to add additional custom filtering.
7272
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. |
73+
showBlankOption | boolean | no | Whether or not to show a blank option. Default is false. Works only when `multiSelect` is false. |
7474

7575
Enum `FieldsOrderBy`
7676

docs/documentation/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ nav:
2525
- "Radar Chart": 'controls/charts/RadarChart.md'
2626
- "Scatter Chart": 'controls/charts/ScatterChart.md'
2727
- ComboBoxListItemPicker: 'controls/ComboBoxListItemPicker.md'
28+
- ContentTypePicker: 'controls/ContentTypePicker.md'
2829
- Dashboard: 'controls/Dashboard.md'
2930
- DateTimePicker: 'controls/DateTimePicker.md'
3031
- DragDropFiles: 'controls/DragDropFiles.md'

src/ContentTypePicker.ts

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

src/common/SPEntities.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,17 @@
77
/**
88
* Represents SP List ContentType
99
*/
10-
export interface ISPListContentType {
10+
export interface ISPContentType {
1111
Id: ISPContentTypeId;
12+
Name?: string;
13+
Description?: string;
14+
Group?: string;
15+
Hidden?: boolean;
16+
ReadOnly?: boolean;
17+
StringId?: string;
18+
DocumentTemplate?: string;
19+
DocumentTemplateUrl?: string;
20+
SchemaXml?: string;
1221
}
1322
/**
1423
* Represents SP List
@@ -17,7 +26,7 @@ export interface ISPList {
1726
Id: string;
1827
Title: string;
1928
BaseTemplate: string;
20-
ContentTypes? :ISPListContentType[];
29+
ContentTypes? :ISPContentType[];
2130
}
2231

2332
/**
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import * as telemetry from '../../common/telemetry';
2+
import React from 'react';
3+
import { IContentTypePickerProps, IContentTypePickerState } from './IContentTypePicker';
4+
import { ISPService } from '../../services/ISPService';
5+
import { SPServiceFactory } from '../../services/SPServiceFactory';
6+
import { cloneDeep } from 'lodash';
7+
import { Dropdown, IDropdownOption, IDropdownProps, Spinner, SpinnerSize } from 'office-ui-fabric-react';
8+
9+
const EMPTY_CONTENTTYPE_KEY = 'NO_CONTENTTYPE_SELECTED';
10+
11+
export class ContentTypePicker extends React.Component<IContentTypePickerProps, IContentTypePickerState> {
12+
13+
private _selectedContentTypes: string | string[] = null;
14+
15+
constructor(props: IContentTypePickerProps) {
16+
super(props);
17+
18+
telemetry.track('ReactContentTypePicker');
19+
20+
this.state = {
21+
contentTypes: [],
22+
loading: false,
23+
};
24+
}
25+
26+
public componentDidMount(): void {
27+
this.loadContentTypes();
28+
}
29+
30+
private async loadContentTypes(): Promise<void> {
31+
const {
32+
context,
33+
listId,
34+
includeHidden,
35+
includeReadOnly,
36+
orderBy,
37+
filter,
38+
group,
39+
webAbsoluteUrl,
40+
filterItems,
41+
} = this.props;
42+
43+
// Show the loading indicator and disable the dropdown
44+
this.setState({ loading: true });
45+
46+
const service: ISPService = SPServiceFactory.createService(context, true, 5000, webAbsoluteUrl);
47+
let results = await service.getContentTypes(
48+
{
49+
listId,
50+
filter,
51+
includeHidden,
52+
includeReadOnly,
53+
orderBy,
54+
group,
55+
}
56+
);
57+
58+
// Check if custom filter is specified
59+
if (filterItems) {
60+
results = filterItems(results);
61+
}
62+
63+
// Hide the loading indicator and set the dropdown options
64+
this.setState({
65+
loading: false,
66+
contentTypes: results,
67+
});
68+
69+
this.setSelectedContentTypes();
70+
}
71+
72+
/**
73+
* Set the currently selected content type(s).
74+
*/
75+
private setSelectedContentTypes(): void {
76+
this._selectedContentTypes = cloneDeep(this.props.selectedContentTypes);
77+
78+
this.setState({
79+
selectedContentTypes: this._selectedContentTypes,
80+
});
81+
}
82+
83+
public componentDidUpdate(prevProps: Readonly<IContentTypePickerProps>, prevState: Readonly<IContentTypePickerState>): void {
84+
const {
85+
includeHidden,
86+
includeReadOnly,
87+
orderBy,
88+
webAbsoluteUrl,
89+
selectedContentTypes,
90+
listId,
91+
} = this.props;
92+
93+
if (
94+
prevProps.includeHidden !== includeHidden ||
95+
prevProps.includeReadOnly !== includeReadOnly ||
96+
prevProps.orderBy !== orderBy ||
97+
prevProps.webAbsoluteUrl !== webAbsoluteUrl ||
98+
prevProps.listId !== listId
99+
) {
100+
this.loadContentTypes();
101+
}
102+
103+
if (prevProps.selectedContentTypes !== selectedContentTypes) {
104+
this.setSelectedContentTypes();
105+
}
106+
}
107+
108+
/**
109+
* Fires when an item has been selected from the dropdown.
110+
* @param event Event that has been fired.
111+
* @param option The new selection.
112+
* @param index Index of the selection.
113+
*/
114+
private onChange = (event: React.FormEvent<HTMLDivElement>, option: IDropdownOption, index?: number): void => {
115+
const { multiSelect, onSelectionChanged } = this.props;
116+
const { contentTypes } = this.state;
117+
118+
if (multiSelect) {
119+
let selectedContentTypes = this._selectedContentTypes ? cloneDeep(this._selectedContentTypes) as string[] : [];
120+
121+
if (option.selected) {
122+
selectedContentTypes.push(option.key.toString());
123+
}
124+
else {
125+
selectedContentTypes = selectedContentTypes.filter(ct => ct !== option.key);
126+
}
127+
this._selectedContentTypes = selectedContentTypes;
128+
}
129+
else {
130+
this._selectedContentTypes = option.key.toString();
131+
}
132+
133+
if (onSelectionChanged) {
134+
if (multiSelect) {
135+
onSelectionChanged(cloneDeep(contentTypes.filter(ct => (this._selectedContentTypes as string[]).some(sct => ct.StringId === sct))));
136+
}
137+
else {
138+
onSelectionChanged(cloneDeep(contentTypes.find(ct => ct.StringId === this._selectedContentTypes as string)));
139+
}
140+
}
141+
}
142+
143+
public render(): React.ReactElement<IContentTypePickerProps> {
144+
const { loading, contentTypes, selectedContentTypes } = this.state;
145+
const { className, disabled, multiSelect, label, placeholder, showBlankOption } = this.props;
146+
147+
const options: IDropdownOption[] = contentTypes.map(f => ({
148+
key: f.StringId,
149+
text: f.Name,
150+
}));
151+
152+
if (showBlankOption && !multiSelect) {
153+
// Provide empty option
154+
options.unshift({
155+
key: EMPTY_CONTENTTYPE_KEY,
156+
text: '',
157+
});
158+
}
159+
160+
const dropdownProps: IDropdownProps = {
161+
className,
162+
options,
163+
disabled: loading || disabled,
164+
label,
165+
placeholder,
166+
onChange: this.onChange,
167+
};
168+
169+
if (multiSelect) {
170+
dropdownProps.multiSelect = true;
171+
dropdownProps.selectedKeys = selectedContentTypes as string[];
172+
}
173+
else {
174+
dropdownProps.selectedKey = selectedContentTypes as string;
175+
}
176+
177+
return (
178+
<>
179+
{loading && <Spinner size={SpinnerSize.xSmall} />}
180+
<Dropdown {...dropdownProps} />
181+
</>
182+
);
183+
}
184+
}

0 commit comments

Comments
 (0)