Refactors view management in calendar component

Introduces ViewSelectorManager to handle view state and UI interactions

Separates view logic from configuration management
Adds explicit tracking of current calendar view
Enhances view selection and state management

Improves modularity and separation of concerns
This commit is contained in:
Janus C. H. Knudsen 2025-11-07 23:07:00 +01:00
parent c1e0da056c
commit 29ba0bfa37
6 changed files with 162 additions and 123 deletions

View file

@ -3,6 +3,7 @@ import { IGridSettings } from './GridSettings';
import { IDateViewSettings } from './DateViewSettings';
import { ITimeFormatConfig } from './TimeFormatConfig';
import { IWorkWeekSettings } from './WorkWeekSettings';
import { CalendarView } from '../types/CalendarTypes';
/**
* All-day event layout constants
@ -65,6 +66,7 @@ export class Configuration {
public dateViewSettings: IDateViewSettings;
public timeFormatConfig: ITimeFormatConfig;
public currentWorkWeek: string;
public currentView: CalendarView;
public selectedDate: Date;
public apiEndpoint: string = '/api';
@ -74,6 +76,7 @@ export class Configuration {
dateViewSettings: IDateViewSettings,
timeFormatConfig: ITimeFormatConfig,
currentWorkWeek: string,
currentView: CalendarView,
selectedDate: Date = new Date()
) {
this.config = config;
@ -81,6 +84,7 @@ export class Configuration {
this.dateViewSettings = dateViewSettings;
this.timeFormatConfig = timeFormatConfig;
this.currentWorkWeek = currentWorkWeek;
this.currentView = currentView;
this.selectedDate = selectedDate;
// Store as singleton instance for web components

View file

@ -92,7 +92,8 @@ export class ConfigManager {
data.gridSettings,
data.dateViewSettings,
data.timeFormatConfig,
data.currentWorkWeek
data.currentWorkWeek,
data.currentView || 'week'
);
// Configure TimeFormatter

View file

@ -12,7 +12,7 @@ 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 { ViewSelectorManager } from './managers/ViewSelectorManager';
import { CalendarManager } from './managers/CalendarManager';
import { DragDropManager } from './managers/DragDropManager';
import { AllDayManager } from './managers/AllDayManager';
@ -124,7 +124,7 @@ async function initializeCalendar(): Promise<void> {
builder.registerType(GridManager).as<GridManager>();
builder.registerType(ScrollManager).as<ScrollManager>();
builder.registerType(NavigationManager).as<NavigationManager>();
builder.registerType(ViewManager).as<ViewManager>();
builder.registerType(ViewSelectorManager).as<ViewSelectorManager>();
builder.registerType(DragDropManager).as<DragDropManager>();
builder.registerType(AllDayManager).as<AllDayManager>();
builder.registerType(ResizeHandleManager).as<ResizeHandleManager>();
@ -146,7 +146,7 @@ async function initializeCalendar(): Promise<void> {
const resizeHandleManager = app.resolveType<ResizeHandleManager>();
const headerManager = app.resolveType<HeaderManager>();
const dragDropManager = app.resolveType<DragDropManager>();
const viewManager = app.resolveType<ViewManager>();
const viewSelectorManager = app.resolveType<ViewSelectorManager>();
const navigationManager = app.resolveType<NavigationManager>();
const edgeScrollManager = app.resolveType<EdgeScrollManager>();
const allDayManager = app.resolveType<AllDayManager>();

View file

@ -1,119 +0,0 @@
import { CalendarView, IEventBus } from '../types/CalendarTypes';
import { Configuration } from '../configurations/CalendarConfig';
import { CoreEvents } from '../constants/CoreEvents';
export class ViewManager {
private eventBus: IEventBus;
private config: Configuration;
private currentView: CalendarView = 'week';
private buttonListeners: Map<Element, EventListener> = new Map();
constructor(eventBus: IEventBus, config: Configuration) {
this.eventBus = eventBus;
this.config = config;
this.setupEventListeners();
}
private setupEventListeners(): void {
this.setupEventBusListeners();
this.setupButtonHandlers();
}
private setupEventBusListeners(): void {
this.eventBus.on(CoreEvents.INITIALIZED, () => {
this.initializeView();
});
this.eventBus.on(CoreEvents.DATE_CHANGED, () => {
this.refreshCurrentView();
});
}
private setupButtonHandlers(): void {
this.setupButtonGroup('swp-view-button[data-view]', 'data-view', (value) => {
if (this.isValidView(value)) {
this.changeView(value as CalendarView);
}
});
// NOTE: Workweek preset buttons are now handled by WorkweekPresetsManager
}
private setupButtonGroup(selector: string, attribute: string, handler: (value: string) => void): void {
const buttons = document.querySelectorAll(selector);
buttons.forEach(button => {
const clickHandler = (event: Event) => {
event.preventDefault();
const value = button.getAttribute(attribute);
if (value) {
handler(value);
}
};
button.addEventListener('click', clickHandler);
this.buttonListeners.set(button, clickHandler);
});
}
private getViewButtons(): NodeListOf<Element> {
return document.querySelectorAll('swp-view-button[data-view]');
}
private initializeView(): void {
this.updateAllButtons();
this.emitViewRendered();
}
private changeView(newView: CalendarView): void {
if (newView === this.currentView) return;
const previousView = this.currentView;
this.currentView = newView;
this.updateAllButtons();
this.eventBus.emit(CoreEvents.VIEW_CHANGED, {
previousView,
currentView: newView
});
}
private updateAllButtons(): void {
this.updateButtonGroup(
this.getViewButtons(),
'data-view',
this.currentView
);
// NOTE: Workweek button states are now managed by WorkweekPresetsManager
}
private updateButtonGroup(buttons: NodeListOf<Element>, attribute: string, activeValue: string): void {
buttons.forEach(button => {
const buttonValue = button.getAttribute(attribute);
if (buttonValue === activeValue) {
button.setAttribute('data-active', 'true');
} else {
button.removeAttribute('data-active');
}
});
}
private emitViewRendered(): void {
this.eventBus.emit(CoreEvents.VIEW_RENDERED, {
view: this.currentView
});
}
private refreshCurrentView(): void {
this.emitViewRendered();
}
private isValidView(view: string): view is CalendarView {
return ['day', 'week', 'month'].includes(view);
}
}

View file

@ -0,0 +1,152 @@
import { CalendarView, IEventBus } from '../types/CalendarTypes';
import { CoreEvents } from '../constants/CoreEvents';
import { Configuration } from '../configurations/CalendarConfig';
/**
* ViewSelectorManager - Manages view selector UI and state
*
* RESPONSIBILITY:
* ===============
* This manager owns all logic related to the <swp-view-selector> UI element.
* It follows the principle that each functional UI element has its own manager.
*
* RESPONSIBILITIES:
* - Handles button clicks on swp-view-button elements
* - Manages current view state (day/week/month)
* - Validates view values
* - Emits VIEW_CHANGED and VIEW_RENDERED events
* - Updates button UI states (data-active attributes)
*
* EVENT FLOW:
* ===========
* User clicks button changeView() validate update state emit event update UI
*
* IMPLEMENTATION STATUS:
* ======================
* - Week view: FULLY IMPLEMENTED
* - Day view: NOT IMPLEMENTED (button exists but no rendering)
* - Month view: NOT IMPLEMENTED (button exists but no rendering)
*
* SUBSCRIBERS:
* ============
* - GridRenderer: Uses view parameter (currently only supports 'week')
* - Future: DayRenderer, MonthRenderer when implemented
*/
export class ViewSelectorManager {
private eventBus: IEventBus;
private config: Configuration;
private buttonListeners: Map<Element, EventListener> = new Map();
constructor(eventBus: IEventBus, config: Configuration) {
this.eventBus = eventBus;
this.config = config;
this.setupButtonListeners();
this.setupEventListeners();
}
/**
* Setup click listeners on all view selector buttons
*/
private setupButtonListeners(): void {
const buttons = document.querySelectorAll('swp-view-button[data-view]');
buttons.forEach(button => {
const clickHandler = (event: Event) => {
event.preventDefault();
const view = button.getAttribute('data-view');
if (view && this.isValidView(view)) {
this.changeView(view as CalendarView);
}
};
button.addEventListener('click', clickHandler);
this.buttonListeners.set(button, clickHandler);
});
// Initialize button states
this.updateButtonStates();
}
/**
* Setup event bus listeners
*/
private setupEventListeners(): void {
this.eventBus.on(CoreEvents.INITIALIZED, () => {
this.initializeView();
});
this.eventBus.on(CoreEvents.DATE_CHANGED, () => {
this.refreshCurrentView();
});
}
/**
* Change the active view
*/
private changeView(newView: CalendarView): void {
if (newView === this.config.currentView) {
return; // No change
}
const previousView = this.config.currentView;
this.config.currentView = newView;
// Update button UI states
this.updateButtonStates();
// Emit event for subscribers
this.eventBus.emit(CoreEvents.VIEW_CHANGED, {
previousView,
currentView: newView
});
}
/**
* Update button states (data-active attributes)
*/
private updateButtonStates(): void {
const buttons = document.querySelectorAll('swp-view-button[data-view]');
buttons.forEach(button => {
const buttonView = button.getAttribute('data-view');
if (buttonView === this.config.currentView) {
button.setAttribute('data-active', 'true');
} else {
button.removeAttribute('data-active');
}
});
}
/**
* Initialize view on INITIALIZED event
*/
private initializeView(): void {
this.updateButtonStates();
this.emitViewRendered();
}
/**
* Emit VIEW_RENDERED event
*/
private emitViewRendered(): void {
this.eventBus.emit(CoreEvents.VIEW_RENDERED, {
view: this.config.currentView
});
}
/**
* Refresh current view on DATE_CHANGED event
*/
private refreshCurrentView(): void {
this.emitViewRendered();
}
/**
* Validate if string is a valid CalendarView type
*/
private isValidView(view: string): view is CalendarView {
return ['day', 'week', 'month'].includes(view);
}
}