diff --git a/src/index.ts b/src/index.ts index f2b71ae..8d337f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,9 +22,9 @@ import { HeaderManager } from './managers/HeaderManager'; import { ConfigManager } from './managers/ConfigManager'; // Import renderers -import { DateHeaderRenderer, type HeaderRenderer } from './renderers/HeaderRenderer'; +import { DateHeaderRenderer, type IHeaderRenderer } from './renderers/DateHeaderRenderer'; import { DateColumnRenderer, type ColumnRenderer } from './renderers/ColumnRenderer'; -import { DateEventRenderer, type EventRendererStrategy } from './renderers/EventRenderer'; +import { DateEventRenderer, type IEventRenderer } from './renderers/EventRenderer'; import { AllDayEventRenderer } from './renderers/AllDayEventRenderer'; import { GridRenderer } from './renderers/GridRenderer'; import { NavigationRenderer } from './renderers/NavigationRenderer'; @@ -87,9 +87,9 @@ async function initializeCalendar(): Promise { builder.registerInstance(eventBus).as(); // Register renderers - builder.registerType(DateHeaderRenderer).as().singleInstance(); + builder.registerType(DateHeaderRenderer).as().singleInstance(); builder.registerType(DateColumnRenderer).as().singleInstance(); - builder.registerType(DateEventRenderer).as().singleInstance(); + builder.registerType(DateEventRenderer).as().singleInstance(); // Register core services and utilities builder.registerType(DateService).as().singleInstance(); diff --git a/src/managers/HeaderManager.ts b/src/managers/HeaderManager.ts index b722698..8c6af1e 100644 --- a/src/managers/HeaderManager.ts +++ b/src/managers/HeaderManager.ts @@ -1,7 +1,7 @@ import { eventBus } from '../core/EventBus'; import { CalendarConfig } from '../core/CalendarConfig'; import { CoreEvents } from '../constants/CoreEvents'; -import { HeaderRenderer, HeaderRenderContext } from '../renderers/HeaderRenderer'; +import { IHeaderRenderer, HeaderRenderContext } from '../renderers/DateHeaderRenderer'; import { DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, HeaderReadyEventPayload } from '../types/EventTypes'; import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils'; @@ -11,10 +11,10 @@ import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils'; * Uses dependency injection for renderer strategy */ export class HeaderManager { - private headerRenderer: HeaderRenderer; + private headerRenderer: IHeaderRenderer; private config: CalendarConfig; - constructor(headerRenderer: HeaderRenderer, config: CalendarConfig) { + constructor(headerRenderer: IHeaderRenderer, config: CalendarConfig) { this.headerRenderer = headerRenderer; this.config = config; diff --git a/src/renderers/AllDayEventRenderer.ts b/src/renderers/AllDayEventRenderer.ts index 9548b54..7c6b8e3 100644 --- a/src/renderers/AllDayEventRenderer.ts +++ b/src/renderers/AllDayEventRenderer.ts @@ -4,7 +4,7 @@ import { EventLayout } from '../utils/AllDayLayoutEngine'; import { ColumnBounds } from '../utils/ColumnDetectionUtils'; import { EventManager } from '../managers/EventManager'; import { DragStartEventPayload } from '../types/EventTypes'; -import { EventRendererStrategy } from './EventRenderer'; +import { IEventRenderer } from './EventRenderer'; export class AllDayEventRenderer { diff --git a/src/renderers/HeaderRenderer.ts b/src/renderers/DateHeaderRenderer.ts similarity index 89% rename from src/renderers/HeaderRenderer.ts rename to src/renderers/DateHeaderRenderer.ts index bba6fcd..ff18396 100644 --- a/src/renderers/HeaderRenderer.ts +++ b/src/renderers/DateHeaderRenderer.ts @@ -6,7 +6,7 @@ import { DateService } from '../utils/DateService'; /** * Interface for header rendering strategies */ -export interface HeaderRenderer { +export interface IHeaderRenderer { render(calendarHeader: HTMLElement, context: HeaderRenderContext): void; } @@ -22,7 +22,7 @@ export interface HeaderRenderContext { /** * Date-based header renderer (original functionality) */ -export class DateHeaderRenderer implements HeaderRenderer { +export class DateHeaderRenderer implements IHeaderRenderer { private dateService!: DateService; render(calendarHeader: HTMLElement, context: HeaderRenderContext): void { @@ -33,8 +33,8 @@ export class DateHeaderRenderer implements HeaderRenderer { calendarHeader.appendChild(allDayContainer); // Initialize date service with timezone and locale from config - const timezone = config.getTimezone?.() || 'Europe/Copenhagen'; - const locale = config.getLocale?.() || 'da-DK'; + const timezone = config.getTimezone(); + const locale = config.getLocale(); this.dateService = new DateService(config); const workWeekSettings = config.getWorkWeekSettings(); diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts index 3a84582..f56515d 100644 --- a/src/renderers/EventRenderer.ts +++ b/src/renderers/EventRenderer.ts @@ -13,7 +13,7 @@ import { EventLayoutCoordinator, GridGroupLayout, StackedEventLayout } from '../ /** * Interface for event rendering strategies */ -export interface EventRendererStrategy { +export interface IEventRenderer { renderEvents(events: CalendarEvent[], container: HTMLElement): void; clearEvents(container?: HTMLElement): void; handleDragStart?(payload: DragStartEventPayload): void; @@ -29,7 +29,7 @@ export interface EventRendererStrategy { /** * Date-based event renderer */ -export class DateEventRenderer implements EventRendererStrategy { +export class DateEventRenderer implements IEventRenderer { private dateService: DateService; private stackManager: EventStackManager; diff --git a/src/renderers/EventRendererManager.ts b/src/renderers/EventRendererManager.ts index 9b93582..8a34a7e 100644 --- a/src/renderers/EventRendererManager.ts +++ b/src/renderers/EventRendererManager.ts @@ -2,7 +2,7 @@ import { EventBus } from '../core/EventBus'; import { IEventBus, CalendarEvent, RenderContext } from '../types/CalendarTypes'; import { CoreEvents } from '../constants/CoreEvents'; import { EventManager } from '../managers/EventManager'; -import { EventRendererStrategy } from './EventRenderer'; +import { IEventRenderer } from './EventRenderer'; import { SwpEventElement } from '../elements/SwpEventElement'; import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, DragMouseEnterColumnEventPayload, DragColumnChangeEventPayload, HeaderReadyEventPayload, ResizeEndEventPayload } from '../types/EventTypes'; import { DateService } from '../utils/DateService'; @@ -14,7 +14,7 @@ import { ColumnBounds } from '../utils/ColumnDetectionUtils'; export class EventRenderingService { private eventBus: IEventBus; private eventManager: EventManager; - private strategy: EventRendererStrategy; + private strategy: IEventRenderer; private dateService: DateService; private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null; @@ -22,7 +22,7 @@ export class EventRenderingService { constructor( eventBus: IEventBus, eventManager: EventManager, - strategy: EventRendererStrategy, + strategy: IEventRenderer, dateService: DateService ) { this.eventBus = eventBus; diff --git a/src/renderers/GridRenderer.ts b/src/renderers/GridRenderer.ts index 46b1a54..1fe47b8 100644 --- a/src/renderers/GridRenderer.ts +++ b/src/renderers/GridRenderer.ts @@ -7,8 +7,76 @@ import { CoreEvents } from '../constants/CoreEvents'; import { TimeFormatter } from '../utils/TimeFormatter'; /** - * GridRenderer - Centralized DOM rendering for calendar grid - * Optimized to reduce redundant DOM operations and improve performance + * GridRenderer - Centralized DOM rendering for calendar grid structure + * + * ARCHITECTURE OVERVIEW: + * ===================== + * GridRenderer is responsible for creating and managing the complete DOM structure + * of the calendar grid. It follows the Strategy Pattern by delegating specific + * rendering tasks to specialized renderers (DateHeaderRenderer, ColumnRenderer). + * + * RESPONSIBILITY HIERARCHY: + * ======================== + * GridRenderer (this file) + * ├─ Creates overall grid skeleton + * ├─ Manages time axis (hour markers) + * └─ Delegates to specialized renderers: + * ├─ DateHeaderRenderer → Renders date headers + * └─ ColumnRenderer → Renders day columns + * + * DOM STRUCTURE CREATED: + * ===================== + * + * ← GridRenderer + * ← GridRenderer + * 00:00 ← GridRenderer (iterates hours) + * + * ← GridRenderer + * ← GridRenderer creates container + * ← DateHeaderRenderer (iterates dates) + * + * ← GridRenderer + * ← GridRenderer + * ← GridRenderer + * ← GridRenderer creates container + * ← ColumnRenderer (iterates dates) + * + * + * + * + * + * + * RENDERING FLOW: + * ============== + * 1. renderGrid() - Entry point called by GridManager + * ├─ First render: createCompleteGridStructure() + * └─ Updates: updateGridContent() + * + * 2. createCompleteGridStructure() + * ├─ Creates header spacer + * ├─ Creates time axis (calls createOptimizedTimeAxis) + * └─ Creates grid container (calls createOptimizedGridContainer) + * + * 3. createOptimizedGridContainer() + * ├─ Creates calendar header container + * ├─ Creates scrollable content structure + * └─ Creates column container (calls renderColumnContainer) + * + * 4. renderColumnContainer() + * └─ Delegates to ColumnRenderer.render() + * └─ ColumnRenderer iterates dates and creates columns + * + * OPTIMIZATION STRATEGY: + * ===================== + * - Caches DOM references (cachedGridContainer, cachedTimeAxis) + * - Uses DocumentFragment for batch DOM insertions + * - Only updates changed content on re-renders + * - Delegates specialized tasks to strategy renderers + * + * USAGE EXAMPLE: + * ============= + * const gridRenderer = new GridRenderer(columnRenderer, dateService, config); + * gridRenderer.renderGrid(containerElement, new Date(), 'week'); */ export class GridRenderer { private cachedGridContainer: HTMLElement | null = null; @@ -27,6 +95,16 @@ export class GridRenderer { this.config = config; } + /** + * Main entry point for rendering the complete calendar grid + * + * This method decides between full render (first time) or optimized update. + * It caches the grid reference for performance. + * + * @param grid - Container element where grid will be rendered + * @param currentDate - Base date for the current view (e.g., any date in the week) + * @param view - Calendar view type (day/week/month) + */ public renderGrid( grid: HTMLElement, currentDate: Date, @@ -43,8 +121,6 @@ export class GridRenderer { // Only clear and rebuild if grid is empty (first render) if (grid.children.length === 0) { this.createCompleteGridStructure(grid, currentDate, view); - // Setup grid-related event listeners on first render - // this.setupGridEventListeners(); } else { // Optimized update - only refresh dynamic content this.updateGridContent(grid, currentDate, view); @@ -52,7 +128,19 @@ export class GridRenderer { } /** - * Create complete grid structure in one operation + * Creates the complete grid structure from scratch + * + * Uses DocumentFragment for optimal performance by minimizing reflows. + * Creates all child elements in memory first, then appends everything at once. + * + * Structure created: + * 1. Header spacer (placeholder for alignment) + * 2. Time axis (hour markers 00:00-23:00) + * 3. Grid container (header + scrollable content) + * + * @param grid - Parent container + * @param currentDate - Current view date + * @param view - View type */ private createCompleteGridStructure( grid: HTMLElement, @@ -80,6 +168,14 @@ export class GridRenderer { grid.appendChild(fragment); } + /** + * Creates the time axis with hour markers + * + * Iterates from dayStartHour to dayEndHour (configured in GridSettings). + * Each marker shows the hour in the configured time format. + * + * @returns Time axis element with all hour markers + */ private createOptimizedTimeAxis(): HTMLElement { const timeAxis = document.createElement('swp-time-axis'); const timeAxisContent = document.createElement('swp-time-axis-content'); @@ -96,11 +192,23 @@ export class GridRenderer { } timeAxisContent.appendChild(fragment); - timeAxisContent.style.top = '-1px'; + timeAxisContent.style.top = '-1px'; timeAxis.appendChild(timeAxisContent); return timeAxis; } + /** + * Creates the main grid container with header and columns + * + * This is the scrollable area containing: + * - Calendar header (dates/resources) - created here, populated by DateHeaderRenderer + * - Time grid (grid lines + day columns) - structure created here + * - Column container - created here, populated by ColumnRenderer + * + * @param currentDate - Current view date + * @param view - View type + * @returns Complete grid container element + */ private createOptimizedGridContainer( currentDate: Date, view: CalendarView @@ -127,14 +235,20 @@ export class GridRenderer { scrollableContent.appendChild(timeGrid); gridContainer.appendChild(scrollableContent); - console.log('✅ GridRenderer: Created grid container with header'); - return gridContainer; } /** - * Render column container with view awareness + * Delegates column rendering to the injected ColumnRenderer strategy + * + * This is where the Strategy Pattern is applied: + * - DateColumnRenderer iterates dates and creates day columns + * - Could be swapped with other implementations (e.g., ResourceColumnRenderer) + * + * @param columnContainer - Empty container to populate + * @param currentDate - Current view date + * @param view - View type */ private renderColumnContainer( columnContainer: HTMLElement, @@ -151,6 +265,13 @@ export class GridRenderer { /** * Optimized update of grid content without full rebuild + * + * Only updates the column container content, leaving the structure intact. + * This is much faster than recreating the entire grid. + * + * @param grid - Existing grid element + * @param currentDate - New view date + * @param view - View type */ private updateGridContent( grid: HTMLElement, @@ -165,17 +286,18 @@ export class GridRenderer { } } /** - * Create navigation grid container for slide animations - * Now uses same implementation as initial load for consistency + * Creates a new grid for slide animations during navigation + * + * Used by NavigationManager for smooth week-to-week transitions. + * Creates a complete grid positioned absolutely for animation. + * + * Note: Positioning is handled by Animation API, not here. + * + * @param parentContainer - Container for the new grid + * @param weekStart - Start date of the new week + * @returns New grid element ready for animation */ public createNavigationGrid(parentContainer: HTMLElement, weekStart: Date): HTMLElement { - console.group('🔧 GridRenderer.createNavigationGrid'); - console.log('Week start:', weekStart); - console.log('Parent container:', parentContainer); - console.log('Using same grid creation as initial load'); - - const weekEnd = this.dateService.addDays(weekStart, 6); - // Use SAME method as initial load - respects workweek settings const newGrid = this.createOptimizedGridContainer(weekStart, 'week'); @@ -189,10 +311,6 @@ export class GridRenderer { // Add to parent container parentContainer.appendChild(newGrid); - console.log('Grid created using createOptimizedGridContainer:', newGrid); - console.log('Grid creation complete - caller will emit GRID_RENDERED'); - - console.groupEnd(); return newGrid; } } \ No newline at end of file