Refactors calendar managers and renderers

Improves calendar rendering performance by centralizing DOM manipulation in a dedicated `GridRenderer` class. This reduces redundant DOM queries and improves overall efficiency.

Introduces `EventManager` for optimized event lifecycle management with caching and optimized data processing.

The `ViewManager` is refactored for optimized view switching and event handling, further streamlining the application's architecture.

This change moves from a strategy-based `GridManager` to a simpler approach leveraging the `GridRenderer` directly for DOM updates. This eliminates unnecessary abstractions and improves code maintainability.

The changes include removing the old `GridManager`, `EventManager` and introducing new versions.
This commit is contained in:
Janus Knudsen 2025-09-03 18:51:19 +02:00
parent b8b44ddae8
commit 05bb074e9a
4 changed files with 566 additions and 271 deletions

View file

@ -1,31 +1,29 @@
/**
* GridManager - Simplified grid manager using Strategy Pattern
* Now delegates view-specific logic to strategy implementations
* GridManager - Simplified grid manager using centralized GridRenderer
* Delegates DOM rendering to GridRenderer, focuses on coordination
*/
import { eventBus } from '../core/EventBus';
import { calendarConfig } from '../core/CalendarConfig';
import { CoreEvents } from '../constants/CoreEvents';
import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes';
import { ViewStrategy, ViewContext } from '../strategies/ViewStrategy';
import { WeekViewStrategy } from '../strategies/WeekViewStrategy';
import { MonthViewStrategy } from '../strategies/MonthViewStrategy';
import { GridRenderer } from '../renderers/GridRenderer';
import { DateCalculator } from '../utils/DateCalculator';
/**
* Simplified GridManager focused on coordination, not implementation
* Simplified GridManager focused on coordination, delegates rendering to GridRenderer
*/
export class GridManager {
private container: HTMLElement | null = null;
private currentDate: Date = new Date();
private resourceData: ResourceCalendarData | null = null;
private currentStrategy: ViewStrategy;
private currentView: CalendarView = 'week';
private gridRenderer: GridRenderer;
private eventCleanup: (() => void)[] = [];
constructor() {
// Default to week view strategy
this.currentStrategy = new WeekViewStrategy();
// Initialize GridRenderer with config
this.gridRenderer = new GridRenderer(calendarConfig);
this.init();
}
@ -40,11 +38,12 @@ export class GridManager {
}
private subscribeToEvents(): void {
// Listen for view changes to switch strategies
// Listen for view changes
this.eventCleanup.push(
eventBus.on(CoreEvents.VIEW_CHANGED, (e: Event) => {
const detail = (e as CustomEvent).detail;
this.switchViewStrategy(detail.currentView);
this.currentView = detail.currentView;
this.render();
})
);
@ -63,27 +62,10 @@ export class GridManager {
}
/**
* Switch to a different view strategy
* Switch to a different view
*/
public switchViewStrategy(view: CalendarView): void {
// Clean up current strategy
this.currentStrategy.destroy();
// Create new strategy based on view
switch (view) {
case 'week':
case 'day':
this.currentStrategy = new WeekViewStrategy();
break;
case 'month':
this.currentStrategy = new MonthViewStrategy();
break;
default:
this.currentStrategy = new WeekViewStrategy();
}
// Re-render with new strategy
public switchView(view: CalendarView): void {
this.currentView = view;
this.render();
}
@ -96,32 +78,27 @@ export class GridManager {
}
/**
* Main render method - delegates to current strategy
* Main render method - delegates to GridRenderer
*/
public async render(): Promise<void> {
if (!this.container) {
return;
}
// Delegate to GridRenderer with current view context
this.gridRenderer.renderGrid(
this.container,
this.currentDate,
this.resourceData
);
// Create context for strategy
const context: ViewContext = {
currentDate: this.currentDate,
container: this.container,
resourceData: this.resourceData
};
// Calculate period range using DateCalculator
const periodRange = this.getPeriodRange();
// Delegate to current strategy
this.currentStrategy.renderGrid(context);
// Get layout config based on current view
const layoutConfig = this.getLayoutConfig();
// Get layout info from strategy
const layoutConfig = this.currentStrategy.getLayoutConfig();
// Get period range from current strategy
const periodRange = this.currentStrategy.getPeriodRange(this.currentDate);
// Emit grid rendered event with explicit date range
// Emit grid rendered event
eventBus.emit(CoreEvents.GRID_RENDERED, {
container: this.container,
currentDate: this.currentDate,
@ -130,22 +107,48 @@ export class GridManager {
layoutConfig: layoutConfig,
columnCount: layoutConfig.columnCount
});
}
/**
* Get current period label from strategy
* Get current period label using DateCalculator
*/
public getCurrentPeriodLabel(): string {
return this.currentStrategy.getPeriodLabel(this.currentDate);
switch (this.currentView) {
case 'week':
case 'day':
const weekStart = DateCalculator.getISOWeekStart(this.currentDate);
const weekEnd = DateCalculator.getWeekEnd(this.currentDate);
return DateCalculator.formatDateRange(weekStart, weekEnd);
case 'month':
return this.currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
default:
const defaultWeekStart = DateCalculator.getISOWeekStart(this.currentDate);
const defaultWeekEnd = DateCalculator.getWeekEnd(this.currentDate);
return DateCalculator.formatDateRange(defaultWeekStart, defaultWeekEnd);
}
}
/**
* Navigate to next period using strategy
* Navigate to next period using DateCalculator
*/
public navigateNext(): void {
const nextDate = this.currentStrategy.getNextPeriod(this.currentDate);
let nextDate: Date;
switch (this.currentView) {
case 'week':
nextDate = DateCalculator.addWeeks(this.currentDate, 1);
break;
case 'month':
nextDate = this.addMonths(this.currentDate, 1);
break;
case 'day':
nextDate = DateCalculator.addDays(this.currentDate, 1);
break;
default:
nextDate = DateCalculator.addWeeks(this.currentDate, 1);
}
this.currentDate = nextDate;
eventBus.emit(CoreEvents.PERIOD_CHANGED, {
@ -158,14 +161,29 @@ export class GridManager {
}
/**
* Navigate to previous period using strategy
* Navigate to previous period using DateCalculator
*/
public navigatePrevious(): void {
const prevDate = this.currentStrategy.getPreviousPeriod(this.currentDate);
let prevDate: Date;
switch (this.currentView) {
case 'week':
prevDate = DateCalculator.addWeeks(this.currentDate, -1);
break;
case 'month':
prevDate = this.addMonths(this.currentDate, -1);
break;
case 'day':
prevDate = DateCalculator.addDays(this.currentDate, -1);
break;
default:
prevDate = DateCalculator.addWeeks(this.currentDate, -1);
}
this.currentDate = prevDate;
eventBus.emit(CoreEvents.PERIOD_CHANGED, {
direction: 'previous',
direction: 'previous',
newDate: prevDate,
periodLabel: this.getCurrentPeriodLabel()
});
@ -188,26 +206,137 @@ export class GridManager {
}
/**
* Get current view's display dates
* Get current view's display dates using DateCalculator
*/
public getDisplayDates(): Date[] {
return this.currentStrategy.getDisplayDates(this.currentDate);
switch (this.currentView) {
case 'week':
const weekStart = DateCalculator.getISOWeekStart(this.currentDate);
return DateCalculator.getFullWeekDates(weekStart);
case 'month':
return this.getMonthDates(this.currentDate);
case 'day':
return [this.currentDate];
default:
const defaultWeekStart = DateCalculator.getISOWeekStart(this.currentDate);
return DateCalculator.getFullWeekDates(defaultWeekStart);
}
}
/**
* Get period range for current view
*/
private getPeriodRange(): { startDate: Date; endDate: Date } {
switch (this.currentView) {
case 'week':
const weekStart = DateCalculator.getISOWeekStart(this.currentDate);
const weekEnd = DateCalculator.getWeekEnd(this.currentDate);
return {
startDate: weekStart,
endDate: weekEnd
};
case 'month':
return {
startDate: this.getMonthStart(this.currentDate),
endDate: this.getMonthEnd(this.currentDate)
};
case 'day':
return {
startDate: this.currentDate,
endDate: this.currentDate
};
default:
const defaultWeekStart = DateCalculator.getISOWeekStart(this.currentDate);
const defaultWeekEnd = DateCalculator.getWeekEnd(this.currentDate);
return {
startDate: defaultWeekStart,
endDate: defaultWeekEnd
};
}
}
/**
* Get layout config for current view
*/
private getLayoutConfig(): any {
switch (this.currentView) {
case 'week':
return {
columnCount: 7,
type: 'week'
};
case 'month':
return {
columnCount: 7,
type: 'month'
};
case 'day':
return {
columnCount: 1,
type: 'day'
};
default:
return {
columnCount: 7,
type: 'week'
};
}
}
/**
* Clean up all resources
*/
public destroy(): void {
// Clean up event listeners
this.eventCleanup.forEach(cleanup => cleanup());
this.eventCleanup = [];
// Clean up current strategy
this.currentStrategy.destroy();
// Clear references
this.container = null;
this.resourceData = null;
}
/**
* Helper method to add months to a date
*/
private addMonths(date: Date, months: number): Date {
const result = new Date(date);
result.setMonth(result.getMonth() + months);
return result;
}
/**
* Helper method to get month start
*/
private getMonthStart(date: Date): Date {
const result = new Date(date);
result.setDate(1);
result.setHours(0, 0, 0, 0);
return result;
}
/**
* Helper method to get month end
*/
private getMonthEnd(date: Date): Date {
const result = new Date(date);
result.setMonth(result.getMonth() + 1, 0);
result.setHours(23, 59, 59, 999);
return result;
}
/**
* Helper method to get all dates in a month
*/
private getMonthDates(date: Date): Date[] {
const dates: Date[] = [];
const monthStart = this.getMonthStart(date);
const monthEnd = this.getMonthEnd(date);
for (let d = new Date(monthStart); d <= monthEnd; d.setDate(d.getDate() + 1)) {
dates.push(new Date(d));
}
return dates;
}
}