chore: update version to 2.0.1 and enhance Classic functionality with dynamic field discovery

This commit is contained in:
Torsten Brendgen
2026-07-18 20:02:49 +02:00
parent 0d6768e24c
commit 0d6da3451d
11 changed files with 71 additions and 20 deletions

View File

@@ -336,7 +336,7 @@ neu laden.
Version 2.0 legt bei der App-Installation `ExpiryIndicatorClassic.js` unter
`SiteAssets/ExpiryIndicator` ab. Das Aktivierungsskript registriert standardmäßig für die angegebene Liste:
- einen listenspezifischen `ScriptLink` für CSR/JSLink,
- einen einmaligen Web-`ScriptLink` für CSR/JSLink, der das gebundene Feld der aktuellen Liste selbst erkennt,
- die Berechnung, Beschriftung und den Zeilenverlauf in der klassischen Ansicht,
- den Ribbon-Befehl **Ablaufdatum +1 Jahr**,
- den Einstellungsdialog über denselben URL-Parameter `expiryIndicatorSettings=1`.
@@ -393,11 +393,12 @@ Das Skript ist in einer SharePoint Management Shell beziehungsweise auf einem Re
### Upgrade von Version 1.x auf 2.0
1. Das vorhandene Paket im App Catalog durch Version `2.0.0.0` ersetzen.
1. Das vorhandene Paket im App Catalog durch Version `2.0.1.0` ersetzen.
2. Die App in der Site aktualisieren. Die enthaltene Feature-UpgradeAction provisioniert ausschließlich das
neue Classic-Asset und legt keine fachliche Spalte an.
3. `Enable-ExpiryIndicator.ps1` für jede bereits angebundene Liste erneut ausführen. Das Skript erhält die
vorhandene Feldkonfiguration und ergänzt idempotent die Classic-ScriptLink- und Ribbon-Registrierungen.
vorhandene Feldkonfiguration und ergänzt idempotent den gemeinsamen Web-ScriptLink sowie die
listenspezifische Ribbon-Registrierung.
4. Modern- und Classic-Ansicht mit geleertem Browsercache neu laden und prüfen.
## Deaktivierung
@@ -409,6 +410,8 @@ Das Skript ist in einer SharePoint Management Shell beziehungsweise auf einem Re
-ExpiryFieldInternalName 'CustomerExpiryDate'
```
Das Skript entfernt die Field-Customizer-Verknüpfung sowie die listenspezifischen Classic-ScriptLink- und
Ribbon-Registrierungen. Die Spalte und alle fachlichen Daten bleiben erhalten. Die modernen Command-Set- und
Das Skript entfernt die Field-Customizer-Verknüpfung sowie die listenspezifische Classic-Ribbon-Registrierung.
Die Spalte und alle fachlichen Daten bleiben erhalten. Der gemeinsam auf Web-Ebene verwendete ScriptLink bleibt
standardmäßig bestehen, da ihn weitere Listen benötigen können. Wenn keine weitere Liste den ExpiryIndicator
verwendet, kann er mit `-RemoveClassicWebScriptLink` entfernt werden. Die modernen Command-Set- und
Application-Customizer-Registrierungen werden durch das Entfernen der App aus der Site beseitigt.

View File

@@ -144,7 +144,7 @@ Beispiel einer Laufzeitkonfiguration:
### Release 2.0
- [x] Versionsstände von Solution, Feature und SPFx-Komponenten auf `2.0.0` anheben.
- [x] Versionsstände von Solution, Feature und SPFx-Komponenten auf `2.0.1` anheben.
- [x] README um V2-Regeln, Classic-Aktivierung und Deaktivierung ergänzen.
- [x] Pakete mit Node.js 8.17.0 erstellen und lokale Package-Validierung erfolgreich ausführen.
- [ ] Feature-Upgrade in SharePoint Server Subscription Edition testen.

View File

@@ -3,7 +3,7 @@
"solution": {
"name": "expiry-indicator-client-side-solution",
"id": "68bb6d2d-9895-45b4-88e8-b98835faa981",
"version": "2.0.0.0",
"version": "2.0.1.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": false,
"features": [
@@ -11,7 +11,7 @@
"title": "ExpiryIndicator extension registration",
"description": "Registers ExpiryIndicator commands and its settings Application Customizer. Does not provision ExpiryDate.",
"id": "913402af-ab9a-4974-9f86-5c2159ae41db",
"version": "2.0.0.0",
"version": "2.0.1.0",
"assets": {
"elementManifests": [
"elements.xml",

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "expiry-indicator",
"version": "2.0.0",
"version": "2.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "expiry-indicator",
"version": "2.0.0",
"version": "2.0.1",
"private": true,
"engines": {
"node": ">=6.9.0 <9.0.0"

View File

@@ -9,7 +9,9 @@ param(
[Parameter(Mandatory = $true)]
[string]$ExpiryFieldInternalName,
[Guid]$FieldCustomizerComponentId = 'a555f4fc-d6a6-4421-8189-457449d9bbde'
[Guid]$FieldCustomizerComponentId = 'a555f4fc-d6a6-4421-8189-457449d9bbde',
[switch]$RemoveClassicWebScriptLink
)
$isapiPath = Join-Path ([Environment]::GetFolderPath('CommonProgramFiles')) 'microsoft shared\Web Server Extensions\16\ISAPI'
@@ -23,9 +25,11 @@ try {
$list = $context.Web.Lists.GetByTitle($ListTitle)
$field = $list.Fields.GetByInternalNameOrTitle($ExpiryFieldInternalName)
$customActions = $list.UserCustomActions
$webCustomActions = $context.Web.UserCustomActions
$context.Load($list)
$context.Load($field)
$context.Load($customActions)
$context.Load($webCustomActions)
$context.ExecuteQuery()
if ($field.ClientSideComponentId -ne $FieldCustomizerComponentId) {
@@ -44,8 +48,19 @@ try {
$_.DeleteObject()
}
if ($RemoveClassicWebScriptLink) {
$webCustomActions | Where-Object {
$_.Name -eq 'ExpiryIndicator.Classic.ScriptLink'
} | ForEach-Object {
$_.DeleteObject()
}
}
$context.ExecuteQuery()
Write-Host 'ExpiryIndicator-Verknüpfung und Classic-Registrierungen wurden entfernt. Feld und Daten bleiben unverändert.'
Write-Host 'ExpiryIndicator-Verknüpfung und listenspezifische Classic-Registrierungen wurden entfernt. Feld und Daten bleiben unverändert.'
if (-not $RemoveClassicWebScriptLink) {
Write-Host 'Der gemeinsam verwendete Classic-Web-ScriptLink bleibt bestehen. Verwende -RemoveClassicWebScriptLink, wenn keine weitere Liste ihn benötigt.'
}
}
}
finally {

View File

@@ -27,9 +27,11 @@ try {
$list = $context.Web.Lists.GetByTitle($ListTitle)
$field = $list.Fields.GetByInternalNameOrTitle($ExpiryFieldInternalName)
$customActions = $list.UserCustomActions
$webCustomActions = $context.Web.UserCustomActions
$context.Load($list)
$context.Load($field)
$context.Load($customActions)
$context.Load($webCustomActions)
$context.ExecuteQuery()
if ($field.InternalName -ne $ExpiryFieldInternalName) {
@@ -55,15 +57,25 @@ try {
$_.DeleteObject()
}
if (-not $SkipClassic) {
$webCustomActions | Where-Object {
$_.Name -eq 'ExpiryIndicator.Classic.ScriptLink'
} | ForEach-Object {
$_.DeleteObject()
}
}
$context.ExecuteQuery()
if (-not $SkipClassic) {
$scriptAction = $list.UserCustomActions.Add()
# SharePoint permits Location=ScriptLink only on Web/Site custom
# actions, not on List.UserCustomActions. The runtime detects the
# current list and its bound expiry field by itself.
$scriptAction = $context.Web.UserCustomActions.Add()
$scriptAction.Name = 'ExpiryIndicator.Classic.ScriptLink'
$scriptAction.Title = 'ExpiryIndicator Classic runtime'
$scriptAction.Location = 'ScriptLink'
$separator = if ($ClassicScriptUrl.Contains('?')) { '&' } else { '?' }
$scriptAction.ScriptSrc = $ClassicScriptUrl + $separator + 'expiryField=' + [Uri]::EscapeDataString($field.InternalName)
$scriptAction.ScriptSrc = $ClassicScriptUrl
$scriptAction.Sequence = 650
$scriptAction.Update()
@@ -103,7 +115,7 @@ try {
$ribbonAction.Update()
$context.ExecuteQuery()
Write-Host 'Classic-Ansicht: CSR/JSLink und Ribbon-Befehl wurden registriert.'
Write-Host 'Classic-Ansicht: Web-ScriptLink und listenspezifischer Ribbon-Befehl wurden registriert.'
}
Write-Host "ExpiryIndicator wurde für '$($list.Title)' an '$($field.InternalName)' gebunden."

View File

@@ -15,6 +15,27 @@
return '';
}
function discoverExpiryField() {
var id = listId(null);
if (!id) { return ''; }
try {
var xhr = new XMLHttpRequest();
xhr.open('GET', listUrl(id) +
'/fields?$select=InternalName,ClientSideComponentId', false);
xhr.setRequestHeader('Accept', 'application/json;odata=verbose');
xhr.setRequestHeader('OData-Version', '3.0');
xhr.send(null);
if (xhr.status < 200 || xhr.status >= 300) { return ''; }
var data = JSON.parse(xhr.responseText || '{}');
var fields = data.d ? data.d.results : (data.value || []);
for (var index = 0; index < fields.length; index++) {
var componentId = String(fields[index].ClientSideComponentId || '').replace(/[{}]/g, '').toLowerCase();
if (componentId === FIELD_CUSTOMIZER_ID) { return fields[index].InternalName; }
}
} catch (ignore) { }
return '';
}
function pageContext() {
return window._spPageContextInfo || {};
}
@@ -473,7 +494,7 @@
function register() {
if (window.SPClientTemplates && SPClientTemplates.TemplateManager) {
var overrides = { OnPostRender: render };
var expiryField = configuredExpiryField();
var expiryField = configuredExpiryField() || discoverExpiryField();
if (expiryField) {
overrides.Templates = { Fields: {} };
overrides.Templates.Fields[expiryField] = {

View File

@@ -4,7 +4,7 @@
"alias": "ExpiryIndicatorFieldCustomizer",
"componentType": "Extension",
"extensionType": "FieldCustomizer",
"version": "2.0.0",
"version": "2.0.1",
"manifestVersion": 2,
"requiresCustomScript": false
}

View File

@@ -4,7 +4,7 @@
"alias": "ExpiryIndicatorApplicationCustomizer",
"componentType": "Extension",
"extensionType": "ApplicationCustomizer",
"version": "2.0.0",
"version": "2.0.1",
"manifestVersion": 2,
"requiresCustomScript": false
}

View File

@@ -4,7 +4,7 @@
"alias": "ExpiryIndicatorCommandSet",
"componentType": "Extension",
"extensionType": "ListViewCommandSet",
"version": "2.0.0",
"version": "2.0.1",
"manifestVersion": 2,
"requiresCustomScript": false,
"items": {