Implement multiline options support and legacy field handling in Dynamic Poll

This commit is contained in:
Torsten Brendgen
2026-08-24 09:32:31 +02:00
parent ca41674188
commit 53690530bc
6 changed files with 104 additions and 24 deletions
+14 -1
View File
@@ -1,5 +1,19 @@
import { IPoll, IPollAnswer, IPollResult } from './PollTypes';
export function parsePollOptionLines(values: string[]): string[] {
const options: string[] = [];
const seen: { [key: string]: boolean } = {};
for (let valueIndex: number = 0; valueIndex < values.length; valueIndex++) {
const lines: string[] = String(values[valueIndex] || '').split(/\r\n|\n|\r/);
for (let lineIndex: number = 0; lineIndex < lines.length; lineIndex++) {
const option: string = lines[lineIndex].trim();
const key: string = option.toLocaleLowerCase();
if (option && !seen[key]) { seen[key] = true; options.push(option); }
}
}
return options;
}
export function selectActivePoll(polls: IPoll[], now: Date): IPoll | undefined {
const timestamp: number = now.getTime();
const active: IPoll[] = polls.filter((poll: IPoll): boolean =>
@@ -34,4 +48,3 @@ export function findUserAnswer(answers: IPollAnswer[], userKey: string): string
}
function dateValue(value?: Date): number { return value ? value.getTime() : 0; }
+37 -17
View File
@@ -1,9 +1,11 @@
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { calculatePollResults, findUserAnswer, selectActivePoll } from './PollCore';
import { calculatePollResults, findUserAnswer, parsePollOptionLines, selectActivePoll } from './PollCore';
import { IListOption, IPoll, IPollAnswer, IPollSnapshot } from './PollTypes';
export class PollDataService {
private optionsFieldNames: { [listId: string]: string } = {};
public constructor(private context: WebPartContext) { }
public getLists(): Promise<IListOption[]> {
@@ -56,19 +58,37 @@ export class PollDataService {
}
private getActivePoll(listId: string): Promise<IPoll | undefined> {
const url: string = this.webUrl + "/_api/web/lists(guid'" + this.validateGuid(listId) + "')/items" +
'?$select=Id,Title,Question,Options,StartDate,EndDate,IsActive&$filter=IsActive eq 1&$orderby=StartDate desc&$top=100';
return this.getJson(url).then((payload: any): IPoll | undefined => { // tslint:disable-line:no-any
const polls: IPoll[] = this.results(payload).map((item: any): IPoll => ({ // tslint:disable-line:no-any
id: Number(item.Id),
title: String(item.Title || ''),
question: String(item.Question || item.Title || ''),
options: this.toStringArray(item.Options),
startDate: this.toDate(item.StartDate),
endDate: this.toDate(item.EndDate),
isActive: item.IsActive === true || item.IsActive === 1
}));
return selectActivePoll(polls, new Date());
return this.getOptionsFieldName(listId).then((optionsField: string): Promise<IPoll | undefined> => {
const url: string = this.webUrl + "/_api/web/lists(guid'" + this.validateGuid(listId) + "')/items" +
'?$select=Id,Title,Question,' + optionsField +
',StartDate,EndDate,IsActive&$filter=IsActive eq 1&$orderby=StartDate desc&$top=100';
return this.getJson(url).then((payload: any): IPoll | undefined => { // tslint:disable-line:no-any
const polls: IPoll[] = this.results(payload).map((item: any): IPoll => ({ // tslint:disable-line:no-any
id: Number(item.Id),
title: String(item.Title || ''),
question: String(item.Question || item.Title || ''),
options: this.toStringArray(item[optionsField]),
startDate: this.toDate(item.StartDate),
endDate: this.toDate(item.EndDate),
isActive: item.IsActive === true || item.IsActive === 1
}));
return selectActivePoll(polls, new Date());
});
});
}
private getOptionsFieldName(listId: string): Promise<string> {
const cleanListId: string = this.validateGuid(listId);
if (this.optionsFieldNames[cleanListId]) { return Promise.resolve(this.optionsFieldNames[cleanListId]); }
const url: string = this.webUrl + "/_api/web/lists(guid'" + cleanListId + "')/fields" +
'?$select=InternalName';
return this.getJson(url).then((payload: any): string => { // tslint:disable-line:no-any
const names: string[] = this.results(payload).map((field: any): string => String(field.InternalName || '')); // tslint:disable-line:no-any
const fieldName: string = names.indexOf('OptionsText') >= 0 ? 'OptionsText' :
(names.indexOf('Options') >= 0 ? 'Options' : '');
if (!fieldName) { throw new Error('The poll options field is missing.'); }
this.optionsFieldNames[cleanListId] = fieldName;
return fieldName;
});
}
@@ -107,9 +127,9 @@ export class PollDataService {
return source && Array.isArray(source.results) ? source.results : [];
}
private toStringArray(value: any): string[] { // tslint:disable-line:no-any
if (typeof value === 'string' && value.trim()) { return [value.trim()]; }
const source: any[] = value && Array.isArray(value.results) ? value.results : (Array.isArray(value) ? value : []); // tslint:disable-line:no-any
return source.map((entry: any): string => String(entry || '').trim()).filter((entry: string): boolean => !!entry); // tslint:disable-line:no-any
const source: any[] = value && Array.isArray(value.results) ? value.results : // tslint:disable-line:no-any
(Array.isArray(value) ? value : [value]);
return parsePollOptionLines(source.map((entry: any): string => String(entry || ''))); // tslint:disable-line:no-any
}
private toDate(value: any): Date | undefined { // tslint:disable-line:no-any
if (!value) { return undefined; }