315 lines
14 KiB
TypeScript
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);
|
|
}
|
|
}
|