Füge Filter für die angezeigte Kategorie hinzu und aktualisiere die WebPart-Einstellungen
This commit is contained in:
@@ -9,6 +9,7 @@ kompatible SPFx-1.4.1-/React-15-Toolchain.
|
||||
- Auswahl einer nicht ausgeblendeten Liste aus dem aktuellen Web
|
||||
- dynamische Auswahl von Überschrift-, Inhalt-, Kategorie- und Sortierspalte
|
||||
- optionales Gruppieren nach Kategorien
|
||||
- Filter auf eine konkrete, aktuell in der Liste verwendete Kategorie
|
||||
- auf- oder absteigende Sortierung
|
||||
- initial vollständig geschlossen, erster Eintrag geöffnet oder alle Einträge geöffnet
|
||||
- wahlweise genau ein oder mehrere gleichzeitig geöffnete Bereiche
|
||||
@@ -28,8 +29,10 @@ Es wird keine fachliche Liste provisioniert. Eine einfache FAQ-Liste kann beispi
|
||||
| `Category` | Auswahl oder Text | optionale Gruppierung |
|
||||
| `SortOrder` | Zahl | optionale Sortierung |
|
||||
|
||||
Nach dem Hinzufügen des WebParts werden Liste und Spalten im Property Pane ausgewählt. Änderungen an den
|
||||
Listeneinträgen erscheinen beim nächsten Laden beziehungsweise Rendern der Seite automatisch.
|
||||
Nach dem Hinzufügen des WebParts werden Liste und Spalten im Property Pane ausgewählt. Sobald eine
|
||||
Kategorie-Spalte festgelegt ist, kann unter **Angezeigte Kategorie** entweder eine der aktuell verwendeten
|
||||
Kategorien oder **Alle Kategorien** ausgewählt werden. Änderungen an den Listeneinträgen erscheinen beim
|
||||
nächsten Laden beziehungsweise Rendern der Seite automatisch.
|
||||
|
||||
## Version und Build
|
||||
|
||||
@@ -59,4 +62,3 @@ werden entfernt.
|
||||
|
||||
Die fachliche Vorlage ist das PnP Community Sample:
|
||||
https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-accordion-dynamic-section
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "accordion",
|
||||
"name": "accordion-web-part",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
|
||||
@@ -36,7 +36,8 @@ export class AccordionDataService {
|
||||
contentField: string,
|
||||
categoryField: string,
|
||||
sortField: string,
|
||||
sortAscending: boolean
|
||||
sortAscending: boolean,
|
||||
categoryValue: string
|
||||
): Promise<IAccordionItem[]> {
|
||||
if (!this.isGuid(listId)) { return Promise.reject(new Error('Invalid list ID.')); }
|
||||
const fields: string[] = this.uniqueFields(['Id', titleField, contentField, categoryField]);
|
||||
@@ -48,12 +49,36 @@ export class AccordionDataService {
|
||||
'?$select=' + fields.join(',') + '&$orderby=' + orderField + (sortAscending ? ' asc' : ' desc') + '&$top=5000';
|
||||
return this.context.spHttpClient.get(url, SPHttpClient.configurations.v1, { headers: this.headers })
|
||||
.then((response: SPHttpClientResponse): Promise<any> => this.ensureJson(response)) // tslint:disable-line:no-any
|
||||
.then((payload: any): IAccordionItem[] => this.results(payload).map((item: any): IAccordionItem => ({ // tslint:disable-line:no-any
|
||||
id: Number(item.Id),
|
||||
title: this.toText(item[titleField]),
|
||||
content: this.toText(item[contentField]),
|
||||
category: categoryField ? this.toText(item[categoryField]) : ''
|
||||
})));
|
||||
.then((payload: any): IAccordionItem[] => this.results(payload) // tslint:disable-line:no-any
|
||||
.filter((item: any): boolean => !categoryValue || // tslint:disable-line:no-any
|
||||
this.toValues(item[categoryField]).indexOf(categoryValue) >= 0)
|
||||
.map((item: any): IAccordionItem => ({ // tslint:disable-line:no-any
|
||||
id: Number(item.Id),
|
||||
title: this.toText(item[titleField]),
|
||||
content: this.toText(item[contentField]),
|
||||
category: categoryField ? this.toText(item[categoryField]) : ''
|
||||
})));
|
||||
}
|
||||
|
||||
public getCategoryValues(listId: string, categoryField: string): Promise<string[]> {
|
||||
if (!this.isGuid(listId) || !this.isFieldName(categoryField)) { return Promise.resolve([]); }
|
||||
const url: string = this.webUrl + "/_api/web/lists(guid'" + this.cleanGuid(listId) + "')/items" +
|
||||
'?$select=' + categoryField + '&$top=5000';
|
||||
return this.context.spHttpClient.get(url, SPHttpClient.configurations.v1, { headers: this.headers })
|
||||
.then((response: SPHttpClientResponse): Promise<any> => this.ensureJson(response)) // tslint:disable-line:no-any
|
||||
.then((payload: any): string[] => { // tslint:disable-line:no-any
|
||||
const values: string[] = [];
|
||||
const keys: { [key: string]: boolean } = {};
|
||||
const items: any[] = this.results(payload); // tslint:disable-line:no-any
|
||||
for (let i: number = 0; i < items.length; i++) {
|
||||
const current: string[] = this.toValues(items[i][categoryField]);
|
||||
for (let valueIndex: number = 0; valueIndex < current.length; valueIndex++) {
|
||||
const key: string = current[valueIndex].toLocaleLowerCase();
|
||||
if (!keys[key]) { keys[key] = true; values.push(current[valueIndex]); }
|
||||
}
|
||||
}
|
||||
return values.sort((left: string, right: string): number => left.localeCompare(right));
|
||||
});
|
||||
}
|
||||
|
||||
private get webUrl(): string { return this.context.pageContext.web.absoluteUrl.replace(/\/$/, ''); }
|
||||
@@ -88,6 +113,13 @@ export class AccordionDataService {
|
||||
return '';
|
||||
}
|
||||
|
||||
private toValues(value: any): string[] { // tslint:disable-line:no-any
|
||||
if (value === undefined || value === null) { return []; }
|
||||
const source: any[] = value && Array.isArray(value.results) ? value.results : [value]; // tslint:disable-line:no-any
|
||||
return source.map((entry: any): string => this.toText(entry).trim()) // tslint:disable-line:no-any
|
||||
.filter((entry: string): boolean => !!entry);
|
||||
}
|
||||
|
||||
private isSupportedField(type: string): boolean {
|
||||
return ['Text', 'Note', 'Choice', 'MultiChoice', 'Number', 'Integer', 'Boolean', 'DateTime', 'Computed'].indexOf(String(type)) >= 0;
|
||||
}
|
||||
@@ -95,4 +127,3 @@ export class AccordionDataService {
|
||||
private isGuid(value: string): boolean { return /^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(this.cleanGuid(value)); }
|
||||
private cleanGuid(value: string): string { return String(value || '').replace(/[{}]/g, '').toLowerCase(); }
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"titleField": "Title",
|
||||
"contentField": "",
|
||||
"categoryField": "",
|
||||
"selectedCategory": "",
|
||||
"sortField": "Title",
|
||||
"sortAscending": true,
|
||||
"allowMultiple": false,
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface IAccordionWebPartProps {
|
||||
titleField: string;
|
||||
contentField: string;
|
||||
categoryField: string;
|
||||
selectedCategory: string;
|
||||
sortField: string;
|
||||
sortAscending: boolean;
|
||||
allowMultiple: boolean;
|
||||
@@ -36,15 +37,19 @@ export default class AccordionWebPart extends BaseClientSideWebPart<IAccordionWe
|
||||
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.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 {
|
||||
@@ -60,7 +65,8 @@ export default class AccordionWebPart extends BaseClientSideWebPart<IAccordionWe
|
||||
this.properties.contentField,
|
||||
this.properties.categoryField || '',
|
||||
this.properties.sortField || this.properties.titleField,
|
||||
this.properties.sortAscending !== false
|
||||
this.properties.sortAscending !== false,
|
||||
this.properties.selectedCategory || ''
|
||||
).then((items: IAccordionItem[]): void => {
|
||||
if (this.disposed || version !== this.requestVersion) { return; }
|
||||
if (!items.length) { this.renderStatus(strings.EmptyMessage, false); return; }
|
||||
@@ -94,6 +100,9 @@ export default class AccordionWebPart extends BaseClientSideWebPart<IAccordionWe
|
||||
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
|
||||
@@ -102,10 +111,17 @@ export default class AccordionWebPart extends BaseClientSideWebPart<IAccordionWe
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +130,12 @@ export default class AccordionWebPart extends BaseClientSideWebPart<IAccordionWe
|
||||
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 < fieldOptions.length; i++) { categoryOptions.push(fieldOptions[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 });
|
||||
}
|
||||
@@ -142,6 +162,10 @@ export default class AccordionWebPart extends BaseClientSideWebPart<IAccordionWe
|
||||
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
|
||||
@@ -200,6 +224,28 @@ export default class AccordionWebPart extends BaseClientSideWebPart<IAccordionWe
|
||||
});
|
||||
}
|
||||
|
||||
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).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 names: string[] = this.fields.map((field: IAccordionFieldOption): string => field.internalName);
|
||||
if (names.indexOf(this.properties.titleField) < 0) {
|
||||
|
||||
@@ -7,6 +7,7 @@ define([], function() {
|
||||
"TitleFieldLabel": "Spalte für Überschrift",
|
||||
"ContentFieldLabel": "Spalte für Inhalt",
|
||||
"CategoryFieldLabel": "Spalte für Kategorie (optional)",
|
||||
"CategoryFilterLabel": "Angezeigte Kategorie",
|
||||
"SortFieldLabel": "Sortierspalte",
|
||||
"SortAscendingLabel": "Aufsteigend sortieren",
|
||||
"AllowMultipleLabel": "Mehrere geöffnete Bereiche erlauben",
|
||||
@@ -17,6 +18,7 @@ define([], function() {
|
||||
"ShowCategoriesLabel": "Kategorieüberschriften anzeigen",
|
||||
"SelectOption": "Auswählen...",
|
||||
"NoCategoryOption": "Keine Kategorien",
|
||||
"AllCategoriesOption": "Alle Kategorien",
|
||||
"ConfigureMessage": "Konfigurieren Sie in den WebPart-Einstellungen eine Liste sowie Überschrift- und Inhaltsspalte.",
|
||||
"LoadingMessage": "Akkordeon wird geladen...",
|
||||
"EmptyMessage": "Es wurden keine Einträge gefunden.",
|
||||
@@ -26,4 +28,3 @@ define([], function() {
|
||||
"DefaultCategory": "Weitere"
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ define([], function() {
|
||||
"TitleFieldLabel": "Heading column",
|
||||
"ContentFieldLabel": "Content column",
|
||||
"CategoryFieldLabel": "Category column (optional)",
|
||||
"CategoryFilterLabel": "Displayed category",
|
||||
"SortFieldLabel": "Sort column",
|
||||
"SortAscendingLabel": "Sort ascending",
|
||||
"AllowMultipleLabel": "Allow multiple open sections",
|
||||
@@ -17,6 +18,7 @@ define([], function() {
|
||||
"ShowCategoriesLabel": "Show category headings",
|
||||
"SelectOption": "Select...",
|
||||
"NoCategoryOption": "No categories",
|
||||
"AllCategoriesOption": "All categories",
|
||||
"ConfigureMessage": "Configure a list, heading column and content column in the web part settings.",
|
||||
"LoadingMessage": "Loading accordion...",
|
||||
"EmptyMessage": "No entries were found.",
|
||||
@@ -26,4 +28,3 @@ define([], function() {
|
||||
"DefaultCategory": "Other"
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
+2
@@ -6,6 +6,7 @@ declare interface IAccordionWebPartStrings {
|
||||
TitleFieldLabel: string;
|
||||
ContentFieldLabel: string;
|
||||
CategoryFieldLabel: string;
|
||||
CategoryFilterLabel: string;
|
||||
SortFieldLabel: string;
|
||||
SortAscendingLabel: string;
|
||||
AllowMultipleLabel: string;
|
||||
@@ -16,6 +17,7 @@ declare interface IAccordionWebPartStrings {
|
||||
ShowCategoriesLabel: string;
|
||||
SelectOption: string;
|
||||
NoCategoryOption: string;
|
||||
AllCategoriesOption: string;
|
||||
ConfigureMessage: string;
|
||||
LoadingMessage: string;
|
||||
EmptyMessage: string;
|
||||
|
||||
@@ -27,6 +27,9 @@ assert(!!config.bundles['accordion-web-part'], 'Accordion bundle is missing.');
|
||||
assert(webPart.indexOf("PropertyPaneDropdown('listId'") >= 0, 'Dynamic list selection is missing.');
|
||||
assert(webPart.indexOf("PropertyPaneDropdown('contentField'") >= 0, 'Dynamic content field selection is missing.');
|
||||
assert(webPart.indexOf("PropertyPaneDropdown('categoryField'") >= 0, 'Category field selection is missing.');
|
||||
assert(webPart.indexOf("PropertyPaneDropdown('selectedCategory'") >= 0, 'Category value filter is missing.');
|
||||
assert(read('src/webparts/accordion/AccordionDataService.ts').indexOf('getCategoryValues') >= 0,
|
||||
'Dynamic category values are missing.');
|
||||
assert(component.indexOf("aria-expanded={open}") >= 0 && component.indexOf("role='region'") >= 0,
|
||||
'Accessible accordion semantics are missing.');
|
||||
assert(component.indexOf('sanitizeAccordionHtml(item.content)') >= 0, 'Rich text is not sanitized before rendering.');
|
||||
|
||||
Reference in New Issue
Block a user