Add Accordion web part implementation with dynamic SharePoint list integration
- Created Accordion web part with SPFx 1.4.1 framework. - Implemented dynamic loading of SharePoint lists and fields. - Added configuration options for title, content, and category fields. - Included support for multiple open sections and initial state settings. - Developed HTML sanitizer for rich text content. - Implemented accessible accordion UI with category grouping. - Added localization support for English and German languages. - Created necessary configuration files for deployment and asset management. - Added validation script to ensure project compliance with standards.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
|
||||
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import { IAccordionFieldOption, IAccordionItem, IAccordionListOption } from './AccordionTypes';
|
||||
|
||||
export class AccordionDataService {
|
||||
public constructor(private context: WebPartContext) { }
|
||||
|
||||
public getLists(): Promise<IAccordionListOption[]> {
|
||||
const url: string = this.webUrl + '/_api/web/lists?$select=Id,Title&$filter=Hidden eq false&$orderby=Title';
|
||||
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): IAccordionListOption[] => this.results(payload).map((list: any): IAccordionListOption => ({ // tslint:disable-line:no-any
|
||||
id: this.cleanGuid(list.Id),
|
||||
title: String(list.Title || list.Id)
|
||||
})));
|
||||
}
|
||||
|
||||
public getFields(listId: string): Promise<IAccordionFieldOption[]> {
|
||||
if (!this.isGuid(listId)) { return Promise.resolve([]); }
|
||||
const url: string = this.webUrl + "/_api/web/lists(guid'" + this.cleanGuid(listId) +
|
||||
"')/fields?$select=InternalName,Title,TypeAsString,Hidden,ReadOnlyField&$filter=Hidden eq false&$orderby=Title";
|
||||
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): IAccordionFieldOption[] => this.results(payload) // tslint:disable-line:no-any
|
||||
.filter((field: any): boolean => !field.ReadOnlyField && this.isSupportedField(field.TypeAsString)) // tslint:disable-line:no-any
|
||||
.map((field: any): IAccordionFieldOption => ({ // tslint:disable-line:no-any
|
||||
internalName: String(field.InternalName || ''),
|
||||
title: String(field.Title || field.InternalName || ''),
|
||||
type: String(field.TypeAsString || '')
|
||||
})));
|
||||
}
|
||||
|
||||
public getItems(
|
||||
listId: string,
|
||||
titleField: string,
|
||||
contentField: string,
|
||||
categoryField: string,
|
||||
sortField: string,
|
||||
sortAscending: boolean
|
||||
): Promise<IAccordionItem[]> {
|
||||
if (!this.isGuid(listId)) { return Promise.reject(new Error('Invalid list ID.')); }
|
||||
const fields: string[] = this.uniqueFields(['Id', titleField, contentField, categoryField]);
|
||||
for (let i: number = 0; i < fields.length; i++) {
|
||||
if (!this.isFieldName(fields[i])) { return Promise.reject(new Error('Invalid field name.')); }
|
||||
}
|
||||
const orderField: string = this.isFieldName(sortField) ? sortField : titleField;
|
||||
const url: string = this.webUrl + "/_api/web/lists(guid'" + this.cleanGuid(listId) + "')/items" +
|
||||
'?$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]) : ''
|
||||
})));
|
||||
}
|
||||
|
||||
private get webUrl(): string { return this.context.pageContext.web.absoluteUrl.replace(/\/$/, ''); }
|
||||
private get headers(): any { return { 'Accept': 'application/json;odata=verbose', 'OData-Version': '3.0' }; } // tslint:disable-line:no-any
|
||||
|
||||
private ensureJson(response: SPHttpClientResponse): Promise<any> { // tslint:disable-line:no-any
|
||||
if (response.ok) { return response.json(); }
|
||||
return response.text().then((text: string): Promise<any> => // tslint:disable-line:no-any
|
||||
Promise.reject(new Error('SharePoint request failed (' + response.status + '): ' + text)));
|
||||
}
|
||||
|
||||
private results(payload: any): any[] { // tslint:disable-line:no-any
|
||||
const source: any = payload && payload.d !== undefined ? payload.d : payload; // tslint:disable-line:no-any
|
||||
return source && Array.isArray(source.results) ? source.results : (Array.isArray(source) ? source : []);
|
||||
}
|
||||
|
||||
private uniqueFields(values: string[]): string[] {
|
||||
const result: string[] = [];
|
||||
for (let i: number = 0; i < values.length; i++) {
|
||||
const value: string = String(values[i] || '').trim();
|
||||
if (value && result.indexOf(value) < 0) { result.push(value); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private toText(value: any): string { // tslint:disable-line:no-any
|
||||
if (value === undefined || value === null) { return ''; }
|
||||
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { return String(value); }
|
||||
if (value.Label) { return String(value.Label); }
|
||||
if (value.Description || value.Url) { return String(value.Description || value.Url); }
|
||||
if (Array.isArray(value.results)) { return value.results.map((entry: any): string => this.toText(entry)).join(', '); } // tslint:disable-line:no-any
|
||||
return '';
|
||||
}
|
||||
|
||||
private isSupportedField(type: string): boolean {
|
||||
return ['Text', 'Note', 'Choice', 'MultiChoice', 'Number', 'Integer', 'Boolean', 'DateTime', 'Computed'].indexOf(String(type)) >= 0;
|
||||
}
|
||||
private isFieldName(value: string): boolean { return /^[A-Za-z_][A-Za-z0-9_]*$/.test(String(value || '')); }
|
||||
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(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user