From bd8f5ae6c6b185544ab84d96c72c0e0405b700c1 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Fri, 7 Nov 2025 23:23:19 +0100 Subject: [PATCH] Adds navigation buttons management and refactors navigation Introduces NavigationButtonsManager to handle navigation button interactions Renames NavigationRenderer to WeekInfoRenderer for clarity Adds new NAV_BUTTON_CLICKED event for better separation of concerns Improves event-driven navigation workflow --- src/constants/CoreEvents.ts | 3 +- src/index.ts | 7 +- src/managers/NavigationButtonsManager.ts | 71 +++++++++++++++++++ src/managers/NavigationManager.ts | 21 +++--- ...igationRenderer.ts => WeekInfoRenderer.ts} | 30 ++++---- 5 files changed, 102 insertions(+), 30 deletions(-) create mode 100644 src/managers/NavigationButtonsManager.ts rename src/renderers/{NavigationRenderer.ts => WeekInfoRenderer.ts} (90%) diff --git a/src/constants/CoreEvents.ts b/src/constants/CoreEvents.ts index 8105bea..06d4b6f 100644 --- a/src/constants/CoreEvents.ts +++ b/src/constants/CoreEvents.ts @@ -13,7 +13,8 @@ export const CoreEvents = { VIEW_RENDERED: 'view:rendered', WORKWEEK_CHANGED: 'workweek:changed', - // Navigation events (4) + // Navigation events (5) + NAV_BUTTON_CLICKED: 'nav:button-clicked', DATE_CHANGED: 'nav:date-changed', NAVIGATION_COMPLETED: 'nav:navigation-completed', PERIOD_INFO_UPDATE: 'nav:period-info-update', diff --git a/src/index.ts b/src/index.ts index 064ec33..08eda9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import { EventRenderingService } from './renderers/EventRendererManager'; import { GridManager } from './managers/GridManager'; import { ScrollManager } from './managers/ScrollManager'; import { NavigationManager } from './managers/NavigationManager'; +import { NavigationButtonsManager } from './managers/NavigationButtonsManager'; import { ViewSelectorManager } from './managers/ViewSelectorManager'; import { CalendarManager } from './managers/CalendarManager'; import { DragDropManager } from './managers/DragDropManager'; @@ -38,7 +39,7 @@ import { DateColumnRenderer, type IColumnRenderer } from './renderers/ColumnRend import { DateEventRenderer, type IEventRenderer } from './renderers/EventRenderer'; import { AllDayEventRenderer } from './renderers/AllDayEventRenderer'; import { GridRenderer } from './renderers/GridRenderer'; -import { NavigationRenderer } from './renderers/NavigationRenderer'; +import { WeekInfoRenderer } from './renderers/WeekInfoRenderer'; // Import utilities and services import { DateService } from './utils/DateService'; @@ -116,7 +117,7 @@ async function initializeCalendar(): Promise { builder.registerType(TimeFormatter).as(); builder.registerType(PositionUtils).as(); // Note: AllDayLayoutEngine is instantiated per-operation with specific dates, not a singleton - builder.registerType(NavigationRenderer).as(); + builder.registerType(WeekInfoRenderer).as(); builder.registerType(AllDayEventRenderer).as(); builder.registerType(EventRenderingService).as(); @@ -124,6 +125,7 @@ async function initializeCalendar(): Promise { builder.registerType(GridManager).as(); builder.registerType(ScrollManager).as(); builder.registerType(NavigationManager).as(); + builder.registerType(NavigationButtonsManager).as(); builder.registerType(ViewSelectorManager).as(); builder.registerType(DragDropManager).as(); builder.registerType(AllDayManager).as(); @@ -148,6 +150,7 @@ async function initializeCalendar(): Promise { const dragDropManager = app.resolveType(); const viewSelectorManager = app.resolveType(); const navigationManager = app.resolveType(); + const navigationButtonsManager = app.resolveType(); const edgeScrollManager = app.resolveType(); const allDayManager = app.resolveType(); const urlManager = app.resolveType(); diff --git a/src/managers/NavigationButtonsManager.ts b/src/managers/NavigationButtonsManager.ts new file mode 100644 index 0000000..a5bed98 --- /dev/null +++ b/src/managers/NavigationButtonsManager.ts @@ -0,0 +1,71 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { CoreEvents } from '../constants/CoreEvents'; + +/** + * NavigationButtonsManager - Manages navigation button UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-nav-button elements + * - Validates navigation actions (prev, next, today) + * - Emits NAV_BUTTON_CLICKED events + * - Manages button UI listeners + * + * EVENT FLOW: + * =========== + * User clicks button → validateAction() → emit event → NavigationManager handles navigation + * + * SUBSCRIBERS: + * ============ + * - NavigationManager: Performs actual navigation logic (animations, grid updates, week calculations) + */ +export class NavigationButtonsManager { + private eventBus: IEventBus; + private buttonListeners: Map = new Map(); + + constructor(eventBus: IEventBus) { + this.eventBus = eventBus; + this.setupButtonListeners(); + } + + /** + * Setup click listeners on all navigation buttons + */ + private setupButtonListeners(): void { + const buttons = document.querySelectorAll('swp-nav-button[data-action]'); + + buttons.forEach(button => { + const clickHandler = (event: Event) => { + event.preventDefault(); + const action = button.getAttribute('data-action'); + if (action && this.isValidAction(action)) { + this.handleNavigation(action); + } + }; + + button.addEventListener('click', clickHandler); + this.buttonListeners.set(button, clickHandler); + }); + } + + /** + * Handle navigation action + */ + private handleNavigation(action: string): void { + // Emit navigation button clicked event + this.eventBus.emit(CoreEvents.NAV_BUTTON_CLICKED, { + action: action + }); + } + + /** + * Validate if string is a valid navigation action + */ + private isValidAction(action: string): boolean { + return ['prev', 'next', 'today'].includes(action); + } +} diff --git a/src/managers/NavigationManager.ts b/src/managers/NavigationManager.ts index b16174d..1f170fb 100644 --- a/src/managers/NavigationManager.ts +++ b/src/managers/NavigationManager.ts @@ -2,12 +2,12 @@ import { IEventBus } from '../types/CalendarTypes'; import { EventRenderingService } from '../renderers/EventRendererManager'; import { DateService } from '../utils/DateService'; import { CoreEvents } from '../constants/CoreEvents'; -import { NavigationRenderer } from '../renderers/NavigationRenderer'; +import { WeekInfoRenderer } from '../renderers/WeekInfoRenderer'; import { GridRenderer } from '../renderers/GridRenderer'; export class NavigationManager { private eventBus: IEventBus; - private navigationRenderer: NavigationRenderer; + private weekInfoRenderer: WeekInfoRenderer; private gridRenderer: GridRenderer; private dateService: DateService; private currentWeek: Date; @@ -19,11 +19,11 @@ export class NavigationManager { eventRenderer: EventRenderingService, gridRenderer: GridRenderer, dateService: DateService, - navigationRenderer: NavigationRenderer + weekInfoRenderer: WeekInfoRenderer ) { this.eventBus = eventBus; this.dateService = dateService; - this.navigationRenderer = navigationRenderer; + this.weekInfoRenderer = weekInfoRenderer; this.gridRenderer = gridRenderer; this.currentWeek = this.getISOWeekStart(new Date()); this.targetWeek = new Date(this.currentWeek); @@ -54,17 +54,12 @@ export class NavigationManager { // Listen for filter changes and apply to pre-rendered grids this.eventBus.on(CoreEvents.FILTER_CHANGED, (e: Event) => { const detail = (e as CustomEvent).detail; - this.navigationRenderer.applyFilterToPreRenderedGrids(detail); + this.weekInfoRenderer.applyFilterToPreRenderedGrids(detail); }); - // Listen for navigation button clicks - document.addEventListener('click', (e) => { - const target = e.target as HTMLElement; - const navButton = target.closest('[data-action]') as HTMLElement; - - if (!navButton) return; - - const action = navButton.dataset.action; + // Listen for navigation button clicks from NavigationButtonsManager + this.eventBus.on(CoreEvents.NAV_BUTTON_CLICKED, (event: Event) => { + const { action } = (event as CustomEvent).detail; switch (action) { case 'prev': diff --git a/src/renderers/NavigationRenderer.ts b/src/renderers/WeekInfoRenderer.ts similarity index 90% rename from src/renderers/NavigationRenderer.ts rename to src/renderers/WeekInfoRenderer.ts index fa4ed7f..5ee6149 100644 --- a/src/renderers/NavigationRenderer.ts +++ b/src/renderers/WeekInfoRenderer.ts @@ -3,20 +3,22 @@ import { CoreEvents } from '../constants/CoreEvents'; import { EventRenderingService } from './EventRendererManager'; /** - * NavigationRenderer - Handles DOM rendering for navigation containers - * Separated from NavigationManager to follow Single Responsibility Principle + * WeekInfoRenderer - Handles DOM rendering for week info display + * Updates swp-week-number and swp-date-range elements + * + * Renamed from NavigationRenderer to better reflect its actual responsibility */ -export class NavigationRenderer { +export class WeekInfoRenderer { private eventBus: IEventBus; constructor(eventBus: IEventBus, eventRenderer: EventRenderingService) { this.eventBus = eventBus; this.setupEventListeners(); } - - + + /** * Setup event listeners for DOM updates */ @@ -28,36 +30,36 @@ export class NavigationRenderer { }); } - + private updateWeekInfoInDOM(weekNumber: number, dateRange: string): void { const weekNumberElement = document.querySelector('swp-week-number'); const dateRangeElement = document.querySelector('swp-date-range'); - + if (weekNumberElement) { weekNumberElement.textContent = `Week ${weekNumber}`; } - + if (dateRangeElement) { dateRangeElement.textContent = dateRange; } } - + /** * Apply filter state to pre-rendered grids */ public applyFilterToPreRenderedGrids(filterState: { active: boolean; matchingIds: string[] }): void { // Find all grid containers (including pre-rendered ones) const allGridContainers = document.querySelectorAll('swp-grid-container'); - + allGridContainers.forEach(container => { const eventsLayers = container.querySelectorAll('swp-events-layer'); - + eventsLayers.forEach(layer => { if (filterState.active) { // Apply filter active state layer.setAttribute('data-filter-active', 'true'); - + // Mark matching events in this layer const events = layer.querySelectorAll('swp-event'); events.forEach(event => { @@ -71,7 +73,7 @@ export class NavigationRenderer { } else { // Remove filter state layer.removeAttribute('data-filter-active'); - + // Remove all match attributes const events = layer.querySelectorAll('swp-event'); events.forEach(event => { @@ -82,4 +84,4 @@ export class NavigationRenderer { }); } -} \ No newline at end of file +}