Now.js Framework Documentation
EventSystemManager - DOM Event System
EventSystemManager - DOM Event System
เอกสารสำหรับ EventSystemManager ซึ่งเป็นระบบจัดการ DOM Events แบบ High-Performance
📋 สารบัญ
- ภาพรวม
- การติดตั้งและนำเข้า
- การใช้งานพื้นฐาน
- Event Delegation
- Keyboard Events
- Window Events
- Performance Optimization
- Memory Management
- Priority System
- Configuration
- ตัวอย่างการใช้งาน
- API Reference
- Best Practices
ภาพรวม
EventSystemManager เป็นระบบจัดการ DOM Events ที่ออกแบบมาเพื่อประสิทธิภาพสูงสุด พร้อมคุณสมบัติขั้นสูงเหนือกว่า addEventListener ปกติ
คุณสมบัติหลัก
- ✅ Event Delegation: จัดการ events แบบ centralized
- ✅ Auto Cleanup: ลบ handlers อัตโนมัติเมื่อ element ถูกลบ
- ✅ Memory Management: ตรวจสอบและจัดการหน่วยความจำ
- ✅ Priority System: กำหนดลำดับการทำงานของ handlers
- ✅ UI Event Optimization: ใช้ requestAnimationFrame สำหรับ UI events
- ✅ Keyboard Event Enhancement: ข้อมูล keyboard event ที่ครบถ้วน
- ✅ Throttle/Debounce: จำกัดอัตราการทำงานของ events
- ✅ Component Integration: เชื่อมต่อกับ Now.js Components
- ✅ MutationObserver: ติดตาม DOM changes
- ✅ Performance Monitoring: วัดและติดตามประสิทธิภาพ
ความแตกต่างจาก EventManager
| คุณสมบัติ | EventSystemManager | EventManager |
|---|---|---|
| จุดประสงค์ | DOM Events | Application Events |
| API หลัก | addHandler(), removeHandler() |
on(), emit(), off() |
| Event Types | DOM events (click, submit, etc.) | Custom events |
| Target | DOM elements | Application logic |
| Event Delegation | ✅ รองรับ | ❌ ไม่รองรับ |
| Auto Cleanup | ✅ รองรับ | ⚠️ Manual |
| Memory Monitoring | ✅ รองรับ | ❌ ไม่รองรับ |
| UI Optimization | ✅ requestAnimationFrame | ❌ ไม่รองรับ |
เมื่อใดควรใช้ EventSystemManager
✅ ใช้ EventSystemManager เมื่อ:
- ต้องการจัดการ DOM Events (click, submit, input, etc.)
- ต้องการ Event Delegation
- ต้องการ Auto Cleanup เมื่อ element ถูกลบ
- ต้องการ Performance Optimization
- ต้องการ Memory Management อัตโนมัติ
- ต้องการ Priority-based event handling
❌ อย่าใช้ EventSystemManager เมื่อ:
- ต้องการ Custom Application Events (ใช้ EventManager)
- ต้องการ Pattern Matching (ใช้ EventManager)
- ต้องการ Middleware (ใช้ EventManager)
- ต้องการ Event Groups (ใช้ EventManager)
การติดตั้งและนำเข้า
EventSystemManager โหลดพร้อมกับ Now.js Framework และเริ่มต้นอัตโนมัติ:
// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.EventSystemManager); // EventSystemManager object
// เข้าถึงผ่าน Now.js
const eventSystem = Now.getManager('eventsystem');Dependencies
EventSystemManager ต้องการ dependencies เหล่านี้:
- ErrorManager - สำหรับจัดการข้อผิดพลาด
การเริ่มต้นใช้งาน
EventSystemManager เริ่มต้นอัตโนมัติเมื่อโหลด Now.js:
// Auto-initialized
EventSystemManager.init();
// หรือ init พร้อม custom config
EventSystemManager.init({
delegation: {
enabled: true,
maxDelegationDepth: 10
},
memoryManagement: {
maxHandlersPerElement: 100,
maxCacheSize: 1000
}
});การใช้งานพื้นฐาน
1. Add Event Handler
// พื้นฐาน
const element = document.querySelector('#my-button');
const handlerId = EventSystemManager.addHandler(element, 'click', (event) => {
console.log('Button clicked!', event);
});
// พร้อม 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
// ลบโดยใช้ handler ID
EventSystemManager.removeHandler(handlerId);
// ลบทุก handlers ของ component
EventSystemManager.removeComponentHandlers('my-component-id');3. Window Events
// Window events จัดการอัตโนมัติ
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 สามารถเรียก built-in action ได้โดยไม่ต้องเขียน 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 ใช้ httpAction ภายใน ดังนั้น response จาก API จะถูกส่งเข้า ResponseHandler อัตโนมัติ ทำให้ server สามารถสั่ง notification, redirect, modal หรือ form ได้โดยไม่ต้องมี JavaScript เฉพาะปุ่มนั้น
attributes ที่รองรับ:
| Attribute | คำอธิบาย |
|---|---|
data-api-url |
endpoint ของ API ที่ต้องการเรียก |
data-api-method |
HTTP method (get, post, put, patch, delete) |
data-param-* |
request parameters เช่น data-param-user-id="15" จะกลายเป็น user_id=15 |
data-param-*="{field_name}" |
resolve ค่าจาก form ใกล้เคียง, dataset ของ element, URL params หรือ ancestor dataset โดย input แบบ enhanced จะใช้ submitted hidden value |
data-param-*="{#selector}" |
resolve ค่าที่ submit จริงจาก element ที่ระบุด้วย selector โดย input แบบ enhanced จะใช้ hidden value ให้อัตโนมัติ |
data-loading-class |
class loading ระหว่างรอ request |
data-response-target |
selector หรือ alias (self, trigger, form) ที่จะส่งให้ ResponseHandler ใช้เป็น action target |
data-response-bind |
โหมด bind ฝั่ง client สำหรับ payload ดิบที่สำเร็จ ใช้ค่า template เพื่อให้ TemplateManager bind ลง data-response-target อีกครั้ง |
data-response-bind-path |
dot/bracket path ภายใน payload สำเร็จที่ต้องการใช้เป็นแหล่งข้อมูลก่อน bind target |
data-response-loading-class |
class ที่จะถูกเติมลง data-response-target ระหว่างรอ request |
data-request-api-on-programmatic-change="true" |
อนุญาตให้ change:requestApi ทำงานกับ synthetic/programmatic change event ได้ ปกติจะถูกข้าม |
data-confirm |
ข้อความยืนยันก่อนส่ง request |
data-loading-text |
ข้อความชั่วคราวบนปุ่มหรือ input ระหว่างรอ request |
data-notify-success="true" |
แสดง success notification โดยใช้ข้อความจาก API |
data-required-fields |
รายชื่อ field name/id คั่นด้วย comma ที่ต้องกรอกก่อนส่ง request |
data-required-message |
ข้อความ error เมื่อ field ที่ต้องใช้ยังว่าง |
ถ้า requestApi ถูกผูกกับ change ระบบจะข้าม synthetic/programmatic change event โดย default เพื่อกัน request ซ้ำตอน data-load-api, setFormData() หรือ property handler กำหนดค่าในช่วง init ถ้าหน้าใดตั้งใจให้การเปลี่ยนค่าจากโค้ด trigger API ด้วย ให้เพิ่ม data-request-api-on-programmatic-change="true"
ตัวอย่างการดึงค่าจากฟอร์มปัจจุบัน:
<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>ตัวอย่างการระบุ element โดยตรงและส่งค่า id ที่ใช้ submit จริง:
<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>patterns ที่นำกลับมาใช้ซ้ำได้:
- เปิด modal จาก server โดยไม่ต้องมี 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>API สามารถตอบกลับด้วย modal action ได้เลย ปุ่มจึงไม่ต้องมี click handler เฉพาะตัวเอง
ถ้า server ตอบกลับเป็น action ฝั่ง DOM เช่น update, class, attribute, focus หรือ scroll ก็สามารถอ้าง target แบบ alias ได้ เช่น trigger, self, current, form หรือ target โดย trigger จะหมายถึง element ที่ถูกคลิก และ target จะชี้ไปยัง element จาก data-response-target ถ้ามีการกำหนดไว้
- bind payload ดิบกลับเข้า target โดยไม่ต้องให้ server ส่ง
actionsสำหรับ render
<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>ในโหมดนี้ API สามารถส่งข้อมูลดิบกลับมาอย่างเดียวได้ requestApi จะยังส่ง actions ให้ ResponseHandler ตามปกติ และถ้า response สำเร็จยังสามารถสั่ง TemplateManager ให้ bind data-response-target ใหม่จาก payload เต็ม หรือจากสาขาย่อยที่เลือกด้วย data-response-bind-path
- เรียก utility action ที่มี confirm และ 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>- ส่งค่าจากฟอร์มปัจจุบันโดยไม่ต้องเขียน 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>placeholder แบบ {field_name} จะ resolve จากฟอร์มใกล้เคียงก่อน แล้วจึง fallback ไปที่ dataset และ URL ค่าบริบทที่จำเป็นต้องคงไว้เสมอ เช่น hidden member_id สำหรับหน้าแอดมิน ควรถูกส่งแบบ explicit ผ่าน data-param-* ทุกครั้ง ส่วนกรณี derived payload ทั้งฟอร์มที่ต้อง bind กลับเข้าฟอร์มเดิม ให้พิจารณา data-watch-api ของ FormManager แทน
Event Delegation
Event Delegation ช่วยให้จัดการ events ของ child elements ได้จาก parent
Basic Delegation
// จับ click บน child elements ที่ตรงกับ selector
const container = document.querySelector('#container');
EventSystemManager.addHandler(container, 'click', (event) => {
console.log('Button clicked:', event.delegateTarget);
}, {
selector: '.item-button' // จะทำงานเฉพาะ .item-button ภายใน container
});Use Cases สำหรับ Delegation
1. Dynamic Lists
// List ที่มี items เพิ่ม-ลบได้
const list = document.querySelector('#todo-list');
// จัดการ click บน delete buttons
EventSystemManager.addHandler(list, 'click', (event) => {
const item = event.delegateTarget;
const itemId = item.dataset.id;
deleteItem(itemId);
}, {
selector: '.delete-button'
});
// จัดการ click บน checkbox
EventSystemManager.addHandler(list, 'click', (event) => {
const checkbox = event.delegateTarget;
toggleItemComplete(checkbox);
}, {
selector: '.item-checkbox'
});2. Tables
// จัดการ click บน 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'
});
// จัดการ click บน 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
// จัดการ click บน backdrop เพื่อปิด modal
EventSystemManager.addHandler(document.body, 'click', (event) => {
if (event.delegateTarget.classList.contains('modal-backdrop')) {
closeModal();
}
}, {
selector: '.modal-backdrop'
});Keyboard Events
EventSystemManager มี 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
จัดการ window-level events เช่น 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 มีกลไกเพิ่มประสิทธิภาพหลายอย่าง
UI Event Optimization
UI events (resize, scroll, mousemove) ใช้ requestAnimationFrame:
// Optimized automatically
EventSystemManager.addHandler(window, 'scroll', (event) => {
// ทำงานใน requestAnimationFrame
updateScrollPosition();
});
EventSystemManager.addHandler(window, 'resize', (event) => {
// Batched ใน requestAnimationFrame
recalculateLayout();
});
EventSystemManager.addHandler(element, 'mousemove', (event) => {
// Queued และประมวลผลรวมกัน
updateMousePosition(event);
});Throttle และ Debounce
// Throttle - จำกัดอัตราการทำงาน
let lastTime = 0;
EventSystemManager.addHandler(window, 'scroll', (event) => {
const now = Date.now();
if (now - lastTime < 100) return; // throttle 100ms
lastTime = now;
handleScroll();
});
// Debounce - รอให้หยุดแล้วค่อยทำงาน
let debounceTimer;
EventSystemManager.addHandler(window, 'resize', (event) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
handleResize();
}, 200); // debounce 200ms
});Event Filtering
EventSystemManager มี 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 จัดการหน่วยความจำอัตโนมัติ
Auto Cleanup
// Handlers ถูกลบอัตโนมัติเมื่อ element ถูกลบจาก DOM
const element = document.createElement('div');
document.body.appendChild(element);
const id = EventSystemManager.addHandler(element, 'click', handler);
// ลบ element - handler ถูกลบอัตโนมัติ
element.remove();Manual Cleanup
// ลบ handler เฉพาะ
EventSystemManager.removeHandler(handlerId);
// ลบทุก handlers ของ component
EventSystemManager.removeComponentHandlers('component-id');
// Cleanup ทั้งหมด
EventSystemManager.cleanup();
// Destroy
EventSystemManager.destroy();Memory Monitoring
// ตรวจสอบ 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, // ตรวจสอบทุก 30 วินาที
maxHandlersPerElement: 100, // handlers สูงสุดต่อ element
maxCacheSize: 1000, // cache size สูงสุด
gcThreshold: 0.8, // trigger GC ที่ 80%
detailedTracking: true // เปิด detailed logging
};Priority System
กำหนดลำดับการทำงานของ handlers
Priority Values
// Priority สูง - ทำก่อน
EventSystemManager.addHandler(element, 'click', validationHandler, {
priority: 100
});
// Priority กลาง
EventSystemManager.addHandler(element, 'click', transformHandler, {
priority: 50
});
// Priority ต่ำ - ทำหลัง
EventSystemManager.addHandler(element, 'click', submitHandler, {
priority: 10
});
// Default priority (0) - ทำหลังสุด
EventSystemManager.addHandler(element, 'click', loggingHandler);Use Cases สำหรับ Priority
1. Form Validation Pipeline
// Validate ก่อน
EventSystemManager.addHandler(form, 'submit', (event) => {
if (!validateForm()) {
event.preventDefault();
event.stopImmediatePropagation();
}
}, {priority: 100});
// Transform data
EventSystemManager.addHandler(form, 'submit', (event) => {
transformFormData();
}, {priority: 50});
// Submit - ทำหลังสุด
EventSystemManager.addHandler(form, 'submit', (event) => {
submitFormData();
}, {priority: 10});2. Click Handlers
// Check permissions ก่อน
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 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 หรือ 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;ตัวอย่างการใช้งาน
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 อ้างอิง
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. ✅ ใช้ Event Delegation
// ❌ Bad: handler แยกต่าง element
items.forEach(item => {
EventSystemManager.addHandler(item, 'click', handler);
});
// ✅ Good: ใช้ delegation
EventSystemManager.addHandler(container, 'click', handler, {
selector: '.item'
});2. ✅ Cleanup เมื่อ Destroy Component
// ✅ Good: เก็บ 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. ✅ ใช้ Component ID
// ✅ Good: ใช้ componentId สำหรับ cleanup ง่าย
const componentId = 'my-component-123';
EventSystemManager.addHandler(element, 'click', handler, {
componentId
});
// Cleanup ทั้ง component พร้อมกัน
EventSystemManager.removeComponentHandlers(componentId);4. ✅ ใช้ Priority อย่างเหมาะสม
// ✅ Good: validation ก่อน submit
EventSystemManager.addHandler(form, 'submit', validateHandler, {
priority: 100
});
EventSystemManager.addHandler(form, 'submit', submitHandler, {
priority: 10
});5. ✅ ใช้ Passive สำหรับ Scroll/Touch
// ✅ Good: passive สำหรับ 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. ✅ ใช้ Once สำหรับ One-time Events
// ✅ Good: once สำหรับ event ที่ทำครั้งเดียว
EventSystemManager.addHandler(button, 'click', handler, {
once: true
});8. ✅ จัดการ Memory อย่างระมัดระวัง
// ✅ Good: ตรวจสอบ memory usage
setInterval(() => {
const stats = EventSystemManager.gatherMemoryStats();
if (stats.handlerCount > 500) {
console.warn('Too many handlers:', stats.handlerCount);
}
}, 60000);9. ✅ ใช้ stopPropagation อย่างระวัง
// ⚠️ Careful: stopPropagation อาจป้องกัน handlers อื่น
EventSystemManager.addHandler(button, 'click', (event) => {
// ใช้เฉพาะเมื่อจำเป็น
event.stopPropagation();
});10. ✅ Test บน Multiple Devices
// ✅ Good: รองรับทั้ง mouse และ touch
EventSystemManager.addHandler(element, 'click', handleClick);
EventSystemManager.addHandler(element, 'touchend', handleClick);สรุป
เมื่อใดควรใช้ EventSystemManager
| Use Case | ใช้ EventSystemManager? | หมายเหตุ |
|---|---|---|
| จัดการ DOM Events | ✅ ใช่ | click, submit, input, etc. |
| Event Delegation | ✅ ใช่ | Dynamic content |
| Auto Cleanup | ✅ ใช่ | Memory management |
| Performance Optimization | ✅ ใช่ | UI events |
| Keyboard Handling | ✅ ใช่ | Enhanced keyboard events |
| Custom Application Events | ❌ ไม่ | ใช้ EventManager |
| Pattern Matching | ❌ ไม่ | ใช้ EventManager |
| Middleware | ❌ ไม่ | ใช้ EventManager |
คุณสมบัติหลัก
| คุณสมบัติ | รองรับ | หมายเหตุ |
|---|---|---|
| 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 |