Skip to content

Commit c6513bd

Browse files
Issue-1439 - New Control ViewPicker
1 parent 7bda9d5 commit c6513bd

File tree

9 files changed

+417
-0
lines changed

9 files changed

+417
-0
lines changed

src/ViewPicker.ts

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

src/common/SPEntities.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,15 @@ export interface IUploadImageResult {
206206
ServerRelativeUrl: string;
207207
UniqueId: string;
208208
}
209+
210+
export interface ISPView {
211+
Id: string;
212+
Title: string;
213+
}
214+
215+
/**
216+
* Defines a collection of SharePoint list views
217+
*/
218+
export interface ISPViews {
219+
value: ISPView[];
220+
}

src/common/utilities/GeneralHelper.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as strings from 'ControlStrings';
44

55
export const IMG_SUPPORTED_EXTENSIONS = ".gif,.jpg,.jpeg,.bmp,.dib,.tif,.tiff,.ico,.png,.jxr,.svg";
66

7+
import * as _ from '@microsoft/sp-lodash-subset';
78
/**
89
* Helper with general methods to simplify some routines
910
*/
@@ -405,3 +406,15 @@ export function dateToNumber(date: string | number | Date): number {
405406

406407
return dateObj.getTime();
407408
}
409+
410+
export const setPropertyValue = (properties: any, targetProperty: string, value: any): void => { // eslint-disable-line @typescript-eslint/no-explicit-any
411+
if (!properties) {
412+
return;
413+
}
414+
if (targetProperty.indexOf('.') === -1) { // simple prop
415+
properties[targetProperty] = value;
416+
}
417+
else {
418+
_.set(properties, targetProperty, value);
419+
}
420+
};
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { BaseComponentContext } from '@microsoft/sp-component-base';
2+
import { ISPView } from "../../../src/common/SPEntities";
3+
import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
4+
5+
6+
/**
7+
* Enum for specifying how the views should be sorted
8+
*/
9+
export enum PropertyFieldViewPickerOrderBy {
10+
Id = 1,
11+
Title
12+
}
13+
14+
export interface IViewPickerProps {
15+
/**
16+
* Context of the current web part
17+
*/
18+
context: BaseComponentContext;
19+
20+
/**
21+
* If provided, additional class name to provide on the dropdown element.
22+
*/
23+
className?: string;
24+
25+
/**
26+
* Custom Field will start to validate after users stop typing for `deferredValidationTime` milliseconds.
27+
* Default value is 200.
28+
*/
29+
deferredValidationTime?: number;
30+
31+
/**
32+
* Whether the property pane field is enabled or not.
33+
*/
34+
disabled?: boolean;
35+
36+
/**
37+
* Filter views from Odata query
38+
*/
39+
filter?: string;
40+
41+
/**
42+
* Property field label displayed on top
43+
*/
44+
label: string;
45+
46+
/**
47+
* The List Id of the list where you want to get the views
48+
*/
49+
listId?: string;
50+
51+
/**
52+
* Input placeholder text. Displayed until option is selected.
53+
*/
54+
placeholder?: string;
55+
56+
/**
57+
* Specify the property on which you want to order the retrieve set of views.
58+
*/
59+
orderBy?: PropertyFieldViewPickerOrderBy;
60+
61+
/**
62+
* Initial selected view of the control
63+
*/
64+
selectedView?: string;
65+
66+
/**
67+
* Whether or not to show a blank option. Default false.
68+
*/
69+
showBlankOption?: boolean;
70+
71+
/**
72+
* Defines view titles which should be excluded from the view picker control
73+
*/
74+
viewsToExclude?: string[];
75+
76+
/**
77+
* Absolute Web Url of target site (user requires permissions)
78+
*/
79+
webAbsoluteUrl?: string;
80+
81+
82+
83+
/**
84+
* Defines a onPropertyChange function to raise when the selected value changed.
85+
* Normally this function must be always defined with the 'this.onPropertyChange'
86+
* method of the web part object.
87+
*/
88+
onPropertyChange? : (newValue: string | string[]) => void;
89+
/**
90+
* Callback that is called before the dropdown is populated
91+
*/
92+
onViewsRetrieved?: (views: ISPView[]) => PromiseLike<ISPView[]> | ISPView[];
93+
94+
}
95+
96+
export interface IViewPickerState {
97+
results: IDropdownOption[];
98+
selectedKey?: string | string[];
99+
errorMessage?: string | string[];
100+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import * as React from 'react';
2+
import cloneDeep from 'lodash/cloneDeep';
3+
import { Dropdown, IDropdownOption, IDropdownProps } from 'office-ui-fabric-react/lib/Dropdown';
4+
import { Async } from 'office-ui-fabric-react/lib/Utilities';
5+
import { Label } from 'office-ui-fabric-react/lib/Label';
6+
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
7+
import * as telemetry from '../../common/telemetry';
8+
import { ISPService } from '../../services/ISPService';
9+
import { SPViewPickerService } from '../../services/SPViewPickerService';
10+
import { IViewPickerProps, IViewPickerState } from './IViewPicker';
11+
import { ISPView, ISPViews } from "../../common/SPEntities";
12+
13+
14+
// Empty view value
15+
const EMPTY_VIEW_KEY = 'NO_VIEW_SELECTED';
16+
17+
export class ViewPicker extends React.Component<IViewPickerProps, IViewPickerState> {
18+
private options: IDropdownOption[] = [];
19+
private selectedKey: string| string[] = null;
20+
private latestValidateValue: string;
21+
private async: Async;
22+
private delayedValidate: (value: string) => void;
23+
24+
25+
constructor(props: IViewPickerProps){
26+
super(props);
27+
28+
telemetry.track('ViewPicker');
29+
this.state = {
30+
results: this.options
31+
}
32+
33+
this.async = new Async(this);
34+
this.validate = this.validate.bind(this);
35+
this.onChanged = this.onChanged.bind(this);
36+
this.notifyAfterValidate = this.notifyAfterValidate.bind(this);
37+
this.delayedValidate = this.async.debounce(this.validate, this.props.deferredValidationTime);
38+
}
39+
40+
41+
public componentDidMount(): void {
42+
// Start retrieving the list views
43+
this.loadViews();
44+
}
45+
46+
public componentDidUpdate(prevProps: IViewPickerProps, _prevState: IViewPickerState): void {
47+
if (
48+
this.props.listId !== prevProps.listId ||
49+
this.props.webAbsoluteUrl !== prevProps.webAbsoluteUrl ||
50+
this.props.orderBy !== prevProps.orderBy
51+
) {
52+
this.loadViews();
53+
}
54+
}
55+
56+
/**
57+
* Called when the component will unmount
58+
*/
59+
public componentWillUnmount(): void {
60+
if (typeof this.async !== 'undefined') {
61+
this.async.dispose();
62+
}
63+
}
64+
65+
private loadViews(): void {
66+
67+
const viewService: SPViewPickerService = new SPViewPickerService(this.props, this.props.context);
68+
const viewsToExclude: string[] = this.props.viewsToExclude || [];
69+
this.options = [];
70+
viewService.getViews().then((response: ISPViews) => {
71+
// Start mapping the views that are selected
72+
response.value.forEach((view: ISPView) => {
73+
if (this.props.selectedView === view.Id) {
74+
this.selectedKey = view.Id;
75+
}
76+
77+
// Make sure that the current view is NOT in the 'viewsToExclude' array
78+
if (viewsToExclude.indexOf(view.Title) === -1 && viewsToExclude.indexOf(view.Id) === -1) {
79+
this.options.push({
80+
key: view.Id,
81+
text: view.Title
82+
});
83+
}
84+
});
85+
86+
// Option to unselect the view
87+
this.options.unshift({
88+
key: EMPTY_VIEW_KEY,
89+
text: EMPTY_VIEW_KEY
90+
});
91+
92+
// Update the current component state
93+
this.setState({
94+
results: this.options,
95+
selectedKey: this.selectedKey
96+
});
97+
}).catch(() => { /* no-op; */ });
98+
}
99+
100+
private onChanged(event: React.FormEvent<HTMLDivElement>,option: IDropdownOption, _index?: number): void {
101+
const newValue: string = option.key as string;
102+
this.delayedValidate(newValue);
103+
}
104+
105+
/**
106+
* Validates the new custom field value
107+
*/
108+
private validate(value: string): void {
109+
this.notifyAfterValidate(this.props.selectedView, value);
110+
if (this.latestValidateValue === value) {
111+
return;
112+
}
113+
}
114+
115+
116+
/**
117+
* Notifies the parent Web Part of a property value change
118+
*/
119+
private notifyAfterValidate(oldValue: string, newValue: string): void {
120+
// Check if the user wanted to unselect the view
121+
const propValue = newValue === EMPTY_VIEW_KEY ? '' : newValue;
122+
123+
// Deselect all options
124+
this.options = this.state.results.map(option => {
125+
if (option.selected) {
126+
option.selected = false;
127+
}
128+
return option;
129+
});
130+
// Set the current selected key
131+
this.selectedKey = newValue;
132+
console.log('Selected View key :'+this.selectedKey);
133+
// Update the state
134+
this.setState({
135+
selectedKey: this.selectedKey,
136+
results: this.options
137+
});
138+
}
139+
140+
/**
141+
* Renders the SPViewPicker controls with Office UI Fabric
142+
*/
143+
public render(): JSX.Element {
144+
const { results, selectedKey } = this.state;
145+
const {className, disabled, label, placeholder, showBlankOption} = this.props;
146+
147+
const options : IDropdownOption[] = results.map(v => ({
148+
key : v.key,
149+
text: v.text
150+
}));
151+
152+
if (showBlankOption) {
153+
// Provide empty option
154+
options.unshift({
155+
key: EMPTY_VIEW_KEY,
156+
text: '',
157+
});
158+
}
159+
160+
const dropdownProps: IDropdownProps = {
161+
className,
162+
options,
163+
disabled: disabled,
164+
label,
165+
placeholder,
166+
onChange: this.onChanged,
167+
};
168+
169+
// Renders content
170+
return (
171+
<>
172+
<Dropdown {...dropdownProps} />
173+
</>
174+
);
175+
}
176+
177+
}

src/controls/viewPicker/index.ts

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

src/services/ISPViewPickerService.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ISPViews } from "../../src/common/SPEntities";
2+
3+
export interface ISPViewPickerService {
4+
getViews(): Promise<ISPViews>;
5+
}

0 commit comments

Comments
 (0)