Bump version to 1.1.3, add detailed lifecycle logging in debug mode, and enhance error handling in services
This commit is contained in:
15
README.md
15
README.md
@@ -4,7 +4,7 @@ Barrierearme, dreistufige Portalnavigation auf Basis von SharePoint Managed Meta
|
|||||||
|
|
||||||
| Eigenschaft | Wert |
|
| Eigenschaft | Wert |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Version | 1.1.2 |
|
| Version | 1.1.3 |
|
||||||
| SharePoint Framework | SPFx 1.4.1 |
|
| SharePoint Framework | SPFx 1.4.1 |
|
||||||
| Node.js | 8.17.0 |
|
| Node.js | 8.17.0 |
|
||||||
| Zieloberfläche | Moderne SharePoint-Seiten |
|
| Zieloberfläche | Moderne SharePoint-Seiten |
|
||||||
@@ -214,6 +214,13 @@ Weder eine gültige `termSetId` noch ein `termSetName` ist gesetzt. UserCustomAc
|
|||||||
|
|
||||||
Für weitere technische Details kann vorübergehend `debug: true` gesetzt werden.
|
Für weitere technische Details kann vorübergehend `debug: true` gesetzt werden.
|
||||||
|
|
||||||
|
Bei aktiviertem Debug-Modus protokolliert MegaMenu den kompletten Startpfad mit den Quellen
|
||||||
|
`MegaMenuApplicationCustomizer`, `TaxonomyNavigationService`, `SPTermStorePickerService` und
|
||||||
|
`MegaMenuRenderer`. Dazu gehören die übergebenen Properties, Placeholder-Erkennung,
|
||||||
|
Taxonomy-Anfrage, Cache-Nutzung, Term-Anzahl, Containerwahl und vollständige Fehlerdetails.
|
||||||
|
Wenn keine dieser Meldungen erscheint, wurde das MegaMenu-Bundle nicht durch den SPFx-Loader
|
||||||
|
ausgeführt.
|
||||||
|
|
||||||
### Änderungen am Termset erscheinen verzögert
|
### Änderungen am Termset erscheinen verzögert
|
||||||
|
|
||||||
Die Navigation wird bis zu `cacheMinutes` im Session-Cache gehalten. Cache leeren oder Ablaufzeit abwarten.
|
Die Navigation wird bis zu `cacheMinutes` im Session-Cache gehalten. Cache leeren oder Ablaufzeit abwarten.
|
||||||
@@ -254,6 +261,12 @@ MegaMenu/
|
|||||||
|
|
||||||
## Versionshistorie
|
## Versionshistorie
|
||||||
|
|
||||||
|
### 1.1.3
|
||||||
|
|
||||||
|
- Ausführliches, ausschließlich über `debug: true` aktiviertes Lifecycle-Logging ergänzt.
|
||||||
|
- Taxonomy-Anfragen, Session-Cache, Term-Filterung und Renderer werden nachvollziehbar protokolliert.
|
||||||
|
- Fehlerausgaben enthalten Name, Meldung und Stacktrace.
|
||||||
|
|
||||||
### 1.1.2
|
### 1.1.2
|
||||||
|
|
||||||
- `cssUrl` zugunsten der zentralen Custom-Branding-Solution entfernt
|
- `cssUrl` zugunsten der zentralen Custom-Branding-Solution entfernt
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"solution": {
|
"solution": {
|
||||||
"name": "mega-menu-client-side-solution",
|
"name": "mega-menu-client-side-solution",
|
||||||
"id": "f4660e06-ce08-43ee-bfb7-5c4464e01133",
|
"id": "f4660e06-ce08-43ee-bfb7-5c4464e01133",
|
||||||
"version": "1.1.2.0",
|
"version": "1.1.3.0",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"skipFeatureDeployment": false,
|
"skipFeatureDeployment": false,
|
||||||
"features": [
|
"features": [
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"title": "MegaMenu extension registration",
|
"title": "MegaMenu extension registration",
|
||||||
"description": "Registers the MegaMenu Application Customizer in the host web.",
|
"description": "Registers the MegaMenu Application Customizer in the host web.",
|
||||||
"id": "1e4fdc74-8053-48b2-a15b-e975d61eb71f",
|
"id": "1e4fdc74-8053-48b2-a15b-e975d61eb71f",
|
||||||
"version": "1.1.2.0",
|
"version": "1.1.3.0",
|
||||||
"assets": {
|
"assets": {
|
||||||
"elementManifests": [
|
"elementManifests": [
|
||||||
"elements.xml"
|
"elements.xml"
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mega-menu",
|
"name": "mega-menu",
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mega-menu",
|
"name": "mega-menu",
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -37,40 +37,66 @@ export default class MegaMenuApplicationCustomizer
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
public onInit(): Promise<void> {
|
public onInit(): Promise<void> {
|
||||||
debugLog(this._isDebugEnabled(), LOG_SOURCE, 'Initialized ' + strings.Title);
|
const debug: boolean = this._isDebugEnabled();
|
||||||
|
debugLog(debug, LOG_SOURCE, 'onInit started.', {
|
||||||
|
componentId: UserCustomActionMegaMenuId,
|
||||||
|
version: '1.1.3',
|
||||||
|
properties: this.properties || {},
|
||||||
|
pageUrl: window.location.href,
|
||||||
|
webUrl: this.context.pageContext.web.absoluteUrl,
|
||||||
|
siteUrl: this.context.pageContext.site.absoluteUrl
|
||||||
|
});
|
||||||
|
|
||||||
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
|
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
|
||||||
|
debugLog(debug, LOG_SOURCE, 'Placeholder changedEvent handler registered.');
|
||||||
this._renderPlaceHolders();
|
this._renderPlaceHolders();
|
||||||
|
|
||||||
|
debugLog(debug, LOG_SOURCE, 'onInit completed. Asynchronous menu loading may still be running.');
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderPlaceHolders(): void {
|
private _renderPlaceHolders(): void {
|
||||||
|
const debug: boolean = this._isDebugEnabled();
|
||||||
const availablePlaceholders: string = this.context.placeholderProvider.placeholderNames
|
const availablePlaceholders: string = this.context.placeholderProvider.placeholderNames
|
||||||
.map(name => PlaceholderName[name])
|
.map(name => PlaceholderName[name])
|
||||||
.join(', ');
|
.join(', ');
|
||||||
|
|
||||||
debugLog(this._isDebugEnabled(), LOG_SOURCE, 'Available placeholders:', availablePlaceholders);
|
debugLog(debug, LOG_SOURCE, 'Rendering placeholders.', {
|
||||||
|
availablePlaceholders: availablePlaceholders,
|
||||||
|
topPlaceholderAlreadyCreated: !!this._topPlaceholder
|
||||||
|
});
|
||||||
|
|
||||||
if (!this._topPlaceholder) {
|
if (!this._topPlaceholder) {
|
||||||
|
debugLog(debug, LOG_SOURCE, 'Requesting the Top placeholder.');
|
||||||
this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
|
this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
|
||||||
PlaceholderName.Top,
|
PlaceholderName.Top,
|
||||||
{ onDispose: this._onDispose }
|
{ onDispose: this._onDispose }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!this._topPlaceholder) {
|
if (!this._topPlaceholder) {
|
||||||
debugError(this._isDebugEnabled(), LOG_SOURCE, 'The expected placeholder (Top) was not found.');
|
debugError(debug, LOG_SOURCE, 'The expected placeholder (Top) was not found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog(debug, LOG_SOURCE, 'Top placeholder acquired.', {
|
||||||
|
domElementId: this._topPlaceholder.domElement.id || '',
|
||||||
|
domElementClassName: this._topPlaceholder.domElement.className || ''
|
||||||
|
});
|
||||||
|
|
||||||
const termSetIdentifier: string = this._getTermSetIdentifier();
|
const termSetIdentifier: string = this._getTermSetIdentifier();
|
||||||
if (!termSetIdentifier) {
|
if (!termSetIdentifier) {
|
||||||
debugWarn(this._isDebugEnabled(), LOG_SOURCE, 'No termSetId or termSetName configured. Mega Menu rendering is skipped.');
|
debugWarn(debug, LOG_SOURCE, 'No termSetId or termSetName configured. Mega Menu rendering is skipped.', this.properties || {});
|
||||||
this._renderStatus(strings.ConfigurationMissing);
|
this._renderStatus(strings.ConfigurationMissing);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._renderMegaMenu(termSetIdentifier);
|
debugLog(debug, LOG_SOURCE, 'Starting MegaMenu rendering.', {
|
||||||
|
termSetIdentifier: termSetIdentifier,
|
||||||
|
cacheMinutes: this._getCacheMinutes()
|
||||||
|
});
|
||||||
|
this._renderMegaMenu(termSetIdentifier, debug);
|
||||||
|
} else {
|
||||||
|
debugLog(debug, LOG_SOURCE, 'Top placeholder is already initialized; no duplicate render is started.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +106,7 @@ export default class MegaMenuApplicationCustomizer
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
debugLog(debug, LOG_SOURCE, 'Creating TaxonomyNavigationService.');
|
||||||
const taxonomyService: TaxonomyNavigationService = new TaxonomyNavigationService(
|
const taxonomyService: TaxonomyNavigationService = new TaxonomyNavigationService(
|
||||||
this.context,
|
this.context,
|
||||||
termSetIdentifier,
|
termSetIdentifier,
|
||||||
@@ -87,9 +114,14 @@ export default class MegaMenuApplicationCustomizer
|
|||||||
this._getCacheMinutes()
|
this._getCacheMinutes()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
debugLog(debug, LOG_SOURCE, 'Requesting navigation items from taxonomy.');
|
||||||
const menuItems: IMenuItem[] = await taxonomyService.getMenuItems();
|
const menuItems: IMenuItem[] = await taxonomyService.getMenuItems();
|
||||||
|
debugLog(debug, LOG_SOURCE, 'Navigation items received.', {
|
||||||
|
topLevelItemCount: menuItems.length
|
||||||
|
});
|
||||||
|
|
||||||
if (this._renderer) {
|
if (this._renderer) {
|
||||||
|
debugLog(debug, LOG_SOURCE, 'Disposing the previous renderer instance.');
|
||||||
this._renderer.dispose();
|
this._renderer.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,14 +133,19 @@ export default class MegaMenuApplicationCustomizer
|
|||||||
this._menuContainer = this._getOrCreateContainer('CustomHeader', this._topPlaceholder);
|
this._menuContainer = this._getOrCreateContainer('CustomHeader', this._topPlaceholder);
|
||||||
|
|
||||||
if (this._menuContainer) {
|
if (this._menuContainer) {
|
||||||
|
debugLog(debug, LOG_SOURCE, 'Rendering into the selected container.', {
|
||||||
|
id: this._menuContainer.id || '',
|
||||||
|
className: this._menuContainer.className || ''
|
||||||
|
});
|
||||||
this._renderer.render(this._menuContainer);
|
this._renderer.render(this._menuContainer);
|
||||||
} else {
|
} else {
|
||||||
|
debugWarn(debug, LOG_SOURCE, 'No dedicated container was returned; rendering into the Top placeholder.');
|
||||||
this._renderer.render(this._topPlaceholder.domElement);
|
this._renderer.render(this._topPlaceholder.domElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog(debug, LOG_SOURCE, 'MegaMenu rendered successfully with ' + menuItems.length + ' top-level items.');
|
debugLog(debug, LOG_SOURCE, 'MegaMenu rendered successfully with ' + menuItems.length + ' top-level items.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugError(debug, LOG_SOURCE, 'Error rendering MegaMenu.', error);
|
debugError(debug, LOG_SOURCE, 'Error rendering MegaMenu.', this._describeError(error), error);
|
||||||
this._renderStatus(strings.LoadError);
|
this._renderStatus(strings.LoadError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,15 +154,26 @@ export default class MegaMenuApplicationCustomizer
|
|||||||
const container = document.getElementById(id);
|
const container = document.getElementById(id);
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
|
debugLog(this._isDebugEnabled(), LOG_SOURCE, 'Existing host container found.', {
|
||||||
|
requestedId: id,
|
||||||
|
containerId: container.id,
|
||||||
|
containerClassName: container.className || ''
|
||||||
|
});
|
||||||
let host: HTMLElement = document.getElementById('MegaMenuHost');
|
let host: HTMLElement = document.getElementById('MegaMenuHost');
|
||||||
if (!host) {
|
if (!host) {
|
||||||
host = document.createElement('div');
|
host = document.createElement('div');
|
||||||
host.id = 'MegaMenuHost';
|
host.id = 'MegaMenuHost';
|
||||||
container.appendChild(host);
|
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.');
|
||||||
}
|
}
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog(this._isDebugEnabled(), LOG_SOURCE, 'No external host container found; the Top placeholder will be used.', {
|
||||||
|
requestedId: id
|
||||||
|
});
|
||||||
return placeholder.domElement;
|
return placeholder.domElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +223,14 @@ export default class MegaMenuApplicationCustomizer
|
|||||||
return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(value);
|
return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _describeError(error: any): { name: string; message: string; stack: string } { // tslint:disable-line:no-any
|
||||||
|
return {
|
||||||
|
name: error && error.name ? String(error.name) : '',
|
||||||
|
message: error && error.message ? String(error.message) : String(error || 'Unknown error'),
|
||||||
|
stack: error && error.stack ? String(error.stack) : ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private _isDebugEnabled(debugOverride?: boolean): boolean {
|
private _isDebugEnabled(debugOverride?: boolean): boolean {
|
||||||
if (typeof debugOverride === 'boolean') {
|
if (typeof debugOverride === 'boolean') {
|
||||||
return debugOverride;
|
return debugOverride;
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ export class MegaMenuRenderer {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
public render(container: HTMLElement): void {
|
public render(container: HTMLElement): void {
|
||||||
|
debugLog(this.debug, LOG_SOURCE, 'Render started.', {
|
||||||
|
menuItemCount: this.menuItems.length,
|
||||||
|
containerId: container.id || '',
|
||||||
|
containerClassName: container.className || ''
|
||||||
|
});
|
||||||
this.dispose();
|
this.dispose();
|
||||||
this._container = container;
|
this._container = container;
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
@@ -47,9 +52,17 @@ export class MegaMenuRenderer {
|
|||||||
|
|
||||||
this.attachEventListeners();
|
this.attachEventListeners();
|
||||||
this.createScreenReaderAnnouncer();
|
this.createScreenReaderAnnouncer();
|
||||||
|
debugLog(this.debug, LOG_SOURCE, 'Render completed.', {
|
||||||
|
navigationId: nav.id,
|
||||||
|
topLevelElements: topLevelUl.children.length
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
|
debugLog(this.debug, LOG_SOURCE, 'Dispose started.', {
|
||||||
|
hasContainer: !!this._container,
|
||||||
|
hasAnnouncer: !!this._announcer
|
||||||
|
});
|
||||||
if (this._documentKeydownHandler) {
|
if (this._documentKeydownHandler) {
|
||||||
document.removeEventListener('keydown', this._documentKeydownHandler);
|
document.removeEventListener('keydown', this._documentKeydownHandler);
|
||||||
this._documentKeydownHandler = undefined;
|
this._documentKeydownHandler = undefined;
|
||||||
@@ -66,6 +79,7 @@ export class MegaMenuRenderer {
|
|||||||
this._container.innerHTML = '';
|
this._container.innerHTML = '';
|
||||||
}
|
}
|
||||||
this._container = undefined;
|
this._container = undefined;
|
||||||
|
debugLog(this.debug, LOG_SOURCE, 'Dispose completed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTopLevelItem(item: IMenuItem): HTMLLIElement {
|
private createTopLevelItem(item: IMenuItem): HTMLLIElement {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { findIndex } from '@microsoft/sp-lodash-subset';
|
|||||||
import { ISPTermStorePickerServiceProps } from './ISPTermStorePickerServiceProps';
|
import { ISPTermStorePickerServiceProps } from './ISPTermStorePickerServiceProps';
|
||||||
import { IPickerTerm } from './IPickerTerm';
|
import { IPickerTerm } from './IPickerTerm';
|
||||||
import { ApplicationCustomizerContext } from '@microsoft/sp-application-base';
|
import { ApplicationCustomizerContext } from '@microsoft/sp-application-base';
|
||||||
import { debugError } from './MegaMenuDebug';
|
import { debugError, debugLog, debugWarn } from './MegaMenuDebug';
|
||||||
|
|
||||||
const EmptyGuid: string = '00000000-0000-0000-0000-000000000000';
|
const EmptyGuid: string = '00000000-0000-0000-0000-000000000000';
|
||||||
const TermCachePrefix: string = 'MegaMenu:TermSet:v2:';
|
const TermCachePrefix: string = 'MegaMenu:TermSet:v2:';
|
||||||
@@ -38,6 +38,12 @@ export default class SPTermStorePickerService {
|
|||||||
constructor(private props: ISPTermStorePickerServiceProps, private context: ApplicationCustomizerContext, private debug: boolean = false) {
|
constructor(private props: ISPTermStorePickerServiceProps, private context: ApplicationCustomizerContext, private debug: boolean = false) {
|
||||||
this.clientServiceUrl = this.context.pageContext.web.absoluteUrl + '/_vti_bin/client.svc/ProcessQuery';
|
this.clientServiceUrl = this.context.pageContext.web.absoluteUrl + '/_vti_bin/client.svc/ProcessQuery';
|
||||||
this.suggestionServiceUrl = this.context.pageContext.web.absoluteUrl + '/_vti_bin/TaxonomyInternalService.json/GetSuggestions';
|
this.suggestionServiceUrl = this.context.pageContext.web.absoluteUrl + '/_vti_bin/TaxonomyInternalService.json/GetSuggestions';
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'Service initialized.', {
|
||||||
|
clientServiceUrl: this.clientServiceUrl,
|
||||||
|
termSetNameOrId: this.props.termsetNameOrID,
|
||||||
|
useSessionStorage: this.props.useSessionStorage,
|
||||||
|
cacheMinutes: this.props.cacheMinutes
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getTermLabels(termId: string): Promise<string[]> {
|
public async getTermLabels(termId: string): Promise<string[]> {
|
||||||
@@ -146,6 +152,13 @@ export default class SPTermStorePickerService {
|
|||||||
cacheMinutes: number = 15
|
cacheMinutes: number = 15
|
||||||
): Promise<ITermSet> {
|
): Promise<ITermSet> {
|
||||||
let termsetId: string = termset;
|
let termsetId: string = termset;
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'getAllTerms started.', {
|
||||||
|
configuredTermSet: termset,
|
||||||
|
hideDeprecatedTags: hideDeprecatedTags === true,
|
||||||
|
hideTagsNotAvailableForTagging: hideTagsNotAvailableForTagging === true,
|
||||||
|
useSessionStorage: useSessionStorage,
|
||||||
|
cacheMinutes: cacheMinutes
|
||||||
|
});
|
||||||
// Check if the provided term set property is a GUID or string
|
// Check if the provided term set property is a GUID or string
|
||||||
if (!this.isGuid(termset)) {
|
if (!this.isGuid(termset)) {
|
||||||
// Fetch the term store information
|
// Fetch the term store information
|
||||||
@@ -154,7 +167,11 @@ export default class SPTermStorePickerService {
|
|||||||
const crntTermSet = this.getTermSetId(termStore, termset);
|
const crntTermSet = this.getTermSetId(termStore, termset);
|
||||||
if (crntTermSet) {
|
if (crntTermSet) {
|
||||||
termsetId = this.cleanGuid(crntTermSet.Id);
|
termsetId = this.cleanGuid(crntTermSet.Id);
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'Term set name resolved to an ID.', {
|
||||||
|
termsetId: termsetId
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
debugWarn(this.debug, 'SPTermStorePickerService', 'Configured term set could not be resolved.', termset);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,9 +179,18 @@ export default class SPTermStorePickerService {
|
|||||||
const childTerms = this.getCachedTermSet(termsetId, useSessionStorage);
|
const childTerms = this.getCachedTermSet(termsetId, useSessionStorage);
|
||||||
|
|
||||||
if (childTerms) {
|
if (childTerms) {
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'Returning terms from session cache.', {
|
||||||
|
termsetId: termsetId,
|
||||||
|
termCount: childTerms.Terms ? childTerms.Terms.length : 0
|
||||||
|
});
|
||||||
return childTerms;
|
return childTerms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'No valid cache entry found; sending ProcessQuery request.', {
|
||||||
|
termsetId: termsetId,
|
||||||
|
url: this.clientServiceUrl
|
||||||
|
});
|
||||||
|
|
||||||
// Request body to retrieve all terms for the given term set
|
// Request body to retrieve all terms for the given term set
|
||||||
const data = `<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Javascript Library"><Actions><ObjectPath Id="1" ObjectPathId="0" /><ObjectIdentityQuery Id="2" ObjectPathId="0" /><ObjectPath Id="4" ObjectPathId="3" /><ObjectIdentityQuery Id="5" ObjectPathId="3" /><ObjectPath Id="7" ObjectPathId="6" /><ObjectIdentityQuery Id="8" ObjectPathId="6" /><ObjectPath Id="10" ObjectPathId="9" /><Query Id="11" ObjectPathId="6"><Query SelectAllProperties="true"><Properties /></Query></Query><Query Id="12" ObjectPathId="9"><Query SelectAllProperties="false"><Properties /></Query><ChildItemQuery SelectAllProperties="false"><Properties><Property Name="IsRoot" SelectAll="true" /><Property Name="Labels" SelectAll="true" /><Property Name="TermsCount" SelectAll="true" /><Property Name="CustomSortOrder" SelectAll="true" /><Property Name="Id" SelectAll="true" /><Property Name="Name" SelectAll="true" /><Property Name="PathOfTerm" SelectAll="true" /><Property Name="Parent" SelectAll="true" /><Property Name="LocalCustomProperties" SelectAll="true" /><Property Name="IsDeprecated" ScalarProperty="true" /><Property Name="IsAvailableForTagging" ScalarProperty="true" /></Properties></ChildItemQuery></Query></Actions><ObjectPaths><StaticMethod Id="0" Name="GetTaxonomySession" TypeId="{981cbc68-9edc-4f8d-872f-71146fcbb84f}" /><Method Id="3" ParentId="0" Name="GetDefaultSiteCollectionTermStore" /><Method Id="6" ParentId="3" Name="GetTermSet"><Parameters><Parameter Type="Guid">${termsetId}</Parameter></Parameters></Method><Method Id="9" ParentId="6" Name="GetAllTerms" /></ObjectPaths></Request>`;
|
const data = `<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Javascript Library"><Actions><ObjectPath Id="1" ObjectPathId="0" /><ObjectIdentityQuery Id="2" ObjectPathId="0" /><ObjectPath Id="4" ObjectPathId="3" /><ObjectIdentityQuery Id="5" ObjectPathId="3" /><ObjectPath Id="7" ObjectPathId="6" /><ObjectIdentityQuery Id="8" ObjectPathId="6" /><ObjectPath Id="10" ObjectPathId="9" /><Query Id="11" ObjectPathId="6"><Query SelectAllProperties="true"><Properties /></Query></Query><Query Id="12" ObjectPathId="9"><Query SelectAllProperties="false"><Properties /></Query><ChildItemQuery SelectAllProperties="false"><Properties><Property Name="IsRoot" SelectAll="true" /><Property Name="Labels" SelectAll="true" /><Property Name="TermsCount" SelectAll="true" /><Property Name="CustomSortOrder" SelectAll="true" /><Property Name="Id" SelectAll="true" /><Property Name="Name" SelectAll="true" /><Property Name="PathOfTerm" SelectAll="true" /><Property Name="Parent" SelectAll="true" /><Property Name="LocalCustomProperties" SelectAll="true" /><Property Name="IsDeprecated" ScalarProperty="true" /><Property Name="IsAvailableForTagging" ScalarProperty="true" /></Properties></ChildItemQuery></Query></Actions><ObjectPaths><StaticMethod Id="0" Name="GetTaxonomySession" TypeId="{981cbc68-9edc-4f8d-872f-71146fcbb84f}" /><Method Id="3" ParentId="0" Name="GetDefaultSiteCollectionTermStore" /><Method Id="6" ParentId="3" Name="GetTermSet"><Parameters><Parameter Type="Guid">${termsetId}</Parameter></Parameters></Method><Method Id="9" ParentId="6" Name="GetAllTerms" /></ObjectPaths></Request>`;
|
||||||
|
|
||||||
@@ -178,6 +204,11 @@ export default class SPTermStorePickerService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return this.context.spHttpClient.post(this.clientServiceUrl, SPHttpClient.configurations.v1, httpPostOptions).then((serviceResponse: SPHttpClientResponse) => {
|
return this.context.spHttpClient.post(this.clientServiceUrl, SPHttpClient.configurations.v1, httpPostOptions).then((serviceResponse: SPHttpClientResponse) => {
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'ProcessQuery response received.', {
|
||||||
|
status: serviceResponse.status,
|
||||||
|
statusText: serviceResponse.statusText,
|
||||||
|
ok: serviceResponse.ok
|
||||||
|
});
|
||||||
this.ensureSuccessfulResponse(serviceResponse, 'Terms');
|
this.ensureSuccessfulResponse(serviceResponse, 'Terms');
|
||||||
return serviceResponse.json().then((serviceJSONResponse: any) => {
|
return serviceResponse.json().then((serviceJSONResponse: any) => {
|
||||||
if (!Array.isArray(serviceJSONResponse)) {
|
if (!Array.isArray(serviceJSONResponse)) {
|
||||||
@@ -195,11 +226,21 @@ export default class SPTermStorePickerService {
|
|||||||
let terms = termStoreResultTerms[0]._Child_Items_;
|
let terms = termStoreResultTerms[0]._Child_Items_;
|
||||||
|
|
||||||
if (hideDeprecatedTags === true) {
|
if (hideDeprecatedTags === true) {
|
||||||
|
const countBeforeDeprecatedFilter: number = terms.length;
|
||||||
terms = terms.filter(d => d.IsDeprecated === false);
|
terms = terms.filter(d => d.IsDeprecated === false);
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'Deprecated terms filtered.', {
|
||||||
|
before: countBeforeDeprecatedFilter,
|
||||||
|
after: terms.length
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hideTagsNotAvailableForTagging === true) {
|
if (hideTagsNotAvailableForTagging === true) {
|
||||||
|
const countBeforeAvailableFilter: number = terms.length;
|
||||||
terms = terms.filter(d => d.IsAvailableForTagging === true);
|
terms = terms.filter(d => d.IsAvailableForTagging === true);
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'Terms unavailable for tagging filtered.', {
|
||||||
|
before: countBeforeAvailableFilter,
|
||||||
|
after: terms.length
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean the term ID and specify the path depth
|
// Clean the term ID and specify the path depth
|
||||||
@@ -233,12 +274,21 @@ export default class SPTermStorePickerService {
|
|||||||
value: termStoreResultTermSet
|
value: termStoreResultTermSet
|
||||||
};
|
};
|
||||||
window.sessionStorage.setItem(TermCachePrefix + termsetId, JSON.stringify(cacheEntry));
|
window.sessionStorage.setItem(TermCachePrefix + termsetId, JSON.stringify(cacheEntry));
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'Term set stored in session cache.', {
|
||||||
|
key: TermCachePrefix + termsetId,
|
||||||
|
expiresAt: cacheEntry.expiresAt
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Do nothing, sometimes "storage quota exceeded" error if too many items
|
debugWarn(this.debug, 'SPTermStorePickerService', 'Term cache could not be written.', error);
|
||||||
}
|
}
|
||||||
|
debugLog(this.debug, 'SPTermStorePickerService', 'getAllTerms completed.', {
|
||||||
|
termsetId: termsetId,
|
||||||
|
termCount: termStoreResultTermSet.Terms ? termStoreResultTermSet.Terms.length : 0
|
||||||
|
});
|
||||||
return termStoreResultTermSet;
|
return termStoreResultTermSet;
|
||||||
}
|
}
|
||||||
|
debugWarn(this.debug, 'SPTermStorePickerService', 'ProcessQuery response contained no term set.');
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ApplicationCustomizerContext } from '@microsoft/sp-application-base';
|
|||||||
import SPTermStorePickerService from './SPTermStorePickerService';
|
import SPTermStorePickerService from './SPTermStorePickerService';
|
||||||
import { ITerm, ITermSet } from './ISPTermStorePickerService';
|
import { ITerm, ITermSet } from './ISPTermStorePickerService';
|
||||||
import { ItemDictionary } from './ItemDictionary';
|
import { ItemDictionary } from './ItemDictionary';
|
||||||
import { debugWarn } from './MegaMenuDebug';
|
import { debugLog, debugWarn } from './MegaMenuDebug';
|
||||||
|
|
||||||
const LOG_SOURCE: string = 'TaxonomyNavigationService';
|
const LOG_SOURCE: string = 'TaxonomyNavigationService';
|
||||||
|
|
||||||
@@ -18,6 +18,12 @@ export class TaxonomyNavigationService implements ITaxonomyNavigationService {
|
|||||||
private debug: boolean = false,
|
private debug: boolean = false,
|
||||||
private cacheMinutes: number = 15
|
private cacheMinutes: number = 15
|
||||||
) {
|
) {
|
||||||
|
debugLog(this.debug, LOG_SOURCE, 'Initializing taxonomy navigation service.', {
|
||||||
|
termSetNameOrId: this.termSetNameOrId,
|
||||||
|
cacheMinutes: this.cacheMinutes,
|
||||||
|
webUrl: this.context.pageContext.web.absoluteUrl,
|
||||||
|
siteUrl: this.context.pageContext.site.absoluteUrl
|
||||||
|
});
|
||||||
sp.setup({
|
sp.setup({
|
||||||
spfxContext: context
|
spfxContext: context
|
||||||
});
|
});
|
||||||
@@ -37,6 +43,10 @@ export class TaxonomyNavigationService implements ITaxonomyNavigationService {
|
|||||||
|
|
||||||
public async getMenuItems(): Promise<IMenuItem[]> {
|
public async getMenuItems(): Promise<IMenuItem[]> {
|
||||||
const siteCollectionUrl: string = this.context.pageContext.site.absoluteUrl;
|
const siteCollectionUrl: string = this.context.pageContext.site.absoluteUrl;
|
||||||
|
debugLog(this.debug, LOG_SOURCE, 'Reading all terms.', {
|
||||||
|
termSetNameOrId: this.termSetNameOrId,
|
||||||
|
cacheMinutes: this.cacheMinutes
|
||||||
|
});
|
||||||
const termset: ITermSet = await this._taxonomyPickerService.getAllTerms(
|
const termset: ITermSet = await this._taxonomyPickerService.getAllTerms(
|
||||||
this.termSetNameOrId,
|
this.termSetNameOrId,
|
||||||
true,
|
true,
|
||||||
@@ -44,6 +54,10 @@ export class TaxonomyNavigationService implements ITaxonomyNavigationService {
|
|||||||
true,
|
true,
|
||||||
this.cacheMinutes
|
this.cacheMinutes
|
||||||
);
|
);
|
||||||
|
debugLog(this.debug, LOG_SOURCE, 'Term set response received.', {
|
||||||
|
termSetFound: !!termset,
|
||||||
|
termCount: termset && termset.Terms ? termset.Terms.length : 0
|
||||||
|
});
|
||||||
const itemsDict: ItemDictionary<IMenuItem> = new ItemDictionary<IMenuItem>();
|
const itemsDict: ItemDictionary<IMenuItem> = new ItemDictionary<IMenuItem>();
|
||||||
const menuItems: IMenuItem[] = [];
|
const menuItems: IMenuItem[] = [];
|
||||||
|
|
||||||
@@ -67,6 +81,10 @@ export class TaxonomyNavigationService implements ITaxonomyNavigationService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debugLog(this.debug, LOG_SOURCE, 'Navigation hierarchy built.', {
|
||||||
|
totalTerms: termset.Terms.length,
|
||||||
|
topLevelItems: menuItems.length
|
||||||
|
});
|
||||||
return menuItems;
|
return menuItems;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user