import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http'; import { WebPartContext } from '@microsoft/sp-webpart-base'; import { getAccordionCategoryValues } from './AccordionCategoryValues'; import { IAccordionFieldOption, IAccordionItem, IAccordionListOption } from './AccordionTypes'; export class AccordionDataService { public constructor(private context: WebPartContext) { } public getLists(): Promise { 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 => 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 { 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 => 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, categoryValue: string, categoryFieldType: string ): Promise { 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 taxonomyCategory: boolean = this.isTaxonomyType(categoryFieldType) && !!categoryField; const formattedSelect: string = taxonomyCategory ? ',FieldValuesAsText/' + categoryField : ''; const formattedExpand: string = taxonomyCategory ? '&$expand=FieldValuesAsText' : ''; const url: string = this.webUrl + "/_api/web/lists(guid'" + this.cleanGuid(listId) + "')/items" + '?$select=' + fields.join(',') + formattedSelect + formattedExpand + '&$orderby=' + orderField + (sortAscending ? ' asc' : ' desc') + '&$top=5000'; return this.context.spHttpClient.get(url, SPHttpClient.configurations.v1, { headers: this.headers }) .then((response: SPHttpClientResponse): Promise => this.ensureJson(response)) // tslint:disable-line:no-any .then((payload: any): IAccordionItem[] => this.results(payload) // tslint:disable-line:no-any .filter((item: any): boolean => !categoryValue || // tslint:disable-line:no-any this.itemCategoryValues(item, categoryField, taxonomyCategory).indexOf(categoryValue) >= 0) .map((item: any): IAccordionItem => { // tslint:disable-line:no-any const categories: string[] = categoryField ? this.itemCategoryValues(item, categoryField, taxonomyCategory) : []; return { id: Number(item.Id), title: this.toText(item[titleField]), content: this.toText(item[contentField]), categories: categories }; })); } public getCategoryValues(listId: string, categoryField: string, categoryFieldType: string): Promise { if (!this.isGuid(listId) || !this.isFieldName(categoryField)) { return Promise.resolve([]); } const taxonomyCategory: boolean = this.isTaxonomyType(categoryFieldType); const formattedSelect: string = taxonomyCategory ? ',FieldValuesAsText/' + categoryField : ''; const formattedExpand: string = taxonomyCategory ? '&$expand=FieldValuesAsText' : ''; const url: string = this.webUrl + "/_api/web/lists(guid'" + this.cleanGuid(listId) + "')/items" + '?$select=' + categoryField + formattedSelect + formattedExpand + '&$top=5000'; return this.context.spHttpClient.get(url, SPHttpClient.configurations.v1, { headers: this.headers }) .then((response: SPHttpClientResponse): Promise => 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.itemCategoryValues(items[i], categoryField, taxonomyCategory); 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(/\/$/, ''); } private get headers(): any { return { 'Accept': 'application/json;odata=verbose', 'OData-Version': '3.0' }; } // tslint:disable-line:no-any private ensureJson(response: SPHttpClientResponse): Promise { // tslint:disable-line:no-any if (response.ok) { return response.json(); } return response.text().then((text: string): Promise => // 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 itemCategoryValues(item: any, categoryField: string, taxonomyCategory: boolean): string[] { // tslint:disable-line:no-any if (taxonomyCategory && item && item.FieldValuesAsText) { const formatted: string[] = getAccordionCategoryValues( item.FieldValuesAsText[categoryField], true ); if (formatted.length) { return formatted; } } return getAccordionCategoryValues(item ? item[categoryField] : undefined); } 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', 'TaxonomyFieldType', 'TaxonomyFieldTypeMulti'].indexOf(String(type)) >= 0; } private isTaxonomyType(type: string): boolean { return type === 'TaxonomyFieldType' || type === 'TaxonomyFieldTypeMulti'; } 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(); } }