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

New List Picker Feature #60

Merged
merged 8 commits into from
Apr 25, 2018
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
1 change: 1 addition & 0 deletions src/ListPicker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './controls/listPicker/index';
16 changes: 16 additions & 0 deletions src/common/SPEntities.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/**
* Represents SP List
*/
export interface ISPList {
Id: string;
Title: string;
BaseTemplate: string;
}

/**
* Replica of the returned value from the REST api
*/
export interface ISPLists {
value: ISPList[];
}

/**
* Represents SP Field
*/
Expand Down
67 changes: 67 additions & 0 deletions src/controls/listPicker/IListPicker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import IWebPartContext from "@microsoft/sp-webpart-base/lib/core/IWebPartContext";
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";

import { LibsOrderBy } from "../../services/ISPService";

export interface IListPickerProps {
/**
* The web part context
*/
context: IWebPartContext;
/**
* If provided, additional class name to provide on the dropdown element.
*/
className?: string;
/**
* Whether or not the control is disabled
*/
disabled?: boolean;
/**
* The SharePoint BaseTemplate to filter the list options by
*/
baseTemplate?: number;
/**
* Whether or not to include hidden lists. Default is true
*/
includeHidden?: boolean;
/**
* How to order the lists retrieved from SharePoint
*/
orderBy?: LibsOrderBy;
/**
* Keys 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.
*/
selectedList?: string | string[];
/**
* Optional mode indicates if multi-choice selections is allowed. Default to false
*/
multiSelect?: boolean;
/**
* The label to use
*/
label?: string;
/**
* Input placeholder text. Displayed until option is selected.
*/
placeHolder?: string;
/**
* Callback issues when the selected option changes
*/
onSelectionChanged?: (newValue: string | string[]) => void;
}

export interface IListPickerState {
/**
* The options available to the listPicker
*/
options: IDropdownOption[];
/**
* Whether or not the listPicker options are loading
*/
loading: boolean;
/**
* Keys of the currently selected item(s).
*/
selectedList?: string | string[];
}
7 changes: 7 additions & 0 deletions src/controls/listPicker/ListPicker.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.listPicker {
.spinner {
float: right;
margin-top: 10px;
margin-right: -20px;
}
}
142 changes: 142 additions & 0 deletions src/controls/listPicker/ListPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import * as React from 'react';
import { IDropdownOption, IDropdownProps, Dropdown } from 'office-ui-fabric-react/lib/components/Dropdown';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/components/Spinner';

import { IListPickerProps, IListPickerState } from './IListPicker';
import { ISPService } from '../../services/ISPService';
import { SPServiceFactory } from '../../services/SPServiceFactory';

import styles from './ListPicker.module.scss';

/**
* Empty list value, to be checked for single list selection
*/
const EMPTY_LIST_KEY = 'NO_LIST_SELECTED';

/**
* Renders the controls for the ListPicker component
*/
export class ListPicker extends React.Component<IListPickerProps, IListPickerState> {
private _options: IDropdownOption[] = [];
private _selectedList: string | string[];

/**
* Constructor method
*/
constructor(props: IListPickerProps) {
super(props);

console.debug('selectedList', this.props.selectedList);

this.state = {
options: this._options,
loading: false
};

this.onChanged = this.onChanged.bind(this);
}

/**
* Lifecycle hook when component is mounted
*/
public componentDidMount() {
this.loadLists();
}

/**
* Loads the list from SharePoint current web site
*/
private loadLists() {
const { context, baseTemplate, includeHidden, orderBy, multiSelect, selectedList } = this.props;

// Show the loading indicator and disable the dropdown
this.setState({ loading: true });

const service: ISPService = SPServiceFactory.createService(context, true, 5000);
service.getLibs({
baseTemplate: baseTemplate,
includeHidden: includeHidden,
orderBy: orderBy
}).then((results) => {
// Start mapping the lists to the dropdown option
results.value.map(list => {
this._options.push({
key: list.Id,
text: list.Title
});
});

if (multiSelect !== true) {
// Add option to unselct list
this._options.unshift({
key: EMPTY_LIST_KEY,
text: ''
});
}

this._selectedList = this.props.selectedList;

// Hide the loading indicator and set the dropdown options and enable the dropdown
this.setState({
loading: false,
options: this._options,
selectedList: this._selectedList
});
});
}

/**
* Raises when a list has been selected
* @param option the new selection
* @param index the index of the selection
*/
private onChanged(option: IDropdownOption, index?: number): void {
const { multiSelect, onSelectionChanged } = this.props;

if (multiSelect === true) {
if (this._selectedList === undefined) {
this._selectedList = new Array<string>();
}
(this._selectedList as string[]).push(option.key as string);
} else {
this._selectedList = option.key as string;
}

if (onSelectionChanged) {
onSelectionChanged(this._selectedList);
}
}

/**
* Renders the ListPicker controls with Office UI Fabric
*/
public render(): JSX.Element {
const { loading, options, selectedList } = this.state;
const { className, disabled, multiSelect, label, placeHolder } = this.props;

const dropdownOptions: IDropdownProps = {
className: className,
options: options,
disabled: ( loading || disabled ),
label: label,
placeHolder: placeHolder,
onChanged: this.onChanged
};

if (multiSelect === true) {
dropdownOptions.multiSelect = true;
dropdownOptions.selectedKeys = selectedList as string[];
} else {
dropdownOptions.selectedKey = selectedList as string;
}

return (
<div className={ styles.listPicker }>
{ loading && <Spinner className={ styles.spinner } size={SpinnerSize.xSmall} /> }
<Dropdown {...dropdownOptions} />
</div>
);
}
}

export default ListPicker;
2 changes: 2 additions & 0 deletions src/controls/listPicker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './IListPicker';
export * from './ListPicker';
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export * from './ListView';
export * from './Placeholder';
export * from './SiteBreadcrumb';
export * from './WebPartTitle';
export * from './ListPicker';

export * from './IFrameDialog';

export * from './Common';
Expand Down
21 changes: 21 additions & 0 deletions src/services/ISPService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ISPLists } from "../common/SPEntities";

export enum LibsOrderBy {
Id = 1,
Title
}
/**
* Options used to sort and filter
*/
export interface ILibsOptions {
orderBy?: LibsOrderBy;
baseTemplate?: number;
includeHidden?: boolean;
}
export interface ISPService {
/**
* Get the lists from SharePoint
* @param options Options used to order and filter during the API query
*/
getLibs(options?: ILibsOptions): Promise<ISPLists>;
}
32 changes: 32 additions & 0 deletions src/services/SPService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ISPService, ILibsOptions, LibsOrderBy } from "./ISPService";
import { ISPLists } from "../common/SPEntities";
import { IWebPartContext } from "@microsoft/sp-webpart-base";
import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http";

export class SPService implements ISPService {
private readonly _context: IWebPartContext;
constructor(context: IWebPartContext) {
this._context = context;
}
public getLibs(options?: ILibsOptions): Promise<ISPLists> {
let filtered: boolean;
let queryUrl: string = `${this._context.pageContext.web.absoluteUrl}/_api/web/lists?$select=Title,id,BaseTemplate`;
if (options.orderBy !== null) {
queryUrl += `&$orderby=${options.orderBy === LibsOrderBy.Id ? 'Id': 'Title'}`;
}
if (options.baseTemplate !== null) {
queryUrl += `&$filter=BaseTemplate eq ${options.baseTemplate}`;
filtered = true;
}
if (options.includeHidden === false) {
queryUrl += filtered ? ' and Hidden eq false' : '&$filter=Hidden eq false';
filtered = true;
}
return this._context.spHttpClient.get(queryUrl, SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
}) as Promise<ISPLists>;
}
}

export default SPService;
14 changes: 14 additions & 0 deletions src/services/SPServiceFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IWebPartContext } from "@microsoft/sp-webpart-base";
import { ISPService } from "./ISPService";
import { Environment, EnvironmentType } from "@microsoft/sp-core-library";
import SPServiceMock from "./SPServiceMock";
import SPService from "./SPService";

export class SPServiceFactory {
public static createService(context: IWebPartContext, includeDelay?: boolean, delayTimeout?: number): ISPService {
if (Environment.type === EnvironmentType.Local) {
return new SPServiceMock(includeDelay, delayTimeout);
}
return new SPService(context);
}
}
40 changes: 40 additions & 0 deletions src/services/SPServiceMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ISPService, ILibsOptions } from "./ISPService";
import { ISPLists } from "../common/SPEntities";

export class SPServiceMock implements ISPService {
private _includeDelay?: boolean;
private _delayTimeout?: number;

constructor(includeDelay?: boolean, delayTimeout?: number) {
this._includeDelay = includeDelay;
this._delayTimeout = delayTimeout || 500;
}

/**
* The mock lists to present to the local workbench
*/
private static _lists: ISPLists = {
value: [
{ Id: '8dc80f2e-0e01-43ee-b59e-fbbca2d1f35e', Title: 'Mock List One', BaseTemplate: '109' },
{ Id: '772a30d4-2d62-42da-aa48-c2a37971d693', Title: 'Mock List Two', BaseTemplate: '109' },
{ Id: '16c0d1c6-b467-4823-a37b-c308cf730366', Title: 'Mock List Three', BaseTemplate: '109' }
]
};
public getLibs(options?: ILibsOptions): Promise<ISPLists> {
return new Promise<ISPLists>(async resolve => {
if (this._includeDelay === true) {
await this.sleep(this._delayTimeout); // Simulate network load
}
resolve(SPServiceMock._lists);
});
}
/**
* Locks the thread for the specified amount of time
* @param ms Milliseconds to wait
*/
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

export default SPServiceMock;