Now.js Framework Documentation
EventSystemManager - DOM Event System
EventSystemManager - DOM Event System
Documentation for EventSystemManager, a high-performance DOM event management system.
📋 Table of Contents
- Overview
- Installation and Import
- Basic Usage
- Event Delegation
- Keyboard Events
- Window Events
- Performance Optimization
- Memory Management
- Priority System
- Configuration
- Usage Examples
- API Reference
- Best Practices
Overview
EventSystemManager is a high-performance DOM event management system designed with advanced features beyond standard addEventListener.
Key Features
- ✅ Event Delegation: Centralized event management
- ✅ Auto Cleanup: Automatically removes handlers when elements are removed
- ✅ Memory Management: Monitors and manages memory usage
- ✅ Priority System: Controls handler execution order
- ✅ UI Event Optimization: Uses requestAnimationFrame for UI events
- ✅ Keyboard Event Enhancement: Comprehensive keyboard event information
- ✅ Throttle/Debounce: Built-in event rate limiting
- ✅ Component Integration: Integrates with Now.js Components
- ✅ MutationObserver: Tracks DOM changes
- ✅ Performance Monitoring: Measures and tracks performance
Differences from EventManager
| Feature | EventSystemManager | EventManager |
|---|---|---|
| Purpose | DOM Events | Application Events |
| Main API | addHandler(), removeHandler() |
on(), emit(), off() |
| Event Types | DOM events (click, submit, etc.) | Custom events |
| Target | DOM elements | Application logic |
| Event Delegation | ✅ Supported | ❌ Not supported |
| Auto Cleanup | ✅ Supported | ⚠️ Manual |
| Memory Monitoring | ✅ Supported | ❌ Not supported |
| UI Optimization | ✅ requestAnimationFrame | ❌ Not supported |
When to Use EventSystemManager
✅ Use EventSystemManager when:
- Managing DOM Events (click, submit, input, etc.)
- Need Event Delegation
- Need Auto Cleanup when elements are removed
- Need Performance Optimization
- Need automatic Memory Management
- Need Priority-based event handling
❌ Don't use EventSystemManager when:
- Need Custom Application Events (use EventManager)
- Need Pattern Matching (use EventManager)
- Need Middleware (use EventManager)
- Need Event Groups (use EventManager)
Installation and Import
EventSystemManager loads with Now.js Framework and initializes automatically:
// No need to import - ready to use immediately
console.log(window.EventSystemManager); // EventSystemManager object
// Access through Now.js
const eventSystem = Now.getManager('eventsystem');Dependencies
EventSystemManager requires these dependencies:
- ErrorManager - For error handling
Initialization
EventSystemManager initializes automatically when Now.js loads:
// Auto-initialized
EventSystemManager.init();
// Or init with custom config
EventSystemManager.init({
delegation: {
enabled: true,
maxDelegationDepth: 10
},
memoryManagement: {
maxHandlersPerElement: 100,
maxCacheSize: 1000
}
});Basic Usage
1. Add Event Handler
// Basic
const element = document.querySelector('#my-button');
const handlerId = EventSystemManager.addHandler(element, 'click', (event) => {
console.log('Button clicked!', event);
});
// With options
const id = EventSystemManager.addHandler(element, 'click', (event) => {
console.log('Clicked:', event.target);
}, {
capture: false,
once: false,
passive: true,
priority: 10
});2. Remove Event Handler
// Remove by handler ID
EventSystemManager.removeHandler(handlerId);
// Remove all handlers for a component
EventSystemManager.removeComponentHandlers('my-component-id');3. Window Events
// Window events are handled automatically
EventSystemManager.addHandler(window, 'resize', (event) => {
console.log('Window resized');
});
EventSystemManager.addHandler(window, 'scroll', (event) => {
console.log('Window scrolled');
});4. Document Events
// Document events
EventSystemManager.addHandler(document, 'click', (event) => {
console.log('Document clicked');
});5. Declarative Button Actions
data-action can dispatch built-in actions without writing page-specific JavaScript.
<button
data-action="click.prevent:requestApi"
data-api-url="api/tro/customer/get"
data-api-method="get"
data-param-id="0"
data-param-context="job">
Add Customer
</button>requestApi uses httpAction internally, so the API response is passed to ResponseHandler automatically. That means the server can return actions such as notification, redirect, modal, or form without any custom button JavaScript.
Supported attributes:
| Attribute | Description |
|---|---|
data-api-url |
API endpoint to call |
data-api-method |
HTTP method (get, post, put, patch, delete) |
data-param-* |
Request parameters; data-param-user-id="15" becomes user_id=15 |
data-param-*="{field_name}" |
Resolve a value from the closest form, element dataset, URL params, or ancestor dataset; enhanced inputs use their submitted hidden value |
data-param-*="{#selector}" |
Resolve the submitted value from a specific element selector; enhanced inputs use their hidden submitted value automatically |
data-loading-class |
Optional loading class while the request is in progress |
data-response-target |
Optional selector or alias (self, trigger, form) exposed to ResponseHandler as the action target |
data-response-bind |
Optional client-side bind mode for successful raw payloads. Use template to re-run TemplateManager on data-response-target |
data-response-bind-path |
Optional dot/bracket path inside the successful payload used as the bind source before rendering the target |
data-response-loading-class |
Optional class added to data-response-target while the request is in progress |
data-request-api-on-programmatic-change="true" |
Allow change:requestApi to fire for synthetic/programmatic change events. By default these are skipped |
data-confirm |
Optional confirmation message before sending the request |
data-loading-text |
Temporary button text/value while the request is in progress |
data-notify-success="true" |
Show a success notification using the API response message |
data-required-fields |
Comma-separated form field names/ids that must be filled before sending |
data-required-message |
Error message shown when required request fields are missing |
When requestApi is bound to change, synthetic/programmatic change events are skipped by default. This prevents duplicate requests when data-load-api, setFormData(), or a property handler assigns a value during initialization. If a page intentionally relies on programmatic value updates to trigger the request, add data-request-api-on-programmatic-change="true".
Example with values pulled from the current form:
<button
type="button"
data-action="click.prevent:requestApi"
data-api-url="api/index/settings/testTelegram"
data-api-method="post"
data-param-bot-token="{telegram_bot_token}"
data-param-chat-id="{telegram_chat_id}"
data-required-fields="telegram_bot_token,telegram_chat_id"
data-loading-text="Sending..."
data-notify-success="true">
Send Test
</button>Example targeting a specific element and sending its submitted id value:
<button
type="button"
data-action="click.prevent:requestApi"
data-api-url="api/tro/customer/get"
data-api-method="get"
data-param-id="{#customer_id}">
Edit Selected Customer
</button>Reusable patterns:
- Open a modal from the server without custom page JavaScript.
<button
data-action="click.prevent:requestApi"
data-api-url="api/tro/customer/get"
data-api-method="get"
data-param-id="0"
data-param-context="job">
Add Customer
</button>The API can reply with a modal action and the button does not need any custom click handler.
When the server returns DOM-oriented actions such as update, class, attribute, focus, or scroll, the action target may use aliases like trigger, self, current, form, or target. trigger resolves to the clicked element, while target resolves to data-response-target when provided.
- Rebind raw payload data into a target without server-side
actions.
<form data-form="statistics" data-load-api="api/eleave/statistics">
<select
id="current_fiscal_year"
name="year"
data-action="change.prevent:requestApi"
data-api-url="api/eleave/statistics/render"
data-api-method="get"
data-param-year="{#current_fiscal_year}"
data-param-member_id="{member_id}"
data-response-target="#statisticsPanel"
data-response-bind="template">
</select>
<input type="hidden" id="member_id" name="member_id">
</form>
<section id="statisticsPanel">
<div data-for="item in rows">
<template>
<div class="bar" data-style="{ width: item.percent + '%', backgroundColor: item.color }"></div>
</template>
</div>
</section>In this mode the API can return raw data only. requestApi still forwards any actions to ResponseHandler, but when the response is successful it can also re-run TemplateManager on data-response-target using the full payload, or a nested branch selected by data-response-bind-path.
- Run a utility action with confirmation and loading text.
<button
data-action="click.prevent:requestApi"
data-api-url="api/index/cache/clear"
data-api-method="post"
data-confirm="Are you sure you want to clear all cache?"
data-loading-text="Clearing Cache"
data-notify-success="true">
Clear All Cache
</button>- Send selected form values without writing JavaScript.
<button
type="button"
data-action="click.prevent:requestApi"
data-api-url="api/index/settings/testTelegram"
data-api-method="post"
data-param-bot-token="{telegram_bot_token}"
data-param-chat-id="{telegram_chat_id}"
data-required-fields="telegram_bot_token,telegram_chat_id"
data-required-message="Please fill in Bot token and Chat ID"
data-loading-text="Sending..."
data-notify-success="true">
Send Test
</button>requestApi does not serialize the whole form automatically. Instead, each data-param-* entry may resolve {field_name} placeholders from the closest form first, then from dataset and URL sources. Include every required context value explicitly, such as hidden member_id fields for admin detail views. For form-wide derived payloads that should be rebound into the current form state, prefer FormManager watched API binding (data-watch-api).
Event Delegation
Event Delegation allows managing events of child elements from a parent.
Basic Delegation
// Capture clicks on child elements matching selector
const container = document.querySelector('#container');
EventSystemManager.addHandler(container, 'click', (event) => {
console.log('Button clicked:', event.delegateTarget);
}, {
selector: '.item-button' // Only triggers on .item-button within container
});Use Cases for Delegation
1. Dynamic Lists
// List with dynamically added/removed items
const list = document.querySelector('#todo-list');
// Handle clicks on delete buttons
EventSystemManager.addHandler(list, 'click', (event) => {
const item = event.delegateTarget;
const itemId = item.dataset.id;
deleteItem(itemId);
}, {
selector: '.delete-button'
});
// Handle clicks on checkboxes
EventSystemManager.addHandler(list, 'click', (event) => {
const checkbox = event.delegateTarget;
toggleItemComplete(checkbox);
}, {
selector: '.item-checkbox'
});2. Tables
// Handle clicks on table rows
const table = document.querySelector('#data-table');
EventSystemManager.addHandler(table, 'click', (event) => {
const row = event.delegateTarget;
const rowId = row.dataset.id;
showRowDetails(rowId);
}, {
selector: 'tbody tr'
});
// Handle clicks on edit buttons
EventSystemManager.addHandler(table, 'click', (event) => {
const button = event.delegateTarget;
const rowId = button.closest('tr').dataset.id;
editRow(rowId);
}, {
selector: '.edit-button'
});3. Modal/Dialog
// Handle clicks on backdrop to close modal
EventSystemManager.addHandler(document.body, 'click', (event) => {
if (event.delegateTarget.classList.contains('modal-backdrop')) {
closeModal();
}
}, {
selector: '.modal-backdrop'
});Keyboard Events
EventSystemManager has special keyboard event enhancement.
Enhanced Keyboard Events
EventSystemManager.addHandler(input, 'keydown', (event) => {
const enhanced = event.originalEvent._enhanced;
console.log('Key:', enhanced.key);
console.log('Code:', enhanced.code);
// Helper properties
if (enhanced.isEnter) {
console.log('Enter pressed');
}
if (enhanced.isShiftEnter) {
console.log('Shift+Enter pressed');
}
if (enhanced.isCtrlEnter) {
console.log('Ctrl+Enter pressed');
}
if (enhanced.isTab) {
console.log('Tab pressed');
}
if (enhanced.isShiftTab) {
console.log('Shift+Tab pressed');
}
if (enhanced.isArrowKey) {
console.log('Arrow key pressed:', enhanced.key);
}
// Modifiers
if (enhanced.ctrlKey) console.log('Ctrl is held');
if (enhanced.altKey) console.log('Alt is held');
if (enhanced.shiftKey) console.log('Shift is held');
if (enhanced.metaKey) console.log('Meta/Cmd is held');
});Keyboard Shortcuts
// Submit form with Ctrl+Enter
EventSystemManager.addHandler(textarea, 'keydown', (event) => {
const enhanced = event.originalEvent._enhanced;
if (enhanced.isCtrlEnter) {
event.preventDefault();
submitForm();
}
});
// Tab navigation with Shift+Tab
EventSystemManager.addHandler(form, 'keydown', (event) => {
const enhanced = event.originalEvent._enhanced;
if (enhanced.isShiftTab) {
// Custom tab navigation
navigateToPreviousField();
}
});
// Close on Escape
EventSystemManager.addHandler(document, 'keydown', (event) => {
if (event.key === 'Escape') {
closeAllModals();
}
});Window Events
Handle window-level events such as resize, scroll, popstate.
Supported Window Events
// Resize
EventSystemManager.addHandler(window, 'resize', (event) => {
console.log('Window resized');
handleResize();
});
// Scroll
EventSystemManager.addHandler(window, 'scroll', (event) => {
console.log('Window scrolled');
handleScroll();
});
// Popstate (history navigation)
EventSystemManager.addHandler(window, 'popstate', (event) => {
console.log('History state changed');
handlePopState(event);
});
// Hashchange
EventSystemManager.addHandler(window, 'hashchange', (event) => {
console.log('Hash changed');
handleHashChange();
});
// Load
EventSystemManager.addHandler(window, 'load', (event) => {
console.log('Window loaded');
initialize();
});
// Online/Offline
EventSystemManager.addHandler(window, 'online', (event) => {
console.log('Connection restored');
syncData();
});
EventSystemManager.addHandler(window, 'offline', (event) => {
console.log('Connection lost');
showOfflineMessage();
});Performance Optimization
EventSystemManager has several performance enhancement mechanisms.
UI Event Optimization
UI events (resize, scroll, mousemove) use requestAnimationFrame:
// Optimized automatically
EventSystemManager.addHandler(window, 'scroll', (event) => {
// Runs in requestAnimationFrame
updateScrollPosition();
});
EventSystemManager.addHandler(window, 'resize', (event) => {
// Batched in requestAnimationFrame
recalculateLayout();
});
EventSystemManager.addHandler(element, 'mousemove', (event) => {
// Queued and processed together
updateMousePosition(event);
});Throttle and Debounce
// Throttle - limit execution rate
let lastTime = 0;
EventSystemManager.addHandler(window, 'scroll', (event) => {
const now = Date.now();
if (now - lastTime < 100) return; // throttle 100ms
lastTime = now;
handleScroll();
});
// Debounce - wait until stopped then execute
let debounceTimer;
EventSystemManager.addHandler(window, 'resize', (event) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
handleResize();
}, 200); // debounce 200ms
});Event Filtering
EventSystemManager has built-in event filtering:
// Configuration
EventSystemManager.config.filtering = {
enabled: true,
maxThrottleRate: 60, // events per second
debounceWait: 100,
highFrequencyEvents: new Set([
'mousemove',
'scroll',
'resize',
'touchmove',
'pointermove'
])
};Memory Management
EventSystemManager manages memory automatically.
Auto Cleanup
// Handlers are automatically removed when element is removed from DOM
const element = document.createElement('div');
document.body.appendChild(element);
const id = EventSystemManager.addHandler(element, 'click', handler);
// Remove element - handler is automatically removed
element.remove();Manual Cleanup
// Remove specific handler
EventSystemManager.removeHandler(handlerId);
// Remove all handlers for a component
EventSystemManager.removeComponentHandlers('component-id');
// Cleanup everything
EventSystemManager.cleanup();
// Destroy
EventSystemManager.destroy();Memory Monitoring
// Check memory usage
const stats = EventSystemManager.gatherMemoryStats();
console.log('Handlers:', stats.handlerCount);
console.log('Cache size:', stats.cacheSize);
console.log('WeakMap size:', stats.weakMapSize);
// Memory stats
console.log(EventSystemManager.state.memoryStats);
// {
// handlerCount: 42,
// cacheSize: 15,
// weakMapSize: 8,
// lastGC: 1698765432100,
// peakMemoryUsage: 50,
// memoryWarnings: 0
// }Garbage Collection
// Manual GC trigger
EventSystemManager.performGC();
// Auto GC configuration
EventSystemManager.config.memoryManagement = {
checkInterval: 30000, // Check every 30 seconds
maxHandlersPerElement: 100, // Max handlers per element
maxCacheSize: 1000, // Max cache size
gcThreshold: 0.8, // Trigger GC at 80%
detailedTracking: true // Enable detailed logging
};Priority System
Control handler execution order.
Priority Values
// High priority - executes first
EventSystemManager.addHandler(element, 'click', validationHandler, {
priority: 100
});
// Medium priority
EventSystemManager.addHandler(element, 'click', transformHandler, {
priority: 50
});
// Low priority - executes last
EventSystemManager.addHandler(element, 'click', submitHandler, {
priority: 10
});
// Default priority (0) - executes last
EventSystemManager.addHandler(element, 'click', loggingHandler);Use Cases for Priority
1. Form Validation Pipeline
// Validate first
EventSystemManager.addHandler(form, 'submit', (event) => {
if (!validateForm()) {
event.preventDefault();
event.stopImmediatePropagation();
}
}, {priority: 100});
// Transform data
EventSystemManager.addHandler(form, 'submit', (event) => {
transformFormData();
}, {priority: 50});
// Submit - executes last
EventSystemManager.addHandler(form, 'submit', (event) => {
submitFormData();
}, {priority: 10});2. Click Handlers
// Check permissions first
EventSystemManager.addHandler(button, 'click', (event) => {
if (!hasPermission()) {
event.preventDefault();
event.stopPropagation();
showPermissionError();
}
}, {priority: 100});
// Actual action
EventSystemManager.addHandler(button, 'click', (event) => {
performAction();
}, {priority: 50});Configuration
Configuration Options
const config = {
cleanupInterval: 600000, // Cleanup interval (ms)
maxMemoryUsage: 52428800, // Max memory (50MB)
delegation: {
enabled: true,
matchingStrategy: 'closestFirst',
maxDelegationDepth: 10,
optimizeSelectors: true,
rootElement: document.body
},
memoryManagement: {
checkInterval: 30000,
maxHandlersPerElement: 100,
maxCacheSize: 1000,
gcThreshold: 0.8,
detailedTracking: true
},
filtering: {
enabled: true,
maxThrottleRate: 60,
debounceWait: 100,
highFrequencyEvents: null // Set or null
}
};Update Configuration
// Update individual settings
EventSystemManager.config.memoryManagement.maxHandlersPerElement = 200;
EventSystemManager.config.delegation.maxDelegationDepth = 15;
// Update filtering
EventSystemManager.config.filtering.enabled = true;
EventSystemManager.config.filtering.maxThrottleRate = 30;Usage Examples
1. Form Handling
const form = document.querySelector('#registration-form');
// Submit handler
EventSystemManager.addHandler(form, 'submit', (event) => {
event.preventDefault();
// Validate
if (!validateForm(form)) {
showErrors();
return;
}
// Submit
const formData = new FormData(form);
submitRegistration(formData);
}, {
priority: 50
});
// Real-time validation
const inputs = form.querySelectorAll('input');
inputs.forEach(input => {
EventSystemManager.addHandler(input, 'blur', (event) => {
validateField(event.target);
});
EventSystemManager.addHandler(input, 'input', (event) => {
clearFieldError(event.target);
});
});2. Dynamic List with Delegation
const todoList = document.querySelector('#todo-list');
// Add item
EventSystemManager.addHandler(todoList, 'click', (event) => {
const item = event.delegateTarget;
const itemId = item.dataset.id;
addTodoItem(itemId);
}, {
selector: '.add-button'
});
// Toggle complete
EventSystemManager.addHandler(todoList, 'click', (event) => {
const checkbox = event.delegateTarget;
const itemId = checkbox.closest('.todo-item').dataset.id;
toggleComplete(itemId, checkbox.checked);
}, {
selector: '.todo-checkbox'
});
// Delete item
EventSystemManager.addHandler(todoList, 'click', (event) => {
const button = event.delegateTarget;
const itemId = button.closest('.todo-item').dataset.id;
deleteItem(itemId);
}, {
selector: '.delete-button'
});
// Edit item
EventSystemManager.addHandler(todoList, 'click', (event) => {
const button = event.delegateTarget;
const itemId = button.closest('.todo-item').dataset.id;
editItem(itemId);
}, {
selector: '.edit-button'
});3. Keyboard Navigation
const searchBox = document.querySelector('#search-box');
const results = document.querySelector('#search-results');
EventSystemManager.addHandler(searchBox, 'keydown', (event) => {
const enhanced = event.originalEvent._enhanced;
if (enhanced.isArrowKey) {
event.preventDefault();
if (enhanced.key === 'ArrowDown') {
focusNextResult();
} else if (enhanced.key === 'ArrowUp') {
focusPreviousResult();
}
}
if (enhanced.isEnter) {
const focused = results.querySelector('.focused');
if (focused) {
selectResult(focused);
}
}
if (event.key === 'Escape') {
closeResults();
}
});4. Scroll Handling
// Infinite scroll
let isLoading = false;
EventSystemManager.addHandler(window, 'scroll', (event) => {
if (isLoading) return;
const scrollPosition = window.scrollY + window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
if (scrollPosition >= documentHeight - 200) {
isLoading = true;
loadMoreContent().then(() => {
isLoading = false;
});
}
});
// Sticky header
const header = document.querySelector('#header');
EventSystemManager.addHandler(window, 'scroll', (event) => {
if (window.scrollY > 100) {
header.classList.add('sticky');
} else {
header.classList.remove('sticky');
}
});5. Drag and Drop
const draggable = document.querySelector('.draggable');
const dropzone = document.querySelector('.dropzone');
// Drag start
EventSystemManager.addHandler(draggable, 'dragstart', (event) => {
event.originalEvent.dataTransfer.effectAllowed = 'move';
event.originalEvent.dataTransfer.setData('text/html', event.target.innerHTML);
event.target.classList.add('dragging');
});
// Drag end
EventSystemManager.addHandler(draggable, 'dragend', (event) => {
event.target.classList.remove('dragging');
});
// Drag over
EventSystemManager.addHandler(dropzone, 'dragover', (event) => {
event.preventDefault();
event.originalEvent.dataTransfer.dropEffect = 'move';
dropzone.classList.add('drag-over');
});
// Drag leave
EventSystemManager.addHandler(dropzone, 'dragleave', (event) => {
dropzone.classList.remove('drag-over');
});
// Drop
EventSystemManager.addHandler(dropzone, 'drop', (event) => {
event.preventDefault();
dropzone.classList.remove('drag-over');
const data = event.originalEvent.dataTransfer.getData('text/html');
handleDrop(data);
});6. Modal Management
// Open modal
function openModal(modalId) {
const modal = document.querySelector(`#${modalId}`);
modal.classList.add('active');
// Close on backdrop click
const backdropId = EventSystemManager.addHandler(modal, 'click', (event) => {
if (event.target === modal) {
closeModal(modalId);
EventSystemManager.removeHandler(backdropId);
}
});
// Close on Escape
const escapeId = EventSystemManager.addHandler(document, 'keydown', (event) => {
if (event.key === 'Escape') {
closeModal(modalId);
EventSystemManager.removeHandler(escapeId);
}
});
}
function closeModal(modalId) {
const modal = document.querySelector(`#${modalId}`);
modal.classList.remove('active');
}7. Context Menu
const contextMenu = document.querySelector('#context-menu');
// Show context menu
EventSystemManager.addHandler(document, 'contextmenu', (event) => {
if (!event.target.closest('.has-context-menu')) return;
event.preventDefault();
contextMenu.style.left = event.originalEvent.pageX + 'px';
contextMenu.style.top = event.originalEvent.pageY + 'px';
contextMenu.classList.add('active');
});
// Hide context menu
EventSystemManager.addHandler(document, 'click', (event) => {
if (!event.target.closest('#context-menu')) {
contextMenu.classList.remove('active');
}
});
// Context menu actions
EventSystemManager.addHandler(contextMenu, 'click', (event) => {
const action = event.delegateTarget.dataset.action;
handleContextMenuAction(action);
contextMenu.classList.remove('active');
}, {
selector: '[data-action]'
});8. Table Sorting
const table = document.querySelector('#data-table');
EventSystemManager.addHandler(table, 'click', (event) => {
const th = event.delegateTarget;
const columnIndex = Array.from(th.parentElement.children).indexOf(th);
const currentSort = th.dataset.sort || 'none';
// Toggle sort direction
let newSort;
if (currentSort === 'none') newSort = 'asc';
else if (currentSort === 'asc') newSort = 'desc';
else newSort = 'none';
// Update UI
table.querySelectorAll('th').forEach(header => {
header.dataset.sort = 'none';
});
th.dataset.sort = newSort;
// Sort table
sortTable(table, columnIndex, newSort);
}, {
selector: 'th[data-sortable]'
});9. File Upload with Drag and Drop
const dropzone = document.querySelector('#file-dropzone');
// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
EventSystemManager.addHandler(dropzone, eventName, (event) => {
event.preventDefault();
event.stopPropagation();
});
});
// Highlight dropzone
['dragenter', 'dragover'].forEach(eventName => {
EventSystemManager.addHandler(dropzone, eventName, (event) => {
dropzone.classList.add('highlight');
});
});
['dragleave', 'drop'].forEach(eventName => {
EventSystemManager.addHandler(dropzone, eventName, (event) => {
dropzone.classList.remove('highlight');
});
});
// Handle drop
EventSystemManager.addHandler(dropzone, 'drop', (event) => {
const files = event.originalEvent.dataTransfer.files;
handleFiles(files);
});
// Click to upload
EventSystemManager.addHandler(dropzone, 'click', (event) => {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.onchange = (e) => handleFiles(e.target.files);
input.click();
});10. Responsive Navigation
const nav = document.querySelector('#main-nav');
const toggle = document.querySelector('#nav-toggle');
// Toggle mobile menu
EventSystemManager.addHandler(toggle, 'click', (event) => {
nav.classList.toggle('active');
});
// Close on outside click
EventSystemManager.addHandler(document, 'click', (event) => {
if (!event.target.closest('#main-nav') &&
!event.target.closest('#nav-toggle')) {
nav.classList.remove('active');
}
});
// Close on Escape
EventSystemManager.addHandler(document, 'keydown', (event) => {
if (event.key === 'Escape') {
nav.classList.remove('active');
}
});
// Handle resize
EventSystemManager.addHandler(window, 'resize', (event) => {
if (window.innerWidth > 768) {
nav.classList.remove('active');
}
});API Reference
Configuration
{
cleanupInterval: number, // Cleanup interval (ms)
maxMemoryUsage: number, // Max memory usage (bytes)
delegation: {
enabled: boolean, // Enable delegation
matchingStrategy: string, // 'closestFirst'
maxDelegationDepth: number, // Max depth
optimizeSelectors: boolean, // Optimize selectors
rootElement: HTMLElement // Root element
},
memoryManagement: {
checkInterval: number, // Check interval (ms)
maxHandlersPerElement: number, // Max handlers per element
maxCacheSize: number, // Max cache size
gcThreshold: number, // GC threshold (0-1)
detailedTracking: boolean // Detailed tracking
},
filtering: {
enabled: boolean, // Enable filtering
maxThrottleRate: number, // Max events per second
debounceWait: number, // Debounce wait (ms)
highFrequencyEvents: Set // High frequency events
}
}Methods
Core Methods
// Initialize
init(): EventSystemManager
// Add handler
addHandler(
element: Element|Window|Document,
type: string,
handler: function,
options?: {
capture?: boolean,
once?: boolean,
passive?: boolean,
priority?: number,
componentId?: string,
selector?: string
}
): number
// Remove handler
removeHandler(id: number): boolean
// Remove component handlers
removeComponentHandlers(componentId: string): voidCleanup Methods
// Manual cleanup
cleanup(): void
// Remove element handlers
removeElementHandlers(element: Element): void
// Destroy
destroy(): voidMemory Methods
// Gather memory stats
gatherMemoryStats(): object
// Check memory usage
checkMemoryUsage(): void
// Perform GC
performGC(): voidEvent Object
{
type: string, // Event type
key: string, // Key (keyboard events)
shiftKey: boolean, // Shift key pressed
ctrlKey: boolean, // Ctrl key pressed
altKey: boolean, // Alt key pressed
metaKey: boolean, // Meta key pressed
target: Element, // Event target
currentTarget: Element, // Current target
delegateTarget: Element, // Delegate target
timestamp: number, // Timestamp
originalEvent: Event, // Original DOM event
preventDefault(): void,
stopPropagation(): void,
stopImmediatePropagation(): void,
matches(selector: string): boolean
}Handler Options
{
capture: boolean, // Capture phase
once: boolean, // Execute once
passive: boolean, // Passive listener
priority: number, // Priority (higher = first)
componentId: string, // Component ID
selector: string // Delegation selector
}Best Practices
1. ✅ Use Event Delegation
// ❌ Bad: separate handlers for each element
items.forEach(item => {
EventSystemManager.addHandler(item, 'click', handler);
});
// ✅ Good: use delegation
EventSystemManager.addHandler(container, 'click', handler, {
selector: '.item'
});2. ✅ Cleanup When Destroying Component
// ✅ Good: store handler IDs
const MyComponent = {
handlerIds: [],
init() {
const id1 = EventSystemManager.addHandler(this.element, 'click', handler);
const id2 = EventSystemManager.addHandler(this.button, 'submit', handler);
this.handlerIds.push(id1, id2);
},
destroy() {
this.handlerIds.forEach(id => {
EventSystemManager.removeHandler(id);
});
}
};3. ✅ Use Component ID
// ✅ Good: use componentId for easy cleanup
const componentId = 'my-component-123';
EventSystemManager.addHandler(element, 'click', handler, {
componentId
});
// Cleanup entire component at once
EventSystemManager.removeComponentHandlers(componentId);4. ✅ Use Priority Appropriately
// ✅ Good: validation before submit
EventSystemManager.addHandler(form, 'submit', validateHandler, {
priority: 100
});
EventSystemManager.addHandler(form, 'submit', submitHandler, {
priority: 10
});5. ✅ Use Passive for Scroll/Touch
// ✅ Good: passive for better performance
EventSystemManager.addHandler(element, 'touchstart', handler, {
passive: true
});
EventSystemManager.addHandler(window, 'scroll', handler, {
passive: true
});6. ✅ Throttle/Debounce UI Events
// ✅ Good: throttle scroll event
let lastTime = 0;
EventSystemManager.addHandler(window, 'scroll', (event) => {
const now = Date.now();
if (now - lastTime < 100) return;
lastTime = now;
handleScroll();
});7. ✅ Use Once for One-time Events
// ✅ Good: once for one-time events
EventSystemManager.addHandler(button, 'click', handler, {
once: true
});8. ✅ Manage Memory Carefully
// ✅ Good: monitor memory usage
setInterval(() => {
const stats = EventSystemManager.gatherMemoryStats();
if (stats.handlerCount > 500) {
console.warn('Too many handlers:', stats.handlerCount);
}
}, 60000);9. ✅ Use stopPropagation Carefully
// ⚠️ Careful: stopPropagation may prevent other handlers
EventSystemManager.addHandler(button, 'click', (event) => {
// Only use when necessary
event.stopPropagation();
});10. ✅ Test on Multiple Devices
// ✅ Good: support both mouse and touch
EventSystemManager.addHandler(element, 'click', handleClick);
EventSystemManager.addHandler(element, 'touchend', handleClick);Summary
When to Use EventSystemManager
| Use Case | Use EventSystemManager? | Notes |
|---|---|---|
| Manage DOM Events | ✅ Yes | click, submit, input, etc. |
| Event Delegation | ✅ Yes | Dynamic content |
| Auto Cleanup | ✅ Yes | Memory management |
| Performance Optimization | ✅ Yes | UI events |
| Keyboard Handling | ✅ Yes | Enhanced keyboard events |
| Custom Application Events | ❌ No | Use EventManager |
| Pattern Matching | ❌ No | Use EventManager |
| Middleware | ❌ No | Use EventManager |
Key Features
| Feature | Supported | Notes |
|---|---|---|
| Event Delegation | ✅ | Selector-based |
| Auto Cleanup | ✅ | MutationObserver |
| Memory Management | ✅ | Auto GC |
| Priority System | ✅ | Ordered execution |
| UI Optimization | ✅ | requestAnimationFrame |
| Keyboard Enhancement | ✅ | Helper properties |
| Throttle/Debounce | ✅ | Built-in filtering |
| Component Integration | ✅ | Component IDs |