Implements dependency injection with Brandi

Introduces Brandi DI container for managing application dependencies.

This change replaces the existing factory pattern with a proper
dependency injection mechanism, improving code modularity,
testability, and maintainability. It configures all managers and core
services within the container, ensuring proper dependency resolution.
This commit is contained in:
Janus C. H. Knudsen 2025-10-14 22:53:28 +02:00
parent 020d5c3efc
commit f7c67e6253
5 changed files with 170 additions and 24 deletions

7
package-lock.json generated
View file

@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@rollup/rollup-win32-x64-msvc": "^4.52.2", "@rollup/rollup-win32-x64-msvc": "^4.52.2",
"brandi": "^5.0.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"fuse.js": "^7.1.0" "fuse.js": "^7.1.0"
@ -1124,6 +1125,12 @@
"require-from-string": "^2.0.2" "require-from-string": "^2.0.2"
} }
}, },
"node_modules/brandi": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/brandi/-/brandi-5.0.0.tgz",
"integrity": "sha512-oztvITQgvuFb2K+NWdHLx0mMH8TGO3ASrQ43FZzmfiq5rCj0DRlsuZ6Efi/yeu3hyGx/Y+Z1xLGp2qzDWpiNYA==",
"license": "ISC"
},
"node_modules/cac": { "node_modules/cac": {
"version": "6.7.14", "version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",

View file

@ -21,6 +21,7 @@
}, },
"dependencies": { "dependencies": {
"@rollup/rollup-win32-x64-msvc": "^4.52.2", "@rollup/rollup-win32-x64-msvc": "^4.52.2",
"brandi": "^5.0.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"fuse.js": "^7.1.0" "fuse.js": "^7.1.0"

85
src/di/container.ts Normal file
View file

@ -0,0 +1,85 @@
/**
* Brandi Dependency Injection Container Configuration
* Configures all bindings with proper dependency resolution
*/
import { Container } from 'brandi';
import { TOKENS } from './tokens';
import { eventBus } from '../core/EventBus';
// Import all managers
import { EventManager } from '../managers/EventManager';
import { EventRenderingService } from '../renderers/EventRendererManager';
import { GridManager } from '../managers/GridManager';
import { ScrollManager } from '../managers/ScrollManager';
import { NavigationManager } from '../managers/NavigationManager';
import { ViewManager } from '../managers/ViewManager';
import { CalendarManager } from '../managers/CalendarManager';
import { DragDropManager } from '../managers/DragDropManager';
import { AllDayManager } from '../managers/AllDayManager';
import { ResizeHandleManager } from '../managers/ResizeHandleManager';
import { EdgeScrollManager } from '../managers/EdgeScrollManager';
import { DragHoverManager } from '../managers/DragHoverManager';
import { HeaderManager } from '../managers/HeaderManager';
/**
* Create and configure the DI container
* Using manual instantiation instead of automatic injection
*/
export function createContainer(): Container {
const container = new Container();
// Bind core services as constant
container.bind(TOKENS.eventBus).toConstant(eventBus);
// Create instances manually and bind as constants (singletons)
// This is simpler than using decorators or complex factory setup
const eventManager = new EventManager(eventBus);
container.bind(TOKENS.eventManager).toConstant(eventManager);
const eventRenderer = new EventRenderingService(eventBus, eventManager);
container.bind(TOKENS.eventRenderer).toConstant(eventRenderer);
const gridManager = new GridManager();
container.bind(TOKENS.gridManager).toConstant(gridManager);
const scrollManager = new ScrollManager();
container.bind(TOKENS.scrollManager).toConstant(scrollManager);
const navigationManager = new NavigationManager(eventBus, eventRenderer);
container.bind(TOKENS.navigationManager).toConstant(navigationManager);
const viewManager = new ViewManager(eventBus);
container.bind(TOKENS.viewManager).toConstant(viewManager);
const dragDropManager = new DragDropManager(eventBus);
container.bind(TOKENS.dragDropManager).toConstant(dragDropManager);
const allDayManager = new AllDayManager(eventManager);
container.bind(TOKENS.allDayManager).toConstant(allDayManager);
const resizeHandleManager = new ResizeHandleManager();
container.bind(TOKENS.resizeHandleManager).toConstant(resizeHandleManager);
const edgeScrollManager = new EdgeScrollManager(eventBus);
container.bind(TOKENS.edgeScrollManager).toConstant(edgeScrollManager);
const dragHoverManager = new DragHoverManager(eventBus);
container.bind(TOKENS.dragHoverManager).toConstant(dragHoverManager);
const headerManager = new HeaderManager();
container.bind(TOKENS.headerManager).toConstant(headerManager);
// CalendarManager depends on multiple managers
const calendarManager = new CalendarManager(
eventBus,
eventManager,
gridManager,
eventRenderer,
scrollManager
);
container.bind(TOKENS.calendarManager).toConstant(calendarManager);
return container;
}

43
src/di/tokens.ts Normal file
View file

@ -0,0 +1,43 @@
/**
* Dependency Injection Tokens for Brandi Container
* Type-safe tokens for all managers and services
*/
import { token } from 'brandi';
import { IEventBus } from '../types/CalendarTypes';
import { EventManager } from '../managers/EventManager';
import { EventRenderingService } from '../renderers/EventRendererManager';
import { GridManager } from '../managers/GridManager';
import { ScrollManager } from '../managers/ScrollManager';
import { NavigationManager } from '../managers/NavigationManager';
import { ViewManager } from '../managers/ViewManager';
import { CalendarManager } from '../managers/CalendarManager';
import { DragDropManager } from '../managers/DragDropManager';
import { AllDayManager } from '../managers/AllDayManager';
import { ResizeHandleManager } from '../managers/ResizeHandleManager';
import { EdgeScrollManager } from '../managers/EdgeScrollManager';
import { DragHoverManager } from '../managers/DragHoverManager';
import { HeaderManager } from '../managers/HeaderManager';
/**
* DI Tokens - Type-safe identifiers for dependency injection
*/
export const TOKENS = {
// Core services
eventBus: token<IEventBus>('eventBus'),
// Managers
eventManager: token<EventManager>('eventManager'),
eventRenderer: token<EventRenderingService>('eventRenderer'),
gridManager: token<GridManager>('gridManager'),
scrollManager: token<ScrollManager>('scrollManager'),
navigationManager: token<NavigationManager>('navigationManager'),
viewManager: token<ViewManager>('viewManager'),
calendarManager: token<CalendarManager>('calendarManager'),
dragDropManager: token<DragDropManager>('dragDropManager'),
allDayManager: token<AllDayManager>('allDayManager'),
resizeHandleManager: token<ResizeHandleManager>('resizeHandleManager'),
edgeScrollManager: token<EdgeScrollManager>('edgeScrollManager'),
dragHoverManager: token<DragHoverManager>('dragHoverManager'),
headerManager: token<HeaderManager>('headerManager'),
};

View file

@ -2,14 +2,15 @@
import { eventBus } from './core/EventBus'; import { eventBus } from './core/EventBus';
import { calendarConfig } from './core/CalendarConfig'; import { calendarConfig } from './core/CalendarConfig';
import { CalendarTypeFactory } from './factories/CalendarTypeFactory'; import { CalendarTypeFactory } from './factories/CalendarTypeFactory';
import { ManagerFactory } from './factories/ManagerFactory'; import { createContainer } from './di/container';
import { TOKENS } from './di/tokens';
import { URLManager } from './utils/URLManager'; import { URLManager } from './utils/URLManager';
import { CalendarManagers } from './types/ManagerTypes'; import { EventManager } from './managers/EventManager';
/** /**
* Handle deep linking functionality after managers are initialized * Handle deep linking functionality after managers are initialized
*/ */
async function handleDeepLinking(managers: CalendarManagers): Promise<void> { async function handleDeepLinking(eventManager: EventManager): Promise<void> {
try { try {
const urlManager = new URLManager(eventBus); const urlManager = new URLManager(eventBus);
const eventId = urlManager.parseEventIdFromURL(); const eventId = urlManager.parseEventIdFromURL();
@ -19,7 +20,7 @@ async function handleDeepLinking(managers: CalendarManagers): Promise<void> {
// Wait a bit for managers to be fully ready // Wait a bit for managers to be fully ready
setTimeout(() => { setTimeout(() => {
const success = managers.eventManager.navigateToEvent(eventId); const success = eventManager.navigateToEvent(eventId);
if (!success) { if (!success) {
console.warn(`Deep linking failed: Event with ID ${eventId} not found`); console.warn(`Deep linking failed: Event with ID ${eventId} not found`);
} }
@ -31,10 +32,9 @@ async function handleDeepLinking(managers: CalendarManagers): Promise<void> {
} }
/** /**
* Initialize the calendar application with simple direct calls * Initialize the calendar application using Brandi DI
*/ */
async function initializeCalendar(): Promise<void> { async function initializeCalendar(): Promise<void> {
try { try {
// Use the singleton calendar configuration // Use the singleton calendar configuration
const config = calendarConfig; const config = calendarConfig;
@ -42,27 +42,37 @@ async function initializeCalendar(): Promise<void> {
// Initialize the CalendarTypeFactory before creating managers // Initialize the CalendarTypeFactory before creating managers
CalendarTypeFactory.initialize(); CalendarTypeFactory.initialize();
// Create managers using factory pattern // Create Brandi DI container (all managers instantiated here)
const managerFactory = ManagerFactory.getInstance(); const container = createContainer();
const managers = managerFactory.createManagers(eventBus);
// Enable debug mode for development // Enable debug mode for development
eventBus.setDebug(true); eventBus.setDebug(true);
// Initialize all managers // Get managers from container
await managerFactory.initializeManagers(managers); const calendarManager = container.get(TOKENS.calendarManager);
const eventManager = container.get(TOKENS.eventManager);
const resizeHandleManager = container.get(TOKENS.resizeHandleManager);
// Initialize managers
await calendarManager.initialize?.();
await resizeHandleManager.initialize?.();
// Handle deep linking after managers are initialized // Handle deep linking after managers are initialized
await handleDeepLinking(managers); await handleDeepLinking(eventManager);
// Expose to window for debugging (with proper typing) // Expose to window for debugging (with proper typing)
(window as Window & { (window as Window & {
calendarDebug?: { calendarDebug?: {
eventBus: typeof eventBus; eventBus: typeof eventBus;
} & CalendarManagers; container: typeof container;
calendarManager: typeof calendarManager;
eventManager: typeof eventManager;
};
}).calendarDebug = { }).calendarDebug = {
eventBus, eventBus,
...managers container,
calendarManager,
eventManager,
}; };
} catch (error) { } catch (error) {