Files
Accordion/src/webparts/accordion/AccordionWebPart.ts
T

315 lines
14 KiB
TypeScript

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
IPropertyPaneDropdownOption,
PropertyPaneDropdown,
PropertyPaneTextField,
PropertyPaneToggle
} from '@microsoft/sp-webpart-base';
import * as strings from 'AccordionWebPartStrings';
import Accordion from './components/Accordion';
import { IAccordionProps } from './components/IAccordionProps';
import { AccordionDataService } from './AccordionDataService';
import {
AccordionInitialState,
IAccordionFieldOption,
IAccordionItem,
IAccordionListOption
} from './AccordionTypes';
import styles from './components/Accordion.module.scss';
export interface IAccordionWebPartProps {
title: string;
listId: string;
titleField: string;
contentField: string;
categoryField: string;
selectedCategory: string;
sortField: string;
sortAscending: boolean;
allowMultiple: boolean;
initialState: AccordionInitialState;
showCategories: boolean;
}
export default class AccordionWebPart extends BaseClientSideWebPart<IAccordionWebPartProps> {
private service: AccordionDataService;
private lists: IAccordionListOption[] = [];
private fields: IAccordionFieldOption[] = [];
private categories: string[] = [];
private loadingLists: boolean = false;
private loadingFields: boolean = false;
private loadingCategories: boolean = false;
private requestVersion: number = 0;
private disposed: boolean = false;
protected onInit(): Promise<void> {
this.service = new AccordionDataService(this.context);
return super.onInit().then((): Promise<void> => this.loadLists())
.then((): Promise<void> => this.properties.listId ? this.loadFields(this.properties.listId) : Promise.resolve())
.then((): Promise<void> => this.properties.listId && this.properties.categoryField
? this.loadCategories(this.properties.listId, this.properties.categoryField) : Promise.resolve());
}
public render(): void {
const version: number = ++this.requestVersion;
if (!this.isConfigured()) {
this.renderStatus(strings.ConfigureMessage, false);
return;
}
this.renderStatus(strings.LoadingMessage, false);
this.service.getItems(
this.properties.listId,
this.properties.titleField,
this.properties.contentField,
this.properties.categoryField || '',
this.properties.sortField || this.properties.titleField,
this.properties.sortAscending !== false,
this.properties.selectedCategory || '',
this.getFieldType(this.properties.categoryField)
).then((items: IAccordionItem[]): void => {
if (this.disposed || version !== this.requestVersion) { return; }
if (!items.length) { this.renderStatus(strings.EmptyMessage, false); return; }
const element: React.ReactElement<IAccordionProps> = React.createElement(Accordion, {
items: items,
title: String(this.properties.title || '').trim(),
allowMultiple: this.properties.allowMultiple === true,
initialState: this.normalizeInitialState(this.properties.initialState),
showCategories: this.properties.showCategories !== false && !!this.properties.categoryField,
expandLabel: strings.ExpandLabel,
collapseLabel: strings.CollapseLabel,
defaultCategory: strings.DefaultCategory
});
ReactDom.render(element, this.domElement);
}).catch((error: Error): void => {
if (this.disposed || version !== this.requestVersion) { return; }
if (window.console) { console.error('[Accordion] ' + strings.LoadErrorMessage, error); }
this.renderStatus(strings.LoadErrorMessage, true);
});
}
protected onDispose(): void {
this.disposed = true;
this.requestVersion++;
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version { return Version.parse('1.0'); }
protected onPropertyPaneConfigurationStart(): void {
if (!this.lists.length && !this.loadingLists) { this.loadLists(); }
if (this.properties.listId && !this.fields.length && !this.loadingFields) {
this.loadFields(this.properties.listId);
}
if (this.properties.listId && this.properties.categoryField && !this.categories.length && !this.loadingCategories) {
this.loadCategories(this.properties.listId, this.properties.categoryField);
}
}
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void { // tslint:disable-line:no-any
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
if (propertyPath === 'listId' && newValue !== oldValue) {
this.properties.titleField = '';
this.properties.contentField = '';
this.properties.categoryField = '';
this.properties.selectedCategory = '';
this.properties.sortField = '';
this.fields = [];
this.categories = [];
this.context.propertyPane.refresh();
this.loadFields(String(newValue || '')).then((): void => this.render());
} else if (propertyPath === 'categoryField' && newValue !== oldValue) {
this.properties.selectedCategory = '';
this.categories = [];
this.context.propertyPane.refresh();
this.loadCategories(this.properties.listId, String(newValue || '')).then((): void => this.render());
}
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
const standardFields: IAccordionFieldOption[] = this.fields.filter((field: IAccordionFieldOption): boolean =>
!this.isTaxonomyField(field));
const fieldOptions: IPropertyPaneDropdownOption[] = standardFields.map((field: IAccordionFieldOption): IPropertyPaneDropdownOption => ({
key: field.internalName, text: field.title + ' (' + field.internalName + ')'
}));
const categoryFieldOptions: IPropertyPaneDropdownOption[] = this.fields.map((field: IAccordionFieldOption): IPropertyPaneDropdownOption => ({
key: field.internalName, text: field.title + ' (' + field.internalName + ')'
}));
const categoryOptions: IPropertyPaneDropdownOption[] = [{ key: '', text: strings.NoCategoryOption }];
const categoryValueOptions: IPropertyPaneDropdownOption[] = [{ key: '', text: strings.AllCategoriesOption }];
const listOptions: IPropertyPaneDropdownOption[] = [{ key: '', text: strings.SelectOption }];
for (let i: number = 0; i < categoryFieldOptions.length; i++) { categoryOptions.push(categoryFieldOptions[i]); }
for (let i: number = 0; i < this.categories.length; i++) {
categoryValueOptions.push({ key: this.categories[i], text: this.categories[i] });
}
for (let i: number = 0; i < this.lists.length; i++) {
listOptions.push({ key: this.lists[i].id, text: this.lists[i].title });
}
return {
pages: [{
header: { description: strings.PropertyPaneDescription },
groups: [{
groupName: strings.DataGroupName,
groupFields: [
PropertyPaneTextField('title', { label: strings.WebPartTitleFieldLabel }),
PropertyPaneDropdown('listId', {
label: strings.ListFieldLabel,
options: listOptions,
disabled: this.loadingLists
}),
PropertyPaneDropdown('titleField', {
label: strings.TitleFieldLabel, options: fieldOptions,
disabled: !this.properties.listId || this.loadingFields
}),
PropertyPaneDropdown('contentField', {
label: strings.ContentFieldLabel, options: fieldOptions,
disabled: !this.properties.listId || this.loadingFields
}),
PropertyPaneDropdown('categoryField', {
label: strings.CategoryFieldLabel, options: categoryOptions,
disabled: !this.properties.listId || this.loadingFields
}),
PropertyPaneDropdown('selectedCategory', {
label: strings.CategoryFilterLabel, options: categoryValueOptions,
disabled: !this.properties.categoryField || this.loadingCategories
}),
PropertyPaneDropdown('sortField', {
label: strings.SortFieldLabel, options: fieldOptions,
disabled: !this.properties.listId || this.loadingFields
}),
PropertyPaneToggle('sortAscending', { label: strings.SortAscendingLabel })
]
}, {
groupName: strings.BehaviorGroupName,
groupFields: [
PropertyPaneDropdown('initialState', {
label: strings.InitialStateFieldLabel,
options: [
{ key: 'collapsed', text: strings.InitialCollapsed },
{ key: 'first', text: strings.InitialFirst },
{ key: 'all', text: strings.InitialAll }
]
}),
PropertyPaneToggle('allowMultiple', { label: strings.AllowMultipleLabel }),
PropertyPaneToggle('showCategories', {
label: strings.ShowCategoriesLabel,
disabled: !this.properties.categoryField
})
]
}]
}]
};
}
private loadLists(): Promise<void> {
this.loadingLists = true;
this.refreshPropertyPane();
return this.service.getLists().then((lists: IAccordionListOption[]): void => {
this.lists = lists;
}).catch((error: Error): void => {
this.lists = [];
if (window.console) { console.error('[Accordion] Lists could not be loaded.', error); }
}).then((): void => {
this.loadingLists = false;
this.refreshPropertyPane();
});
}
private loadFields(listId: string): Promise<void> {
if (!listId) { this.fields = []; return Promise.resolve(); }
this.loadingFields = true;
this.refreshPropertyPane();
return this.service.getFields(listId).then((fields: IAccordionFieldOption[]): void => {
this.fields = fields;
this.applyFieldDefaults();
}).catch((error: Error): void => {
this.fields = [];
if (window.console) { console.error('[Accordion] Fields could not be loaded.', error); }
}).then((): void => {
this.loadingFields = false;
this.refreshPropertyPane();
});
}
private loadCategories(listId: string, categoryField: string): Promise<void> {
if (!listId || !categoryField) {
this.categories = [];
this.loadingCategories = false;
this.refreshPropertyPane();
return Promise.resolve();
}
this.loadingCategories = true;
this.refreshPropertyPane();
return this.service.getCategoryValues(listId, categoryField, this.getFieldType(categoryField)).then((categories: string[]): void => {
this.categories = categories;
if (categories.indexOf(this.properties.selectedCategory) < 0) { this.properties.selectedCategory = ''; }
}).catch((error: Error): void => {
this.categories = [];
this.properties.selectedCategory = '';
if (window.console) { console.error('[Accordion] Categories could not be loaded.', error); }
}).then((): void => {
this.loadingCategories = false;
this.refreshPropertyPane();
});
}
private applyFieldDefaults(): void {
const standardFields: IAccordionFieldOption[] = this.fields.filter((field: IAccordionFieldOption): boolean =>
!this.isTaxonomyField(field));
const names: string[] = standardFields.map((field: IAccordionFieldOption): string => field.internalName);
const categoryNames: string[] = this.fields.map((field: IAccordionFieldOption): string => field.internalName);
if (names.indexOf(this.properties.titleField) < 0) {
this.properties.titleField = names.indexOf('Title') >= 0 ? 'Title' : (names[0] || '');
}
if (names.indexOf(this.properties.contentField) < 0) {
const note: IAccordionFieldOption = standardFields.filter((field: IAccordionFieldOption): boolean =>
field.type === 'Note' && field.internalName !== this.properties.titleField)[0];
this.properties.contentField = note ? note.internalName : this.firstDifferentField(this.properties.titleField);
}
if (categoryNames.indexOf(this.properties.categoryField) < 0) { this.properties.categoryField = ''; }
if (names.indexOf(this.properties.sortField) < 0) { this.properties.sortField = this.properties.titleField; }
}
private firstDifferentField(excluded: string): string {
for (let i: number = 0; i < this.fields.length; i++) {
if (this.fields[i].internalName !== excluded && !this.isTaxonomyField(this.fields[i])) {
return this.fields[i].internalName;
}
}
return '';
}
private isTaxonomyField(field: IAccordionFieldOption): boolean {
return field.type === 'TaxonomyFieldType' || field.type === 'TaxonomyFieldTypeMulti';
}
private getFieldType(internalName: string): string {
for (let i: number = 0; i < this.fields.length; i++) {
if (this.fields[i].internalName === internalName) { return this.fields[i].type; }
}
return '';
}
private isConfigured(): boolean {
return !!(this.service && this.properties.listId && this.properties.titleField && this.properties.contentField);
}
private normalizeInitialState(value: string): AccordionInitialState {
return value === 'first' || value === 'all' ? value : 'collapsed';
}
private refreshPropertyPane(): void {
if (this.context && this.context.propertyPane) { this.context.propertyPane.refresh(); }
}
private renderStatus(message: string, error: boolean): void {
ReactDom.unmountComponentAtNode(this.domElement);
const status: HTMLDivElement = document.createElement('div');
status.className = error ? styles.error : styles.status;
status.setAttribute('role', error ? 'alert' : 'status');
status.textContent = message;
this.domElement.innerHTML = '';
this.domElement.appendChild(status);
}
}