|
// Base class for shared UI functionality
|
class UIHandler {
|
constructor() {
|
this.elements = {};
|
this.activeComponents = new Set();
|
this.componentStates = new Map();
|
this.observers = new Map();
|
this.handleOutsideClick = this.handleOutsideClick.bind(this);
|
this.handleEscapeKey = this.handleEscapeKey.bind(this);
|
|
}
|
|
bindElements() {
|
console.error('bindElements must be implemented by child class');
|
}
|
|
|
|
// Add shared event binding
|
bindComponentEvents() {
|
|
if (!this.handlers) return;
|
Object.entries(this.handlers).forEach(([elementKey, config]) => {
|
const elements = this.elements[elementKey];
|
if (!elements) return;
|
|
// Handle NodeList
|
if (elements instanceof NodeList || Array.isArray(elements)) {
|
elements.forEach(element => {
|
this.bindEventsToElement(element, config);
|
});
|
}
|
// Handle single element
|
else {
|
this.bindEventsToElement(elements, config);
|
}
|
});
|
}
|
|
bindEventsToElement(element, config) {
|
if (typeof config === 'function') {
|
// If config is a function, bind it to click event
|
element.addEventListener('click', config.bind(this));
|
} else if (typeof config === 'object') {
|
// Handle object with multiple events
|
Object.entries(config).forEach(([event, handler]) => {
|
if (event !== 'forEach' && typeof handler === 'function') {
|
element.addEventListener(event, handler);
|
}
|
});
|
}
|
}
|
bindEvents() {
|
document.addEventListener('click', this.handleOutsideClick);
|
document.addEventListener('keydown', this.handleEscapeKey);
|
}
|
|
// Component State Management
|
isComponentActive(componentKey) {
|
return this.activeComponents.has(componentKey);
|
}
|
|
// Add helper method for handling component state
|
setComponentState(e, t, n = {}) {
|
const {
|
element: s,
|
toggle: i,
|
activeClass: r = "open",
|
focusElement: o = null,
|
ariaLabel: c = null,
|
ariaHidden: a = null,
|
cleanup: l = null
|
} = n;
|
|
if (s) {
|
t ? this.activeComponents.add(e) : this.activeComponents.delete(e);
|
s.classList.toggle(r, t);
|
|
if (i) {
|
i.setAttribute("aria-expanded", t.toString());
|
c && i.setAttribute("aria-label", c);
|
}
|
|
if (null !== a) {
|
s.setAttribute("aria-hidden", (!t).toString());
|
}
|
|
// Add null check before calling focus()
|
if (o && typeof o.focus === 'function') {
|
o.focus();
|
}
|
|
if (!t && l) {
|
l();
|
}
|
|
this.componentStates.set(e, {
|
isActive: t,
|
activeClass: r,
|
options: n
|
});
|
}
|
}
|
|
|
// Add keyboard navigation management
|
initializeKeyboardNavigation(config) {
|
this.keyboardConfig = config;
|
|
Object.entries(config).forEach(([elementKey, keyHandlers]) => {
|
const element = this.elements[elementKey];
|
if (!element) return;
|
|
element.addEventListener('keydown', (e) => {
|
const handler = keyHandlers[e.key];
|
if (handler) {
|
handler.call(this, e);
|
}
|
});
|
});
|
}
|
|
|
handleOutsideClick(event) {
|
console.error('handleOutsideClick must be implemented by child class');
|
}
|
handleEscapeKey(event) {
|
console.error('handleEscapeKey must be implemented by child class');
|
}
|
|
// Add shared handler initialization
|
initializeHandlers(handlers) {
|
if (!handlers || typeof handlers !== 'object') {
|
console.error('Invalid handlers configuration');
|
return;
|
}
|
|
this.handlers = Object.entries(handlers).reduce((acc, [key, value]) => {
|
if (typeof value === 'function') {
|
acc[key] = value.bind(this);
|
} else if (value.forEach) {
|
acc[key] = {
|
...value,
|
handler: value.handler?.bind(this)
|
};
|
} else if (typeof value === 'object') {
|
acc[key] = Object.entries(value).reduce((events, [event, handler]) => {
|
events[event] = typeof handler === 'function' ? handler.bind(this) : handler;
|
return events;
|
}, {});
|
}
|
return acc;
|
}, {});
|
}
|
createObserver(options, callback) {
|
const defaultOptions = {
|
root: null,
|
rootMargin: '0px',
|
threshold: 0
|
};
|
|
return new IntersectionObserver(
|
callback,
|
{ ...defaultOptions, ...options }
|
);
|
}
|
|
initializeObserver(observerId, elements, options, callback) {
|
if (!elements || !elements.length) return;
|
|
// Cleanup existing observer if it exists
|
this.cleanupObserver(observerId);
|
|
// Create and store new observer
|
const observer = this.createObserver(options, callback);
|
this.observers.set(observerId, {
|
observer,
|
elements: new Set(elements)
|
});
|
|
// Start observing elements
|
elements.forEach(element => {
|
if (element) {
|
observer.observe(element);
|
}
|
});
|
|
return observer;
|
}
|
|
cleanupObserver(observerId) {
|
const observerData = this.observers.get(observerId);
|
if (observerData) {
|
const { observer, elements } = observerData;
|
elements.forEach(element => {
|
if (element) {
|
observer.unobserve(element);
|
}
|
});
|
observer.disconnect();
|
this.observers.delete(observerId);
|
}
|
}
|
|
cleanupAllObservers() {
|
this.observers.forEach((_, observerId) => {
|
this.cleanupObserver(observerId);
|
});
|
}
|
|
// Optional cleanup method for removing event listeners
|
cleanup() {
|
document.removeEventListener('click', this.handleOutsideClick);
|
document.removeEventListener('keydown', this.handleEscapeKey);
|
this.cleanupComponentEvents();
|
this.cleanupAllObservers();
|
}
|
// Add shared event cleanup
|
cleanupComponentEvents() {
|
Object.entries(this.handlers).forEach(([elementKey, config]) => {
|
const element = this.elements[elementKey];
|
if (!element) return;
|
|
if (config.forEach && element.forEach) {
|
element.forEach(item => {
|
if (item._boundHandler) {
|
item.removeEventListener('click', item._boundHandler);
|
delete item._boundHandler;
|
}
|
});
|
} else if (typeof config === 'object') {
|
Object.entries(config).forEach(([event, handler]) => {
|
if (event !== 'forEach') {
|
element.removeEventListener(event, handler);
|
}
|
});
|
}
|
});
|
}
|
|
handleSearchCheckboxes(form) {
|
if (!form) return;
|
|
const allCheckbox = form.querySelector('input[type="checkbox"][value="1"]');
|
const otherCheckboxes = form.querySelectorAll('input[type="checkbox"]:not([value="1"])');
|
|
if (!allCheckbox) return;
|
|
const updateCheckboxes = (e) => {
|
const checkbox = e.target;
|
|
if (checkbox === allCheckbox) {
|
// If 'all' is checked, uncheck others
|
if (checkbox.checked) {
|
otherCheckboxes.forEach(cb => {
|
cb.checked = false;
|
});
|
}
|
} else {
|
// If any other checkbox is checked, uncheck 'all'
|
if (checkbox.checked) {
|
allCheckbox.checked = false;
|
} else {
|
// If no other checkboxes are checked, check 'all'
|
const anyOthersChecked = Array.from(otherCheckboxes)
|
.some(cb => cb.checked);
|
if (!anyOthersChecked) {
|
allCheckbox.checked = true;
|
}
|
}
|
}
|
};
|
|
// Add event listeners to all checkboxes
|
form.querySelectorAll('input[type="checkbox"]')
|
.forEach(checkbox => {
|
checkbox.addEventListener('change', updateCheckboxes);
|
});
|
|
// Store cleanup function
|
form._removeCheckboxListeners = () => {
|
form.querySelectorAll('input[type="checkbox"]')
|
.forEach(checkbox => {
|
checkbox.removeEventListener('change', updateCheckboxes);
|
});
|
};
|
}
|
}
|
|
window.UIHandler = UIHandler;
|