feat: Improve dropdown menu handling and visibility for OTS-specific menus
This commit is contained in:
@@ -34,8 +34,8 @@
|
||||
<p class="text-muted">{% trans "Manage time-based scheduling rules." %}</p>
|
||||
</div>
|
||||
|
||||
{% embed 'custom/otssignange/views/partials/_dashboard-card.twig' with {'classes':'ots-displays-card'} %}
|
||||
{% block body %}
|
||||
<div class="widget dashboard-card ots-displays-card">
|
||||
<div class="widget-body ots-displays-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
<div class="XiboFilter card mb-3 bg-light dashboard-card ots-filter-card">
|
||||
<div class="ots-filter-header">
|
||||
@@ -46,43 +46,44 @@
|
||||
</div>
|
||||
<div class="ots-filter-content collapsed" id="ots-filter-content">
|
||||
<div class="FilterDiv card-body" id="Filter">
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{{ inline.inputNameGrid('name', title) }}
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{{ inline.inputNameGrid('name', title) }}
|
||||
|
||||
{% set title %}{% trans "Retired" %}{% endset %}
|
||||
{% set option1 = "Yes"|trans %}
|
||||
{% set option2 = "No"|trans %}
|
||||
{% set values = [{id: 1, value: option1}, {id: 0, value: option2}] %}
|
||||
{{ inline.dropdown("isRetired", "single", title, 0, values, "id", "value") }}
|
||||
</form>
|
||||
{% set title %}{% trans "Retired" %}{% endset %}
|
||||
{% set option1 = "Yes"|trans %}
|
||||
{% set option2 = "No"|trans %}
|
||||
{% set values = [{id: 1, value: option1}, {id: 0, value: option2}] %}
|
||||
{{ inline.dropdown("isRetired", "single", title, 0, values, "id", "value") }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||
<div class="ots-table-toolbar">
|
||||
{% if currentUser.featureEnabled("daypart.add") %}
|
||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Daypart" %}" href="{{ url_for("daypart.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Daypart" %}</button>
|
||||
{% endif %}
|
||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<table id="dayparts" class="table table-striped" data-state-preference-name="daypartGrid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
<th>{% trans "Start Time" %}</th>
|
||||
<th>{% trans "End Time" %}</th>
|
||||
<th class="rowMenu"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<div class="XiboData card pt-3 dashboard-card ots-table-card">
|
||||
<div class="ots-table-toolbar">
|
||||
{% if currentUser.featureEnabled("daypart.add") %}
|
||||
<button class="btn btn-sm btn-success XiboFormButton" title="{% trans "Add a new Daypart" %}" href="{{ url_for("daypart.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Daypart" %}</button>
|
||||
{% endif %}
|
||||
<button class="btn btn-sm btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<table id="dayparts" class="table table-striped" data-state-preference-name="daypartGrid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
<th>{% trans "Start Time" %}</th>
|
||||
<th>{% trans "End Time" %}</th>
|
||||
<th class="rowMenu"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -315,79 +315,63 @@
|
||||
group.classList.toggle('is-open', !isOpen);
|
||||
target.setAttribute('aria-expanded', (!isOpen).toString());
|
||||
submenu.style.display = isOpen ? 'none' : 'block';
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize dropdown menus
|
||||
*/
|
||||
function initDropdowns() {
|
||||
const dropdowns = document.querySelectorAll('.dropdown');
|
||||
|
||||
dropdowns.forEach(dropdown => {
|
||||
const button = dropdown.querySelector('.dropdown-menu');
|
||||
|
||||
if (!button) return;
|
||||
|
||||
const menu = dropdown.querySelector('.dropdown-menu');
|
||||
|
||||
// Toggle menu on button click
|
||||
dropdown.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]') || e.target.closest('#navbarUserMenu')) {
|
||||
e.preventDefault();
|
||||
const nowActive = !dropdown.classList.contains('active');
|
||||
dropdown.classList.toggle('active');
|
||||
// Only handle the user-menu dropdown.
|
||||
// Let Bootstrap handle topbar nav dropdowns (Schedule, Design, etc.) natively
|
||||
// so that links like Dayparting navigate normally.
|
||||
const userDropdown = document.querySelector('#navbarUserMenu') && document.querySelector('#navbarUserMenu').closest('.dropdown');
|
||||
if (!userDropdown) return;
|
||||
|
||||
// If the dropdown has a menu, float it out of any overflowed container
|
||||
try {
|
||||
const ddMenu = dropdown.querySelector('.dropdown-menu');
|
||||
if (ddMenu) {
|
||||
if (nowActive) {
|
||||
floatMenu(ddMenu, dropdown);
|
||||
} else {
|
||||
unfloatMenu(ddMenu);
|
||||
}
|
||||
}
|
||||
} catch (err) { /* ignore */ }
|
||||
const userMenu = userDropdown.querySelector('.dropdown-menu');
|
||||
if (!userMenu) return;
|
||||
|
||||
// If this dropdown contains the user menu, compute placement to avoid going off-screen
|
||||
const menu = dropdown.querySelector('.dropdown-menu.ots-user-menu');
|
||||
const trigger = dropdown.querySelector('#navbarUserMenu');
|
||||
if (menu && trigger) {
|
||||
// Reset any previous placement classes
|
||||
menu.classList.remove('dropdown-menu-left');
|
||||
menu.classList.remove('dropdown-menu-right');
|
||||
userDropdown.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]') || e.target.closest('#navbarUserMenu')) {
|
||||
e.preventDefault();
|
||||
const nowActive = !userDropdown.classList.contains('active');
|
||||
userDropdown.classList.toggle('active');
|
||||
|
||||
// Use getBoundingClientRect for accurate placement
|
||||
const trigRect = trigger.getBoundingClientRect();
|
||||
// Ensure menu is in DOM and has an offsetWidth
|
||||
const menuWidth = menu.offsetWidth || 220; // fallback estimate
|
||||
// Float / unfloat the user menu
|
||||
try {
|
||||
if (nowActive) {
|
||||
floatMenu(userMenu, userDropdown);
|
||||
} else {
|
||||
unfloatMenu(userMenu);
|
||||
}
|
||||
} catch (err) { /* ignore */ }
|
||||
|
||||
const spaceRight = window.innerWidth - trigRect.right;
|
||||
const spaceLeft = trigRect.left;
|
||||
|
||||
// Prefer opening to the right where possible, otherwise open to the left
|
||||
if (spaceRight < menuWidth && spaceLeft > menuWidth) {
|
||||
// not enough space on the right, open to left
|
||||
menu.classList.add('dropdown-menu-left');
|
||||
} else {
|
||||
// default to right-aligned
|
||||
menu.classList.add('dropdown-menu-right');
|
||||
}
|
||||
// Compute placement to avoid going off-screen
|
||||
const trigger = userDropdown.querySelector('#navbarUserMenu');
|
||||
if (trigger) {
|
||||
userMenu.classList.remove('dropdown-menu-left', 'dropdown-menu-right');
|
||||
const trigRect = trigger.getBoundingClientRect();
|
||||
const menuWidth = userMenu.offsetWidth || 220;
|
||||
const spaceRight = window.innerWidth - trigRect.right;
|
||||
const spaceLeft = trigRect.left;
|
||||
if (spaceRight < menuWidth && spaceLeft > menuWidth) {
|
||||
userMenu.classList.add('dropdown-menu-left');
|
||||
} else {
|
||||
userMenu.classList.add('dropdown-menu-right');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!dropdown.contains(e.target)) {
|
||||
const hasActive = dropdown.classList.contains('active');
|
||||
dropdown.classList.remove('active');
|
||||
if (hasActive) {
|
||||
try { const ddMenu = dropdown.querySelector('.dropdown-menu'); if (ddMenu) unfloatMenu(ddMenu); } catch (err) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close user menu when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!userDropdown.contains(e.target) && !userMenu.contains(e.target)) {
|
||||
const hadActive = userDropdown.classList.contains('active');
|
||||
userDropdown.classList.remove('active');
|
||||
if (hadActive) {
|
||||
try { unfloatMenu(userMenu); } catch (err) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user