From 3ee1247efe9e355f76f00754ff6375bb77e9c40e Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Wed, 26 Aug 2026 23:35:10 +0200 Subject: [PATCH] =?UTF-8?q?Aktualisiere=20Version=20auf=201.1.1,=20f=C3=BC?= =?UTF-8?q?ge=20Unterst=C3=BCtzung=20f=C3=BCr=20die=20Aufteilung=20von=20T?= =?UTF-8?q?axonomie-Werten=20hinzu=20und=20verbessere=20die=20Handhabung?= =?UTF-8?q?=20von=20Kategoriewerten=20im=20Accordion-WebPart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- config/package-solution.json | 2 +- package.json | 2 +- .../accordion/AccordionCategoryValues.ts | 8 ++-- .../accordion/AccordionDataService.ts | 38 +++++++++++++++---- src/webparts/accordion/AccordionWebPart.ts | 12 +++++- tests/AccordionCategoryValues.test.ts | 2 + tests/validate-project.js | 7 +++- 8 files changed, 56 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index fe1f46e..0c60964 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Accordion -Accordion 1.1.0 ist ein listenbasiertes SPFx-WebPart für SharePoint Server Subscription Edition. Es orientiert +Accordion 1.1.1 ist ein listenbasiertes SPFx-WebPart für SharePoint Server Subscription Edition. Es orientiert sich funktional am PnP-Beispiel `react-accordion-dynamic-section`, verwendet aber die mit SharePoint SE kompatible SPFx-1.4.1-/React-15-Toolchain. @@ -39,7 +39,7 @@ nächsten Laden beziehungsweise Rendern der Seite automatisch. | Eigenschaft | Wert | |---|---| -| Version | 1.1.0 | +| Version | 1.1.1 | | SharePoint Framework | 1.4.1 | | React | 15.6.2 | | Node.js | 8.17.0 | diff --git a/config/package-solution.json b/config/package-solution.json index dc1838a..45e57ec 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -4,7 +4,7 @@ "name": "accordion-web-part", "title": "accordion-web-part", "id": "27a6fd90-42a1-4e39-8862-08ed0e7803bc", - "version": "1.1.0.0", + "version": "1.1.1.0", "includeClientSideAssets": true, "skipFeatureDeployment": true }, diff --git a/package.json b/package.json index 475b05d..87f5d7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "accordion-web-part", - "version": "1.1.0", + "version": "1.1.1", "private": true, "main": "lib/index.js", "engines": { diff --git a/src/webparts/accordion/AccordionCategoryValues.ts b/src/webparts/accordion/AccordionCategoryValues.ts index 3b7d15a..f2ca7cf 100644 --- a/src/webparts/accordion/AccordionCategoryValues.ts +++ b/src/webparts/accordion/AccordionCategoryValues.ts @@ -1,10 +1,10 @@ -export function getAccordionCategoryValues(value: any): string[] { // tslint:disable-line:no-any +export function getAccordionCategoryValues(value: any, splitPlainSemicolon?: boolean): 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 const values: string[] = []; const keys: { [key: string]: boolean } = {}; for (let i: number = 0; i < source.length; i++) { - const current: string[] = categoryEntryValues(source[i]); + const current: string[] = categoryEntryValues(source[i], splitPlainSemicolon === true); for (let valueIndex: number = 0; valueIndex < current.length; valueIndex++) { const text: string = current[valueIndex].trim(); const key: string = '$' + text.toLocaleLowerCase(); @@ -14,7 +14,7 @@ export function getAccordionCategoryValues(value: any): string[] { // tslint:dis return values; } -function categoryEntryValues(entry: any): string[] { // tslint:disable-line:no-any +function categoryEntryValues(entry: any, splitPlainSemicolon: boolean): string[] { // tslint:disable-line:no-any if (entry === undefined || entry === null) { return []; } if (entry.Label) { return [String(entry.Label)]; } if (typeof entry !== 'string') { @@ -32,7 +32,7 @@ function categoryEntryValues(entry: any): string[] { // tslint:disable-line:no-a const semicolonParts: string[] = candidate.split(';'); const allTaxonomyValues: boolean = semicolonParts.every((part: string): boolean => /\|[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(part.trim())); - if (allTaxonomyValues) { parts = semicolonParts; } + if (allTaxonomyValues || splitPlainSemicolon) { parts = semicolonParts; } } const values: string[] = []; diff --git a/src/webparts/accordion/AccordionDataService.ts b/src/webparts/accordion/AccordionDataService.ts index fe61bce..86b33f6 100644 --- a/src/webparts/accordion/AccordionDataService.ts +++ b/src/webparts/accordion/AccordionDataService.ts @@ -38,7 +38,8 @@ export class AccordionDataService { categoryField: string, sortField: string, sortAscending: boolean, - categoryValue: string + 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]); @@ -46,15 +47,21 @@ export class AccordionDataService { 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(',') + '&$orderby=' + orderField + (sortAscending ? ' asc' : ' desc') + '&$top=5000'; + '?$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 - getAccordionCategoryValues(item[categoryField]).indexOf(categoryValue) >= 0) + this.itemCategoryValues(item, categoryField, taxonomyCategory).indexOf(categoryValue) >= 0) .map((item: any): IAccordionItem => { // tslint:disable-line:no-any - const categories: string[] = categoryField ? getAccordionCategoryValues(item[categoryField]) : []; + const categories: string[] = categoryField + ? this.itemCategoryValues(item, categoryField, taxonomyCategory) + : []; return { id: Number(item.Id), title: this.toText(item[titleField]), @@ -64,10 +71,13 @@ export class AccordionDataService { })); } - public getCategoryValues(listId: string, categoryField: string): Promise { + 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 + '&$top=5000'; + '?$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 @@ -75,7 +85,7 @@ export class AccordionDataService { 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[] = getAccordionCategoryValues(items[i][categoryField]); + 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]); } @@ -108,6 +118,17 @@ export class AccordionDataService { 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); } @@ -121,6 +142,9 @@ export class AccordionDataService { 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(); } diff --git a/src/webparts/accordion/AccordionWebPart.ts b/src/webparts/accordion/AccordionWebPart.ts index 8fad6cc..5a80db7 100644 --- a/src/webparts/accordion/AccordionWebPart.ts +++ b/src/webparts/accordion/AccordionWebPart.ts @@ -66,7 +66,8 @@ export default class AccordionWebPart extends BaseClientSideWebPart { if (this.disposed || version !== this.requestVersion) { return; } if (!items.length) { this.renderStatus(strings.EmptyMessage, false); return; } @@ -238,7 +239,7 @@ export default class AccordionWebPart extends BaseClientSideWebPart { + 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 => { @@ -281,6 +282,13 @@ export default class AccordionWebPart extends BaseClientSideWebPart= 0 && dataService.indexOf('getAccordionCategoryValues') >= 0 && categoryValues.indexOf('entry.Label') >= 0 && categoryValues.indexOf("candidate.indexOf(';#')") >= 0, 'Managed Metadata category support is missing.'); +assert(dataService.indexOf('FieldValuesAsText/') >= 0 && + dataService.indexOf('itemCategoryValues') >= 0 && categoryValues.indexOf('splitPlainSemicolon') >= 0, + 'Managed Metadata labels must be resolved instead of displaying WSS IDs.'); assert(component.indexOf('items[i].categories') >= 0 && component.indexOf('nameIndex') >= 0, 'Multi-value category grouping is missing.'); assert(component.indexOf("aria-expanded={open}") >= 0 && component.indexOf("role='region'") >= 0,