Files
ExpiryIndicator/src/extensions/expiryIndicator/ExpiryIndicatorFieldCustomizer.ts
Torsten Brendgen c1f1d02d9c Add Expiry Indicator functionality with configuration and command set
- Implemented ExpiryItemService to manage expiry date logic for list items.
- Created ExpiryModels to define types and structures for expiry configurations and rules.
- Added RestError utility for handling API response errors.
- Developed ExpiryIndicatorFieldCustomizer to display expiry information in list views.
- Introduced ExpiryIndicatorCommandSet for extending expiry dates and managing settings.
- Added settings dialog for configuring expiry settings.
- Implemented localization support for English and German languages.
- Added TypeScript configuration files for project setup.
- Included TSLint configuration for code quality enforcement.
2026-07-16 21:55:30 +02:00

158 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { override } from '@microsoft/decorators';
import {
BaseFieldCustomizer,
IFieldCustomizerCellEventParameters
} from '@microsoft/sp-listview-extensibility';
import { evaluateExpiry, formatDate, parseSharePointDate } from '../../common/ExpiryDateCalculator';
import { ExpiryConfigService } from '../../common/ExpiryConfigService';
import { ExpiryItemService } from '../../common/ExpiryItemService';
import {
createDefaultConfig,
IExpiryConfig,
IExpiryEvaluation,
IItemDateValues,
resolveBehavior
} from '../../common/ExpiryModels';
import styles from './ExpiryIndicatorFieldCustomizer.module.scss';
import * as strings from 'ExpiryIndicatorStrings';
export interface IExpiryIndicatorFieldCustomizerProperties {
}
export default class ExpiryIndicatorFieldCustomizer
extends BaseFieldCustomizer<IExpiryIndicatorFieldCustomizerProperties> {
private _config: IExpiryConfig = createDefaultConfig();
private _itemService: ExpiryItemService;
private _itemCache: { [id: string]: Promise<IItemDateValues> } = {};
@override
public onInit(): Promise<void> {
if (!this.context.pageContext.list) {
return Promise.resolve();
}
const service: ExpiryConfigService = new ExpiryConfigService(
this.context.spHttpClient,
this.context.pageContext.web.absoluteUrl
);
this._itemService = new ExpiryItemService(
this.context.spHttpClient,
this.context.pageContext.web.absoluteUrl,
this.context.pageContext.list.id.toString()
);
return service.getConfig(this.context.pageContext.list.id.toString())
.then((config: IExpiryConfig): void => { this._config = config; })
.catch((): void => { this._config = createDefaultConfig(); });
}
@override
public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
this._clear(event.domElement);
const expiry: Date | undefined = parseSharePointDate(event.fieldValue);
const createdValue: any = event.listItem && event.listItem.getValueByName
? event.listItem.getValueByName(this._config.baseField)
: undefined;
const created: Date | undefined = parseSharePointDate(createdValue);
const idValue: any = event.listItem && event.listItem.getValueByName
? (event.listItem.getValueByName('ID') || event.listItem.getValueByName('Id'))
: undefined;
const itemId: number = Number(idValue);
if (itemId > 0 && this._itemService) {
const requestKey: string = String(itemId);
event.domElement.setAttribute('data-expiry-item-id', requestKey);
this._renderNeutral(event.domElement, strings.Loading);
if (!this._itemCache[requestKey]) {
this._itemCache[requestKey] = this._itemService.getItemDates(itemId, this._config);
}
this._itemCache[requestKey].then((item: IItemDateValues): void => {
if (event.domElement.getAttribute('data-expiry-item-id') !== requestKey) {
return;
}
this._clear(event.domElement);
this._renderValues(event.domElement, item.created, item.expiry, item.fieldValues);
}).catch((): void => {
if (event.domElement.getAttribute('data-expiry-item-id') === requestKey) {
this._clear(event.domElement);
this._renderValues(event.domElement, created, expiry, {});
}
});
return;
}
this._renderValues(event.domElement, created, expiry, {});
}
@override
public onDisposeCell(event: IFieldCustomizerCellEventParameters): void {
this._clear(event.domElement);
super.onDisposeCell(event);
}
private _renderEvaluation(container: HTMLElement, evaluation: IExpiryEvaluation): void {
const wrapper: HTMLSpanElement = document.createElement('span');
wrapper.className = styles.cell + (evaluation.wasCalculated ? ' ' + styles.calculated : '');
if (evaluation.matchedRule) {
wrapper.style.backgroundColor = evaluation.matchedRule.backgroundColor;
wrapper.style.color = evaluation.matchedRule.textColor;
}
const date: HTMLSpanElement = document.createElement('span');
date.className = styles.date;
date.textContent = formatDate(evaluation.effectiveExpiryDate);
wrapper.appendChild(date);
if (evaluation.matchedRule && evaluation.matchedRule.label) {
const label: HTMLSpanElement = document.createElement('span');
label.className = styles.label;
label.textContent = evaluation.matchedRule.label;
wrapper.appendChild(label);
}
const calculationHint: string = evaluation.wasCalculated ? ' ' + strings.CalculatedHint : '';
wrapper.title = strings.DaysUntilExpiry.replace('{0}', String(evaluation.daysUntilExpiry)) + calculationHint;
container.appendChild(wrapper);
}
private _renderValues(
container: HTMLElement,
created: Date | undefined,
expiry: Date | undefined,
fieldValues: { [fieldName: string]: any }
): void {
if (!expiry && !created) {
this._renderNeutral(container, this._config.nullText);
return;
}
const evaluation: IExpiryEvaluation = evaluateExpiry(
created || expiry as Date,
expiry,
this._config,
undefined,
resolveBehavior(this._config, fieldValues)
);
this._renderEvaluation(container, evaluation);
}
private _renderNeutral(container: HTMLElement, text: string): void {
const wrapper: HTMLSpanElement = document.createElement('span');
wrapper.className = styles.cell + ' ' + styles.neutral;
wrapper.textContent = text;
container.appendChild(wrapper);
}
private _clear(container: HTMLElement): void {
while (container.firstChild) {
container.removeChild(container.firstChild);
}
}
}