Refactors event rendering to be event-driven
Moves event rendering logic into a dedicated EventRenderer class that uses a strategy pattern for different calendar types. The rendering is now triggered by `GRID_RENDERED` and `CONTAINER_READY_FOR_EVENTS` events, emitted by the GridManager and NavigationManager respectively. This change decouples the CalendarManager from direct event rendering and allows for more flexible and efficient event updates. The EventManager now has a method to fetch events for a given time period. Removes direct calls to event rendering from CalendarManager. Improves animation transitions by using pre-rendered containers in the NavigationManager.
This commit is contained in:
parent
afe5b6b899
commit
a03f314c4a
9 changed files with 271 additions and 166 deletions
|
|
@ -76,14 +76,18 @@ export class CalendarManager {
|
|||
this.setView(this.currentView);
|
||||
this.setCurrentDate(this.currentDate);
|
||||
|
||||
// Step 5: Render events (after view is set) - only render events for current period
|
||||
console.log('🎨 Rendering events for current period...');
|
||||
const events = this.getEventsForCurrentPeriod();
|
||||
await this.eventRenderer.renderEvents(events);
|
||||
// Step 5: Event rendering will be triggered by GRID_RENDERED event
|
||||
console.log('🎨 Event rendering will be triggered automatically by grid events...');
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('✅ CalendarManager: Simple initialization complete');
|
||||
|
||||
// Emit initialization complete event
|
||||
this.eventBus.emit(EventTypes.CALENDAR_INITIALIZED, {
|
||||
currentDate: this.currentDate,
|
||||
currentView: this.currentView
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ CalendarManager initialization failed:', error);
|
||||
throw error;
|
||||
|
|
@ -110,10 +114,7 @@ export class CalendarManager {
|
|||
date: this.currentDate
|
||||
});
|
||||
|
||||
// Re-render events for new view if calendar is initialized
|
||||
if (this.isInitialized) {
|
||||
this.rerenderEventsForCurrentPeriod();
|
||||
}
|
||||
// Grid re-rendering will trigger event rendering automatically via GRID_RENDERED event
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -132,10 +133,7 @@ export class CalendarManager {
|
|||
view: this.currentView
|
||||
});
|
||||
|
||||
// Re-render events for new period if calendar is initialized
|
||||
if (this.isInitialized) {
|
||||
this.rerenderEventsForCurrentPeriod();
|
||||
}
|
||||
// Grid update for new date will trigger event rendering automatically via GRID_RENDERED event
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -313,34 +311,6 @@ export class CalendarManager {
|
|||
return previousDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get events filtered for the current period (week/month/day)
|
||||
*/
|
||||
private getEventsForCurrentPeriod(): CalendarEvent[] {
|
||||
const allEvents = this.eventManager.getEvents();
|
||||
|
||||
// Calculate current period based on view
|
||||
const period = this.calculateCurrentPeriod();
|
||||
|
||||
// Filter events to only include those in the current period
|
||||
const filteredEvents = allEvents.filter(event => {
|
||||
const eventStart = new Date(event.start);
|
||||
const eventEnd = new Date(event.end);
|
||||
const periodStart = new Date(period.start);
|
||||
const periodEnd = new Date(period.end);
|
||||
|
||||
// Include event if it overlaps with the period
|
||||
return eventStart <= periodEnd && eventEnd >= periodStart;
|
||||
});
|
||||
|
||||
// Also filter out all-day events (handled by GridManager)
|
||||
const nonAllDayEvents = filteredEvents.filter(event => !event.allDay);
|
||||
|
||||
console.log(`CalendarManager: Filtered ${allEvents.length} total events to ${nonAllDayEvents.length} non-all-day events for current period`);
|
||||
|
||||
return nonAllDayEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the current period based on view and date
|
||||
*/
|
||||
|
|
@ -399,12 +369,4 @@ export class CalendarManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-render events for the current period
|
||||
*/
|
||||
private async rerenderEventsForCurrentPeriod(): Promise<void> {
|
||||
console.log('CalendarManager: Re-rendering events for current period');
|
||||
const events = this.getEventsForCurrentPeriod();
|
||||
await this.eventRenderer.renderEvents(events);
|
||||
}
|
||||
}
|
||||
|
|
@ -119,6 +119,19 @@ export class EventManager {
|
|||
return this.events.find(event => event.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get events for a specific time period
|
||||
*/
|
||||
public getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[] {
|
||||
return this.events.filter(event => {
|
||||
const eventStart = new Date(event.start);
|
||||
const eventEnd = new Date(event.end);
|
||||
|
||||
// Event overlaps period if it starts before period ends AND ends after period starts
|
||||
return eventStart <= endDate && eventEnd >= startDate;
|
||||
});
|
||||
}
|
||||
|
||||
public addEvent(event: Omit<CalendarEvent, 'id'>): CalendarEvent {
|
||||
const newEvent: CalendarEvent = {
|
||||
...event,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { EventBus } from '../core/EventBus';
|
||||
import { IEventBus, CalendarEvent } from '../types/CalendarTypes';
|
||||
import { IEventBus, CalendarEvent, RenderContext } from '../types/CalendarTypes';
|
||||
import { EventTypes } from '../constants/EventTypes';
|
||||
import { StateEvents } from '../types/CalendarState';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
||||
import { EventManager } from './EventManager';
|
||||
import { EventRendererStrategy } from '../renderers/EventRenderer';
|
||||
|
||||
/**
|
||||
* EventRenderer - Render events i DOM med positionering using Strategy Pattern
|
||||
|
|
@ -11,73 +13,133 @@ import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
|||
*/
|
||||
export class EventRenderer {
|
||||
private eventBus: IEventBus;
|
||||
private eventManager: EventManager;
|
||||
private strategy: EventRendererStrategy;
|
||||
|
||||
constructor(eventBus: IEventBus) {
|
||||
constructor(eventBus: IEventBus, eventManager: EventManager) {
|
||||
this.eventBus = eventBus;
|
||||
this.eventManager = eventManager;
|
||||
|
||||
// Cache strategy at initialization
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
this.strategy = CalendarTypeFactory.getEventRenderer(calendarType);
|
||||
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method to render events - called directly by CalendarManager
|
||||
* Render events in a specific container for a given period
|
||||
*/
|
||||
public async renderEvents(events: CalendarEvent[]): Promise<void> {
|
||||
console.log('EventRenderer: Direct renderEvents called with', events.length, 'events');
|
||||
public renderEvents(context: RenderContext): void {
|
||||
console.log('EventRenderer: Rendering events for period', {
|
||||
startDate: context.startDate,
|
||||
endDate: context.endDate,
|
||||
container: context.container
|
||||
});
|
||||
|
||||
// Get events from EventManager for the period
|
||||
const events = this.eventManager.getEventsForPeriod(
|
||||
context.startDate,
|
||||
context.endDate
|
||||
);
|
||||
|
||||
console.log(`EventRenderer: Found ${events.length} events for period`);
|
||||
|
||||
// Debug: Check if we have any events
|
||||
if (events.length === 0) {
|
||||
console.warn('EventRenderer: No events to render');
|
||||
console.log('EventRenderer: No events to render for this period');
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug: Log first event details
|
||||
console.log('EventRenderer: First event details:', {
|
||||
title: events[0].title,
|
||||
start: events[0].start,
|
||||
end: events[0].end,
|
||||
allDay: events[0].allDay
|
||||
});
|
||||
|
||||
// Get the appropriate event renderer strategy
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
const eventRenderer = CalendarTypeFactory.getEventRenderer(calendarType);
|
||||
|
||||
console.log(`EventRenderer: Using ${calendarType} event renderer strategy`);
|
||||
|
||||
// Debug: Check if columns exist
|
||||
const columns = document.querySelectorAll('swp-day-column');
|
||||
console.log(`EventRenderer: Found ${columns.length} day columns in DOM`);
|
||||
|
||||
// Use strategy to render events
|
||||
eventRenderer.renderEvents(events, calendarConfig);
|
||||
// Use cached strategy to render events in the specific container
|
||||
this.strategy.renderEvents(events, context.container, calendarConfig);
|
||||
|
||||
console.log(`EventRenderer: Successfully rendered ${events.length} events`);
|
||||
}
|
||||
|
||||
private setupEventListeners(): void {
|
||||
// Keep only UI-related event listeners
|
||||
this.eventBus.on(EventTypes.VIEW_RENDERED, () => {
|
||||
// Clear existing events when view changes
|
||||
this.clearEvents();
|
||||
// Event-driven rendering: React to grid and container events
|
||||
this.eventBus.on(EventTypes.GRID_RENDERED, (event: Event) => {
|
||||
console.log('EventRenderer: Received GRID_RENDERED event');
|
||||
this.handleGridRendered(event as CustomEvent);
|
||||
});
|
||||
|
||||
// Handle calendar type changes
|
||||
this.eventBus.on(EventTypes.CONTAINER_READY_FOR_EVENTS, (event: Event) => {
|
||||
console.log('EventRenderer: Received CONTAINER_READY_FOR_EVENTS event');
|
||||
this.handleContainerReady(event as CustomEvent);
|
||||
});
|
||||
|
||||
this.eventBus.on(EventTypes.VIEW_CHANGED, (event: Event) => {
|
||||
console.log('EventRenderer: Received VIEW_CHANGED event');
|
||||
this.handleViewChanged(event as CustomEvent);
|
||||
});
|
||||
|
||||
// Handle calendar type changes - update cached strategy
|
||||
this.eventBus.on(EventTypes.CALENDAR_TYPE_CHANGED, () => {
|
||||
// Re-render would need to be triggered by CalendarManager now
|
||||
this.clearEvents();
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
this.strategy = CalendarTypeFactory.getEventRenderer(calendarType);
|
||||
console.log(`EventRenderer: Updated strategy to ${calendarType}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private clearEvents(): void {
|
||||
console.warn(`🗑️ EventRenderer: clearEvents() called from EventRenderer manager`);
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
const eventRenderer = CalendarTypeFactory.getEventRenderer(calendarType);
|
||||
eventRenderer.clearEvents();
|
||||
/**
|
||||
* Handle GRID_RENDERED event - render events in the current grid
|
||||
*/
|
||||
private handleGridRendered(event: CustomEvent): void {
|
||||
const { container, startDate, endDate } = event.detail;
|
||||
|
||||
if (!container) {
|
||||
console.error('EventRenderer: No container in GRID_RENDERED event', event.detail);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use period from event or fallback to calculated period
|
||||
const periodStart = startDate;
|
||||
const periodEnd = endDate;
|
||||
|
||||
this.renderEvents({
|
||||
container: container,
|
||||
startDate: periodStart,
|
||||
endDate: periodEnd
|
||||
});
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
// Refresh would need to be coordinated by CalendarManager now
|
||||
/**
|
||||
* Handle CONTAINER_READY_FOR_EVENTS event - render events in pre-rendered container
|
||||
*/
|
||||
private handleContainerReady(event: CustomEvent): void {
|
||||
const { container, startDate, endDate } = event.detail;
|
||||
|
||||
if (!container || !startDate || !endDate) {
|
||||
console.error('EventRenderer: Invalid CONTAINER_READY_FOR_EVENTS event data', event.detail);
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderEvents({
|
||||
container: container,
|
||||
startDate: new Date(startDate),
|
||||
endDate: new Date(endDate)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle VIEW_CHANGED event - clear and re-render for new view
|
||||
*/
|
||||
private handleViewChanged(event: CustomEvent): void {
|
||||
// Clear all existing events since view structure may have changed
|
||||
this.clearEvents();
|
||||
|
||||
// New rendering will be triggered by subsequent GRID_RENDERED event
|
||||
console.log('EventRenderer: Cleared events for view change, waiting for GRID_RENDERED');
|
||||
}
|
||||
private clearEvents(container?: HTMLElement): void {
|
||||
console.log(`EventRenderer: Clearing events`, container ? 'in container' : 'globally');
|
||||
this.strategy.clearEvents(container);
|
||||
}
|
||||
|
||||
public refresh(container?: HTMLElement): void {
|
||||
// Clear events in specific container or globally
|
||||
this.clearEvents(container);
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,16 @@ export class GridManager {
|
|||
|
||||
const columnCount = this.getColumnCount();
|
||||
console.log(`GridManager: Render complete - created ${columnCount} columns`);
|
||||
|
||||
// Emit GRID_RENDERED event to trigger event rendering
|
||||
const weekEnd = this.currentWeek ? new Date(this.currentWeek.getTime() + 6 * 24 * 60 * 60 * 1000) : null;
|
||||
eventBus.emit(EventTypes.GRID_RENDERED, {
|
||||
container: this.grid,
|
||||
currentWeek: this.currentWeek,
|
||||
startDate: this.currentWeek,
|
||||
endDate: weekEnd,
|
||||
columnCount: columnCount
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -113,11 +113,11 @@ export class NavigationManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* POC-style animation transition - creates new grid container and slides it in
|
||||
* Animation transition using pre-rendered containers when available
|
||||
*/
|
||||
private animateTransition(direction: 'prev' | 'next', targetWeek: Date): void {
|
||||
const container = document.querySelector('swp-calendar-container');
|
||||
const currentGrid = container?.querySelector('swp-grid-container');
|
||||
const currentGrid = container?.querySelector('swp-grid-container:not([data-prerendered])');
|
||||
|
||||
if (!container || !currentGrid) {
|
||||
console.warn('NavigationManager: Required DOM elements not found');
|
||||
|
|
@ -126,31 +126,15 @@ export class NavigationManager {
|
|||
|
||||
console.log(`NavigationManager: Starting ${direction} animation to ${targetWeek.toDateString()}`);
|
||||
|
||||
// Create new grid container (POC approach)
|
||||
const newGrid = document.createElement('swp-grid-container');
|
||||
newGrid.innerHTML = `
|
||||
<swp-calendar-header></swp-calendar-header>
|
||||
<swp-scrollable-content>
|
||||
<swp-time-grid>
|
||||
<swp-grid-lines></swp-grid-lines>
|
||||
<swp-day-columns></swp-day-columns>
|
||||
</swp-time-grid>
|
||||
</swp-scrollable-content>
|
||||
`;
|
||||
let newGrid: HTMLElement;
|
||||
|
||||
// Position new grid off-screen (POC positioning)
|
||||
newGrid.style.position = 'absolute';
|
||||
newGrid.style.top = '0';
|
||||
newGrid.style.left = '0';
|
||||
newGrid.style.width = '100%';
|
||||
newGrid.style.height = '100%';
|
||||
newGrid.style.transform = direction === 'next' ? 'translateX(100%)' : 'translateX(-100%)';
|
||||
// Always create a fresh container for consistent behavior
|
||||
console.log('NavigationManager: Creating new container');
|
||||
newGrid = this.renderContainer(container as HTMLElement, targetWeek);
|
||||
|
||||
// Add to container
|
||||
container.appendChild(newGrid);
|
||||
|
||||
// Render new content for target week
|
||||
this.renderWeekContent(newGrid, targetWeek);
|
||||
// Clear any existing transforms before animation
|
||||
newGrid.style.transform = '';
|
||||
(currentGrid as HTMLElement).style.transform = '';
|
||||
|
||||
// Animate transition using Web Animations API
|
||||
const slideOutAnimation = (currentGrid as HTMLElement).animate([
|
||||
|
|
@ -181,6 +165,7 @@ export class NavigationManager {
|
|||
|
||||
// Reset positioning
|
||||
newGrid.style.position = 'relative';
|
||||
newGrid.removeAttribute('data-prerendered');
|
||||
|
||||
// Update state
|
||||
this.currentWeek = new Date(targetWeek);
|
||||
|
|
@ -198,6 +183,7 @@ export class NavigationManager {
|
|||
weekEnd: DateUtils.addDays(this.currentWeek, 6)
|
||||
});
|
||||
|
||||
|
||||
// Emit animation complete event for ScrollManager
|
||||
this.eventBus.emit(EventTypes.NAVIGATION_ANIMATION_COMPLETE, {
|
||||
direction,
|
||||
|
|
@ -335,4 +321,97 @@ export class NavigationManager {
|
|||
weekEnd: DateUtils.addDays(this.currentWeek, 6)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a complete container with content and events
|
||||
*/
|
||||
private renderContainer(parentContainer: HTMLElement, weekStart: Date): HTMLElement {
|
||||
console.log('NavigationManager: Rendering new container for week:', weekStart.toDateString());
|
||||
|
||||
// Create new grid container
|
||||
const newGrid = document.createElement('swp-grid-container');
|
||||
newGrid.innerHTML = `
|
||||
<swp-calendar-header></swp-calendar-header>
|
||||
<swp-scrollable-content>
|
||||
<swp-time-grid>
|
||||
<swp-grid-lines></swp-grid-lines>
|
||||
<swp-day-columns></swp-day-columns>
|
||||
</swp-time-grid>
|
||||
</swp-scrollable-content>
|
||||
`;
|
||||
|
||||
// Position new grid - NO transform here, let Animation API handle it
|
||||
newGrid.style.position = 'absolute';
|
||||
newGrid.style.top = '0';
|
||||
newGrid.style.left = '0';
|
||||
newGrid.style.width = '100%';
|
||||
newGrid.style.height = '100%';
|
||||
|
||||
// Add to parent container
|
||||
parentContainer.appendChild(newGrid);
|
||||
|
||||
// Render week content (headers and columns)
|
||||
this.renderWeekContentInContainer(newGrid, weekStart);
|
||||
|
||||
// Emit event to trigger event rendering
|
||||
const weekEnd = DateUtils.addDays(weekStart, 6);
|
||||
this.eventBus.emit(EventTypes.CONTAINER_READY_FOR_EVENTS, {
|
||||
container: newGrid,
|
||||
startDate: weekStart,
|
||||
endDate: weekEnd
|
||||
});
|
||||
|
||||
console.log('NavigationManager: Container rendered with content and events triggered');
|
||||
return newGrid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Render week content in specific container
|
||||
*/
|
||||
private renderWeekContentInContainer(gridContainer: HTMLElement, weekStart: Date): void {
|
||||
const header = gridContainer.querySelector('swp-calendar-header');
|
||||
const dayColumns = gridContainer.querySelector('swp-day-columns');
|
||||
|
||||
if (!header || !dayColumns) return;
|
||||
|
||||
// Clear existing content
|
||||
header.innerHTML = '';
|
||||
dayColumns.innerHTML = '';
|
||||
|
||||
// Render headers for target week
|
||||
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const date = new Date(weekStart);
|
||||
date.setDate(date.getDate() + i);
|
||||
|
||||
const headerElement = document.createElement('swp-day-header');
|
||||
if (this.isToday(date)) {
|
||||
headerElement.dataset.today = 'true';
|
||||
}
|
||||
|
||||
headerElement.innerHTML = `
|
||||
<swp-day-name>${days[date.getDay()]}</swp-day-name>
|
||||
<swp-day-date>${date.getDate()}</swp-day-date>
|
||||
`;
|
||||
headerElement.dataset.date = this.formatDate(date);
|
||||
|
||||
header.appendChild(headerElement);
|
||||
}
|
||||
|
||||
// Render day columns for target week (with hardcoded test event)
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const column = document.createElement('swp-day-column');
|
||||
const date = new Date(weekStart);
|
||||
date.setDate(date.getDate() + i);
|
||||
column.dataset.date = this.formatDate(date);
|
||||
|
||||
const eventsLayer = document.createElement('swp-events-layer');
|
||||
column.appendChild(eventsLayer);
|
||||
|
||||
|
||||
dayColumns.appendChild(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue