Refactor page structure: Update page classes for consistency
- Changed class from "ots-displays-page" to "ots-static-page ots-displays-page" in multiple Twig view files to standardize page layout. - Enhanced schedule-page.twig with improved calendar navigation and dropdown management. - Added global dropdown dismissal functionality to improve user experience across modals and dropdowns.
This commit is contained in:
@@ -1131,6 +1131,9 @@
|
||||
* (before Bootstrap sees it), prevent Bootstrap from handling it, and
|
||||
* manage show/hide/position entirely ourselves.
|
||||
*/
|
||||
// Module-level reference to row dropdown's closeMenu, set by initRowDropdowns().
|
||||
var _closeRowDropdown = null;
|
||||
|
||||
function initRowDropdowns() {
|
||||
var TOGGLE_SEL = '.dropdown-menu-container [data-toggle="dropdown"], .btn-group [data-toggle="dropdown"]';
|
||||
var activeMenu = null; // currently open menu element (in <body>)
|
||||
@@ -1209,6 +1212,9 @@
|
||||
activeTrigger = null;
|
||||
}
|
||||
|
||||
// Expose closeMenu so closeAllDropdowns() can reach it
|
||||
_closeRowDropdown = closeMenu;
|
||||
|
||||
// Intercept clicks in CAPTURE phase — runs BEFORE Bootstrap's handler.
|
||||
document.addEventListener('click', function(e) {
|
||||
var toggle = e.target.closest(TOGGLE_SEL);
|
||||
@@ -1263,6 +1269,121 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close every open dropdown / popover on the page.
|
||||
* Covers: Bootstrap 4 native dropdowns, OTS custom dropdowns,
|
||||
* the user-menu, notification drawer, and DataTable row menus.
|
||||
*/
|
||||
function closeAllDropdowns() {
|
||||
try {
|
||||
// 0. Close row dropdown managed by initRowDropdowns()
|
||||
if (typeof _closeRowDropdown === 'function') _closeRowDropdown();
|
||||
|
||||
// Also force-remove any stray ots-row-dropdown elements left on <body>
|
||||
document.querySelectorAll('.ots-row-dropdown').forEach(function(m) {
|
||||
m.classList.remove('show', 'ots-row-dropdown');
|
||||
m.style.cssText = '';
|
||||
});
|
||||
|
||||
// 1. Bootstrap 4 native dropdowns (.show on the wrapper or the menu)
|
||||
document.querySelectorAll('.dropdown.show, .btn-group.show').forEach(function(el) {
|
||||
el.classList.remove('show');
|
||||
var m = el.querySelector('.dropdown-menu.show');
|
||||
if (m) m.classList.remove('show');
|
||||
});
|
||||
document.querySelectorAll('.dropdown-menu.show').forEach(function(m) {
|
||||
m.classList.remove('show');
|
||||
});
|
||||
|
||||
// 2. OTS custom dropdowns that use .active
|
||||
document.querySelectorAll('.dropdown.active, .ots-topbar-action .dropdown.active, .ots-page-actions .dropdown.active').forEach(function(el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
|
||||
// 3. User menu (body-level floating menu)
|
||||
var userMenu = document.querySelector('.ots-user-menu-body.ots-user-menu-open');
|
||||
if (userMenu) {
|
||||
userMenu.classList.remove('ots-user-menu-open');
|
||||
var userDropdown = document.querySelector('#navbarUserMenu');
|
||||
if (userDropdown) {
|
||||
var dd = userDropdown.closest('.dropdown');
|
||||
if (dd) dd.classList.remove('active', 'show');
|
||||
}
|
||||
}
|
||||
|
||||
// 4. DataTable button collections
|
||||
document.querySelectorAll('.dt-buttons.active').forEach(function(w) {
|
||||
w.classList.remove('active');
|
||||
});
|
||||
document.querySelectorAll('.dt-button-collection.show').forEach(function(c) {
|
||||
c.classList.remove('show');
|
||||
c.style.display = 'none';
|
||||
});
|
||||
|
||||
// 5. jQuery-level Bootstrap cleanup (if available)
|
||||
var jq = window.jQuery || window.$;
|
||||
if (jq) {
|
||||
jq('.dropdown-toggle[aria-expanded="true"]').attr('aria-expanded', 'false');
|
||||
jq('.dropdown-menu.show').removeClass('show');
|
||||
jq('.dropdown.show, .btn-group.show').removeClass('show');
|
||||
}
|
||||
} catch (err) {
|
||||
// never let this break the page
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire up global listeners that trigger closeAllDropdowns().
|
||||
* Called once from init().
|
||||
*/
|
||||
function initGlobalDropdownDismiss() {
|
||||
// ── Close when a Bootstrap modal / dialog opens ─────────────────
|
||||
document.addEventListener('show.bs.modal', closeAllDropdowns, true);
|
||||
try {
|
||||
var jq = window.jQuery || window.$;
|
||||
if (jq) {
|
||||
jq(document).on('show.bs.modal', closeAllDropdowns);
|
||||
// Xibo opens modals when .XiboFormButton / .XiboAjaxSubmit are clicked
|
||||
jq(document).on('click', '.XiboFormButton, .XiboAjaxSubmit, .XiboFormRender', function() {
|
||||
closeAllDropdowns();
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// ── Close when any <a> inside a dropdown is clicked (page nav) ──
|
||||
document.addEventListener('click', function(e) {
|
||||
var link = e.target.closest('.dropdown-menu a, .ots-topbar a.nav-link, .ots-topbar a.dropdown-item, .XiboFormButton');
|
||||
if (link && !e.defaultPrevented) {
|
||||
closeAllDropdowns();
|
||||
}
|
||||
});
|
||||
|
||||
// ── Close on History navigation (Xibo uses pushState for AJAX pages) ──
|
||||
window.addEventListener('popstate', closeAllDropdowns);
|
||||
// Intercept pushState / replaceState so we catch Xibo's AJAX navigation
|
||||
try {
|
||||
var origPush = history.pushState;
|
||||
var origReplace = history.replaceState;
|
||||
history.pushState = function() {
|
||||
origPush.apply(this, arguments);
|
||||
closeAllDropdowns();
|
||||
};
|
||||
history.replaceState = function() {
|
||||
origReplace.apply(this, arguments);
|
||||
closeAllDropdowns();
|
||||
};
|
||||
} catch (e) {}
|
||||
|
||||
// ── Close when main content area changes (AJAX page swap) ───────
|
||||
try {
|
||||
var content = document.getElementById('content') || document.querySelector('.ots-content');
|
||||
if (content) {
|
||||
var contentObs = new MutationObserver(debounce(closeAllDropdowns, 80));
|
||||
contentObs.observe(content, { childList: true });
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all features when DOM is ready
|
||||
*/
|
||||
@@ -1273,6 +1394,7 @@
|
||||
initThemeToggle();
|
||||
initDropdowns();
|
||||
initRowDropdowns();
|
||||
initGlobalDropdownDismiss();
|
||||
initSearch();
|
||||
initPageInteractions();
|
||||
initDataTables();
|
||||
@@ -1301,6 +1423,57 @@
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* OTS: Enhance all Xibo form modals to match the upload modal design.
|
||||
* Runs on every shown.bs.modal event and also exposed as window.otsEnhanceModal()
|
||||
* for direct invocation from form callbacks like mediaEditFormOpen.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var OTS_CLOSE_SVG = '<button type="button" class="ots-upload-close" data-dismiss="modal" aria-label="Close">' +
|
||||
'<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M15 5L5 15M5 5l10 10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>' +
|
||||
'</button>';
|
||||
|
||||
function enhanceModal(modal) {
|
||||
var $m = window.jQuery ? window.jQuery(modal) : null;
|
||||
if (!$m || !$m.length) return;
|
||||
|
||||
// Don't re-enhance
|
||||
if ($m.data('ots-enhanced')) return;
|
||||
$m.data('ots-enhanced', true);
|
||||
|
||||
// Skip the custom upload modal (it has its own styling)
|
||||
if ($m.hasClass('ots-upload-modal') || $m.attr('id') === 'ots-upload-modal') return;
|
||||
|
||||
// Add the OTS edit modal class
|
||||
$m.addClass('ots-edit-media-modal');
|
||||
|
||||
// Replace the default close button with SVG version
|
||||
var $closeBtn = $m.find('.modal-header .close, .modal-header button[data-dismiss="modal"]:not(.ots-upload-close)');
|
||||
if ($closeBtn.length) {
|
||||
$closeBtn.first().replaceWith(OTS_CLOSE_SVG);
|
||||
}
|
||||
}
|
||||
|
||||
// Expose globally so page callbacks can invoke it directly
|
||||
window.otsEnhanceModal = enhanceModal;
|
||||
|
||||
// Hook into every modal show event
|
||||
if (window.jQuery) {
|
||||
window.jQuery(document).on('shown.bs.modal', '.modal', function() {
|
||||
enhanceModal(this);
|
||||
});
|
||||
} else {
|
||||
document.addEventListener('shown.bs.modal', function(e) {
|
||||
var modal = e.target;
|
||||
if (modal && modal.classList && modal.classList.contains('modal')) {
|
||||
enhanceModal(modal);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
})();
|
||||
|
||||
// Replace broken QR images in user profile modals with a friendly placeholder
|
||||
function initUserProfileQrFix() {
|
||||
function replaceIfEmptyDataUri(el) {
|
||||
|
||||
Reference in New Issue
Block a user