mcp-quiz-server
Version:
๐ง AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.
136 lines (121 loc) โข 4.52 kB
text/typescript
/**
* @moduleName: EventManagement
* @version: 2.0.0
* @since: 2025-07-26
* @lastUpdated: 2025-07-26
* @projectSummary: Enhanced MCP Quiz Server - Event Management System
* @techStack: TypeScript, DOM API, Event Delegation
* @dependency: None (core utility)
* @interModuleDependency: Used by QuizContent, AnswerHandler, NavigationController
* @requirementsTraceability:
* {@link Requirements.REQ_UI_003} (Event Handling)
* {@link Requirements.REQ_BUG_021} (Infinite Recursion Fix)
* @briefDescription: Centralized event management system preventing memory leaks and infinite recursion
* @methods: addListener, removeListener, cleanup, delegate
* @contributors: GitHub Copilot
* @examples:
* - EventManagement.addListener(button, 'click', handler, 'continue-button')
* - EventManagement.cleanup(['continue-button', 'complete-button'])
* @vulnerabilitiesAssessment: Memory leak prevention, proper event cleanup, no sensitive data
*/
export class EventManagement {
private static eventListeners = new Map<
string,
{
element: Element;
event: string;
handler: EventListener;
}
>();
/**
* Add an event listener with automatic cleanup of existing listeners
* @param element - The DOM element to attach the listener to
* @param event - The event type (e.g., 'click', 'keydown')
* @param handler - The event handler function
* @param key - Unique key for this listener (enables cleanup)
*/
static addListener(element: Element, event: string, handler: EventListener, key: string): void {
// Remove existing listener if present to prevent duplicates
if (this.eventListeners.has(key)) {
const existing = this.eventListeners.get(key)!;
existing.element.removeEventListener(existing.event, existing.handler);
}
// Add new listener
element.addEventListener(event, handler);
this.eventListeners.set(key, {
element,
event,
handler,
});
console.log(`๐ Event listener added: ${key} (${event})`);
}
/**
* Remove a specific event listener
* @param key - The unique key of the listener to remove
*/
static removeListener(key: string): void {
if (this.eventListeners.has(key)) {
const listener = this.eventListeners.get(key)!;
listener.element.removeEventListener(listener.event, listener.handler);
this.eventListeners.delete(key);
console.log(`๐๏ธ Event listener removed: ${key}`);
}
}
/**
* Clean up multiple listeners by their keys
* @param keys - Array of listener keys to clean up, or undefined to clean all
*/
static cleanup(keys?: string[]): void {
const keysToClean = keys || Array.from(this.eventListeners.keys());
keysToClean.forEach(key => {
this.removeListener(key);
});
console.log(`๐งน Event cleanup completed: ${keysToClean.length} listeners removed`);
}
/**
* Get the count of active listeners (for debugging)
*/
static getActiveListenerCount(): number {
return this.eventListeners.size;
}
/**
* Get all active listener keys (for debugging)
*/
static getActiveListenerKeys(): string[] {
return Array.from(this.eventListeners.keys());
}
/**
* Event delegation helper for dynamic content
* @param container - The container element to delegate from
* @param selector - CSS selector for target elements
* @param event - Event type to listen for
* @param handler - Event handler function
* @param key - Unique key for cleanup
*/
static delegate(
container: Element,
selector: string,
event: string,
handler: (event: Event, target: Element) => void,
key: string
): void {
const delegatedHandler = (e: Event) => {
const target = (e.target as Element).closest(selector);
if (target) {
handler(e, target);
}
};
this.addListener(container, event, delegatedHandler, key);
}
/**
* Safe cleanup for component destruction
* Call this when a component is being destroyed to prevent memory leaks
*/
static componentCleanup(componentPrefix: string): void {
const componentKeys = Array.from(this.eventListeners.keys()).filter(key =>
key.startsWith(componentPrefix)
);
this.cleanup(componentKeys);
console.log(`๐๏ธ Component cleanup: ${componentPrefix} (${componentKeys.length} listeners)`);
}
}