Refactors event handling and grid rendering

Improves calendar performance and data flow by streamlining event emissions and grid rendering logic.

- Replaces generic CONFIG_UPDATE events with REFRESH_REQUESTED
  for more specific refresh triggers.
- Removes redundant grid re-renders on DATE_CHANGED and
  WEEK_CHANGED events, delegating navigation to NavigationManager.
- Introduces VIEW_CHANGED and DATE_CHANGED events for calendar
  mode and date selection, respectively.
- NavigationManager now handles date validation.
- Moves rendering logic from NavigationManager to NavigationRenderer.
- Syncs scroll position based on PERIOD_CHANGED instead of
  NAVIGATION_ANIMATION_COMPLETE.

This change optimizes the calendar's responsiveness and reduces
unnecessary re-renders, leading to a smoother user experience.
This commit is contained in:
Janus Knudsen 2025-08-20 21:38:54 +02:00
parent 4b4dbdc0d6
commit 83c0ce801c
9 changed files with 59 additions and 47 deletions

View file

@ -215,7 +215,7 @@ export class CalendarConfig {
// Update computed values handled in specific update methods // Update computed values handled in specific update methods
// Emit config update event // Emit config update event
eventBus.emit(CoreEvents.CONFIG_UPDATE, { eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key, key,
value, value,
oldValue oldValue
@ -292,7 +292,7 @@ export class CalendarConfig {
} }
// Emit grid settings update event // Emit grid settings update event
eventBus.emit(CoreEvents.CONFIG_UPDATE, { eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key: 'gridSettings', key: 'gridSettings',
value: this.gridSettings, value: this.gridSettings,
oldValue: this.gridSettings oldValue: this.gridSettings
@ -320,7 +320,7 @@ export class CalendarConfig {
this.dateViewSettings = { ...this.dateViewSettings, ...updates }; this.dateViewSettings = { ...this.dateViewSettings, ...updates };
// Emit date view settings update event // Emit date view settings update event
eventBus.emit(CoreEvents.CONFIG_UPDATE, { eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key: 'dateViewSettings', key: 'dateViewSettings',
value: this.dateViewSettings, value: this.dateViewSettings,
oldValue: this.dateViewSettings oldValue: this.dateViewSettings
@ -355,7 +355,7 @@ export class CalendarConfig {
this.resourceViewSettings = { ...this.resourceViewSettings, ...updates }; this.resourceViewSettings = { ...this.resourceViewSettings, ...updates };
// Emit resource view settings update event // Emit resource view settings update event
eventBus.emit(CoreEvents.CONFIG_UPDATE, { eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key: 'resourceViewSettings', key: 'resourceViewSettings',
value: this.resourceViewSettings, value: this.resourceViewSettings,
oldValue: this.resourceViewSettings oldValue: this.resourceViewSettings
@ -409,7 +409,7 @@ export class CalendarConfig {
this.calendarMode = mode; this.calendarMode = mode;
// Emit calendar mode change event // Emit calendar mode change event
eventBus.emit(CoreEvents.CALENDAR_TYPE_CHANGED, { eventBus.emit(CoreEvents.VIEW_CHANGED, {
oldType: oldMode, oldType: oldMode,
newType: mode newType: mode
}); });
@ -429,7 +429,7 @@ export class CalendarConfig {
this.selectedDate = date; this.selectedDate = date;
// Emit date change event // Emit date change event
eventBus.emit(CoreEvents.SELECTED_DATE_CHANGED, { eventBus.emit(CoreEvents.DATE_CHANGED, {
date: date date: date
}); });
} }

View file

@ -144,6 +144,7 @@ export class CalendarManager {
this.eventBus.emit(CoreEvents.DATE_CHANGED, { this.eventBus.emit(CoreEvents.DATE_CHANGED, {
previousDate, previousDate,
currentDate: this.currentDate, currentDate: this.currentDate,
date: this.currentDate, // Add for backward compatibility
view: this.currentView view: this.currentView
}); });

View file

@ -54,21 +54,25 @@ export class GridManager {
); );
// Listen for data changes // Listen for data changes
this.eventCleanup.push( // REMOVED: GridManager should not re-render on DATE_CHANGED
eventBus.on(CoreEvents.DATE_CHANGED, (e: Event) => { // Date navigation is handled by NavigationManager
const detail = (e as CustomEvent).detail; // this.eventCleanup.push(
this.currentDate = detail.currentDate; // eventBus.on(CoreEvents.DATE_CHANGED, (e: Event) => {
this.render(); // const detail = (e as CustomEvent).detail;
}) // this.currentDate = detail.currentDate;
); // this.render();
// })
// );
this.eventCleanup.push( // REMOVED: GridManager should not re-render on WEEK_CHANGED
eventBus.on(CoreEvents.WEEK_CHANGED, (e: Event) => { // Navigation is handled by NavigationManager + NavigationRenderer
const detail = (e as CustomEvent).detail; // this.eventCleanup.push(
this.currentDate = detail.weekStart; // eventBus.on(CoreEvents.WEEK_CHANGED, (e: Event) => {
this.render(); // const detail = (e as CustomEvent).detail;
}) // this.currentDate = detail.weekStart;
); // this.render();
// })
// );
this.eventCleanup.push( this.eventCleanup.push(
eventBus.on(CoreEvents.DATA_LOADED, (e: Event) => { eventBus.on(CoreEvents.DATA_LOADED, (e: Event) => {
@ -136,8 +140,7 @@ export class GridManager {
return; return;
} }
console.group(`🎨 GRID RENDER: ${this.currentDate.toDateString()}`); console.log(`🎨 GridManager: Rendering ${this.currentDate.toDateString()} using ${this.currentStrategy.constructor.name}`);
console.log(`Using strategy: ${this.currentStrategy.constructor.name}`);
// Create context for strategy // Create context for strategy
const context: ViewContext = { const context: ViewContext = {
@ -153,6 +156,8 @@ export class GridManager {
// Get layout info from strategy // Get layout info from strategy
const layoutConfig = this.currentStrategy.getLayoutConfig(); const layoutConfig = this.currentStrategy.getLayoutConfig();
console.log(`GridManager: Emitting GRID_RENDERED for main container`);
// Emit grid rendered event // Emit grid rendered event
eventBus.emit(CoreEvents.GRID_RENDERED, { eventBus.emit(CoreEvents.GRID_RENDERED, {
container: this.container, container: this.container,
@ -161,8 +166,7 @@ export class GridManager {
columnCount: layoutConfig.columnCount columnCount: layoutConfig.columnCount
}); });
console.log(`Grid rendered with ${layoutConfig.columnCount} columns`); console.log(`✅ Grid rendered with ${layoutConfig.columnCount} columns`);
console.groupEnd();
} }
/** /**

View file

@ -35,7 +35,7 @@ export class NavigationManager {
private setupEventListeners(): void { private setupEventListeners(): void {
// Initial DOM update when calendar is initialized // Initial DOM update when calendar is initialized
this.eventBus.on(CoreEvents.CALENDAR_INITIALIZED, () => { this.eventBus.on(CoreEvents.INITIALIZED, () => {
console.log('NavigationManager: Received CALENDAR_INITIALIZED, updating week info'); console.log('NavigationManager: Received CALENDAR_INITIALIZED, updating week info');
this.updateWeekInfo(); this.updateWeekInfo();
}); });
@ -63,9 +63,22 @@ export class NavigationManager {
}); });
// Listen for external navigation requests // Listen for external navigation requests
this.eventBus.on(CoreEvents.NAVIGATE_TO_DATE, (event: Event) => { this.eventBus.on(CoreEvents.DATE_CHANGED, (event: Event) => {
const customEvent = event as CustomEvent; const customEvent = event as CustomEvent;
const targetDate = new Date(customEvent.detail.date); const dateFromEvent = customEvent.detail.date || customEvent.detail.currentDate;
// Validate date before processing
if (!dateFromEvent) {
console.warn('NavigationManager: No date provided in DATE_CHANGED event', customEvent.detail);
return;
}
const targetDate = new Date(dateFromEvent);
if (isNaN(targetDate.getTime())) {
console.warn('NavigationManager: Invalid date in DATE_CHANGED event', dateFromEvent);
return;
}
this.navigateToDate(targetDate); this.navigateToDate(targetDate);
}); });
} }
@ -189,10 +202,6 @@ export class NavigationManager {
// Update week info and notify other managers // Update week info and notify other managers
this.updateWeekInfo(); this.updateWeekInfo();
this.eventBus.emit(CoreEvents.WEEK_CHANGED, {
weekStart: this.currentWeek,
weekEnd: this.dateCalculator.addDays(this.currentWeek, 6)
});
// Emit period change event for ScrollManager // Emit period change event for ScrollManager
this.eventBus.emit(CoreEvents.PERIOD_CHANGED, { this.eventBus.emit(CoreEvents.PERIOD_CHANGED, {
@ -247,11 +256,6 @@ export class NavigationManager {
this.currentWeek = new Date(weekStart); this.currentWeek = new Date(weekStart);
this.targetWeek = new Date(weekStart); this.targetWeek = new Date(weekStart);
this.updateWeekInfo(); this.updateWeekInfo();
this.eventBus.emit(CoreEvents.WEEK_CHANGED, {
weekStart: this.currentWeek,
weekEnd: DateUtils.addDays(this.currentWeek, 6)
});
} }
// Rendering methods moved to NavigationRenderer for better separation of concerns // Rendering methods moved to NavigationRenderer for better separation of concerns

View file

@ -31,7 +31,7 @@ export class ScrollManager {
private subscribeToEvents(): void { private subscribeToEvents(): void {
// Handle navigation animation completion - sync time axis position // Handle navigation animation completion - sync time axis position
eventBus.on(CoreEvents.NAVIGATION_ANIMATION_COMPLETE, () => { eventBus.on(CoreEvents.PERIOD_CHANGED, () => {
this.syncTimeAxisPosition(); this.syncTimeAxisPosition();
this.setupScrolling(); this.setupScrolling();
}); });

View file

@ -24,7 +24,7 @@ export interface ColumnRenderContext {
* Date-based column renderer (original functionality) * Date-based column renderer (original functionality)
*/ */
export class DateColumnRenderer implements ColumnRenderer { export class DateColumnRenderer implements ColumnRenderer {
private dateCalculator: DateCalculator; private dateCalculator!: DateCalculator;
render(columnContainer: HTMLElement, context: ColumnRenderContext): void { render(columnContainer: HTMLElement, context: ColumnRenderContext): void {
const { currentWeek, config } = context; const { currentWeek, config } = context;

View file

@ -176,6 +176,7 @@ export class DateEventRenderer extends BaseEventRenderer {
const matches = eventDateStr === columnDate; const matches = eventDateStr === columnDate;
if (!matches) { if (!matches) {
if(event.title == 'Architecture Planning')
console.log(`DateEventRenderer: Event ${event.title} (${eventDateStr}) does not match column (${columnDate})`); console.log(`DateEventRenderer: Event ${event.title} (${eventDateStr}) does not match column (${columnDate})`);
} }

View file

@ -25,7 +25,7 @@ export interface HeaderRenderContext {
* Date-based header renderer (original functionality) * Date-based header renderer (original functionality)
*/ */
export class DateHeaderRenderer implements HeaderRenderer { export class DateHeaderRenderer implements HeaderRenderer {
private dateCalculator: DateCalculator; private dateCalculator!: DateCalculator;
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void { render(calendarHeader: HTMLElement, context: HeaderRenderContext): void {
const { currentWeek, config, allDayEvents = [] } = context; const { currentWeek, config, allDayEvents = [] } = context;
@ -34,7 +34,7 @@ export class DateHeaderRenderer implements HeaderRenderer {
this.dateCalculator = new DateCalculator(config); this.dateCalculator = new DateCalculator(config);
const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const dates = this.dateCalculator.getWorkWeekDates(currentWeek);
const weekDays = config.get('weekDays'); const weekDays = config.getDateViewSettings().weekDays;
const daysToShow = dates.slice(0, weekDays); const daysToShow = dates.slice(0, weekDays);
daysToShow.forEach((date, index) => { daysToShow.forEach((date, index) => {
@ -62,7 +62,7 @@ export class DateHeaderRenderer implements HeaderRenderer {
const { currentWeek, config, allDayEvents = [] } = context; const { currentWeek, config, allDayEvents = [] } = context;
const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const dates = this.dateCalculator.getWorkWeekDates(currentWeek);
const weekDays = config.get('weekDays'); const weekDays = config.getDateViewSettings().weekDays;
const daysToShow = dates.slice(0, weekDays); const daysToShow = dates.slice(0, weekDays);
// Process each all-day event to calculate its span // Process each all-day event to calculate its span

View file

@ -26,7 +26,7 @@ export class NavigationRenderer {
* Setup event listeners for DOM updates * Setup event listeners for DOM updates
*/ */
private setupEventListeners(): void { private setupEventListeners(): void {
this.eventBus.on(CoreEvents.WEEK_INFO_UPDATED, (event: Event) => { this.eventBus.on(CoreEvents.WEEK_CHANGED, (event: Event) => {
const customEvent = event as CustomEvent; const customEvent = event as CustomEvent;
const { weekNumber, dateRange } = customEvent.detail; const { weekNumber, dateRange } = customEvent.detail;
this.updateWeekInfoInDOM(weekNumber, dateRange); this.updateWeekInfoInDOM(weekNumber, dateRange);
@ -83,14 +83,16 @@ export class NavigationRenderer {
console.log('2. Rendering headers and columns...'); console.log('2. Rendering headers and columns...');
this.renderWeekContentInContainer(newGrid, weekStart); this.renderWeekContentInContainer(newGrid, weekStart);
console.log('3. Pre-rendering events synchronously...'); console.log('3. Emitting GRID_RENDERED for navigation container...');
this.eventRenderer.renderEvents({ this.eventBus.emit(CoreEvents.GRID_RENDERED, {
container: newGrid, container: newGrid, // Specific grid container, not parent
currentDate: weekStart,
startDate: weekStart, startDate: weekStart,
endDate: weekEnd endDate: weekEnd,
isNavigation: true // Flag to indicate this is navigation rendering
}); });
console.log('✅ Container ready with pre-rendered events'); console.log('✅ Container ready with GRID_RENDERED event emitted');
console.groupEnd(); console.groupEnd();
return newGrid; return newGrid;
} }