Now.js Framework Documentation

Now.js Framework Documentation

EventManager - Custom Event System

TH 31 Oct 2025 01:08

EventManager - Custom Event System

เอกสารสำหรับ EventManager ซึ่งเป็น Custom Event System สำหรับการจัดการ Events ในระดับ Application

📋 สารบัญ

  1. ภาพรวม
  2. การติดตั้งและนำเข้า
  3. การใช้งานพื้นฐาน
  4. API หลัก
  5. Patterns และ Wildcards
  6. Event Groups
  7. Priority System
  8. Middleware
  9. Async Events
  10. การจัดการ Context
  11. Cleanup และ Memory Management
  12. Integration กับ Now.js
  13. ตัวอย่างการใช้งาน
  14. API Reference
  15. Best Practices

ภาพรวม

EventManager เป็น Custom Event System ที่ออกแบบมาสำหรับจัดการ Events ในระดับ Application (ไม่ใช่ DOM Events) มีความสามารถขั้นสูงเหนือกว่า EventEmitter แบบปกติ

คุณสมบัติหลัก

  • Event Bus Pattern: ระบบ Publish/Subscribe แบบกลาง
  • Pattern Matching: รองรับ Wildcards และ Regular Expression
  • Event Hierarchy: รองรับ Namespaced Events (dot notation)
  • Event Groups: จัดกลุ่ม Events เพื่อจัดการร่วมกัน
  • Priority System: กำหนดลำดับการทำงานของ Listeners
  • Middleware: Intercept และแก้ไข Events ก่อนส่งต่อ
  • Async Support: รองรับ Async/Await และ Promises
  • Context Control: ควบคุม Event Propagation
  • Auto Cleanup: จัดการหน่วยความจำอัตโนมัติ
  • Debug Mode: ติดตาม Event History และข้อมูล Debug

ความแตกต่างจาก EventSystemManager

คุณสมบัติ EventManager EventSystemManager
จุดประสงค์ Application Events DOM Events
API หลัก on(), emit(), off() addEventListener()
Event Types Custom events DOM events (click, submit, etc.)
Target Application logic DOM elements
Patterns ✅ รองรับ wildcards ❌ ไม่รองรับ
Middleware ✅ รองรับ ❌ ไม่รองรับ
Groups ✅ รองรับ ❌ ไม่รองรับ
Priority ✅ รองรับ ✅ รองรับ

เมื่อใดควรใช้ EventManager

ใช้ EventManager เมื่อ:

  • ต้องการ Event Bus สำหรับ Application
  • ต้องการสื่อสารระหว่าง Components
  • ต้องการ Decouple ระหว่าง Modules
  • ต้องการ Pattern Matching (wildcards)
  • ต้องการ Event Groups
  • ต้องการ Middleware

อย่าใช้ EventManager เมื่อ:

  • ต้องการจัดการ DOM Events (ใช้ EventSystemManager)
  • ต้องการ Event Delegation
  • ต้องการ Native DOM Event Features

การติดตั้งและนำเข้า

EventManager โหลดพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:

// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.EventManager); // EventManager object

// หรือเข้าถึงผ่าน Now.js
const eventManager = Now.getManager('event');

Dependencies

EventManager ต้องการ dependencies เหล่านี้:

  • ErrorManager - สำหรับจัดการข้อผิดพลาด
  • Utils - สำหรับ UUID generation (optional)

การเริ่มต้นใช้งาน

// เริ่มต้นด้วย default config
await EventManager.init();

// หรือเริ่มต้นพร้อม custom config
await EventManager.init({
  asyncTimeout: 10000,
  maxListeners: 1000,
  traceEvents: true,
  cleanup: {
    enabled: true,
    interval: 60000,
    maxAge: 1800000
  }
});

การใช้งานพื้นฐาน

1. ลงทะเบียน Listener (on)

// พื้นฐาน
EventManager.on('user:login', (context) => {
  console.log('User logged in:', context.data);
});

// พร้อม options
EventManager.on('user:login', (context) => {
  console.log('User logged in:', context.data.userId);
}, {
  priority: 10,
  once: false
});

2. ส่ง Event (emit)

// ส่ง event พร้อม data
await EventManager.emit('user:login', {
  userId: 123,
  username: 'john_doe',
  timestamp: Date.now()
});

// รับค่า results จาก listeners
const results = await EventManager.emit('calculate:sum', {
  numbers: [1, 2, 3, 4, 5]
});
console.log('Results:', results); // [15]

3. ยกเลิก Listener (off)

// ยกเลิกทั้งหมดของ event
EventManager.off('user:login');

// ยกเลิกเฉพาะ listener ที่ระบุ
const unsubscribe = EventManager.on('user:login', handler);
unsubscribe(); // ยกเลิก listener นี้

// หรือใช้ listener ID
EventManager.off('user:login', listenerId);

4. Listen ครั้งเดียว (once)

// ทำงานครั้งเดียวแล้วถูกลบ
EventManager.once('app:initialized', (context) => {
  console.log('App initialized!');
});

// หรือใช้ option once
EventManager.on('popup:shown', handler, {once: true});

API หลัก

on(event, callback, options?)

ลงทะเบียน listener สำหรับ event

const unsubscribe = EventManager.on(eventName, callback, options);

Parameters:

  • event (string) - ชื่อ event หรือ pattern
  • callback (function) - ฟังก์ชันที่จะถูกเรียก รับ context object
  • options (object) - optional
    • priority (number) - ลำดับความสำคัญ (เลขมากทำก่อน) default: 0
    • once (boolean) - ทำงานครั้งเดียว default: false
    • async (boolean) - รองรับ async handler default: false
    • group (string) - จัดอยู่ใน group
    • timeout (number) - async timeout (ms) default: 5000
    • maxRetries (number) - จำนวนครั้งที่ retry เมื่อเกิดข้อผิดพลาด default: 1
    • onError (function) - error handler

Returns: Function สำหรับยกเลิก listener

ตัวอย่าง:

// พื้นฐาน
EventManager.on('message:received', (context) => {
  console.log('Message:', context.data.text);
});

// พร้อม priority
EventManager.on('data:processing', handleData, {
  priority: 100 // ทำก่อน listeners อื่น
});

// พร้อม group
EventManager.on('notification:*', handleNotification, {
  group: 'notifications'
});

// async handler
EventManager.on('user:register', async (context) => {
  await sendWelcomeEmail(context.data.email);
  return {success: true};
}, {
  async: true,
  timeout: 10000
});

emit(event, data?)

ส่ง event พร้อม data

const results = await EventManager.emit(eventName, data);

Parameters:

  • event (string) - ชื่อ event
  • data (object) - ข้อมูลที่ต้องการส่ง optional

Returns: Promise - array ของ results จาก listeners

ตัวอย่าง:

// ส่ง event
await EventManager.emit('cart:item-added', {
  productId: 123,
  quantity: 2,
  price: 99.99
});

// รับ results
const results = await EventManager.emit('validate:form', {
  formData: {...}
});

const isValid = results.every(r => r === true);

// ส่งโดยไม่มี data
await EventManager.emit('app:ready');

off(event, identifier?)

ยกเลิก listeners

const removed = EventManager.off(eventName, identifier);

Parameters:

  • event (string) - ชื่อ event หรือ pattern
  • identifier (string|function) - optional
    • ถ้าไม่ระบุ: ยกเลิกทั้งหมด
    • string: ยกเลิกตาม ID
    • function: ยกเลิกตาม callback reference

Returns: boolean - true ถ้ายกเลิกสำเร็จ

ตัวอย่าง:

// ยกเลิกทั้งหมด
EventManager.off('user:login');

// ยกเลิกเฉพาะ listener
const handler = (ctx) => console.log(ctx.data);
EventManager.on('event', handler);
EventManager.off('event', handler);

// ใช้ unsubscribe function
const unsubscribe = EventManager.on('event', handler);
unsubscribe();

once(event, callback, options?)

ลงทะเบียน listener ที่ทำงานครั้งเดียว

const unsubscribe = EventManager.once(eventName, callback, options);

เหมือนกับ on() แต่ตั้ง once: true อัตโนมัติ

ตัวอย่าง:

// ทำงานครั้งเดียว
EventManager.once('app:loaded', (context) => {
  console.log('App loaded at:', context.timestamp);
});

// พร้อม priority
EventManager.once('init:complete', handler, {
  priority: 100
});

Patterns และ Wildcards

EventManager รองรับ pattern matching สำหรับ event names

Wildcard Operators

Operator ความหมาย ตัวอย่าง ตรงกับ
* ตรงกับอักขระใดๆ จำนวนเท่าไหร่ก็ได้ user:* user:login, user:logout, user:register
? ตรงกับอักขระเดี่ยว 1 ตัว user:log?n user:login
+ ตรงกับอักขระก่อนหน้า 1 ครั้งขึ้นไป (ใช้กับ regex) -
| OR operator (ใช้กับ regex) -

ตัวอย่าง Patterns

// ตรงกับ events ทั้งหมดที่ขึ้นต้นด้วย user:
EventManager.on('user:*', (context) => {
  console.log('User event:', context.event);
});

// ตรงกับ notification ทุกประเภท
EventManager.on('notification:*', (context) => {
  showNotification(context.data.message);
});

// ตรงกับ events หลายระดับ
EventManager.on('api:*:success', (context) => {
  // ตรงกับ api:user:success, api:product:success, etc.
  console.log('API success:', context.event);
});

// ใช้ single character wildcard
EventManager.on('page:?', (context) => {
  // ตรงกับ page:1, page:2, page:a, page:b
});

Custom Regex Patterns

// ใช้ regex pattern โดยตรง
EventManager.on(/^user:(login|logout|register)$/, (context) => {
  console.log('Auth event:', context.event);
});

// Pattern ซับซ้อน
EventManager.on(/^api:\w+:(get|post|put|delete)$/, (context) => {
  logApiCall(context);
});

Pattern Priority

เมื่อ event ตรงกับหลาย patterns:

  1. Exact match ทำก่อน
  2. Pattern matches ทำตาม priority
  3. Listeners ภายใน pattern เดียวกันทำตาม priority
// Exact match - priority สูงสุด
EventManager.on('user:login', handler1, {priority: 10});

// Pattern match - ทำหลัง exact match
EventManager.on('user:*', handler2, {priority: 100}); // ถึงแม้ priority สูงกว่า

// เมื่อ emit('user:login')
// จะทำ: handler1 -> handler2

Event Groups

จัดกลุ่ม events เพื่อจัดการร่วมกัน

สร้าง Group

// เพิ่ม listeners เข้า group
EventManager.on('notification:info', handler1, {group: 'notifications'});
EventManager.on('notification:warning', handler2, {group: 'notifications'});
EventManager.on('notification:error', handler3, {group: 'notifications'});

Emit Group

ส่ง events ทั้งหมดใน group

// ส่งไปยังทุก event ใน group
const results = await EventManager.emitGroup('notifications', {
  message: 'System update completed',
  level: 'info'
});

Use Cases สำหรับ Groups

1. Notification System

// จัดกลุ่ม notification handlers
EventManager.on('notification:info', showInfoNotification, {
  group: 'notifications'
});

EventManager.on('notification:success', showSuccessNotification, {
  group: 'notifications'
});

EventManager.on('notification:error', showErrorNotification, {
  group: 'notifications'
});

// ส่งไปยังทั้ง group
await EventManager.emitGroup('notifications', {
  message: 'Operation completed'
});

2. Analytics Tracking

// จัดกลุ่ม analytics events
EventManager.on('analytics:pageview', trackPageView, {
  group: 'analytics'
});

EventManager.on('analytics:click', trackClick, {
  group: 'analytics'
});

EventManager.on('analytics:conversion', trackConversion, {
  group: 'analytics'
});

// Track all analytics
await EventManager.emitGroup('analytics', {
  session: sessionId,
  timestamp: Date.now()
});

3. Feature Toggles

// จัดกลุ่ม feature events
EventManager.on('feature:enabled', enableFeature, {
  group: 'features'
});

EventManager.on('feature:disabled', disableFeature, {
  group: 'features'
});

// ส่งไปยัง features ทั้งหมด
await EventManager.emitGroup('features', {
  feature: 'dark-mode',
  enabled: true
});

Priority System

กำหนดลำดับการทำงานของ listeners

Priority Values

  • เลขมาก = priority สูง (ทำก่อน)
  • เลขน้อย = priority ต่ำ (ทำหลัง)
  • Default = 0
// Priority สูง - ทำก่อน
EventManager.on('data:process', validateData, {
  priority: 100
});

// Priority กลาง
EventManager.on('data:process', transformData, {
  priority: 50
});

// Priority ต่ำ - ทำหลัง
EventManager.on('data:process', saveData, {
  priority: 10
});

// Default priority (0) - ทำหลังสุด
EventManager.on('data:process', logData);

// เมื่อ emit('data:process')
// จะทำตามลำดับ: validateData -> transformData -> saveData -> logData

Use Cases สำหรับ Priority

1. Data Processing Pipeline

// Validation ต้องทำก่อน
EventManager.on('form:submit', validateForm, {
  priority: 100
});

// Transformation ทำหลัง validation
EventManager.on('form:submit', transformData, {
  priority: 80
});

// Sanitization
EventManager.on('form:submit', sanitizeData, {
  priority: 60
});

// บันทึกข้อมูล - ทำหลังสุด
EventManager.on('form:submit', saveToDatabase, {
  priority: 10
});

2. Authentication Flow

// ตรวจสอบ token ก่อน
EventManager.on('request:*', verifyToken, {
  priority: 1000
});

// ตรวจสอบ permissions
EventManager.on('request:*', checkPermissions, {
  priority: 900
});

// Rate limiting
EventManager.on('request:*', checkRateLimit, {
  priority: 800
});

// ทำงานจริง - priority ต่ำกว่า
EventManager.on('request:create', createResource, {
  priority: 100
});

3. Error Handling

// Log error ก่อน
EventManager.on('error:*', logError, {
  priority: 100
});

// แจ้งเตือน admin
EventManager.on('error:critical', notifyAdmin, {
  priority: 90
});

// แสดง error ให้ user
EventManager.on('error:*', showErrorMessage, {
  priority: 50
});

Middleware

Intercept และแก้ไข events ก่อนส่งต่อไปยง listeners

การสร้าง Middleware

// Middleware แบบ function
EventManager.use((context) => {
  console.log('Event:', context.event);
  console.log('Data:', context.data);

  // return false เพื่อหยุด event
  if (context.data.blocked) {
    return false;
  }

  // return true หรือ undefined เพื่อส่งต่อ
  return true;
});

// Middleware แบบ object พร้อม hooks
EventManager.use({
  beforeEmit: (context) => {
    // ทำงานก่อน emit
    console.log('Before emit:', context.event);

    // แก้ไข data
    context.data.timestamp = Date.now();

    return true; // ส่งต่อ
  },

  afterEmit: (context) => {
    // ทำงานหลัง emit
    console.log('After emit:', context.event);
    console.log('Results:', context.results);
  }
});

Middleware Hooks

Hook เมื่อใด Use Case
beforeEmit ก่อนส่ง event Validation, logging, data transformation
afterEmit หลังส่ง event เสร็จ Logging results, cleanup, notifications

ตัวอย่าง Middleware

1. Logging Middleware

EventManager.use({
  beforeEmit: (context) => {
    console.log(`[Event] ${context.event}`, {
      timestamp: context.timestamp,
      data: context.data
    });
  },

  afterEmit: (context) => {
    console.log(`[Event Complete] ${context.event}`, {
      duration: Date.now() - context.timestamp,
      results: context.results.length
    });
  }
});

2. Authentication Middleware

EventManager.use((context) => {
  // ตรวจสอบ authentication สำหรับ events ที่ต้อง auth
  if (context.event.startsWith('api:')) {
    if (!isAuthenticated()) {
      console.error('Unauthorized event:', context.event);
      return false; // block event
    }
  }

  return true;
});

3. Data Validation Middleware

EventManager.use({
  beforeEmit: (context) => {
    // Validate data structure
    if (context.data && typeof context.data !== 'object') {
      console.error('Invalid data type for event:', context.event);
      return false;
    }

    // Add default values
    context.data.timestamp = context.data.timestamp || Date.now();
    context.data.source = context.data.source || 'app';

    return true;
  }
});

4. Rate Limiting Middleware

const eventCounts = new Map();

EventManager.use((context) => {
  const key = `${context.event}:${Date.now() / 1000 | 0}`;
  const count = eventCounts.get(key) || 0;

  if (count >= 100) {
    console.warn('Rate limit exceeded for:', context.event);
    return false;
  }

  eventCounts.set(key, count + 1);
  return true;
});

5. Error Tracking Middleware

EventManager.use({
  afterEmit: (context) => {
    // Track errors in results
    const errors = context.results.filter(r => r instanceof Error);

    if (errors.length > 0) {
      console.error('Errors in event:', context.event, errors);

      // Send to error tracking service
      ErrorManager.handle(new Error('Event execution errors'), {
        context: context.event,
        data: {errors}
      });
    }
  }
});

Async Events

รองรับ asynchronous event handlers และ promises

Async Handlers

// Async handler
EventManager.on('user:register', async (context) => {
  const {email, password} = context.data;

  // Async operations
  await createUserAccount(email, password);
  await sendWelcomeEmail(email);
  await logActivity('user:registered', email);

  return {success: true, userId: 123};
}, {
  async: true,
  timeout: 10000 // 10 seconds timeout
});

// Emit และรอ results
const results = await EventManager.emit('user:register', {
  email: 'user@example.com',
  password: 'securepass'
});

console.log('Registration results:', results);

Promise Handlers

EventManager.on('data:fetch', (context) => {
  // Return promise
  return fetch(context.data.url)
    .then(res => res.json())
    .then(data => {
      console.log('Data fetched:', data);
      return data;
    });
}, {
  async: true
});

// Emit และรอ
const results = await EventManager.emit('data:fetch', {
  url: '/api/users'
});

Timeout Configuration

// Global timeout (default: 5000ms)
await EventManager.init({
  asyncTimeout: 10000
});

// Per-listener timeout
EventManager.on('heavy:operation', heavyHandler, {
  async: true,
  timeout: 30000 // 30 seconds
});

Error Handling ใน Async

EventManager.on('risky:operation', async (context) => {
  try {
    const result = await riskyAsyncOperation(context.data);
    return result;
  } catch (error) {
    console.error('Operation failed:', error);
    return {error: error.message};
  }
}, {
  async: true,
  onError: (error) => {
    // Custom error handler
    console.error('Async handler error:', error);
    notifyAdmin(error);
  }
});

Parallel Async Execution

// หลาย async handlers ทำงานแบบ parallel
EventManager.on('data:sync', async (context) => {
  await syncToDatabase(context.data);
  return 'db-synced';
}, {async: true});

EventManager.on('data:sync', async (context) => {
  await syncToCache(context.data);
  return 'cache-synced';
}, {async: true});

EventManager.on('data:sync', async (context) => {
  await syncToSearch(context.data);
  return 'search-synced';
}, {async: true});

// รอทั้งหมดเสร็จ
const results = await EventManager.emit('data:sync', {
  data: {...}
});

console.log('Sync results:', results);
// ['db-synced', 'cache-synced', 'search-synced']

การจัดการ Context

Event context object ที่ส่งไปยัง listeners

Context Structure

{
  event: string,              // ชื่อ event
  data: object,               // ข้อมูลที่ส่งมา
  timestamp: number,          // เวลาที่ส่ง event
  preventDefault: boolean,    // flag สำหรับยกเลิก default behavior
  stopPropagation: boolean,   // flag สำหรับหยุดส่งต่อ
  stopImmediatePropagation: boolean, // หยุดทันที
  results: array,             // results จาก listeners
  source: any,                // แหล่งที่มาของ event
  path: array,                // event hierarchy path
  currentPath: string         // current event path
}

Control Propagation

EventManager.on('form:submit', (context) => {
  // ตรวจสอบข้อมูล
  if (!validateForm(context.data)) {
    // หยุดการส่งต่อไปยัง listeners อื่น
    context.stopPropagation = true;
    return {valid: false};
  }

  return {valid: true};
}, {priority: 100});

EventManager.on('form:submit', (context) => {
  // จะไม่ทำงานถ้า validation ไม่ผ่าน
  saveForm(context.data);
});

Prevent Default Behavior

EventManager.on('link:click', (context) => {
  // ยกเลิก default behavior
  context.preventDefault = true;

  // ทำ custom behavior
  handleCustomNavigation(context.data.url);
});

Stop Immediate Propagation

EventManager.on('critical:error', (context) => {
  // หยุด listeners อื่นทันที
  context.stopImmediatePropagation = true;

  // Handle critical error
  handleCriticalError(context.data);
}, {priority: 1000});

// Listeners อื่นจะไม่ทำงาน
EventManager.on('critical:error', (context) => {
  // จะไม่ถูกเรียก
  logError(context.data);
});

Access Event Hierarchy

EventManager.on('api:user:created', (context) => {
  console.log('Event:', context.event);
  // 'api:user:created'

  console.log('Path:', context.path);
  // ['api:user:created', 'api:user', 'api']

  // listeners จะถูกเรียกตาม hierarchy
});

// Listeners ทั้งหมดเหล่านี้จะถูกเรียก:
EventManager.on('api:user:created', handler1);
EventManager.on('api:user', handler2);
EventManager.on('api', handler3);

Return Values

EventManager.on('calculate:sum', (context) => {
  const sum = context.data.numbers.reduce((a, b) => a + b, 0);
  return sum; // return value จะถูกเก็บใน context.results
});

EventManager.on('calculate:sum', (context) => {
  const avg = context.data.numbers.reduce((a, b) => a + b, 0) / context.data.numbers.length;
  return avg;
});

// Emit และรับ results
const results = await EventManager.emit('calculate:sum', {
  numbers: [1, 2, 3, 4, 5]
});

console.log('Results:', results); // [15, 3]

Cleanup และ Memory Management

EventManager มีระบบ cleanup อัตโนมัติเพื่อจัดการหน่วยความจำ

Auto Cleanup

// เปิดใช้งาน cleanup (default: enabled)
await EventManager.init({
  cleanup: {
    enabled: true,
    interval: 60000,    // ตรวจสอบทุก 60 วินาที
    maxAge: 1800000,    // ลบ listeners เก่ากว่า 30 นาที
    batchSize: 100      // ทำทีละ 100 รายการ
  }
});

Manual Cleanup

// ลบ listeners ที่ไม่ใช้แล้ว
EventManager.cleanup();

// Clear events ทั้งหมด
EventManager.clear();

// Destroy และ cleanup ทุกอย่าง
EventManager.destroy();

Cleanup Stale Listeners

// ลบ listeners เก่าของ event เฉพาะ
EventManager.cleanupStaleListeners('old:event');

Best Practices สำหรับ Memory Management

1. ใช้ Once สำหรับ One-time Events

// ❌ Bad: listener ไม่ถูกลบ
EventManager.on('app:initialized', handler);

// ✅ Good: ลบอัตโนมัติหลังทำงาน
EventManager.once('app:initialized', handler);

2. Unsubscribe เมื่อไม่ใช้แล้ว

// ✅ Good: เก็บ unsubscribe function
const unsubscribe = EventManager.on('data:updated', handler);

// เมื่อ component ถูก destroy
componentWillUnmount() {
  unsubscribe();
}

3. ใช้ Groups สำหรับ Batch Cleanup

// เพิ่มเข้า group
EventManager.on('temp:event1', handler1, {group: 'temporary'});
EventManager.on('temp:event2', handler2, {group: 'temporary'});
EventManager.on('temp:event3', handler3, {group: 'temporary'});

// Cleanup ทั้ง group พร้อมกัน
// (ไม่มี API โดยตรง แต่สามารถใช้ off กับแต่ละ event)
EventManager.off('temp:event1');
EventManager.off('temp:event2');
EventManager.off('temp:event3');

4. Monitor Listener Count

// ตรวจสอบจำนวน listeners
const info = EventManager.getEventInfo('user:login');
console.log('Listener count:', info.listenerCount);

// เตือนเมื่อมีเยอะเกินไป
if (info.listenerCount > 50) {
  console.warn('Too many listeners for event:', 'user:login');
}

Integration กับ Now.js

EventManager ถูก integrate กับ Now.js Framework

Auto-Registration

// EventManager ถูก register อัตโนมัติเมื่อโหลด
console.log(Now.managers.has('event')); // true

เข้าถึงผ่าน Now.js

// Method 1: Now.emit() wrapper
Now.emit('user:login', {userId: 123});
// Note: Now.emit() เพิ่ม timestamp อัตโนมัติ

// Method 2: ผ่าน Manager system
const eventManager = Now.getManager('event');
eventManager.emit('user:login', {userId: 123});

// Method 3: Direct access
EventManager.emit('user:login', {userId: 123});

ใช้กับ Now.js Components

// ใน component
const MyComponent = {
  async init() {
    // ลงทะเบียน listeners
    this.unsubscribe = EventManager.on('data:updated', (context) => {
      this.handleDataUpdate(context.data);
    });
  },

  handleDataUpdate(data) {
    console.log('Data updated:', data);
    this.render();
  },

  async destroy() {
    // Cleanup เมื่อ destroy
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }
};

ComponentManager.define('my-component', MyComponent);

Integration Events

Now.js ส่ง events เหล่านี้ผ่าน EventManager:

// App lifecycle events
EventManager.on('app:mounted', (context) => {
  console.log('App mounted:', context.data.app);
});

EventManager.on('app:ready', (context) => {
  console.log('App ready');
});

// Error events
EventManager.on('error', (context) => {
  console.error('Error:', context.data.error);
});

// Performance events
EventManager.on('performance:recorded', (context) => {
  console.log('Performance metric:', context.data);
});

ตัวอย่างการใช้งาน

1. User Authentication Flow

// Listen สำหรับ login success
EventManager.on('auth:login:success', async (context) => {
  const {user, token} = context.data;

  // บันทึก token
  localStorage.setItem('token', token);

  // โหลดข้อมูล user
  await loadUserData(user.id);

  // Redirect
  window.location.href = '/dashboard';
}, {
  priority: 100
});

// Analytics tracking
EventManager.on('auth:*', (context) => {
  analytics.track(context.event, context.data);
}, {
  priority: 10
});

// ส่ง login event
await EventManager.emit('auth:login:success', {
  user: {id: 123, name: 'John'},
  token: 'abc123'
});

2. Form Validation Pipeline

// Validation - priority สูงสุด
EventManager.on('form:submit', (context) => {
  const errors = validateForm(context.data.formData);

  if (errors.length > 0) {
    context.stopPropagation = true;
    return {valid: false, errors};
  }

  return {valid: true};
}, {
  priority: 100
});

// Sanitization
EventManager.on('form:submit', (context) => {
  context.data.formData = sanitizeFormData(context.data.formData);
}, {
  priority: 80
});

// Transform
EventManager.on('form:submit', (context) => {
  context.data.formData = transformData(context.data.formData);
}, {
  priority: 60
});

// Save - priority ต่ำสุด
EventManager.on('form:submit', async (context) => {
  await saveToDatabase(context.data.formData);
  return {saved: true};
}, {
  priority: 10,
  async: true
});

// Submit form
const results = await EventManager.emit('form:submit', {
  formData: {...}
});

3. Real-time Notification System

// จัดกลุ่ม notification handlers
EventManager.on('notification:info', (context) => {
  showToast(context.data.message, 'info');
}, {group: 'notifications'});

EventManager.on('notification:success', (context) => {
  showToast(context.data.message, 'success');
}, {group: 'notifications'});

EventManager.on('notification:warning', (context) => {
  showToast(context.data.message, 'warning');
}, {group: 'notifications'});

EventManager.on('notification:error', (context) => {
  showToast(context.data.message, 'error');
}, {group: 'notifications'});

// ส่ง notification
await EventManager.emit('notification:success', {
  message: 'Data saved successfully!'
});

// หรือส่งไปยังทุก notification
await EventManager.emitGroup('notifications', {
  message: 'System updated'
});

4. Shopping Cart Events

// Item added
EventManager.on('cart:item-added', async (context) => {
  const {product, quantity} = context.data;

  // อัพเดท UI
  updateCartBadge();

  // บันทึก
  await saveCart();

  // แสดง notification
  await EventManager.emit('notification:success', {
    message: `Added ${product.name} to cart`
  });
});

// Item removed
EventManager.on('cart:item-removed', async (context) => {
  updateCartBadge();
  await saveCart();
});

// Quantity changed
EventManager.on('cart:quantity-changed', async (context) => {
  updateCartTotal();
  await saveCart();
});

// ใช้ pattern สำหรับ analytics
EventManager.on('cart:*', (context) => {
  analytics.track(context.event, context.data);
});

// ใช้งาน
await EventManager.emit('cart:item-added', {
  product: {id: 123, name: 'Product A'},
  quantity: 2
});

5. API Request Lifecycle

// Before request
EventManager.on('api:request:before', (context) => {
  // แสดง loading
  showLoadingSpinner();

  // เพิ่ม headers
  context.data.headers = {
    ...context.data.headers,
    'Authorization': `Bearer ${getToken()}`
  };
}, {
  priority: 100
});

// Success
EventManager.on('api:request:success', async (context) => {
  hideLoadingSpinner();

  await EventManager.emit('notification:success', {
    message: 'Request successful'
  });
});

// Error
EventManager.on('api:request:error', async (context) => {
  hideLoadingSpinner();

  await EventManager.emit('notification:error', {
    message: context.data.error.message
  });

  // Retry logic
  if (context.data.error.code === 401) {
    await refreshToken();
    return {retry: true};
  }
});

// After request (always)
EventManager.on('api:request:after', (context) => {
  logApiCall(context.data);
});

6. WebSocket Message Handler

// เชื่อมต่อ WebSocket
const ws = new WebSocket('ws://localhost:8080');

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);

  // ส่งต่อเป็น events
  EventManager.emit(`ws:${message.type}`, message.data);
};

// จัดการ message types
EventManager.on('ws:user-online', (context) => {
  updateUserStatus(context.data.userId, 'online');
});

EventManager.on('ws:new-message', (context) => {
  displayMessage(context.data);
  playNotificationSound();
});

EventManager.on('ws:typing', (context) => {
  showTypingIndicator(context.data.userId);
});

// Pattern สำหรับ logging
EventManager.on('ws:*', (context) => {
  console.log('WebSocket event:', context.event, context.data);
});

7. Theme Switcher

// Listen สำหรับ theme changes
EventManager.on('theme:changed', (context) => {
  const {theme} = context.data;

  // อัพเดท DOM
  document.body.className = theme;

  // บันทึก preference
  localStorage.setItem('theme', theme);

  // อัพเดท components
  reloadComponents();
}, {
  once: false
});

// Switch theme
async function switchTheme(theme) {
  await EventManager.emit('theme:changed', {theme});
}

// Toggle dark mode
document.getElementById('dark-mode-toggle').addEventListener('click', () => {
  const currentTheme = document.body.className;
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  switchTheme(newTheme);
});

8. Data Synchronization

// Sync เมื่อข้อมูลเปลี่ยน
EventManager.on('data:changed', async (context) => {
  const {entity, data} = context.data;

  // Sync ไปยังหลาย destinations พร้อมกัน
  await Promise.all([
    syncToLocalStorage(entity, data),
    syncToIndexedDB(entity, data),
    syncToServer(entity, data)
  ]);

  return {synced: true};
}, {
  async: true,
  timeout: 10000
});

// Conflict resolution
EventManager.on('data:conflict', async (context) => {
  const {local, remote} = context.data;

  // แสดง UI สำหรับ resolve conflict
  const resolved = await showConflictDialog(local, remote);

  return {resolved};
}, {
  async: true
});

// ใช้งาน
await EventManager.emit('data:changed', {
  entity: 'user',
  data: {id: 123, name: 'John Updated'}
});

9. Feature Toggle System

// Define features
const features = new Map();

// Enable feature
EventManager.on('feature:enable', (context) => {
  const {feature} = context.data;
  features.set(feature, true);

  // Reload affected components
  reloadFeatureDependentComponents(feature);

  console.log('Feature enabled:', feature);
});

// Disable feature
EventManager.on('feature:disable', (context) => {
  const {feature} = context.data;
  features.set(feature, false);

  reloadFeatureDependentComponents(feature);

  console.log('Feature disabled:', feature);
});

// Check feature
function isFeatureEnabled(feature) {
  return features.get(feature) || false;
}

// Toggle
async function toggleFeature(feature) {
  const enabled = isFeatureEnabled(feature);
  const event = enabled ? 'feature:disable' : 'feature:enable';

  await EventManager.emit(event, {feature});
}

// ใช้ใน code
if (isFeatureEnabled('dark-mode')) {
  enableDarkMode();
}

10. Performance Monitoring

// Track performance metrics
const performanceMetrics = [];

EventManager.use({
  beforeEmit: (context) => {
    context.startTime = performance.now();
  },

  afterEmit: (context) => {
    const duration = performance.now() - context.startTime;

    performanceMetrics.push({
      event: context.event,
      duration,
      timestamp: context.timestamp,
      listenerCount: context.results.length
    });

    // Warn ถ้าช้าเกินไป
    if (duration > 100) {
      console.warn('Slow event:', context.event, `${duration}ms`);
    }
  }
});

// Report metrics
function getPerformanceReport() {
  const avgDuration = performanceMetrics.reduce((sum, m) =>
    sum + m.duration, 0) / performanceMetrics.length;

  const slowEvents = performanceMetrics
    .filter(m => m.duration > 100)
    .sort((a, b) => b.duration - a.duration);

  return {
    totalEvents: performanceMetrics.length,
    averageDuration: avgDuration,
    slowEvents: slowEvents.slice(0, 10)
  };
}

// ดู report
console.log('Performance Report:', getPerformanceReport());

API Reference

Configuration

{
  asyncTimeout: number,        // Timeout สำหรับ async handlers (ms) default: 5000
  maxListeners: number,        // จำนวน listeners สูงสุดต่อ event default: 2048
  traceEvents: boolean,        // เก็บ history สำหรับ debug default: false
  cleanup: {
    enabled: boolean,          // เปิดใช้ auto cleanup default: true
    interval: number,          // ตรวจสอบทุก x ms default: 60000
    maxAge: number,            // ลบ listeners เก่ากว่า x ms default: 1800000
    batchSize: number          // ทำทีละ x รายการ default: 100
  }
}

Methods

Core Methods

// Initialize
init(options?: object): Promise<EventManager>

// Register listener
on(event: string, callback: function, options?: object): function

// Register one-time listener
once(event: string, callback: function, options?: object): function

// Emit event
emit(event: string, data?: object): Promise<array>

// Remove listener(s)
off(event: string, identifier?: string|function): boolean

// Emit to group
emitGroup(group: string, data?: object): Promise<array>

Middleware Methods

// Add middleware
use(middleware: function|object): EventManager

// Run middleware hook
runMiddleware(hook: string, context: object): Promise<boolean>

Pattern Methods

// Create regex pattern from string
createPattern(pattern: string): RegExp

// Add pattern listener
addPatternListener(pattern: string, callback: function, options?: object): function

// Remove pattern listener
removePatternListener(pattern: string, identifier?: string|function): boolean

// Check if string is pattern
isPattern(str: string): boolean

Group Methods

// Add listener to group
addToGroup(group: string, event: string, listener: object): void

// Remove listener from group
removeFromGroup(group: string, event: string, listener: object): void

Cleanup Methods

// Manual cleanup
cleanup(): void

// Cleanup specific event
cleanupStaleListeners(event: string): void

// Clear all
clear(): void

// Destroy
destroy(): void

Info Methods

// Get event info
getEventInfo(event: string): object

// Get debug info
getDebugInfo(): object

Context Object

{
  event: string,                    // Event name
  data: object,                     // Event data
  timestamp: number,                // Emit timestamp
  preventDefault: boolean,          // Prevent default flag
  stopPropagation: boolean,         // Stop propagation flag
  stopImmediatePropagation: boolean,// Stop immediately flag
  results: array,                   // Results from listeners
  source: any,                      // Event source
  path: array,                      // Event hierarchy path
  currentPath: string               // Current path in hierarchy
}

Listener Options

{
  priority: number,      // Priority (higher = first) default: 0
  once: boolean,         // Run once and remove default: false
  async: boolean,        // Async handler default: false
  group: string,         // Group name default: null
  timeout: number,       // Async timeout (ms) default: 5000
  maxRetries: number,    // Max retry attempts default: 1
  onError: function      // Error handler default: null
}

Best Practices

1. ✅ ใช้ Namespaced Events

// ❌ Bad: ชื่อทั่วไป
EventManager.on('success', handler);
EventManager.on('error', handler);

// ✅ Good: ชื่อชัดเจน
EventManager.on('auth:login:success', handler);
EventManager.on('api:user:error', handler);

2. ✅ Cleanup Listeners

// ❌ Bad: ไม่ cleanup
EventManager.on('data:updated', handler);

// ✅ Good: cleanup เมื่อไม่ใช้
const unsubscribe = EventManager.on('data:updated', handler);

// เมื่อ component destroy
componentWillUnmount() {
  unsubscribe();
}

3. ✅ ใช้ Once สำหรับ One-time Events

// ❌ Bad: ต้อง off เอง
EventManager.on('app:ready', handler);
EventManager.off('app:ready', handler);

// ✅ Good: ลบอัตโนมัติ
EventManager.once('app:ready', handler);

4. ✅ ใช้ Priority อย่างเหมาะสม

// ❌ Bad: ไม่มี priority
EventManager.on('form:submit', saveData);
EventManager.on('form:submit', validateData);

// ✅ Good: validate ก่อน save
EventManager.on('form:submit', validateData, {priority: 100});
EventManager.on('form:submit', saveData, {priority: 10});

5. ✅ Handle Errors ใน Async

// ❌ Bad: ไม่จัดการ errors
EventManager.on('data:fetch', async (context) => {
  const data = await fetch(context.data.url);
  return data.json();
}, {async: true});

// ✅ Good: มี error handling
EventManager.on('data:fetch', async (context) => {
  try {
    const data = await fetch(context.data.url);
    return await data.json();
  } catch (error) {
    console.error('Fetch error:', error);
    return {error: error.message};
  }
}, {async: true});

6. ✅ ใช้ Patterns อย่างชาญฉลาด

// ❌ Bad: ลงทะเบียนทีละ event
EventManager.on('notification:info', handler);
EventManager.on('notification:success', handler);
EventManager.on('notification:warning', handler);
EventManager.on('notification:error', handler);

// ✅ Good: ใช้ pattern
EventManager.on('notification:*', handler);
// ✅ Good: จัดกลุ่ม related events
EventManager.on('analytics:pageview', trackPageView, {
  group: 'analytics'
});

EventManager.on('analytics:click', trackClick, {
  group: 'analytics'
});

// Emit ทั้ง group
await EventManager.emitGroup('analytics', data);

8. ✅ Document Event Names

// ✅ Good: เขียน documentation
/**
 * Events:
 * - user:login - เมื่อ user login สำเร็จ
 * - user:logout - เมื่อ user logout
 * - user:register - เมื่อ user ลงทะเบียนใหม่
 *
 * Data structure:
 * {
 *   userId: number,
 *   username: string,
 *   email: string
 * }
 */
const AuthEvents = {
  LOGIN: 'user:login',
  LOGOUT: 'user:logout',
  REGISTER: 'user:register'
};

// ใช้ constants
EventManager.on(AuthEvents.LOGIN, handler);

9. ✅ Test Event Flow

// ✅ Good: เขียน tests
describe('EventManager', () => {
  it('should handle user login', async () => {
    const handler = jest.fn();

    EventManager.on('user:login', handler);

    await EventManager.emit('user:login', {
      userId: 123
    });

    expect(handler).toHaveBeenCalled();
    expect(handler).toHaveBeenCalledWith(
      expect.objectContaining({
        event: 'user:login',
        data: {userId: 123}
      })
    );
  });
});

10. ✅ Monitor Performance

// ✅ Good: ติดตาม performance
EventManager.use({
  beforeEmit: (context) => {
    context._startTime = performance.now();
  },

  afterEmit: (context) => {
    const duration = performance.now() - context._startTime;

    if (duration > 100) {
      console.warn('Slow event:', context.event, `${duration}ms`);
    }
  }
});

สรุป

เมื่อใดควรใช้ EventManager

Use Case ใช้ EventManager? หมายเหตุ
สื่อสารระหว่าง Components ✅ ใช่ Event Bus Pattern
จัดการ Application Events ✅ ใช่ Custom events
Decouple Modules ✅ ใช่ Loose coupling
Pattern Matching ✅ ใช่ Wildcards support
Event Groups ✅ ใช่ Built-in support
Middleware ✅ ใช่ Interceptors
จัดการ DOM Events ❌ ไม่ ใช้ EventSystemManager
Event Delegation ❌ ไม่ ใช้ EventSystemManager

คุณสมบัติหลัก

คุณสมบัติ รองรับ หมายเหตุ
Event Bus Publish/Subscribe
Pattern Matching Wildcards, Regex
Event Hierarchy Dot notation
Event Groups Grouped events
Priority Ordered execution
Middleware beforeEmit, afterEmit
Async Support Promises, Async/Await
Context Control stopPropagation, preventDefault
Auto Cleanup Memory management
Debug Mode Event history