Major refactorering to get a hold on all these events
This commit is contained in:
parent
2a766cf685
commit
59b3c64c55
18 changed files with 1901 additions and 357 deletions
270
docs/improved-initialization-strategy.md
Normal file
270
docs/improved-initialization-strategy.md
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue