(function () { function boot() { var root = document.getElementById('adv-megamenu-admin'); if (!root) { return; } var treeContainer = root.querySelector('.js-tree'); var treeInput = document.getElementById('advmegamenu_tree_json'); var ajaxUrl = root.getAttribute('data-ajax-url'); var token = root.getAttribute('data-token'); var defaultLangId = String(root.getAttribute('data-default-lang') || ''); var languages = parseJson(root.querySelector('.js-adv-languages-data'), []); var labels = parseJson(root.querySelector('.js-adv-i18n'), {}); var tree = parseJson(root.querySelector('.js-adv-tree-data'), []); var tempId = Date.now(); var nodeModal = document.getElementById('advNodeModal'); var layoutModal = document.getElementById('advLayoutModal'); var confirmModal = document.getElementById('advConfirmModal'); var nodeModalState = null; var layoutModalState = null; var confirmAction = null; var activeRootId = tree.length ? tree[0].id : null; function nextId() { tempId += 1; return tempId; } function parseJson(element, fallback) { if (!element || !element.textContent) { return fallback; } try { return JSON.parse(element.textContent); } catch (error) { return fallback; } } function t(key, fallback) { return labels[key] || fallback || key; } function showModal(element) { if (window.jQuery && window.jQuery.fn && window.jQuery.fn.modal) { window.jQuery(element).modal('show'); } } function hideModal(element) { if (window.jQuery && window.jQuery.fn && window.jQuery.fn.modal) { window.jQuery(element).modal('hide'); } } function createEmptyNode() { var lang = {}; languages.forEach(function (language) { lang[String(language.id_lang)] = { title: '', description: '', custom_link: '' }; }); return { id: nextId(), active: true, type: 'link', entity_id: 0, icon_path: '', icon_url: '', new_window: false, lang: lang, layouts: [], children: [] }; } function createEmptyLayout() { return { id: nextId(), column_width: 4, show_title: true, custom_image: '', custom_image_url: '', background_color: '', block_type: 'promo', products: [] }; } function ensureNodeDefaults(node) { if (!node.lang) { node.lang = {}; } if (!node.layouts) { node.layouts = []; } if (!node.children) { node.children = []; } languages.forEach(function (language) { var langId = String(language.id_lang); if (!node.lang[langId]) { node.lang[langId] = { title: '', description: '', custom_link: '' }; } }); } function sanitizeTree(nodes, depth) { nodes.forEach(function (node) { ensureNodeDefaults(node); if (depth === 0) { node.icon_path = ''; node.icon_url = ''; Object.keys(node.lang).forEach(function (langId) { node.lang[langId].description = ''; }); } sanitizeTree(node.children || [], depth + 1); }); } function serializeTree() { sanitizeTree(tree, 0); treeInput.value = JSON.stringify(tree); } function render() { serializeTree(); treeContainer.innerHTML = ''; if (!tree.length) { var empty = document.createElement('div'); empty.className = 'adv-megamenu-admin__empty'; empty.textContent = t('emptyTree', 'No menu items yet.'); treeContainer.appendChild(empty); return; } if (!findRootById(activeRootId)) { activeRootId = tree[0].id; } var shell = document.createElement('div'); shell.className = 'adv-menu-admin-root-shell'; shell.innerHTML = '' + '
' + '
'; var tabs = shell.querySelector('.adv-menu-admin-root-tabs'); var panel = shell.querySelector('.adv-menu-admin-root-panel'); tree.forEach(function (node, index) { tabs.appendChild(renderRootTab(node, tree, index)); }); var activeRoot = findRootById(activeRootId) || tree[0]; if (activeRoot) { panel.appendChild(renderNode(activeRoot, tree, getRootIndex(activeRoot.id), 0)); } treeContainer.appendChild(shell); } function renderRootTab(node, collection, index) { var button = document.createElement('button'); button.type = 'button'; button.className = 'adv-menu-admin-root-tab' + (node.id === activeRootId ? ' is-active' : ''); button.innerHTML = '' + '' + ' ' + escapeHtml(getNodeTitle(node) || t('rootItem', 'Root item')) + '' + ' ' + escapeHtml(node.type || 'link') + '' + '' + '' + ' ' + ' ' + ' ' + ' ' + ''; button.addEventListener('click', function () { activeRootId = node.id; render(); }); button.querySelector('.js-root-edit').addEventListener('click', function (event) { event.stopPropagation(); openNodeModal(node, 0); }); button.querySelector('.js-root-up').addEventListener('click', function (event) { event.stopPropagation(); move(collection, index, 'up'); }); button.querySelector('.js-root-down').addEventListener('click', function (event) { event.stopPropagation(); move(collection, index, 'down'); }); button.querySelector('.js-root-delete').addEventListener('click', function (event) { event.stopPropagation(); askConfirm(t('deleteNodeTitle', 'Delete menu item'), t('deleteNodeText', 'This will remove the selected menu item together with its children and attached blocks.'), function () { collection.splice(index, 1); if (activeRootId === node.id) { activeRootId = collection.length ? collection[Math.max(0, index - 1)].id : null; } render(); }); }); return button; } function renderNode(node, collection, index, depth) { ensureNodeDefaults(node); var wrapper = document.createElement('section'); wrapper.className = 'adv-menu-admin-node' + (depth === 0 ? ' adv-menu-admin-node--root' : '') + (depth >= 2 ? ' adv-menu-admin-node--compact' : ''); var title = getNodeTitle(node) || t(depth === 0 ? 'rootItem' : 'childItem', 'Menu item'); var customLink = getNodeLink(node); var imageHtml = depth > 0 && node.icon_url ? '' : ''; var meta = []; meta.push(metaItem(node.type || 'link', 'icon-tag')); if (!node.active) { meta.push(metaItem(t('inactive', 'Inactive'), 'icon-ban-circle')); } if (node.new_window) { meta.push(metaItem(t('newWindow', 'New window'), 'icon-external-link')); } meta.push(metaItem(String((node.children || []).length) + ' ' + t('children', 'children'), 'icon-sitemap')); meta.push(metaItem(String((node.layouts || []).length) + ' ' + t('blocks', 'blocks'), 'icon-th-large')); wrapper.innerHTML = '' + '
' + '
' + '

' + ' ' + ' ' + escapeHtml(title) + '' + '

' + '
' + meta.join('') + '
' + '
' + '
' + actionButton('js-node-up', 'icon-arrow-up', '') + actionButton('js-node-down', 'icon-arrow-down', '') + actionButton('js-node-edit', 'icon-pencil', t('edit', 'Edit')) + actionButton('js-node-add-child', 'icon-level-down', t('addChild', 'Add child')) + actionButton('js-node-add-layout', 'icon-th-large', t('addBlock', 'Add block')) + actionButton('js-node-delete btn-danger', 'icon-trash', t('delete', 'Delete')) + '
' + '
' + '
' + '
' + '
' + (customLink ? ' ' : '') + (depth === 0 ? '

' + escapeHtml(t('rootRule', 'First level only stores title and custom link.')) + '

' : '') + '
' + '
' + imageHtml + '
' + '
' + '
' + '
' + '
' + '
' + '
'; bindNodeActions(wrapper, node, collection, index, depth); var layoutsWrap = wrapper.querySelector('.adv-menu-admin-node__layouts-wrap'); if ((node.layouts || []).length) { var layoutsTitle = document.createElement('h5'); layoutsTitle.className = 'adv-menu-admin-node__section-title'; layoutsTitle.innerHTML = '' + escapeHtml(t('blocks', 'blocks')) + ''; layoutsWrap.appendChild(layoutsTitle); var layoutList = document.createElement('div'); layoutList.className = 'adv-menu-admin-layout-list'; node.layouts.forEach(function (layout, layoutIndex) { layoutList.appendChild(renderLayout(node.layouts, layout, layoutIndex)); }); layoutsWrap.appendChild(layoutList); } var childrenWrap = wrapper.querySelector('.adv-menu-admin-node__children-wrap'); if ((node.children || []).length) { var childrenTitle = document.createElement('h5'); childrenTitle.className = 'adv-menu-admin-node__section-title'; childrenTitle.innerHTML = '' + escapeHtml(t('children', 'children')) + ''; childrenWrap.appendChild(childrenTitle); var childList = document.createElement('div'); childList.className = 'adv-menu-admin-children-list adv-menu-admin-node__children' + (depth >= 1 ? ' adv-menu-admin-node__children--grid' : ''); node.children.forEach(function (child, childIndex) { childList.appendChild(renderNode(child, node.children, childIndex, depth + 1)); }); childrenWrap.appendChild(childList); } return wrapper; } function renderLayout(layouts, layout, index) { var wrapper = document.createElement('section'); wrapper.className = 'adv-menu-admin-layout'; var meta = []; meta.push(metaItem('col-' + (layout.column_width || 4), 'icon-columns')); meta.push(metaItem(layout.block_type || 'promo', 'icon-th')); if (layout.show_title) { meta.push(metaItem(t('showTitle', 'Show title'), 'icon-font')); } if ((layout.products || []).length) { meta.push(metaItem(String(layout.products.length) + ' ' + t('products', 'Products'), 'icon-shopping-cart')); } wrapper.innerHTML = '' + '
' + '
' + '
' + ' ' + ' ' + escapeHtml(t('block', 'Block')) + ' #' + escapeHtml(String(index + 1)) + '' + '
' + '
' + meta.join('') + '
' + '
' + '
' + actionButton('js-layout-up', 'icon-arrow-up', '') + actionButton('js-layout-down', 'icon-arrow-down', '') + actionButton('js-layout-edit', 'icon-pencil', t('edit', 'Edit')) + actionButton('js-layout-delete btn-danger', 'icon-trash', t('delete', 'Delete')) + '
' + '
' + '
' + (layout.custom_image_url ? '' : '') + '
'; wrapper.querySelector('.js-layout-up').addEventListener('click', function () { move(layouts, index, 'up'); }); wrapper.querySelector('.js-layout-down').addEventListener('click', function () { move(layouts, index, 'down'); }); wrapper.querySelector('.js-layout-edit').addEventListener('click', function () { openLayoutModal(layout); }); wrapper.querySelector('.js-layout-delete').addEventListener('click', function () { askConfirm(t('deleteLayoutTitle', 'Delete rich block'), t('deleteLayoutText', 'This will remove the selected rich content block.'), function () { layouts.splice(index, 1); render(); }); }); return wrapper; } function bindNodeActions(wrapper, node, collection, index, depth) { wrapper.querySelector('.js-node-up').addEventListener('click', function () { move(collection, index, 'up'); }); wrapper.querySelector('.js-node-down').addEventListener('click', function () { move(collection, index, 'down'); }); wrapper.querySelector('.js-node-edit').addEventListener('click', function () { openNodeModal(node, depth); }); wrapper.querySelector('.js-node-add-child').addEventListener('click', function () { var child = createEmptyNode(); openNodeModal(child, depth + 1, { onSave: function (savedChild) { node.children.push(savedChild); } }); }); wrapper.querySelector('.js-node-add-layout').addEventListener('click', function () { var layout = createEmptyLayout(); openLayoutModal(layout, { onSave: function (savedLayout) { node.layouts.push(savedLayout); } }); }); wrapper.querySelector('.js-node-delete').addEventListener('click', function () { askConfirm(t('deleteNodeTitle', 'Delete menu item'), t('deleteNodeText', 'This will remove the selected menu item together with its children and attached blocks.'), function () { collection.splice(index, 1); render(); }); }); } function actionButton(className, icon, label) { return '' + ''; } function metaItem(text, icon) { return '' + escapeHtml(text) + ''; } function move(collection, index, direction) { var target = direction === 'up' ? index - 1 : index + 1; if (target < 0 || target >= collection.length) { return; } var current = collection[index]; collection[index] = collection[target]; collection[target] = current; if (collection === tree && current.id === activeRootId) { activeRootId = current.id; } render(); } function findRootById(id) { for (var i = 0; i < tree.length; i += 1) { if (tree[i].id === id) { return tree[i]; } } return null; } function getRootIndex(id) { for (var i = 0; i < tree.length; i += 1) { if (tree[i].id === id) { return i; } } return 0; } function openNodeModal(node, depth, options) { nodeModalState = { node: node, depth: depth, onSave: options && typeof options.onSave === 'function' ? options.onSave : null }; ensureNodeDefaults(node); nodeModal.querySelector('.js-node-modal-title').textContent = t('nodeModalTitle', 'Edit menu item'); nodeModal.querySelector('.js-node-type').value = node.type || 'link'; nodeModal.querySelector('.js-node-entity-id').value = node.entity_id || 0; nodeModal.querySelector('.js-node-active').checked = !!node.active; nodeModal.querySelector('.js-node-new-window').checked = !!node.new_window; var rootHint = nodeModal.querySelector('.js-node-root-hint'); var imageGroup = nodeModal.querySelector('.js-node-image-group'); var previewWrap = nodeModal.querySelector('.js-node-icon-preview'); renderImagePreview(previewWrap, depth > 0 ? node.icon_url : ''); rootHint.style.display = depth === 0 ? 'block' : 'none'; rootHint.textContent = t('rootOnlyHint', 'For first-level items only title and custom link are edited here.'); imageGroup.style.display = depth === 0 ? 'none' : 'block'; renderNodeLanguagePanels(node, depth); var uploadInput = nodeModal.querySelector('.js-node-icon-upload'); uploadInput.value = ''; uploadInput.onchange = function (event) { var currentType = nodeModal.querySelector('.js-node-type').value || node.type || 'link'; uploadImage(event.target.files[0], 'menu_icon', function (response) { node.icon_path = response.path; node.icon_url = response.url; renderImagePreview(previewWrap, response.url); serializeTree(); }, { menuDepth: depth, menuType: currentType }); }; showModal(nodeModal); } function renderNodeLanguagePanels(node, depth) { var wrap = nodeModal.querySelector('.js-node-languages'); wrap.innerHTML = ''; languages.forEach(function (language) { var langId = String(language.id_lang); var lang = node.lang[langId] || { title: '', description: '', custom_link: '' }; var panel = document.createElement('div'); panel.className = 'adv-megamenu-admin__lang-panel'; panel.innerHTML = '' + '
' + escapeHtml(language.iso_code + ' / ' + language.name) + '
' + '
' + ' ' + (depth === 0 ? '' : ( ' ' )) + ' ' + '
'; panel.querySelector('.js-lang-title').addEventListener('input', function (event) { node.lang[langId].title = event.target.value; serializeTree(); }); if (depth > 0) { panel.querySelector('.js-lang-description').addEventListener('input', function (event) { node.lang[langId].description = event.target.value; serializeTree(); }); } panel.querySelector('.js-lang-link').addEventListener('input', function (event) { node.lang[langId].custom_link = event.target.value; serializeTree(); }); wrap.appendChild(panel); }); } function saveNodeModal() { if (!nodeModalState) { return; } var node = nodeModalState.node; var depth = nodeModalState.depth; var onSave = nodeModalState.onSave; node.type = nodeModal.querySelector('.js-node-type').value; node.entity_id = parseInt(nodeModal.querySelector('.js-node-entity-id').value || '0', 10); node.active = nodeModal.querySelector('.js-node-active').checked; node.new_window = nodeModal.querySelector('.js-node-new-window').checked; if (depth === 0) { node.icon_path = ''; node.icon_url = ''; Object.keys(node.lang).forEach(function (langId) { node.lang[langId].description = ''; }); } if (onSave) { onSave(node); } hideModal(nodeModal); nodeModalState = null; render(); } function openLayoutModal(layout, options) { layoutModalState = { layout: layout, onSave: options && typeof options.onSave === 'function' ? options.onSave : null }; layout.products = layout.products || []; layoutModal.querySelector('.js-layout-width').value = layout.column_width || 4; layoutModal.querySelector('.js-layout-type').value = layout.block_type || 'promo'; layoutModal.querySelector('.js-layout-title').checked = !!layout.show_title; layoutModal.querySelector('.js-layout-bg').value = layout.background_color || ''; renderImagePreview(layoutModal.querySelector('.js-layout-preview'), layout.custom_image_url); renderProducts(layout.products, layoutModal.querySelector('.js-layout-products')); layoutModal.querySelector('.js-layout-product-results').innerHTML = ''; layoutModal.querySelector('.js-layout-product-search').value = ''; var uploadInput = layoutModal.querySelector('.js-layout-upload'); uploadInput.value = ''; uploadInput.onchange = function (event) { uploadImage(event.target.files[0], 'layout_image', function (response) { layout.custom_image = response.path; layout.custom_image_url = response.url; renderImagePreview(layoutModal.querySelector('.js-layout-preview'), response.url); serializeTree(); }); }; bindLayoutProductSearch(layout); showModal(layoutModal); } function bindLayoutProductSearch(layout) { var resultsWrap = layoutModal.querySelector('.js-layout-product-results'); var searchInput = layoutModal.querySelector('.js-layout-product-search'); var searchTimer = null; searchInput.oninput = function () { clearTimeout(searchTimer); var value = searchInput.value.trim(); if (value.length < 2) { resultsWrap.innerHTML = ''; return; } searchTimer = window.setTimeout(function () { fetch(ajaxUrl + '&ajax=1&action=searchProducts&token=' + encodeURIComponent(token) + '&q=' + encodeURIComponent(value)) .then(function (response) { return response.json(); }) .then(function (payload) { resultsWrap.innerHTML = ''; (payload.results || []).forEach(function (result) { var button = document.createElement('button'); button.type = 'button'; button.textContent = result.label; button.addEventListener('click', function () { if (layout.products.indexOf(result.id) === -1) { layout.products.push(result.id); } renderProducts(layout.products, layoutModal.querySelector('.js-layout-products')); serializeTree(); }); resultsWrap.appendChild(button); }); }); }, 200); }; } function renderProducts(products, wrap) { wrap.innerHTML = ''; if (!products.length) { wrap.innerHTML = '' + escapeHtml(t('noProducts', 'No products attached.')) + ''; return; } products.forEach(function (productId, index) { var chip = document.createElement('span'); chip.className = 'adv-menu-product-chip'; chip.innerHTML = '#' + escapeHtml(String(productId)) + ''; chip.querySelector('button').addEventListener('click', function () { products.splice(index, 1); renderProducts(products, wrap); serializeTree(); }); wrap.appendChild(chip); }); } function saveLayoutModal() { if (!layoutModalState) { return; } var layout = layoutModalState.layout; var onSave = layoutModalState.onSave; layout.column_width = parseInt(layoutModal.querySelector('.js-layout-width').value || '4', 10); layout.block_type = layoutModal.querySelector('.js-layout-type').value; layout.show_title = layoutModal.querySelector('.js-layout-title').checked; layout.background_color = layoutModal.querySelector('.js-layout-bg').value; if (onSave) { onSave(layout); } hideModal(layoutModal); layoutModalState = null; render(); } function askConfirm(title, text, callback) { confirmAction = callback; confirmModal.querySelector('.js-confirm-title').textContent = title; confirmModal.querySelector('.js-confirm-text').textContent = text; showModal(confirmModal); } function renderImagePreview(wrap, url) { wrap.innerHTML = ''; if (!url) { return; } var image = document.createElement('img'); image.src = url; image.className = 'adv-menu-image-preview'; wrap.appendChild(image); } function getNodeTitle(node) { if (defaultLangId && node.lang[defaultLangId] && node.lang[defaultLangId].title) { return node.lang[defaultLangId].title; } var langIds = Object.keys(node.lang || {}); for (var i = 0; i < langIds.length; i += 1) { if (node.lang[langIds[i]] && node.lang[langIds[i]].title) { return node.lang[langIds[i]].title; } } return ''; } function getNodeLink(node) { if (defaultLangId && node.lang[defaultLangId] && node.lang[defaultLangId].custom_link) { return node.lang[defaultLangId].custom_link; } var langIds = Object.keys(node.lang || {}); for (var i = 0; i < langIds.length; i += 1) { if (node.lang[langIds[i]] && node.lang[langIds[i]].custom_link) { return node.lang[langIds[i]].custom_link; } } return ''; } function uploadImage(file, preset, callback, options) { if (!file) { return; } var uploadOptions = options || {}; var formData = new FormData(); formData.append('ajax', '1'); formData.append('action', 'uploadImage'); formData.append('token', token); formData.append('preset', preset || 'default'); if (typeof uploadOptions.menuDepth !== 'undefined') { formData.append('menu_depth', String(uploadOptions.menuDepth)); } if (typeof uploadOptions.menuType !== 'undefined') { formData.append('menu_type', String(uploadOptions.menuType)); } formData.append('image', file); fetch(ajaxUrl, { method: 'POST', body: formData }) .then(function (response) { return response.json(); }) .then(function (payload) { if (payload && !payload.error) { callback(payload); } }); } function escapeHtml(value) { return String(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function escapeAttribute(value) { return escapeHtml(value).replace(/\n/g, ' '); } root.querySelector('.js-add-root-node').addEventListener('click', function () { var node = createEmptyNode(); openNodeModal(node, 0, { onSave: function (savedNode) { tree.push(savedNode); activeRootId = savedNode.id; } }); }); nodeModal.querySelector('.js-save-node').addEventListener('click', saveNodeModal); layoutModal.querySelector('.js-save-layout').addEventListener('click', saveLayoutModal); confirmModal.querySelector('.js-confirm-action').addEventListener('click', function () { if (typeof confirmAction === 'function') { confirmAction(); } hideModal(confirmModal); confirmAction = null; }); if (window.jQuery) { window.jQuery(nodeModal).on('hidden.bs.modal', function () { nodeModalState = null; }); window.jQuery(layoutModal).on('hidden.bs.modal', function () { layoutModalState = null; }); window.jQuery(confirmModal).on('hidden.bs.modal', function () { confirmAction = null; }); } render(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot); } else { boot(); } })();