feat: enhance mega menu navigation with dynamic image updates and support for nested level-4 content items
This commit is contained in:
+27
@@ -215,6 +215,9 @@
|
|||||||
<td><span class="badge badge-info">${tabCount} tabs</span></td>
|
<td><span class="badge badge-info">${tabCount} tabs</span></td>
|
||||||
<td><code>[plugin:tabbed-livestream id="${escapeHtml(group.id || '')}"]</code></td>
|
<td><code>[plugin:tabbed-livestream id="${escapeHtml(group.id || '')}"]</code></td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
|
<button type="button" class="btn btn-sm btn-info mr-1" onclick="duplicateGroup(${idx})" title="Nhân bản">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-primary mr-1" onclick="openEditGroupModal(${idx})" title="Chỉnh sửa">
|
<button type="button" class="btn btn-sm btn-primary mr-1" onclick="openEditGroupModal(${idx})" title="Chỉnh sửa">
|
||||||
<i class="fas fa-edit"></i> Sửa
|
<i class="fas fa-edit"></i> Sửa
|
||||||
</button>
|
</button>
|
||||||
@@ -269,6 +272,7 @@
|
|||||||
<td><strong>${escapeHtml(tab.title || '')}</strong></td>
|
<td><strong>${escapeHtml(tab.title || '')}</strong></td>
|
||||||
<td class="small text-truncate" style="max-width: 180px;">${escapeHtml(tab.excerpt || '')}</td>
|
<td class="small text-truncate" style="max-width: 180px;">${escapeHtml(tab.excerpt || '')}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
|
<button type="button" class="btn btn-sm btn-info mr-1" onclick="duplicateWorkingTab(${tIdx})" title="Nhân bản"><i class="fas fa-copy"></i></button>
|
||||||
<button type="button" class="btn btn-sm btn-secondary mr-1" onclick="openEditTabModal(${tIdx})"><i class="fas fa-pencil-alt"></i> Sửa</button>
|
<button type="button" class="btn btn-sm btn-secondary mr-1" onclick="openEditTabModal(${tIdx})"><i class="fas fa-pencil-alt"></i> Sửa</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteWorkingTab(${tIdx})"><i class="fas fa-times"></i></button>
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteWorkingTab(${tIdx})"><i class="fas fa-times"></i></button>
|
||||||
</td>
|
</td>
|
||||||
@@ -347,6 +351,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Duplicate tab in modal
|
||||||
|
function duplicateWorkingTab(tIdx) {
|
||||||
|
if (confirm('Bạn có chắc muốn nhân bản tab này?')) {
|
||||||
|
let originalTab = currentWorkingTabs[tIdx];
|
||||||
|
let duplicatedTab = JSON.parse(JSON.stringify(originalTab));
|
||||||
|
duplicatedTab.title = duplicatedTab.title + " (Copy)";
|
||||||
|
currentWorkingTabs.push(duplicatedTab);
|
||||||
|
renderModalTabsTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Media picker inside tab modal
|
// Media picker inside tab modal
|
||||||
function openMediaPickerForModal() {
|
function openMediaPickerForModal() {
|
||||||
if (typeof SISMediaPicker !== 'undefined') {
|
if (typeof SISMediaPicker !== 'undefined') {
|
||||||
@@ -398,6 +413,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Duplicate group
|
||||||
|
function duplicateGroup(idx) {
|
||||||
|
if (confirm(`Bạn có chắc muốn nhân bản danh sách "${groups[idx].name}"?`)) {
|
||||||
|
let originalGroup = groups[idx];
|
||||||
|
let duplicatedGroup = JSON.parse(JSON.stringify(originalGroup));
|
||||||
|
duplicatedGroup.name = duplicatedGroup.name + " (Copy)";
|
||||||
|
duplicatedGroup.id = duplicatedGroup.id + "-copy-" + Math.random().toString(36).substring(2, 6);
|
||||||
|
groups.push(duplicatedGroup);
|
||||||
|
renderGroupsTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save form submission
|
// Save form submission
|
||||||
function saveAllGroups() {
|
function saveAllGroups() {
|
||||||
document.getElementById('groupDataInput').value = JSON.stringify(groups);
|
document.getElementById('groupDataInput').value = JSON.stringify(groups);
|
||||||
|
|||||||
@@ -94,9 +94,22 @@
|
|||||||
const firstL2 = menu.querySelector('.l2-hover-target');
|
const firstL2 = menu.querySelector('.l2-hover-target');
|
||||||
if (firstL2) {
|
if (firstL2) {
|
||||||
firstL2.classList.add('active');
|
firstL2.classList.add('active');
|
||||||
const targetId = firstL2.getAttribute('data-target');
|
const targetId = firstL2.getAttribute('data-target') || firstL2.getAttribute('data-target-fallback');
|
||||||
const targetContent = document.getElementById(targetId);
|
const targetContent = document.getElementById(targetId);
|
||||||
if (targetContent) targetContent.style.display = 'block';
|
if (targetContent) targetContent.style.display = 'block';
|
||||||
|
|
||||||
|
const imgElem = menu.querySelector('.mega-col-img-elem');
|
||||||
|
if (imgElem) {
|
||||||
|
const newImg = firstL2.getAttribute('data-image');
|
||||||
|
const defaultImg = imgElem.getAttribute('data-default-image');
|
||||||
|
const targetImg = newImg && newImg.trim() !== '' && newImg !== 'null' ? newImg : defaultImg;
|
||||||
|
imgElem.src = targetImg;
|
||||||
|
if (targetImg && targetImg.includes('logo.png')) {
|
||||||
|
imgElem.style = "object-fit: contain; background: white; padding: 20px; width: 100%; max-height: 245px;";
|
||||||
|
} else {
|
||||||
|
imgElem.style = "object-fit: cover; border-radius: 8px; width: 100%; max-height: 245px;";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,13 +124,57 @@
|
|||||||
// Hide all L3 content in this menu
|
// Hide all L3 content in this menu
|
||||||
wrapper.querySelectorAll('.l3-content').forEach(el => el.style.display = 'none');
|
wrapper.querySelectorAll('.l3-content').forEach(el => el.style.display = 'none');
|
||||||
|
|
||||||
|
// Hide all L4 content and reset image visibility
|
||||||
|
wrapper.querySelectorAll('.l4-content').forEach(el => el.style.display = 'none');
|
||||||
|
const imgWrapper = wrapper.querySelector('.mega-col-image-wrapper');
|
||||||
|
if (imgWrapper) imgWrapper.style.display = 'flex'; // image column has display: flex originally
|
||||||
|
wrapper.querySelectorAll('.l3-hover-target').forEach(el => el.classList.remove('active', 'font-weight-bold'));
|
||||||
|
|
||||||
// Activate current
|
// Activate current
|
||||||
this.classList.add('active');
|
this.classList.add('active');
|
||||||
const contentId = this.getAttribute('data-target');
|
const contentId = this.getAttribute('data-target') || this.getAttribute('data-target-fallback');
|
||||||
const content = document.getElementById(contentId);
|
const content = document.getElementById(contentId);
|
||||||
if (content) {
|
if (content) {
|
||||||
content.style.display = 'block';
|
content.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle image override
|
||||||
|
const imgElem = wrapper.querySelector('.mega-col-img-elem');
|
||||||
|
if (imgElem) {
|
||||||
|
const newImg = this.getAttribute('data-image');
|
||||||
|
const defaultImg = imgElem.getAttribute('data-default-image');
|
||||||
|
const targetImg = newImg && newImg.trim() !== '' && newImg !== 'null' ? newImg : defaultImg;
|
||||||
|
imgElem.src = targetImg;
|
||||||
|
if (targetImg && targetImg.includes('logo.png')) {
|
||||||
|
imgElem.style = "object-fit: contain; background: white; padding: 20px; width: 100%; max-height: 245px;";
|
||||||
|
} else {
|
||||||
|
imgElem.style = "object-fit: cover; border-radius: 8px; width: 100%; max-height: 245px;";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.l3-hover-target').forEach(target => {
|
||||||
|
target.addEventListener('mouseenter', function() {
|
||||||
|
const wrapper = this.closest('.mega-menu-content');
|
||||||
|
if (!wrapper) return;
|
||||||
|
|
||||||
|
wrapper.querySelectorAll('.l3-hover-target').forEach(el => el.classList.remove('active', 'font-weight-bold'));
|
||||||
|
this.classList.add('active', 'font-weight-bold');
|
||||||
|
|
||||||
|
wrapper.querySelectorAll('.l4-content').forEach(el => el.style.display = 'none');
|
||||||
|
|
||||||
|
const hasChildren = this.getAttribute('data-has-children') === 'true';
|
||||||
|
const imgWrapper = wrapper.querySelector('.mega-col-image-wrapper');
|
||||||
|
|
||||||
|
if (hasChildren) {
|
||||||
|
const contentId = this.getAttribute('data-target');
|
||||||
|
const content = document.getElementById(contentId);
|
||||||
|
if (content) content.style.display = 'block';
|
||||||
|
if (imgWrapper) imgWrapper.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
if (imgWrapper) imgWrapper.style.display = 'flex';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -314,6 +371,32 @@
|
|||||||
color: var(--color-white) !important;
|
color: var(--color-white) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Column 4: Level 4 */
|
||||||
|
.submenu-l4 {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.submenu-l4 .menu-item-l4 {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.submenu-l4 .menu-item-l4 a {
|
||||||
|
display: block;
|
||||||
|
padding: 12px 15px;
|
||||||
|
color: #111827 !important;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.submenu-l4 .menu-item-l4 a:hover {
|
||||||
|
background-color: var(--color-brand) !important;
|
||||||
|
color: var(--color-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide the mobile back button and title on desktop */
|
/* Hide the mobile back button and title on desktop */
|
||||||
.mc--main-menu .megamenu.menu-item .submenu-wrapper .button-back,
|
.mc--main-menu .megamenu.menu-item .submenu-wrapper .button-back,
|
||||||
.mc--main-menu .megamenu.menu-item .submenu-wrapper .navigation-title {
|
.mc--main-menu .megamenu.menu-item .submenu-wrapper .navigation-title {
|
||||||
@@ -389,7 +472,7 @@
|
|||||||
<!-- Column 2: Level 2 Links -->
|
<!-- Column 2: Level 2 Links -->
|
||||||
<div class="mega-col-level2" th:if="${not #lists.isEmpty(item.children)}">
|
<div class="mega-col-level2" th:if="${not #lists.isEmpty(item.children)}">
|
||||||
<ul class="submenu-l2">
|
<ul class="submenu-l2">
|
||||||
<li class="menu-item-l2 l2-hover-target" th:each="child : ${item.children}" th:attr="data-target='l3-' + ${item.id} + '-' + ${child.id}">
|
<li class="menu-item-l2 l2-hover-target" th:each="child : ${item.children}" th:data-target-fallback="'l3-' + ${item.id} + '-' + ${child.id}" th:attr="data-target='l3-' + ${item.id} + '-' + ${child.id}" th:data-image="${child.imageUrl}">
|
||||||
<a th:href="${child.url}" th:text="${child.label}">Child Item</a>
|
<a th:href="${child.url}" th:text="${child.label}">Child Item</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -401,23 +484,37 @@
|
|||||||
<th:block th:each="child : ${item.children}">
|
<th:block th:each="child : ${item.children}">
|
||||||
<ul th:id="'l3-' + ${item.id} + '-' + ${child.id}" class="submenu-l3 l3-content" style="display: none;">
|
<ul th:id="'l3-' + ${item.id} + '-' + ${child.id}" class="submenu-l3 l3-content" style="display: none;">
|
||||||
<th:block th:if="${not #lists.isEmpty(child.children)}">
|
<th:block th:if="${not #lists.isEmpty(child.children)}">
|
||||||
<li class="menu-item-l3" th:each="grandchild : ${child.children}">
|
<li class="menu-item-l3 l3-hover-target" th:each="grandchild : ${child.children}" th:attr="data-target='l4-' + ${item.id} + '-' + ${child.id} + '-' + ${grandchild.id}" th:data-has-children="${not #lists.isEmpty(grandchild.children)}">
|
||||||
<a th:href="${grandchild.url}" th:text="${grandchild.label}" th:classappend="${not #lists.isEmpty(grandchild.children) ? 'font-weight-bold' : ''}">Grandchild Item</a>
|
<a th:href="${grandchild.url}" th:text="${grandchild.label}">Grandchild Item</a>
|
||||||
<ul th:if="${not #lists.isEmpty(grandchild.children)}" class="submenu-l4" style="padding-left: 15px; margin-top: 5px; list-style-type: none;">
|
|
||||||
<li class="menu-item-l4" th:each="greatgrandchild : ${grandchild.children}" style="margin-bottom: 5px;">
|
|
||||||
<a th:href="${greatgrandchild.url}" th:text="${greatgrandchild.label}" style="font-size: 13px; font-weight: normal; color: #555; text-transform: none;">Great-Grandchild Item</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
</th:block>
|
</th:block>
|
||||||
</ul>
|
</ul>
|
||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Column 4: Featured Image / Media Banner -->
|
<!-- Column 4: Featured Image or Level 4 Links -->
|
||||||
<div class="mega-col-image">
|
<div class="mega-col-level4-and-image" style="flex: 1; border-left: 1px solid #d1d5db; padding-left: 20px;">
|
||||||
<img th:if="${not #strings.isEmpty(item.imageUrl)}" th:src="${item.imageUrl}" style="object-fit: cover; border-radius: 8px; width: 100%; max-height: 245px;"/>
|
<!-- Level 4 Containers -->
|
||||||
<img th:if="${#strings.isEmpty(item.imageUrl)}" th:src="@{/theme-assets/umass/images/logo.png}" style="object-fit: contain; background: white; padding: 20px; width: 100%; max-height: 245px;"/>
|
<div class="level4-wrapper">
|
||||||
|
<th:block th:each="child : ${item.children}">
|
||||||
|
<th:block th:each="grandchild : ${child.children}">
|
||||||
|
<ul th:id="'l4-' + ${item.id} + '-' + ${child.id} + '-' + ${grandchild.id}" class="submenu-l4 l4-content" style="display: none; padding:0; margin:0; list-style:none;">
|
||||||
|
<li class="menu-item-l4" th:each="greatgrandchild : ${grandchild.children}">
|
||||||
|
<a th:href="${greatgrandchild.url}" th:text="${greatgrandchild.label}">Great-Grandchild Item</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Featured Image -->
|
||||||
|
<div class="mega-col-image-wrapper mega-col-image" style="border-left: none; padding-left: 0;">
|
||||||
|
<img class="mega-col-img-elem"
|
||||||
|
th:data-default-image="${not #strings.isEmpty(item.imageUrl) ? item.imageUrl : '/theme-assets/umass/images/logo.png'}"
|
||||||
|
th:src="${not #strings.isEmpty(item.imageUrl) ? item.imageUrl : '/theme-assets/umass/images/logo.png'}"
|
||||||
|
th:style="${not #strings.isEmpty(item.imageUrl) ? 'object-fit: cover; border-radius: 8px; width: 100%; max-height: 245px;' : 'object-fit: contain; background: white; padding: 20px; width: 100%; max-height: 245px;'}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- End Mega Menu Layout -->
|
<!-- End Mega Menu Layout -->
|
||||||
|
|||||||
Reference in New Issue
Block a user