Calendar/src/managers/ViewManager.ts

244 lines
7.3 KiB
TypeScript
Raw Normal View History

import { EventBus } from '../core/EventBus';
import { CalendarView, IEventBus } from '../types/CalendarTypes';
import { calendarConfig } from '../core/CalendarConfig';
import { CoreEvents } from '../constants/CoreEvents';
/**
* ViewManager - Optimized view switching with consolidated event handling
* Reduces redundant DOM queries and event listener setups
*/
export class ViewManager {
private eventBus: IEventBus;
private currentView: CalendarView = 'week';
private eventCleanup: (() => void)[] = [];
private buttonListeners: Map<Element, EventListener> = new Map();
// Cached DOM elements for performance
private cachedViewButtons: NodeListOf<Element> | null = null;
private cachedWorkweekButtons: NodeListOf<Element> | null = null;
private lastButtonCacheTime: number = 0;
private readonly CACHE_DURATION = 5000; // 5 seconds
constructor(eventBus: IEventBus) {
this.eventBus = eventBus;
this.setupEventListeners();
}
/**
* Consolidated event listener setup with better organization
*/
private setupEventListeners(): void {
// Event bus listeners
this.setupEventBusListeners();
// DOM button handlers with consolidated logic
this.setupButtonHandlers();
}
/**
* Setup event bus listeners with proper cleanup tracking
*/
private setupEventBusListeners(): void {
this.eventCleanup.push(
this.eventBus.on(CoreEvents.INITIALIZED, () => {
this.initializeView();
})
);
// Remove redundant VIEW_CHANGED listener that causes circular calls
// changeView is called directly from button handlers
this.eventCleanup.push(
this.eventBus.on(CoreEvents.DATE_CHANGED, () => {
this.refreshCurrentView();
})
);
}
/**
* Consolidated button handler setup with shared logic
*/
private setupButtonHandlers(): void {
// Setup view buttons with consolidated handler
this.setupButtonGroup('swp-view-button[data-view]', 'data-view', (value) => {
if (this.isValidView(value)) {
this.changeView(value as CalendarView);
}
});
// Setup workweek buttons with consolidated handler
this.setupButtonGroup('swp-preset-button[data-workweek]', 'data-workweek', (value) => {
this.changeWorkweek(value);
});
}
/**
* Generic button group setup to eliminate duplicate code
*/
private setupButtonGroup(selector: string, attribute: string, handler: (value: string) => void): void {
const buttons = document.querySelectorAll(selector);
buttons.forEach(button => {
const clickHandler = (event: Event) => {
event.preventDefault();
const value = button.getAttribute(attribute);
if (value) {
handler(value);
}
};
button.addEventListener('click', clickHandler);
this.buttonListeners.set(button, clickHandler);
});
}
/**
* Get cached view buttons with cache invalidation
*/
private getViewButtons(): NodeListOf<Element> {
const now = Date.now();
if (!this.cachedViewButtons || (now - this.lastButtonCacheTime) > this.CACHE_DURATION) {
this.cachedViewButtons = document.querySelectorAll('swp-view-button[data-view]');
this.lastButtonCacheTime = now;
}
return this.cachedViewButtons;
}
/**
* Get cached workweek buttons with cache invalidation
*/
private getWorkweekButtons(): NodeListOf<Element> {
const now = Date.now();
if (!this.cachedWorkweekButtons || (now - this.lastButtonCacheTime) > this.CACHE_DURATION) {
this.cachedWorkweekButtons = document.querySelectorAll('swp-preset-button[data-workweek]');
this.lastButtonCacheTime = now;
}
return this.cachedWorkweekButtons;
}
/**
* Initialize view with consolidated button updates
*/
private initializeView(): void {
this.updateAllButtons();
this.emitViewRendered();
}
/**
* Optimized view change with debouncing
*/
private changeView(newView: CalendarView): void {
if (newView === this.currentView) return;
const previousView = this.currentView;
this.currentView = newView;
this.updateAllButtons();
this.eventBus.emit(CoreEvents.VIEW_CHANGED, {
previousView,
currentView: newView
});
}
/**
* Optimized workweek change with consolidated updates
*/
private changeWorkweek(workweekId: string): void {
// Update the calendar config
calendarConfig.setWorkWeek(workweekId);
// Update button states using cached elements
this.updateAllButtons();
// Trigger a workweek change to apply the new workweek
this.eventBus.emit(CoreEvents.WORKWEEK_CHANGED);
}
/**
* Consolidated button update method to eliminate duplicate code
*/
private updateAllButtons(): void {
this.updateButtonGroup(
this.getViewButtons(),
'data-view',
this.currentView
);
this.updateButtonGroup(
this.getWorkweekButtons(),
'data-workweek',
calendarConfig.getCurrentWorkWeek()
);
}
/**
* Generic button group update to eliminate duplicate logic
*/
private updateButtonGroup(buttons: NodeListOf<Element>, attribute: string, activeValue: string): void {
buttons.forEach(button => {
const buttonValue = button.getAttribute(attribute);
if (buttonValue === activeValue) {
button.setAttribute('data-active', 'true');
} else {
button.removeAttribute('data-active');
}
});
}
/**
* Emit view rendered event with current view
*/
private emitViewRendered(): void {
this.eventBus.emit(CoreEvents.VIEW_RENDERED, {
view: this.currentView
});
}
/**
* Refresh current view by emitting view rendered event
*/
private refreshCurrentView(): void {
this.emitViewRendered();
}
/**
* Validate if a string is a valid calendar view
*/
private isValidView(view: string): view is CalendarView {
return ['day', 'week', 'month'].includes(view);
}
/**
* Get current active view
*/
public getCurrentView(): CalendarView {
return this.currentView;
}
/**
* Public refresh method
*/
public refresh(): void {
this.refreshCurrentView();
}
/**
* Clean up all resources and cached elements
*/
public destroy(): void {
// Clean up event bus listeners
this.eventCleanup.forEach(cleanup => cleanup());
this.eventCleanup = [];
// Clean up button listeners
this.buttonListeners.forEach((handler, button) => {
button.removeEventListener('click', handler);
});
this.buttonListeners.clear();
// Clear cached elements
this.cachedViewButtons = null;
this.cachedWorkweekButtons = null;
this.lastButtonCacheTime = 0;
}
}