Initial commit

This commit is contained in:
Torsten Brendgen
2026-04-13 10:26:01 +02:00
commit bc1258ae76
116 changed files with 30409 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
# MegaMenu für klassische SharePoint-Seiten
Diese Dateien ermöglichen die Verwendung des MegaMenus auf klassischen SharePoint-Seiten (2019/SE) ohne SPFx Framework.
## 📁 Dateien
- `megamenu-classic.js` - Hauptlogik für klassische Seiten
- `megamenu-classic.css` - Styling für klassische Seiten
- `classic-deployment.md` - Diese Dokumentation
## 🚀 Installation
### Option 1: Master Page (empfohlen)
1. **Dateien hochladen:**
```
/SiteAssets/megamenu/megamenu-classic.js
/SiteAssets/megamenu/megamenu-classic.css
```
2. **Master Page bearbeiten:**
```html
<!-- In den <head> Bereich -->
<link rel="stylesheet" type="text/css" href="/SiteAssets/megamenu/megamenu-classic.css" />
<!-- Vor dem schließenden </body> Tag -->
<script type="text/javascript" src="/SiteAssets/megamenu/megamenu-classic.js"></script>
```
### Option 2: ScriptLink über PowerShell
```powershell
# CSS hinzufügen
$web = Get-SPWeb "http://your-site-url"
$web.AlternateCSSUrl = "/SiteAssets/megamenu/megamenu-classic.css"
$web.Update()
# JavaScript als Custom Action hinzufügen
$customAction = $web.UserCustomActions.Add()
$customAction.Location = "ScriptLink"
$customAction.ScriptSrc = "/SiteAssets/megamenu/megamenu-classic.js"
$customAction.Sequence = 1000
$customAction.Update()
$web.Update()
```
### Option 3: Content Editor Web Part
```html
<link rel="stylesheet" type="text/css" href="/SiteAssets/megamenu/megamenu-classic.css" />
<script type="text/javascript" src="/SiteAssets/megamenu/megamenu-classic.js"></script>
```
## ⚙️ Konfiguration
### Basis-Konfiguration
```html
<script type="text/javascript">
// Konfiguration vor dem Laden des Scripts setzen
window.MegaMenuConfig = {
termSetName: 'Navigation', // Name des Taxonomy Term Sets
cssUrl: '', // Optional: Externe CSS-Datei
containerId: 's4-titlerow', // SharePoint Container ID
debug: true // Debug-Modus aktivieren
};
</script>
<script type="text/javascript" src="/SiteAssets/megamenu/megamenu-classic.js"></script>
```
### Container IDs für verschiedene SharePoint-Versionen
| SharePoint Version | Container ID | Beschreibung |
|-------------------|--------------|-------------|
| 2019 | `s4-titlerow` | Standard Titel-Bereich |
| SE | `suiteBarTop` | Suite Bar Bereich |
| Custom | `custom-menu-container` | Eigener Container |
### Term Set Konfiguration
Das MegaMenu liest die Navigation aus einem Managed Metadata Term Set:
```
Navigation (Term Set)
├── Produkte (Level 1)
│ ├── Software (Level 2)
│ │ ├── Office 365 (Level 3) → URL
│ │ └── SharePoint (Level 3) → URL
│ └── Hardware (Level 2)
│ ├── Laptops (Level 3) → URL
│ └── Server (Level 3) → URL
└── Services (Level 1)
└── Consulting (Level 2)
└── SharePoint Beratung (Level 3) → URL
```
## 🎨 Anpassungen
### CSS Customization
```css
/* Eigene Farben */
#Mega-Menu-Classic {
background: #your-color;
}
#Mega-Menu > ul > li > span[role="menuitem"] {
color: #your-text-color;
}
/* Eigene Schriftarten */
#Mega-Menu {
font-family: 'Your Font', Arial, sans-serif;
}
```
### JavaScript Events
```javascript
// Nach der Initialisierung eigene Logik hinzufügen
document.addEventListener('DOMContentLoaded', function() {
// Warten bis MegaMenu geladen ist
setTimeout(function() {
if (window.MegaMenuClassic) {
console.log('MegaMenu ist bereit!');
// Eigene Anpassungen hier...
}
}, 1000);
});
```
## 🔧 Erweiterte Konfiguration
### Custom URL Mapping
Die URLs für die Navigation-Links können angepasst werden:
```javascript
// Überschreibe die getTermUrl Funktion
window.MegaMenuGetTermUrl = function(term) {
var termName = term.get_name();
// Eigene URL-Logik
return '/custom-pages/' + termName.toLowerCase() + '.aspx';
};
```
### Mehrsprachigkeit
```javascript
window.MegaMenuConfig = {
termSetName: _spPageContextInfo.currentUICultureName === 'de-DE' ? 'Navigation_DE' : 'Navigation_EN',
// ... andere Konfigurationen
};
```
## 🐛 Troubleshooting
### Häufige Probleme
**Problem:** Menü wird nicht angezeigt
**Lösung:**
- Browser-Konsole auf Fehler prüfen
- Taxonomy Term Set Name überprüfen
- Berechtigungen für Term Store prüfen
**Problem:** JavaScript-Fehler "SP is not defined"
**Lösung:**
- Script erst nach SharePoint-Bibliotheken laden
- `SP.SOD.executeFunc` verwenden
**Problem:** Styling funktioniert nicht
**Lösung:**
- CSS-Pfad überprüfen
- Cache leeren
- CSS-Spezifität erhöhen
### Debug-Modus
```javascript
window.MegaMenuConfig = {
debug: true, // Aktiviert Console-Logging
// ... andere Optionen
};
```
## 📝 Browser-Unterstützung
- Internet Explorer 11+
- Microsoft Edge (alle Versionen)
- Chrome 60+
- Firefox 55+
- Safari 12+
## ⚠️ Wichtige Hinweise
1. **Berechtigungen:** Benutzer benötigen Leserechte auf den Term Store
2. **Performance:** Bei großen Term Sets kann das Laden länger dauern
3. **Caching:** SharePoint cached Taxonomy-Daten - Änderungen können verzögert sichtbar werden
4. **Responsive:** Das Menü ist für mobile Geräte optimiert
## 🔄 Migration von SPFx Version
Falls Sie von der SPFx-Version migrieren:
1. SPFx ApplicationCustomizer deaktivieren
2. Klassische Dateien hochladen und einbinden
3. Konfiguration anpassen (gleiche Term Sets verwendbar)
4. Testen und CSS bei Bedarf anpassen
## 📞 Support
Bei Problemen:
1. Debug-Modus aktivieren
2. Browser-Konsole prüfen
3. Term Set Struktur validieren
4. Dateipfade und Berechtigungen überprüfen

View File

@@ -0,0 +1,74 @@
/**
* MegaMenu CSS for Classic SharePoint Pages
* Version: 1.0.2
*
* This file imports the exact same CSS as the modern SPFx version
* to ensure 100% visual consistency.
*
* Usage:
* 1. Copy the compiled MegaMenu.css from src/extensions/megaMenu/ to your SharePoint assets
* 2. Reference it in your master page or via alternate CSS URL:
* <link rel="stylesheet" href="/SiteAssets/MegaMenu.css" />
*
* This file provides additional classic-specific adjustments if needed.
*/
/*
* The main MegaMenu.css should be loaded first!
* This file only contains classic SharePoint specific overrides.
*/
/* Classic SharePoint specific container adjustments */
.megamenu-classic-container {
/* Insert after s4-titlerow or other SharePoint containers */
width: 100%;
margin: 0;
padding: 0;
}
/* Ensure proper z-index in classic SharePoint context */
#Mega-Menu {
position: relative;
z-index: 999; /* Below SharePoint dialogs but above content */
}
.mega-menu {
z-index: 1000; /* Above the main menu */
}
/* Classic SharePoint ribbon compatibility */
body.ms-backgroundImage #Mega-Menu {
/* Adjust if SharePoint has background images */
position: relative;
}
/* Ensure settings panel works in classic mode too */
.mm-settings-panel {
z-index: 4001; /* Above everything else */
}
/* Classic SharePoint v4.master specific adjustments */
.v4master #Mega-Menu {
/* Any v4.master specific styles if needed */
}
/* Classic SharePoint seattle.master specific adjustments */
.seattle #Mega-Menu {
/* Any seattle.master specific styles if needed */
}
/* Responsive adjustments for classic SharePoint layouts */
@media (max-width: 1024px) {
.megamenu-classic-container {
/* Classic SharePoint is often used on older devices */
overflow-x: auto;
}
}
/* Print styles for classic SharePoint */
@media print {
#Mega-Menu,
.megamenu-classic-container {
display: none !important;
}
}

265
classic/megamenu-classic.js Normal file
View File

@@ -0,0 +1,265 @@
/**
* SharePoint MegaMenu for Classic Pages
* Version: 1.0.2
*
* This version uses standalone services that replicate the SPFx logic
* without requiring the SharePoint Framework.
*
* Prerequisites:
* 1. megamenu-services-standalone.js must be loaded first
* 2. MegaMenu.css must be included
* 3. SP.js and SP.Taxonomy.js must be available
*
* Usage:
* <link rel="stylesheet" href="/SiteAssets/megamenu/MegaMenu.css" />
* <script src="/SiteAssets/megamenu/megamenu-services-standalone.js"></script>
* <script src="/SiteAssets/megamenu/megamenu-classic.js"></script>
*/
(function() {
'use strict';
var MEGAMENU_UCA_ID = 'abc3361f-bb2d-491f-aba3-cd51c19a299b'; // Same as in MegaMenuApplicationCustomizer.ts
var config = window.MegaMenuConfig || {
containerId: 's4-titlerow',
debug: false
};
function log(message, data) {
if (config.debug && console && console.log) {
console.log('[MegaMenu Classic] ' + message, data || '');
}
}
// Wait for dependencies: SharePoint JSOM, Taxonomy, and our standalone services
function waitForDependencies(callback) {
if (typeof SP !== 'undefined' &&
SP.SOD &&
typeof SP.Taxonomy !== 'undefined' &&
typeof window.MegaMenuServices !== 'undefined' &&
document.readyState === 'complete') {
callback();
} else {
setTimeout(function() { waitForDependencies(callback); }, 100);
}
}
// Classic SharePoint context wrapper
function createClassicContext() {
return {
pageContext: {
site: {
absoluteUrl: _spPageContextInfo.siteAbsoluteUrl
},
web: {
absoluteUrl: _spPageContextInfo.webAbsoluteUrl,
permissions: {
hasPermission: function(permission) {
return _spPageContextInfo.isSiteAdmin === true;
}
}
}
}
};
}
// Configuration reader using REST API (same logic as MegaMenuSettings.ts)
function readMegaMenuConfiguration(callback) {
log('Reading MegaMenu configuration from UserCustomAction...');
var restUrl = _spPageContextInfo.siteAbsoluteUrl +
"/_api/site/userCustomActions?$filter=ClientSideComponentId eq guid'" + MEGAMENU_UCA_ID + "'";
fetch(restUrl, {
method: 'GET',
headers: {
'Accept': 'application/json;odata=nometadata'
}
})
.then(function(response) {
if (!response.ok) {
throw new Error('HTTP ' + response.status + ' - ' + response.statusText);
}
return response.json();
})
.then(function(data) {
if (data && data.value && data.value.length > 0) {
var uca = data.value[0];
try {
var props = JSON.parse(uca.ClientSideComponentProperties);
log('Configuration found', props);
callback(props);
} catch (e) {
log('Error parsing UserCustomAction properties: ' + e.message);
callback({ termSetName: 'Navigation', cssUrl: '' });
}
} else {
log('No UserCustomAction found - using defaults');
callback({ termSetName: 'Navigation', cssUrl: '' });
}
})
.catch(function(error) {
log('Error reading configuration: ' + error.message);
callback({ termSetName: 'Navigation', cssUrl: '' });
});
}
// Load external CSS (same as SPFx version)
function loadExternalCSS(cssUrl) {
if (!cssUrl) return;
var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = cssUrl;
link.onerror = function() {
log('Failed to load external CSS: ' + cssUrl);
};
link.onload = function() {
log('External CSS loaded successfully: ' + cssUrl);
};
document.head.appendChild(link);
}
// Main initialization
function initMegaMenu() {
log('Initializing MegaMenu for classic SharePoint...');
// Check if services are available
if (!window.MegaMenuServices) {
console.error('[MegaMenu] Standalone services not found! Please include megamenu-services-standalone.js first.');
return;
}
readMegaMenuConfiguration(function(props) {
log('Using configuration:', props);
// Load external CSS if specified
if (props.cssUrl) {
loadExternalCSS(props.cssUrl);
}
// Create context
var context = createClassicContext();
// Create taxonomy service using standalone implementation
var taxonomyService = new window.MegaMenuServices.TaxonomyNavigationService(
context,
props.termSetName
);
// Load menu items
taxonomyService.getMenuItems()
.then(function(menuItems) {
log('Menu items loaded:', menuItems);
// Find target container
var container = document.getElementById(config.containerId);
if (!container) {
log('Container not found: ' + config.containerId + '. Creating fallback container.');
// Create fallback container at top of page
container = document.createElement('div');
container.id = 'megamenu-fallback-container';
document.body.insertBefore(container, document.body.firstChild);
}
// Create menu wrapper
var menuWrapper = document.createElement('div');
menuWrapper.className = 'megamenu-classic-container';
// Insert menu wrapper
if (container.nextSibling) {
container.parentNode.insertBefore(menuWrapper, container.nextSibling);
} else {
container.parentNode.appendChild(menuWrapper);
}
// Create renderer using standalone implementation
var renderer = new window.MegaMenuServices.MegaMenuRenderer(
context,
menuItems,
function(updatedProps) {
log('Properties updated (not persisted in classic mode):', updatedProps);
}
);
// Render the menu
renderer.render(menuWrapper);
log('✅ MegaMenu rendered successfully!');
})
.catch(function(error) {
console.error('[MegaMenu] Error loading menu items:', error);
// Show error message to user
var container = document.getElementById(config.containerId);
if (container) {
var errorDiv = document.createElement('div');
errorDiv.style.cssText = 'background:#ffebee;color:#c62828;padding:10px;border:1px solid #ef5350;margin:5px 0;';
errorDiv.innerHTML = '<strong>MegaMenu Error:</strong> ' + error.message +
'<br><small>Check browser console for details.</small>';
if (container.nextSibling) {
container.parentNode.insertBefore(errorDiv, container.nextSibling);
} else {
container.parentNode.appendChild(errorDiv);
}
}
});
});
}
// Bootstrap function
function bootstrap() {
log('Bootstrapping MegaMenu...');
// Wait for SharePoint JSOM to be ready
if (typeof SP === 'undefined' || !SP.SOD) {
setTimeout(bootstrap, 100);
return;
}
// Load required SharePoint libraries
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function() {
SP.SOD.executeFunc('sp.taxonomy.js', 'SP.Taxonomy.TaxonomySession', function() {
// Wait for all dependencies and initialize
waitForDependencies(function() {
initMegaMenu();
});
});
});
}
// Auto-start based on DOM state
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bootstrap);
} else {
// DOM already loaded
setTimeout(bootstrap, 100);
}
// Expose public API
window.MegaMenuClassic = {
init: initMegaMenu,
bootstrap: bootstrap,
config: config,
// Utility methods
setConfig: function(newConfig) {
Object.assign(config, newConfig);
},
reload: function() {
// Remove existing menu and reload
var existing = document.querySelector('.megamenu-classic-container');
if (existing) {
existing.remove();
}
initMegaMenu();
}
};
log('MegaMenu Classic wrapper loaded. Waiting for dependencies...');
})();

View File

@@ -0,0 +1,468 @@
/**
* Standalone MegaMenu Services for Classic SharePoint
*
* This file contains extracted and adapted versions of the SPFx services
* that work in classic SharePoint without SPFx dependencies.
*/
(function(global) {
'use strict';
// ===========================================
// 1. UTILITY CLASSES (from SPFx services)
// ===========================================
class ItemDictionary {
constructor() {
this._items = {};
}
Add(key, value) {
this._items[key] = value;
}
Get(key) {
return this._items[key];
}
}
class MenuItem {
constructor(term, depth, siteCollectionUrl) {
this.title = term.Name;
this.url = this._getNavigationUrl(term, siteCollectionUrl);
this.items = [];
this.pathDepth = this._calculateDepth(term.PathOfTerm);
this.parentId = this._getParentId(term.PathOfTerm);
this.id = term.Id;
this._term = term;
}
_calculateDepth(pathOfTerm) {
if (!pathOfTerm) return 1;
return pathOfTerm.split(';').length;
}
_getParentId(pathOfTerm) {
if (!pathOfTerm) return null;
const parts = pathOfTerm.split(';');
if (parts.length <= 1) return null;
// Return parent term ID (simplified - may need adjustment)
return parts[parts.length - 2];
}
_getNavigationUrl(term, siteCollectionUrl) {
// Extract URL from term properties
if (term.LocalCustomProperties && term.LocalCustomProperties._Sys_Nav_SimpleLinkUrl) {
return term.LocalCustomProperties._Sys_Nav_SimpleLinkUrl;
}
// Fallback: generate URL based on term name
const termName = term.Name.toLowerCase().replace(/[^a-z0-9]/g, '-');
return `${siteCollectionUrl}/pages/${termName}.aspx`;
}
}
// ===========================================
// 2. TAXONOMY NAVIGATION SERVICE (adapted)
// ===========================================
class TaxonomyNavigationService {
constructor(context, termSetName) {
this.context = context;
this.termSetName = termSetName;
this._siteCollectionUrl = context.pageContext.site.absoluteUrl;
}
async getMenuItems() {
console.log('[TaxonomyService] Loading terms for:', this.termSetName);
try {
const termSet = await this._loadTermSet(this.termSetName);
return this._processTerms(termSet);
} catch (error) {
console.error('[TaxonomyService] Error loading terms:', error);
return [this._createNoTermsItem()];
}
}
_loadTermSet(termSetName) {
return new Promise((resolve, reject) => {
const context = SP.ClientContext.get_current();
const session = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
const termStore = session.getDefaultSiteCollectionTermStore();
const termSets = termStore.getTermSetsByName(termSetName, 1033);
context.load(termSets);
context.executeQueryAsync(
() => {
if (termSets.get_count() > 0) {
const termSet = termSets.get_item(0);
const terms = termSet.get_terms();
context.load(terms);
context.executeQueryAsync(
() => {
// Load all terms with their properties and children
this._loadAllTermsRecursively(context, terms, resolve, reject);
},
(sender, args) => reject(new Error(args.get_message()))
);
} else {
reject(new Error(`Term set '${termSetName}' not found`));
}
},
(sender, args) => reject(new Error(args.get_message()))
);
});
}
_loadAllTermsRecursively(context, terms, resolve, reject) {
const allTerms = [];
const termsEnum = terms.getEnumerator();
// First pass: collect all terms
while (termsEnum.moveNext()) {
const term = termsEnum.get_current();
context.load(term, 'Id', 'Name', 'PathOfTerm', 'LocalCustomProperties');
allTerms.push(term);
// Load child terms
const childTerms = term.get_terms();
context.load(childTerms);
this._loadChildTermsRecursively(context, childTerms, allTerms);
}
// Execute query to load all data
context.executeQueryAsync(
() => {
const processedTerms = allTerms.map(term => ({
Id: term.get_id().toString(),
Name: term.get_name(),
PathOfTerm: term.get_pathOfTerm(),
LocalCustomProperties: this._getCustomProperties(term),
IsRoot: term.get_pathOfTerm().split(';').length === 1
}));
resolve(processedTerms);
},
(sender, args) => reject(new Error(args.get_message()))
);
}
_loadChildTermsRecursively(context, childTerms, allTerms) {
const childEnum = childTerms.getEnumerator();
while (childEnum.moveNext()) {
const childTerm = childEnum.get_current();
context.load(childTerm, 'Id', 'Name', 'PathOfTerm', 'LocalCustomProperties');
allTerms.push(childTerm);
// Recursively load grandchildren
const grandChildTerms = childTerm.get_terms();
context.load(grandChildTerms);
this._loadChildTermsRecursively(context, grandChildTerms, allTerms);
}
}
_getCustomProperties(term) {
try {
const props = term.get_localCustomProperties();
return {
_Sys_Nav_SimpleLinkUrl: props._Sys_Nav_SimpleLinkUrl || null,
_Sys_Nav_HoverText: props._Sys_Nav_HoverText || null
};
} catch (e) {
return {};
}
}
_processTerms(termsData) {
const itemsDict = new ItemDictionary();
const menuItems = [];
// Create MenuItem objects
termsData.forEach(termData => {
const menuItem = new MenuItem(termData, 0, this._siteCollectionUrl);
itemsDict.Add(termData.Id, menuItem);
if (menuItem.pathDepth === 1) {
menuItems.push(menuItem);
}
});
// Build hierarchy
termsData.forEach(termData => {
if (termData.PathOfTerm && termData.PathOfTerm.split(';').length > 1) {
const menuItem = itemsDict.Get(termData.Id);
const parentId = menuItem.parentId;
const parentItem = itemsDict.Get(parentId);
if (parentItem) {
parentItem.items.push(menuItem);
}
}
});
return menuItems.length > 0 ? menuItems : [this._createNoTermsItem()];
}
_createNoTermsItem() {
return new MenuItem({
Id: 'no-terms',
Name: 'Es wurden keine Terms gefunden. Bitte überprüfen Sie Ihre Einstellungen.',
PathOfTerm: '',
LocalCustomProperties: {}
}, 0, this._siteCollectionUrl);
}
}
// ===========================================
// 3. MEGA MENU RENDERER (adapted from SPFx)
// ===========================================
class MegaMenuRenderer {
constructor(context, menuItems, updateCallback) {
this.context = context;
this.menuItems = menuItems;
this.updateCallback = updateCallback;
}
render(container) {
container.innerHTML = '';
const nav = document.createElement('nav');
nav.id = 'Mega-Menu';
nav.className = 'mega-menu-main';
nav.setAttribute('role', 'navigation');
nav.setAttribute('aria-label', 'Hauptnavigation');
const topLevelUl = document.createElement('ul');
topLevelUl.setAttribute('role', 'menubar');
this.menuItems.forEach(topLevelItem => {
const topLevelLi = this.createTopLevelItem(topLevelItem);
topLevelUl.appendChild(topLevelLi);
});
// Add settings if user has permissions (simplified check)
if (this._hasManagePermissions()) {
topLevelUl.appendChild(this.createSettingsItem());
}
nav.appendChild(topLevelUl);
container.appendChild(nav);
this.attachEventListeners();
this.createScreenReaderAnnouncer();
}
createTopLevelItem(item) {
const li = document.createElement('li');
li.setAttribute('role', 'none');
if (item.url && item.items.length === 0) {
// Simple link
const a = document.createElement('a');
a.href = item.url;
a.textContent = item.title;
a.className = 'menu-item-link';
a.setAttribute('role', 'menuitem');
a.setAttribute('tabindex', '0');
li.appendChild(a);
} else {
// Menu with submenu
const span = document.createElement('span');
span.textContent = item.title;
span.className = 'menu-item-text';
span.setAttribute('role', 'menuitem');
span.setAttribute('tabindex', '0');
span.setAttribute('aria-haspopup', 'true');
span.setAttribute('aria-expanded', 'false');
li.appendChild(span);
if (item.items.length > 0) {
const megaMenu = this.createMegaMenu(item.items);
li.appendChild(megaMenu);
}
}
return li;
}
createMegaMenu(categories) {
const megaMenuDiv = document.createElement('div');
megaMenuDiv.className = 'mega-menu';
megaMenuDiv.setAttribute('role', 'menu');
const gridDiv = document.createElement('div');
gridDiv.className = 'mega-menu-grid';
categories.forEach(category => {
const categoryDiv = this.createCategory(category);
gridDiv.appendChild(categoryDiv);
});
megaMenuDiv.appendChild(gridDiv);
return megaMenuDiv;
}
createCategory(category) {
const categoryDiv = document.createElement('div');
categoryDiv.className = 'mega-menu-category';
const h3 = document.createElement('h3');
if (category.url) {
const a = document.createElement('a');
a.href = category.url;
a.textContent = category.title;
a.setAttribute('tabindex', '0');
h3.appendChild(a);
} else {
const span = document.createElement('span');
span.textContent = category.title;
h3.appendChild(span);
}
categoryDiv.appendChild(h3);
if (category.items.length > 0) {
const ul = document.createElement('ul');
ul.setAttribute('role', 'group');
category.items.forEach(link => {
const li = document.createElement('li');
li.setAttribute('role', 'none');
const a = document.createElement('a');
a.href = link.url;
a.textContent = link.title;
a.setAttribute('role', 'menuitem');
a.setAttribute('tabindex', '0');
li.appendChild(a);
ul.appendChild(li);
});
categoryDiv.appendChild(ul);
}
return categoryDiv;
}
createSettingsItem() {
const li = document.createElement('li');
li.setAttribute('role', 'none');
const button = document.createElement('button');
button.className = 'menu-item-settings';
button.setAttribute('type', 'button');
button.setAttribute('role', 'menuitem');
button.setAttribute('tabindex', '0');
button.setAttribute('aria-label', 'Einstellungen');
button.onclick = () => this._openSettings();
const icon = document.createElement('i');
icon.className = 'ms-Icon ms-Icon--Settings menu-item-settings__icon';
icon.setAttribute('aria-hidden', 'true');
button.appendChild(icon);
li.appendChild(button);
return li;
}
attachEventListeners() {
// Keyboard and mouse event handling (simplified)
const menuItems = document.querySelectorAll('#Mega-Menu [role="menuitem"]');
menuItems.forEach(item => {
item.addEventListener('keydown', this.handleKeyDown.bind(this));
const parent = item.parentElement;
if (parent && parent.querySelector('.mega-menu')) {
parent.addEventListener('mouseenter', this.showMegaMenu);
parent.addEventListener('mouseleave', this.hideMegaMenu);
}
});
}
createScreenReaderAnnouncer() {
// Accessibility support
const announcer = document.createElement('div');
announcer.id = 'mega-menu-announcer';
announcer.className = 'sr-only';
announcer.setAttribute('aria-live', 'polite');
announcer.setAttribute('aria-atomic', 'true');
document.body.appendChild(announcer);
}
showMegaMenu() {
const megaMenu = this.querySelector('.mega-menu');
if (megaMenu) {
megaMenu.classList.add('js-open');
const menuItem = this.querySelector('[role="menuitem"]');
if (menuItem) {
menuItem.setAttribute('aria-expanded', 'true');
}
}
}
hideMegaMenu() {
const megaMenu = this.querySelector('.mega-menu');
if (megaMenu) {
megaMenu.classList.remove('js-open');
const menuItem = this.querySelector('[role="menuitem"]');
if (menuItem) {
menuItem.setAttribute('aria-expanded', 'false');
}
}
}
handleKeyDown(e) {
// Keyboard navigation logic (simplified)
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
const parent = e.target.parentElement;
const megaMenu = parent.querySelector('.mega-menu');
if (megaMenu) {
megaMenu.classList.toggle('js-open');
e.target.setAttribute('aria-expanded',
megaMenu.classList.contains('js-open') ? 'true' : 'false');
}
} else if (e.key === 'Escape') {
const megaMenu = document.querySelector('.mega-menu.js-open');
if (megaMenu) {
megaMenu.classList.remove('js-open');
const menuItem = megaMenu.parentElement.querySelector('[role="menuitem"]');
if (menuItem) {
menuItem.setAttribute('aria-expanded', 'false');
menuItem.focus();
}
}
}
}
_hasManagePermissions() {
// Simplified permission check for classic SharePoint
return _spPageContextInfo.isSiteAdmin || false;
}
_openSettings() {
console.log('[MegaMenu] Settings not implemented in classic mode');
alert('Einstellungen sind nur in der modernen SPFx-Version verfügbar.');
}
}
// ===========================================
// 4. EXPOSE SERVICES GLOBALLY
// ===========================================
global.MegaMenuServices = {
TaxonomyNavigationService: TaxonomyNavigationService,
MegaMenuRenderer: MegaMenuRenderer,
MenuItem: MenuItem,
ItemDictionary: ItemDictionary
};
console.log('[MegaMenu] Standalone services loaded successfully');
})(window);