Files
ComfyUI-Lora-Manager/docs/EventManagerDocs.md
Will Miao 95e2ff5f1e Implement centralized event management system with priority handling and state tracking
- Enhanced EventManager class to support priority-based event handling, conditional execution, and automatic cleanup.
- Integrated event management into BulkManager for global keyboard shortcuts and marquee selection events.
- Migrated mouse tracking and node selector events to UIHelpers for better coordination.
- Established global event handlers for context menu interactions and modal state management.
- Added comprehensive documentation for event management implementation and usage.
- Implemented initialization logic for event management, including error handling and cleanup on page unload.
2025-09-05 16:56:26 +08:00

7.5 KiB

Centralized Event Management System

This document describes the centralized event management system that coordinates event handling across the ComfyUI LoRA Manager application.

Overview

The EventManager class provides a centralized way to handle DOM events with priority-based execution, conditional execution based on application state, and proper cleanup mechanisms.

Features

  • Priority-based execution: Handlers with higher priority run first
  • Conditional execution: Handlers can be executed based on application state
  • Element filtering: Handlers can target specific elements or exclude others
  • Automatic cleanup: Cleanup functions are called when handlers are removed
  • State tracking: Tracks application states like bulk mode, modal open, etc.

Basic Usage

Importing

import { eventManager } from './EventManager.js';

Adding Event Handlers

eventManager.addHandler('click', 'myComponent', (event) => {
    console.log('Button clicked!');
    return true; // Stop propagation to other handlers
}, {
    priority: 100,
    targetSelector: '.my-button',
    skipWhenModalOpen: true
});

Removing Event Handlers

// Remove specific handler
eventManager.removeHandler('click', 'myComponent');

// Remove all handlers for a component
eventManager.removeAllHandlersForSource('myComponent');

Updating Application State

// Set state
eventManager.setState('bulkMode', true);
eventManager.setState('modalOpen', true);

// Get state
const isBulkMode = eventManager.getState('bulkMode');

Available States

  • bulkMode: Whether bulk selection mode is active
  • marqueeActive: Whether marquee selection is in progress
  • modalOpen: Whether any modal is currently open
  • nodeSelectorActive: Whether the node selector popup is active

Handler Options

Priority

Higher numbers = higher priority. Handlers run in descending priority order.

{
    priority: 100 // High priority
}

Conditional Execution

{
    onlyInBulkMode: true,               // Only run when bulk mode is active
    onlyWhenMarqueeActive: true,        // Only run when marquee selection is active
    skipWhenModalOpen: true,            // Skip when any modal is open
    skipWhenNodeSelectorActive: true,   // Skip when node selector is active
    onlyWhenNodeSelectorActive: true    // Only run when node selector is active
}

Element Filtering

{
    targetSelector: '.model-card',      // Only handle events on matching elements
    excludeSelector: 'button, input',  // Exclude events from these elements
    button: 0                          // Only handle specific mouse button (0=left, 1=middle, 2=right)
}

Cleanup Functions

{
    cleanup: () => {
        // Custom cleanup logic
        console.log('Handler cleaned up');
    }
}

Integration Examples

BulkManager Integration

class BulkManager {
    registerEventHandlers() {
        // High priority keyboard shortcuts
        eventManager.addHandler('keydown', 'bulkManager-keyboard', (e) => {
            return this.handleGlobalKeyboard(e);
        }, {
            priority: 100,
            skipWhenModalOpen: true
        });

        // Marquee selection
        eventManager.addHandler('mousedown', 'bulkManager-marquee-start', (e) => {
            return this.handleMarqueeStart(e);
        }, {
            priority: 80,
            skipWhenModalOpen: true,
            targetSelector: '.models-container',
            excludeSelector: '.model-card, button, input',
            button: 0
        });
    }
    
    cleanup() {
        eventManager.removeAllHandlersForSource('bulkManager-keyboard');
        eventManager.removeAllHandlersForSource('bulkManager-marquee-start');
    }
}

Modal Integration

class ModalManager {
    showModal(modalId) {
        // Update state when modal opens
        eventManager.setState('modalOpen', true);
        this.displayModal(modalId);
    }
    
    closeModal(modalId) {
        // Update state when modal closes
        eventManager.setState('modalOpen', false);
        this.hideModal(modalId);
    }
}

Component Event Delegation

export function setupComponentEvents() {
    eventManager.addHandler('click', 'myComponent-actions', (event) => {
        const button = event.target.closest('.action-button');
        if (!button) return false;
        
        this.handleAction(button.dataset.action);
        return true; // Stop propagation
    }, {
        priority: 60,
        targetSelector: '.component-container'
    });
}

Best Practices

1. Use Descriptive Source Names

Use the format componentName-purposeDescription:

// Good
'bulkManager-marqueeSelection'
'nodeSelector-clickOutside'
'modelCard-delegation'

// Avoid
'bulk'
'click'
'handler1'

2. Set Appropriate Priorities

  • 200+: Critical system events (escape keys, critical modals)
  • 100-199: High priority application events (keyboard shortcuts)
  • 50-99: Normal UI interactions (buttons, cards)
  • 1-49: Low priority events (tracking, analytics)

3. Use Conditional Execution

Instead of checking state inside handlers, use options:

// Good
eventManager.addHandler('click', 'bulk-action', handler, {
    onlyInBulkMode: true
});

// Avoid
eventManager.addHandler('click', 'bulk-action', (e) => {
    if (!state.bulkMode) return;
    // handler logic
});

4. Clean Up Properly

Always clean up handlers when components are destroyed:

class MyComponent {
    constructor() {
        this.registerEvents();
    }
    
    destroy() {
        eventManager.removeAllHandlersForSource('myComponent');
    }
}

5. Return Values Matter

  • Return true to stop event propagation to other handlers
  • Return false or undefined to continue with other handlers

Migration Guide

From Direct Event Listeners

Before:

document.addEventListener('click', (e) => {
    if (e.target.closest('.my-button')) {
        this.handleClick(e);
    }
});

After:

eventManager.addHandler('click', 'myComponent-button', (e) => {
    this.handleClick(e);
}, {
    targetSelector: '.my-button'
});

From Event Delegation

Before:

container.addEventListener('click', (e) => {
    const card = e.target.closest('.model-card');
    if (!card) return;
    
    if (e.target.closest('.action-btn')) {
        this.handleAction(e);
    }
});

After:

eventManager.addHandler('click', 'container-actions', (e) => {
    const card = e.target.closest('.model-card');
    if (!card) return false;
    
    if (e.target.closest('.action-btn')) {
        this.handleAction(e);
        return true;
    }
}, {
    targetSelector: '.container'
});

Performance Benefits

  1. Reduced DOM listeners: Single listener per event type instead of multiple
  2. Conditional execution: Handlers only run when conditions are met
  3. Priority ordering: Important handlers run first, avoiding unnecessary work
  4. Automatic cleanup: Prevents memory leaks from orphaned listeners
  5. Centralized debugging: All event handling flows through one system

Debugging

Enable debug logging to trace event handling:

// Add to EventManager.js for debugging
console.log(`Handling ${eventType} event with ${handlers.length} handlers`);

The event manager provides a foundation for coordinated, efficient event handling across the entire application.