270 lines
No EOL
9.1 KiB
Markdown
270 lines
No EOL
9.1 KiB
Markdown
# Improved Calendar Initialization Strategy
|
|
|
|
## Current Problems
|
|
|
|
1. **Race Conditions**: Managers try DOM operations before DOM is ready
|
|
2. **Sequential Blocking**: All initialization happens sequentially
|
|
3. **Poor Error Handling**: No timeouts or retry mechanisms
|
|
4. **Late Data Loading**: Data only loads after all managers are created
|
|
|
|
## Recommended New Architecture
|
|
|
|
### Phase 1: Early Parallel Startup
|
|
```typescript
|
|
// index.ts - Improved initialization
|
|
export class CalendarInitializer {
|
|
async initialize(): Promise<void> {
|
|
console.log('📋 Starting Calendar initialization...');
|
|
|
|
// PHASE 1: Early parallel setup
|
|
const setupPromises = [
|
|
this.initializeConfig(), // Load URL params, DOM attrs
|
|
this.initializeFactory(), // Setup strategy patterns
|
|
this.preloadCalendarData(), // Start data loading early
|
|
this.waitForDOMReady() // Ensure basic DOM exists
|
|
];
|
|
|
|
await Promise.all(setupPromises);
|
|
console.log('✅ Phase 1 complete: Config, Factory, Data preloading started');
|
|
|
|
// PHASE 2: Manager creation with dependencies
|
|
await this.createManagersWithDependencies();
|
|
|
|
// PHASE 3: Coordinated activation
|
|
await this.activateAllManagers();
|
|
|
|
console.log('🎊 Calendar fully initialized!');
|
|
}
|
|
}
|
|
```
|
|
|
|
### Phase 2: Dependency-Aware Manager Creation
|
|
```typescript
|
|
private async createManagersWithDependencies(): Promise<void> {
|
|
const managers = new Map<string, any>();
|
|
|
|
// Core managers (no DOM dependencies)
|
|
managers.set('config', calendarConfig);
|
|
managers.set('eventBus', eventBus);
|
|
managers.set('calendarManager', new CalendarManager(eventBus, calendarConfig));
|
|
|
|
// DOM-dependent managers (wait for DOM readiness)
|
|
await this.waitForRequiredDOM(['swp-calendar', 'swp-calendar-nav']);
|
|
|
|
managers.set('navigationManager', new NavigationManager(eventBus));
|
|
managers.set('viewManager', new ViewManager(eventBus));
|
|
|
|
// Data managers (can work with preloaded data)
|
|
managers.set('eventManager', new EventManager(eventBus));
|
|
managers.set('dataManager', new DataManager());
|
|
|
|
// Layout managers (need DOM structure + other managers)
|
|
await this.waitForRequiredDOM(['swp-calendar-container']);
|
|
|
|
// CRITICAL ORDER: ScrollManager subscribes before GridManager renders
|
|
managers.set('scrollManager', new ScrollManager());
|
|
managers.set('gridManager', new GridManager());
|
|
|
|
// Rendering managers (need grid structure)
|
|
managers.set('eventRenderer', new EventRenderer(eventBus));
|
|
|
|
this.managers = managers;
|
|
}
|
|
```
|
|
|
|
### Phase 3: Coordinated Activation
|
|
```typescript
|
|
private async activateAllManagers(): Promise<void> {
|
|
// All managers created and subscribed, now activate in coordinated fashion
|
|
const calendarManager = this.managers.get('calendarManager');
|
|
|
|
// This triggers CALENDAR_INITIALIZED, but now all managers are ready
|
|
await calendarManager.initialize();
|
|
|
|
// Wait for critical initialization events
|
|
await Promise.all([
|
|
this.waitForEvent('CALENDAR_DATA_LOADED', 10000),
|
|
this.waitForEvent('GRID_RENDERED', 5000),
|
|
this.waitForEvent('EVENTS_LOADED', 10000)
|
|
]);
|
|
|
|
// Ensure event rendering completes
|
|
await this.waitForEvent('EVENT_RENDERED', 3000);
|
|
}
|
|
```
|
|
|
|
## Specific Timing Improvements
|
|
|
|
### 1. Early Data Preloading
|
|
```typescript
|
|
private async preloadCalendarData(): Promise<void> {
|
|
const currentDate = new Date();
|
|
const mode = calendarConfig.getCalendarMode();
|
|
|
|
// Start loading data for current period immediately
|
|
const dataManager = new DataManager();
|
|
const currentPeriod = this.getCurrentPeriod(currentDate, mode);
|
|
|
|
// Don't await - let this run in background
|
|
const dataPromise = dataManager.fetchEventsForPeriod(currentPeriod);
|
|
|
|
// Also preload adjacent periods
|
|
const prevPeriod = this.getPreviousPeriod(currentDate, mode);
|
|
const nextPeriod = this.getNextPeriod(currentDate, mode);
|
|
|
|
// Store promises for later use
|
|
this.preloadPromises = {
|
|
current: dataPromise,
|
|
previous: dataManager.fetchEventsForPeriod(prevPeriod),
|
|
next: dataManager.fetchEventsForPeriod(nextPeriod)
|
|
};
|
|
|
|
console.log('📊 Data preloading started for current, previous, and next periods');
|
|
}
|
|
```
|
|
|
|
### 2. DOM Readiness Verification
|
|
```typescript
|
|
private async waitForRequiredDOM(selectors: string[]): Promise<void> {
|
|
const maxWait = 5000; // 5 seconds max
|
|
const checkInterval = 100; // Check every 100ms
|
|
|
|
const startTime = Date.now();
|
|
|
|
while (Date.now() - startTime < maxWait) {
|
|
const missing = selectors.filter(selector => !document.querySelector(selector));
|
|
|
|
if (missing.length === 0) {
|
|
console.log(`✅ Required DOM elements found: ${selectors.join(', ')}`);
|
|
return;
|
|
}
|
|
|
|
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
}
|
|
|
|
throw new Error(`❌ Timeout waiting for DOM elements: ${selectors.join(', ')}`);
|
|
}
|
|
```
|
|
|
|
### 3. Manager Base Class with Proper Lifecycle
|
|
```typescript
|
|
export abstract class BaseManager {
|
|
protected isInitialized = false;
|
|
protected requiredDOMSelectors: string[] = [];
|
|
|
|
constructor() {
|
|
// Don't call init() immediately in constructor!
|
|
console.log(`${this.constructor.name}: Created but not initialized`);
|
|
}
|
|
|
|
async initialize(): Promise<void> {
|
|
if (this.isInitialized) {
|
|
console.log(`${this.constructor.name}: Already initialized, skipping`);
|
|
return;
|
|
}
|
|
|
|
// Wait for required DOM elements
|
|
if (this.requiredDOMSelectors.length > 0) {
|
|
await this.waitForDOM(this.requiredDOMSelectors);
|
|
}
|
|
|
|
// Perform manager-specific initialization
|
|
await this.performInitialization();
|
|
|
|
this.isInitialized = true;
|
|
console.log(`${this.constructor.name}: Initialization complete`);
|
|
}
|
|
|
|
protected abstract performInitialization(): Promise<void>;
|
|
|
|
private async waitForDOM(selectors: string[]): Promise<void> {
|
|
// Same DOM waiting logic as above
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Enhanced GridManager
|
|
```typescript
|
|
export class GridManager extends BaseManager {
|
|
protected requiredDOMSelectors = ['swp-calendar-container'];
|
|
|
|
constructor() {
|
|
super(); // Don't call this.init()!
|
|
this.currentWeek = this.getWeekStart(new Date());
|
|
}
|
|
|
|
protected async performInitialization(): Promise<void> {
|
|
// Now safe to find elements - DOM guaranteed to exist
|
|
this.findElements();
|
|
this.subscribeToEvents();
|
|
|
|
// Wait for CALENDAR_INITIALIZED before rendering
|
|
await this.waitForEvent('CALENDAR_INITIALIZED');
|
|
|
|
console.log('GridManager: Starting initial render');
|
|
this.render();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Enhanced EventRenderer with Better Synchronization
|
|
```typescript
|
|
export class EventRenderer extends BaseManager {
|
|
private dataReady = false;
|
|
private gridReady = false;
|
|
private pendingEvents: CalendarEvent[] = [];
|
|
|
|
protected async performInitialization(): Promise<void> {
|
|
this.subscribeToEvents();
|
|
|
|
// Wait for both data and grid in parallel
|
|
const [eventsData] = await Promise.all([
|
|
this.waitForEvent('EVENTS_LOADED'),
|
|
this.waitForEvent('GRID_RENDERED')
|
|
]);
|
|
|
|
console.log('EventRenderer: Both events and grid ready, rendering now');
|
|
this.renderEvents(eventsData.events);
|
|
}
|
|
|
|
private subscribeToEvents(): void {
|
|
this.eventBus.on(EventTypes.EVENTS_LOADED, (e: Event) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
this.pendingEvents = detail.events;
|
|
this.dataReady = true;
|
|
this.tryRender();
|
|
});
|
|
|
|
this.eventBus.on(EventTypes.GRID_RENDERED, () => {
|
|
this.gridReady = true;
|
|
this.tryRender();
|
|
});
|
|
}
|
|
|
|
private tryRender(): void {
|
|
if (this.dataReady && this.gridReady && this.pendingEvents.length > 0) {
|
|
this.renderEvents(this.pendingEvents);
|
|
this.pendingEvents = [];
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Benefits of New Architecture
|
|
|
|
1. **🚀 Parallel Operations**: Data loading starts immediately while managers are being created
|
|
2. **🛡️ Race Condition Prevention**: DOM readiness verified before operations
|
|
3. **⚡ Better Performance**: Critical path optimized, non-critical operations parallelized
|
|
4. **🔧 Better Error Handling**: Timeouts and retry mechanisms
|
|
5. **📊 Predictable Timing**: Clear phases with guaranteed completion order
|
|
6. **🐛 Easier Debugging**: Clear lifecycle events and logging
|
|
|
|
## Implementation Strategy
|
|
|
|
1. **Phase 1**: Create BaseManager class and update existing managers
|
|
2. **Phase 2**: Implement CalendarInitializer with parallel setup
|
|
3. **Phase 3**: Add DOM readiness verification throughout
|
|
4. **Phase 4**: Implement data preloading strategy
|
|
5. **Phase 5**: Add comprehensive error handling and timeouts
|
|
|
|
This architecture ensures reliable, fast, and maintainable calendar initialization. |