Files
OTSSignsTheme/custom/otssignange/js/theme.js
Matt Batchelder 2153d3c725 semi functional
2026-02-04 10:00:40 -05:00

271 lines
7.7 KiB
JavaScript

/**
* OTS Signage Modern Theme - Client-Side Utilities
* Sidebar toggle, dropdown menus, and UI interactions
*/
(function() {
'use strict';
const STORAGE_KEYS = {
sidebarCollapsed: 'otsTheme:sidebarCollapsed'
};
/**
* Initialize sidebar toggle functionality
*/
function initSidebarToggle() {
const toggleBtn = document.querySelector('[data-action="toggle-sidebar"]');
const sidebar = document.querySelector('.ots-sidebar');
if (!toggleBtn || !sidebar) return;
toggleBtn.addEventListener('click', function(e) {
e.preventDefault();
sidebar.classList.toggle('active');
});
// Close sidebar when clicking outside on mobile
document.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
const isClickInsideSidebar = sidebar.contains(e.target);
const isClickOnToggle = toggleBtn.contains(e.target);
if (!isClickInsideSidebar && !isClickOnToggle && sidebar.classList.contains('active')) {
sidebar.classList.remove('active');
}
}
});
}
/**
* 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.preventDefault();
dropdown.classList.toggle('active');
}
});
// Close menu when clicking outside
document.addEventListener('click', function(e) {
if (!dropdown.contains(e.target)) {
dropdown.classList.remove('active');
}
});
});
}
/**
* Initialize search functionality
*/
function initSearch() {
const searchForm = document.querySelector('.topbar-search');
if (!searchForm) return;
const input = searchForm.querySelector('.search-input');
if (input) {
input.addEventListener('focus', function() {
searchForm.style.borderColor = 'var(--color-primary)';
});
input.addEventListener('blur', function() {
searchForm.style.borderColor = 'var(--color-border)';
});
}
}
/**
* Initialize page specific interactions
*/
function initPageInteractions() {
// Displays page - folder selection
const folderItems = document.querySelectorAll('.folder-item');
folderItems.forEach(item => {
item.addEventListener('click', function() {
folderItems.forEach(f => f.classList.remove('active'));
this.classList.add('active');
});
});
// Media page - item selection
const mediaItems = document.querySelectorAll('.media-item');
mediaItems.forEach(item => {
item.addEventListener('click', function() {
this.style.opacity = '0.7';
setTimeout(() => this.style.opacity = '1', 200);
});
});
}
/**
* Make sidebar responsive
*/
function makeResponsive() {
const sidebar = document.querySelector('.ots-sidebar');
const main = document.querySelector('.ots-main');
if (!sidebar) return;
// Add toggle button for mobile
if (window.innerWidth <= 768) {
sidebar.classList.add('mobile');
}
window.addEventListener('resize', function() {
if (window.innerWidth > 768) {
sidebar.classList.remove('mobile', 'active');
} else {
sidebar.classList.add('mobile');
}
});
}
/**
* Prevent Chart.js errors when chart elements are missing
*/
function initChartSafeguard() {
if (!window.Chart) return;
if (typeof window.Chart.acquireContext === 'function') {
window.Chart.acquireContext = function(item) {
if (!item) return null;
const candidate = item.length ? item[0] : item;
if (candidate && typeof candidate.getContext === 'function') {
return candidate.getContext('2d');
}
return null;
};
return;
}
if (window.Chart.prototype && typeof window.Chart.prototype.acquireContext === 'function') {
window.Chart.prototype.acquireContext = function(item) {
if (!item) return null;
const candidate = item.length ? item[0] : item;
if (candidate && typeof candidate.getContext === 'function') {
return candidate.getContext('2d');
}
return null;
};
}
}
/**
* Enhance tables: wrap in card, add per-table search box, client-side filtering
* Non-destructive: skips tables already enhanced
*/
function enhanceTables() {
const selector = '.ots-content table, .content table, .container table, .card table, table';
const tables = Array.from(document.querySelectorAll(selector));
let counter = 0;
tables.forEach(table => {
// only enhance tables that have a thead and tbody
if (!table || table.classList.contains('modern-table')) return;
if (!table.querySelector('thead') || !table.querySelector('tbody')) return;
counter += 1;
table.classList.add('modern-table');
// Build wrapper structure
const wrapper = document.createElement('div');
wrapper.className = 'modern-table-card';
const controls = document.createElement('div');
controls.className = 'table-controls';
const input = document.createElement('input');
input.type = 'search';
input.placeholder = 'Search…';
input.className = 'table-search-input';
input.setAttribute('aria-label', 'Table search');
input.style.minWidth = '180px';
controls.appendChild(input);
const tableWrapper = document.createElement('div');
tableWrapper.className = 'table-wrapper';
tableWrapper.style.overflow = 'auto';
// Insert wrapper into DOM in place of the table
const parent = table.parentNode;
parent.replaceChild(wrapper, table);
wrapper.appendChild(controls);
wrapper.appendChild(tableWrapper);
tableWrapper.appendChild(table);
// Simple, light-weight search filtering for this table only
input.addEventListener('input', function (e) {
const term = (e.target.value || '').toLowerCase();
table.querySelectorAll('tbody tr').forEach(tr => {
const text = tr.textContent.toLowerCase();
tr.style.display = term === '' || text.includes(term) ? '' : 'none';
});
});
});
}
/**
* Initialize DataTables for enhanced behavior when available.
* Falls back gracefully if DataTables or jQuery are not present.
*/
function initDataTables() {
if (!window.jQuery) return;
const $ = window.jQuery;
if (!$.fn || !$.fn.dataTable) return;
$('.modern-table, table').each(function () {
try {
if (!$.fn.dataTable.isDataTable(this)) {
$(this).DataTable({
responsive: true,
lengthChange: false,
pageLength: 10,
autoWidth: false,
dom: '<"table-controls"f>rt<"table-meta"ip>',
language: { search: '' }
});
}
} catch (err) {
// If initialization fails, ignore and allow fallback enhancer
console.warn('DataTables init failed for table', this, err);
}
});
}
/**
* Initialize all features when DOM is ready
*/
function init() {
initSidebarToggle();
initDropdowns();
initSearch();
initPageInteractions();
initDataTables();
enhanceTables();
makeResponsive();
initChartSafeguard();
}
// Wait for DOM to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();