diff --git a/README.md b/README.md index 7533df2..3a4347e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Zentrale, barrierearme Portalnavigation aus SharePoint Managed Metadata für Sha | Eigenschaft | Wert | |---|---| -| Version | 2.2.4 | +| Version | 2.2.5 | | SharePoint Framework | 1.4.1 | | Node.js für den Build | 8.17.0 | | Moderne Seiten | SPFx Application Customizer | @@ -272,6 +272,11 @@ MegaMenu/ ## Versionshistorie +### 2.2.5 + +- Das Mega Menu wartet bei vertauschter Ladefolge auf `#CustomHeader` und verschiebt seine bereits gerenderte + Navigation automatisch in den Custom-Branding-Header. + ### 2.2.4 - Der Ladehinweis wird auch dann zuverlässig entfernt, wenn der Custom-Branding-Host während des asynchronen Ladens erzeugt wird. diff --git a/ToDo.md b/ToDo.md index 43d1609..213bdf0 100644 --- a/ToDo.md +++ b/ToDo.md @@ -1,4 +1,4 @@ -# MegaMenu – Arbeitsplan Version 2.2.4 +# MegaMenu – Arbeitsplan Version 2.2.5 Status: Implementierung abgeschlossen; Ship-Build auf der vorgesehenen Node-8-Buildumgebung ausstehend @@ -10,6 +10,7 @@ Status: Implementierung abgeschlossen; Ship-Build auf der vorgesehenen Node-8-Bu - [x] Navigations-URLs validieren und unsichere Protokolle blockieren. - [x] Externe Links und `MegaMenu.OpenInNewWindow` sicher unterstützen. - [x] Lade-, Leer- und Fehlerzustände verständlich und barrierearm darstellen. +- [x] Spät erzeugten Custom-Branding-Header beobachten und die Navigation ohne Reload dorthin verschieben. ## Lokale Term-Eigenschaften diff --git a/config/package-solution.json b/config/package-solution.json index 054b8c6..05d9ad3 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -3,7 +3,7 @@ "solution": { "name": "mega-menu-client-side-solution", "id": "f4660e06-ce08-43ee-bfb7-5c4464e01133", - "version": "2.2.4.0", + "version": "2.2.5.0", "includeClientSideAssets": true, "skipFeatureDeployment": true }, diff --git a/package-lock.json b/package-lock.json index b370c91..eb66ce5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "mega-menu", - "version": "2.2.4", + "version": "2.2.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d13fdf0..ed8b649 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mega-menu", - "version": "2.2.4", + "version": "2.2.5", "private": true, "main": "lib/index.js", "engines": { diff --git a/src/extensions/megaMenu/MegaMenuApplicationCustomizer.ts b/src/extensions/megaMenu/MegaMenuApplicationCustomizer.ts index 8a821d8..bbe352a 100644 --- a/src/extensions/megaMenu/MegaMenuApplicationCustomizer.ts +++ b/src/extensions/megaMenu/MegaMenuApplicationCustomizer.ts @@ -38,13 +38,14 @@ export default class MegaMenuApplicationCustomizer private _menuContainer: HTMLElement | undefined; private _renderer: MegaMenuRenderer | undefined; private _statusElement: HTMLElement | undefined; + private _customHeaderObserver: MutationObserver | undefined; @override public onInit(): Promise { const debug: boolean = this._isDebugEnabled(); debugLog(debug, LOG_SOURCE, 'onInit started.', { componentId: UserCustomActionMegaMenuId, - version: '2.2.4', + version: '2.2.5', properties: this.properties || {}, pageUrl: window.location.href, webUrl: this.context.pageContext.web.absoluteUrl, @@ -178,7 +179,9 @@ export default class MegaMenuApplicationCustomizer } private _getOrCreateContainer(id: string, placeholder: PlaceholderContent): HTMLElement { - const container = document.getElementById(id); + const container: HTMLElement = document.getElementById(id); + let host: HTMLElement = this._menuContainer || document.getElementById('MegaMenuHost') || + document.getElementById('CustomNavigation'); if (container) { debugLog(this._isDebugEnabled(), LOG_SOURCE, 'Existing host container found.', { @@ -186,31 +189,63 @@ export default class MegaMenuApplicationCustomizer containerId: container.id, containerClassName: container.className || '' }); - let host: HTMLElement = document.getElementById('MegaMenuHost'); if (!host) { host = document.createElement('div'); host.id = 'MegaMenuHost'; - container.appendChild(host); - debugLog(this._isDebugEnabled(), LOG_SOURCE, 'MegaMenuHost was created inside the existing host container.'); - } else { - debugLog(this._isDebugEnabled(), LOG_SOURCE, 'Existing MegaMenuHost will be reused.'); } + if (host.parentElement !== container) { container.appendChild(host); } + this._stopCustomHeaderObserver(); + debugLog(this._isDebugEnabled(), LOG_SOURCE, 'MegaMenu host is located inside the existing CustomHeader.'); return host; } - debugLog(this._isDebugEnabled(), LOG_SOURCE, 'No external host container found; the Top placeholder will be used.', { + if (!host) { + host = document.createElement('div'); + host.id = 'MegaMenuHost'; + } + if (host.parentElement !== placeholder.domElement) { placeholder.domElement.appendChild(host); } + this._startCustomHeaderObserver(id); + debugLog(this._isDebugEnabled(), LOG_SOURCE, 'No external host container found; using a temporary host in the Top placeholder.', { requestedId: id }); - return placeholder.domElement; + return host; + } + + private _startCustomHeaderObserver(id: string): void { + if (this._customHeaderObserver || typeof MutationObserver === 'undefined') { return; } + this._customHeaderObserver = new MutationObserver((): void => { + const customHeader: HTMLElement = document.getElementById(id); + if (!customHeader || !this._menuContainer || this._menuContainer === customHeader) { return; } + if (this._menuContainer.parentElement !== customHeader) { + customHeader.appendChild(this._menuContainer); + debugLog(this._isDebugEnabled(), LOG_SOURCE, 'MegaMenu host moved into the late CustomHeader.', { + containerId: this._menuContainer.id || '' + }); + } + this._stopCustomHeaderObserver(); + }); + this._customHeaderObserver.observe(document.documentElement, { childList: true, subtree: true }); + debugLog(this._isDebugEnabled(), LOG_SOURCE, 'Waiting for CustomHeader DOM registration.'); + } + + private _stopCustomHeaderObserver(): void { + if (this._customHeaderObserver) { + this._customHeaderObserver.disconnect(); + this._customHeaderObserver = undefined; + } } private _onDispose = (): void => { this.context.placeholderProvider.changedEvent.remove(this, this._renderPlaceHolders); + this._stopCustomHeaderObserver(); this._clearStatus(); if (this._renderer) { this._renderer.dispose(); this._renderer = undefined; } + if (this._menuContainer && this._menuContainer.parentElement) { + this._menuContainer.parentElement.removeChild(this._menuContainer); + } this._menuContainer = undefined; this._topPlaceholder = undefined; debugLog(this._isDebugEnabled(), LOG_SOURCE, 'Disposed custom top placeholder.'); diff --git a/tests/validate-static-assets.js b/tests/validate-static-assets.js index 89b1527..236081e 100644 --- a/tests/validate-static-assets.js +++ b/tests/validate-static-assets.js @@ -45,6 +45,10 @@ var applicationCustomizer = read('src/extensions/megaMenu/MegaMenuApplicationCus assert('modern loading status is cleared before menu render', applicationCustomizer.indexOf('this._clearStatus();') >= 0 && applicationCustomizer.indexOf('private _statusElement: HTMLElement | undefined;') >= 0); +assert('late CustomHeader is observed and receives the menu host', + applicationCustomizer.indexOf('new MutationObserver') >= 0 && + applicationCustomizer.indexOf('customHeader.appendChild(this._menuContainer)') >= 0 && + applicationCustomizer.indexOf('this._stopCustomHeaderObserver();') >= 0); [ 'white', 'neutralLighter',