/** * 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: * * * */ (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 = 'MegaMenu Error: ' + error.message + '
Check browser console for details.'; 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...'); })();