Files
advancedmegamenu/views/js/admin.js
T
tiamak 8e6b6d0cdd
Build / Build & Release draft (push) Failing after 0s
PHP tests / PHP Syntax check 5.6 => 8.1 (push) Failing after 33s
PHP tests / PHPStan (1.7.1.2) (push) Has been cancelled
PHP tests / PHPStan (1.7.2.5) (push) Has been cancelled
PHP tests / PHPStan (1.7.3.4) (push) Has been cancelled
PHP tests / PHPStan (1.7.4.4) (push) Has been cancelled
PHP tests / PHPStan (1.7.5.1) (push) Has been cancelled
PHP tests / PHPStan (1.7.6) (push) Has been cancelled
PHP tests / PHPStan (1.7.7) (push) Has been cancelled
PHP tests / PHPStan (1.7.8) (push) Has been cancelled
PHP tests / PHPStan (latest) (push) Has been cancelled
PHP tests / PHP-CS-Fixer (push) Has been cancelled
Build advanced mega menu module
2026-04-09 13:29:59 +00:00

427 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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 languages = JSON.parse((root.querySelector('.js-adv-languages-data') || {}).textContent || '[]');
var tree = JSON.parse((root.querySelector('.js-adv-tree-data') || {}).textContent || '[]');
var tempId = Date.now();
function nextId() {
tempId += 1;
return tempId;
}
function createEmptyNode() {
var lang = {};
languages.forEach(function (language) {
lang[language.id_lang] = {
title: '',
description: '',
custom_link: ''
};
});
return {
id: nextId(),
active: true,
type: 'category',
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 serializeTree() {
treeInput.value = JSON.stringify(tree);
}
function render() {
serializeTree();
treeContainer.innerHTML = '';
tree.forEach(function (node, index) {
treeContainer.appendChild(renderNode(node, tree, index, 0));
});
}
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;
render();
}
function remove(collection, index) {
collection.splice(index, 1);
render();
}
function renderNode(node, collection, index, depth) {
var wrapper = document.createElement('section');
wrapper.className = 'adv-menu-node';
var title = getNodeTitle(node);
wrapper.innerHTML = '' +
'<div class="adv-menu-node__header">' +
' <strong>' + escapeHtml(title || 'Menu item') + '</strong>' +
' <div class="adv-menu-node__actions">' +
' <button type="button" class="btn btn-default btn-xs js-move-up">↑</button>' +
' <button type="button" class="btn btn-default btn-xs js-move-down">↓</button>' +
' <button type="button" class="btn btn-default btn-xs js-add-child">+ child</button>' +
' <button type="button" class="btn btn-default btn-xs js-add-layout">+ block</button>' +
' <button type="button" class="btn btn-danger btn-xs js-remove-node">×</button>' +
' </div>' +
'</div>' +
'<div class="adv-menu-node__body">' +
' <div class="adv-menu-node__grid">' +
' <label><span>Type</span><select class="form-control js-node-type">' +
' <option value="category">category</option>' +
' <option value="cms">cms</option>' +
' <option value="link">link</option>' +
' <option value="rich_content">rich_content</option>' +
' </select></label>' +
' <label><span>Entity ID</span><input type="number" class="form-control js-entity-id" min="0"></label>' +
' <label><span>Active</span><input type="checkbox" class="js-active"></label>' +
' <label><span>New window</span><input type="checkbox" class="js-new-window"></label>' +
' <label><span>Icon upload</span><input type="file" class="js-icon-upload" accept="image/*"><input type="hidden" class="js-icon-path"></label>' +
' <div class="js-icon-preview-wrap"></div>' +
' </div>' +
' <div class="adv-menu-node__languages"></div>' +
' <div class="adv-menu-node__layouts"></div>' +
' <div class="adv-menu-node__children"></div>' +
'</div>';
wrapper.querySelector('.js-node-type').value = node.type || 'category';
wrapper.querySelector('.js-entity-id').value = node.entity_id || 0;
wrapper.querySelector('.js-active').checked = !!node.active;
wrapper.querySelector('.js-new-window').checked = !!node.new_window;
wrapper.querySelector('.js-icon-path').value = node.icon_path || '';
var iconPreviewWrap = wrapper.querySelector('.js-icon-preview-wrap');
renderImagePreview(iconPreviewWrap, node.icon_url);
wrapper.querySelector('.js-node-type').addEventListener('change', function (event) {
node.type = event.target.value;
render();
});
wrapper.querySelector('.js-entity-id').addEventListener('input', function (event) {
node.entity_id = parseInt(event.target.value || '0', 10);
serializeTree();
});
wrapper.querySelector('.js-active').addEventListener('change', function (event) {
node.active = event.target.checked;
serializeTree();
});
wrapper.querySelector('.js-new-window').addEventListener('change', function (event) {
node.new_window = event.target.checked;
serializeTree();
});
wrapper.querySelector('.js-icon-upload').addEventListener('change', function (event) {
uploadImage(event.target.files[0], 'menu_icon', function (response) {
node.icon_path = response.path;
node.icon_url = response.url;
serializeTree();
renderImagePreview(iconPreviewWrap, response.url);
});
});
wrapper.querySelector('.js-move-up').addEventListener('click', function () {
move(collection, index, 'up');
});
wrapper.querySelector('.js-move-down').addEventListener('click', function () {
move(collection, index, 'down');
});
wrapper.querySelector('.js-remove-node').addEventListener('click', function () {
remove(collection, index);
});
wrapper.querySelector('.js-add-child').addEventListener('click', function () {
node.children = node.children || [];
node.children.push(createEmptyNode());
render();
});
wrapper.querySelector('.js-add-layout').addEventListener('click', function () {
node.layouts = node.layouts || [];
node.layouts.push(createEmptyLayout());
render();
});
var langWrap = wrapper.querySelector('.adv-menu-node__languages');
languages.forEach(function (language) {
var langId = String(language.id_lang);
if (!node.lang[langId]) {
node.lang[langId] = { title: '', description: '', custom_link: '' };
}
var box = document.createElement('div');
box.className = 'adv-menu-language';
box.innerHTML = '' +
'<h4>' + escapeHtml(language.iso_code + ' / ' + language.name) + '</h4>' +
'<label><span>Title</span><input type="text" class="form-control js-lang-title"></label>' +
'<label><span>Description</span><textarea class="form-control js-lang-description" rows="3"></textarea></label>' +
'<label><span>Custom link</span><input type="text" class="form-control js-lang-link"></label>';
box.querySelector('.js-lang-title').value = node.lang[langId].title || '';
box.querySelector('.js-lang-description').value = node.lang[langId].description || '';
box.querySelector('.js-lang-link').value = node.lang[langId].custom_link || '';
box.querySelector('.js-lang-title').addEventListener('input', function (event) {
node.lang[langId].title = event.target.value;
serializeTree();
});
box.querySelector('.js-lang-description').addEventListener('input', function (event) {
node.lang[langId].description = event.target.value;
serializeTree();
});
box.querySelector('.js-lang-link').addEventListener('input', function (event) {
node.lang[langId].custom_link = event.target.value;
serializeTree();
});
langWrap.appendChild(box);
});
var layoutsWrap = wrapper.querySelector('.adv-menu-node__layouts');
(node.layouts || []).forEach(function (layout, layoutIndex) {
layoutsWrap.appendChild(renderLayout(node.layouts, layout, layoutIndex));
});
var childrenWrap = wrapper.querySelector('.adv-menu-node__children');
(node.children || []).forEach(function (child, childIndex) {
childrenWrap.appendChild(renderNode(child, node.children, childIndex, depth + 1));
});
return wrapper;
}
function renderLayout(layouts, layout, index) {
var wrapper = document.createElement('section');
wrapper.className = 'adv-menu-layout';
wrapper.innerHTML = '' +
'<div class="adv-menu-layout__header">' +
' <strong>Rich block</strong>' +
' <div class="adv-menu-layout__actions">' +
' <button type="button" class="btn btn-default btn-xs js-layout-up">↑</button>' +
' <button type="button" class="btn btn-default btn-xs js-layout-down">↓</button>' +
' <button type="button" class="btn btn-danger btn-xs js-remove-layout">×</button>' +
' </div>' +
'</div>' +
'<div class="adv-menu-layout__body">' +
' <div class="adv-menu-layout__grid">' +
' <label><span>Width</span><input type="number" class="form-control js-layout-width" min="1" max="12"></label>' +
' <label><span>Block type</span><select class="form-control js-layout-type"><option value="promo">promo</option><option value="products">products</option></select></label>' +
' <label><span>Show title</span><input type="checkbox" class="js-layout-title"></label>' +
' <label><span>Background color</span><input type="text" class="form-control js-layout-bg" placeholder="#f3efe8"></label>' +
' <label><span>Image upload</span><input type="file" class="js-layout-upload" accept="image/*"></label>' +
' <div class="js-layout-preview-wrap"></div>' +
' </div>' +
' <label><span>Product search</span><input type="text" class="form-control js-product-search" placeholder="Search product by name, reference or ID"></label>' +
' <div class="adv-menu-product-results js-product-results"></div>' +
' <div class="adv-menu-products js-products"></div>' +
'</div>';
wrapper.querySelector('.js-layout-width').value = layout.column_width || 4;
wrapper.querySelector('.js-layout-type').value = layout.block_type || 'promo';
wrapper.querySelector('.js-layout-title').checked = !!layout.show_title;
wrapper.querySelector('.js-layout-bg').value = layout.background_color || '';
var previewWrap = wrapper.querySelector('.js-layout-preview-wrap');
renderImagePreview(previewWrap, layout.custom_image_url);
wrapper.querySelector('.js-layout-width').addEventListener('input', function (event) {
layout.column_width = parseInt(event.target.value || '4', 10);
serializeTree();
});
wrapper.querySelector('.js-layout-type').addEventListener('change', function (event) {
layout.block_type = event.target.value;
serializeTree();
});
wrapper.querySelector('.js-layout-title').addEventListener('change', function (event) {
layout.show_title = event.target.checked;
serializeTree();
});
wrapper.querySelector('.js-layout-bg').addEventListener('input', function (event) {
layout.background_color = event.target.value;
serializeTree();
});
wrapper.querySelector('.js-layout-upload').addEventListener('change', function (event) {
uploadImage(event.target.files[0], 'layout_image', function (response) {
layout.custom_image = response.path;
layout.custom_image_url = response.url;
serializeTree();
renderImagePreview(previewWrap, response.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-remove-layout').addEventListener('click', function () {
remove(layouts, index);
});
var productsWrap = wrapper.querySelector('.js-products');
renderProducts(layout.products || [], productsWrap, function (nextProducts) {
layout.products = nextProducts;
serializeTree();
});
var resultsWrap = wrapper.querySelector('.js-product-results');
var searchInput = wrapper.querySelector('.js-product-search');
var searchTimer = null;
searchInput.addEventListener('input', 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&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 () {
layout.products = layout.products || [];
if (layout.products.indexOf(result.id) === -1) {
layout.products.push(result.id);
}
render();
});
resultsWrap.appendChild(button);
});
});
}, 200);
});
return wrapper;
}
function renderProducts(products, wrap, onChange) {
wrap.innerHTML = '';
products.forEach(function (productId, index) {
var chip = document.createElement('span');
chip.className = 'adv-menu-product-chip';
chip.innerHTML = '<span>#' + escapeHtml(String(productId)) + '</span><button type="button">×</button>';
chip.querySelector('button').addEventListener('click', function () {
var nextProducts = products.slice();
nextProducts.splice(index, 1);
onChange(nextProducts);
render();
});
wrap.appendChild(chip);
});
}
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) {
var firstLang = languages[0] ? String(languages[0].id_lang) : null;
if (firstLang && node.lang[firstLang] && node.lang[firstLang].title) {
return node.lang[firstLang].title;
}
var langIds = Object.keys(node.lang || {});
if (langIds.length && node.lang[langIds[0]]) {
return node.lang[langIds[0]].title || '';
}
return '';
}
function uploadImage(file, preset, callback) {
if (!file) {
return;
}
var formData = new FormData();
formData.append('ajax', '1');
formData.append('action', 'uploadImage');
formData.append('token', token);
formData.append('preset', preset || 'default');
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 value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
var addRootButton = root.querySelector('.js-add-root-node');
if (addRootButton) {
addRootButton.addEventListener('click', function () {
tree.push(createEmptyNode());
render();
});
}
render();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();