Calendar Controller
A flexible, reactive calendar component for date selection, navigation, and internationalization.
Calendar Controller: Full-Featured Date Management
CalendarControllerClass is a comprehensive date picker controller that provides functionality for date picking and calendar display. Built with a service-oriented architecture, it delegates core functionality to specialized services for maintainable, extensible date management.
The controller offers multiple view modes (day, month, year), comprehensive date selection options (single, range, multi-date), and full internationalization support through a reactive binding system that integrates seamlessly with any frontend framework.
Calendar Controller separates calendar logic from UI implementation, providing methods for date selection, navigation, and view management while exposing reactive bindings for real-time UI synchronization. This architecture enables developers to implement custom calendar interfaces with minimal boilerplate code.
Service Architecture
Specialized services handle calendar operations, date selection, validation, formatting, navigation, and state management for clean separation of concerns.
Multiple View Modes
Day view for date selection, month view for quick navigation, and year view with customizable year ranges for broader time period selection.
Reactive Bindings
Subscribe to calendar state changes through reactive bindings for selectedDate, calendarDays, currentMonth, selectedDateRange, and more.
When to Use Calendar Controller
Calendar Controller is ideal for applications requiring:
- Date pickers with single or range selection capabilities
- Booking and reservation systems with date constraints
- Event calendars with multi-view navigation
- Applications requiring internationalization and localization
- Complex date validation and constraint management
- Framework-agnostic calendar implementations
- Custom UI calendars with reactive state management
Installation
Calendar Controller can be installed using npm, yarn, or pnpm. It has minimal dependencies and is designed to be lightweight and framework-agnostic.
Install the calendar controller package:
1npm install @uplink-protocol/calendar-controller
Requirements
- JavaScript runtime environment (browser or Node.js)
- ECMAScript 6 (ES6) or higher
Version Compatibility
- Supported in all modern browsers
- IE11 support with appropriate polyfills
- Compatible with Node.js for server-side rendering
TypeScript Support
Calendar Controller includes comprehensive TypeScript type definitions out of the box:
1import {2 CalendarController,3 CalendarOptions,4 CalendarDate,5 DateRange6} from '@uplink-protocol/calendar-controller';78// Types are automatically available9const options: CalendarOptions = {10 firstDayOfWeek: 1,11 dateFormat: 'MM/DD/YYYY'12};1314const calendar = CalendarController(options);
Then install the integration package for your framework:
1npm install @uplink-protocol/react
TypeScript Support
Calendar Controller comes with full TypeScript support out of the box. No additional type packages are required.
This enables code completion, type checking, and better developer experience when working with the library.
Key Features
Calendar Controller provides powerful features to handle all aspects of calendar management in modern web applications.
Flexible Date Selection
- Single date selection with validation
- Date range selection with start and end dates
- Multiple non-contiguous date selection
1// Select a single date (year, month, day)2// Note: month is 0-indexed3calendar.methods.selectDate(2025, 4, 21); // May 21, 202545// Enable range selection mode6calendar.methods.setRangeSelectionMode(true);78// Select range start and end9calendar.methods.selectDate(2025, 4, 10); // Start date10calendar.methods.selectDate(2025, 4, 20); // End date
Internationalization
- Support for multiple languages and locales
- Localized weekday and month names
- Regional first day of week support
1// Create calendar with locale configuration2const calendar = CalendarController({3 locale: 'fr-FR',4 firstDayOfWeek: 1 // Monday5});67// Access localized month and weekday names8const monthName = calendar.bindings.monthName.current;9const weekdays = calendar.bindings.weekdays.current;10// ["dimanche", "lundi", "mardi", ...]
Multiple View Modes
- Day view for traditional calendar grid
- Month view for quick month selection
- Year view for broader navigation
1// Access different calendar views2const days = calendar.bindings.calendarDays.current; // Day view3const months = calendar.bindings.calendarMonths.current; // Month view4const years = calendar.bindings.calendarYears.current; // Year view56// Navigate between views via method selection7calendar.methods.selectMonth(3, 2025); // Switch to April 20258calendar.methods.selectYear(2026); // Switch to 2026
Date Constraints
- Min/max date boundaries
- Disabled dates via custom functions
- Special date highlighting
1// Create calendar with date constraints2const calendar = CalendarController({3 minDate: new Date(2025, 0, 1), // January 1, 20254 maxDate: new Date(2025, 11, 31), // December 31, 20255 disabledDates: [6 new Date(2025, 6, 4), // July 4th disabled7 new Date(2025, 11, 25) // Christmas disabled8 ]9});1011// Check if a date is disabled12const isDisabled = calendar.methods.isDateDisabled(new Date(2025, 6, 4));
Event System
- Reactive event subscription
- Comprehensive event coverage
- Subscription cleanup handling
1// Subscribe to reactive bindings2const unsubSelectedDate = calendar.bindings.selectedDate.subscribe((date) => {3 console.log('Selected date:', date);4});56const unsubCalendarDays = calendar.bindings.calendarDays.subscribe((days) => {7 console.log('Calendar updated:', days.length, 'days');8});910const unsubDateRange = calendar.bindings.selectedDateRange.subscribe((range) => {11 console.log('Range:', range.startDate, 'to', range.endDate);12});1314// Clean up subscriptions15unsubSelectedDate.unsubscribe();16unsubCalendarDays.unsubscribe();17unsubDateRange.unsubscribe();
TypeScript Support
- Comprehensive type definitions
- Generic type parameters
- Type-safe event system
1import {2 CalendarController,3 CalendarOptions,4 CalendarDate,5 DateRange6} from '@uplink-protocol/calendar-controller';78// Type-safe configuration9const options: CalendarOptions = {10 firstDayOfWeek: 1,11 dateFormat: 'MM/DD/YYYY',12 initialSelectedDate: new Date()13};1415const calendar = CalendarController(options);
Basic Usage
Getting started with Calendar Controller is straightforward. Learn how to initialize, configure, and perform common operations.
Focus on Logic, Not UI
Initialization
Create a new calendar controller with default or custom configuration:
1import { CalendarController } from '@uplink-protocol/calendar-controller';23// Create with default settings4const calendar = CalendarController();56// Or with custom configuration7const customCalendar = CalendarController({8 firstDayOfWeek: 0, // Sunday (0) as first day of week9 hideOtherMonthDays: false, // Show days from adjacent months10 minDate: new Date(2023, 0, 1), // Jan 1, 2023 as earliest selectable date11 maxDate: new Date(2025, 11, 31), // Dec 31, 2025 as latest selectable date12 locale: 'en-US', // Use US English formatting13 selectionMode: 'single' // Allow only single date selection14});
Date Selection
Select dates with full validation against any constraints you've set:
1// Select a specific date using methods2calendar.methods.selectDate(2025, 4, 21); // May 21, 2025 (year, month, day)34// Get the currently selected date from bindings5const selectedDate = calendar.bindings.selectedDate.current;67// Set range selection mode and select a range8calendar.methods.setRangeSelectionMode(true);9calendar.methods.selectDate(2025, 4, 10); // Start date10calendar.methods.selectDate(2025, 4, 15); // End date1112// Get selected range13const selectedRange = calendar.bindings.selectedDateRange.current;1415// Clear selection16calendar.methods.clearSelection();
Rendering Data
Generate all the data needed to render your calendar UI:
1// Get days to render in the current day view2const days = calendar.bindings.calendarDays.current;3/* Example structure:4[5 {6 date: Date(2025, 3, 30), // Date object for April 307 day: 30, // Day number8 isCurrentMonth: false, // From previous month9 isToday: false, // Not today10 isSelected: false, // Not selected11 isDisabled: false, // Can be selected12 isRangeStart: false, // Not start of range13 isRangeEnd: false, // Not end of range14 isInRange: false // Not within a range15 },16 {17 date: Date(2025, 4, 1), // Date object for May 118 day: 1, // Day number19 isCurrentMonth: true, // From current month20 // ...other properties21 },22 // ...more days23]24*/2526// Get months for month view27const months = calendar.bindings.calendarMonths.current;2829// Get years for year view30const years = calendar.bindings.calendarYears.current;3132// Get current month name and year33const monthName = calendar.bindings.monthName.current;34const currentYear = calendar.bindings.currentYear.current;35// Example: "May", 2025
Event Handling
Subscribe to calendar events to react to user interactions:
1// Subscribe to date selection changes2const unsubscribeFromDateEvents = calendar.bindings.selectedDate.subscribe((selectedDate) => {3 console.log('Date selected:', selectedDate);4 // Update your UI here5});67// Subscribe to month/year navigation changes8const unsubscribeFromMonthEvents = calendar.bindings.monthName.subscribe((monthName) => {9 console.log('Current month:', monthName);10});1112const unsubscribeFromYearEvents = calendar.bindings.currentYear.subscribe((year) => {13 console.log('Current year:', year);14});1516// Subscribe to view mode changes17const unsubscribeFromViewModeEvents = calendar.bindings.viewMode.subscribe((viewMode) => {18 console.log('View mode changed to:', viewMode); // 'day', 'month', or 'year'19});2021// Don't forget to clean up subscriptions when done22unsubscribeFromDateEvents();23unsubscribeFromMonthEvents();24unsubscribeFromYearEvents();25unsubscribeFromViewModeEvents();
Complete Basic Example
Here's a complete example showing a basic React integration:
1import React, { useEffect, useState } from 'react';2import { CalendarController } from '@uplink-protocol/calendar-controller';34function Calendar() {5 const [calendar] = useState(() => CalendarController());6 const [days, setDays] = useState([]);7 const [monthName, setMonthName] = useState('');8 const [currentYear, setCurrentYear] = useState('');910 useEffect(() => {11 // Set up subscriptions to reactive bindings12 const subscriptions = [13 calendar.bindings.calendarDays.subscribe(setDays),14 calendar.bindings.monthName.subscribe(setMonthName),15 calendar.bindings.currentYear.subscribe(setCurrentYear)16 ];1718 // Clean up subscriptions19 return () => {20 subscriptions.forEach(sub => sub());21 };22 }, [calendar]);2324 // Navigation handlers25 const handlePrevMonth = () => calendar.methods.prevMonth();26 const handleNextMonth = () => calendar.methods.nextMonth();27 const handleToday = () => calendar.methods.goToToday();2829 // Date selection handler30 const handleSelectDate = (day) => {31 if (!day.isDisabled && day.date) {32 calendar.methods.selectDate(33 day.date.getFullYear(),34 day.date.getMonth(),35 day.date.getDate()36 );37 }38 };3940 return (41 <div className="calendar-container">42 <div className="calendar-header">43 <button onClick={handlePrevMonth}>Previous</button>44 <h2>{monthName} {currentYear}</h2>45 <button onClick={handleNextMonth}>Next</button>46 <button onClick={handleToday}>Today</button>47 </div>4849 <div className="calendar-grid">50 {/* Calendar days */}51 {days.map((day, index) => (52 <div53 key={index}54 className={`calendar-day ${day.isCurrentMonth ? 'current' : 'other'}55 ${day.isSelected ? 'selected' : ''}56 ${day.isToday ? 'today' : ''}57 ${day.isDisabled ? 'disabled' : ''}`}58 onClick={() => handleSelectDate(day)}59 >60 {day.day}61 </div>62 ))}63 </div>64 </div>65 );66}
Framework Integrations
Calendar Controller is designed to be framework-agnostic, with dedicated binding packages for popular frameworks:
1import { useUplink } from "@uplink-protocol/react";2import { CalendarController } from "@uplink-protocol/calendar-controller";34function MyCalendar() {5 const { state, methods } = useUplink(6 () => CalendarController({7 firstDayOfWeek: 1,8 dateFormat: 'MM/DD/YYYY'9 }),10 { trackBindings: "all" }11 );1213 const handleDateSelect = (day) => {14 if (!day.isDisabled && day.date) {15 methods.selectDate(16 day.date.getFullYear(),17 day.date.getMonth(),18 day.date.getDate()19 );20 }21 };2223 return (24 <div className="calendar">25 <div className="calendar-header">26 <button onClick={() => methods.prevMonth()}>Previous</button>27 <span>{state.monthName} {state.currentYear}</span>28 <button onClick={() => methods.nextMonth()}>Next</button>29 </div>3031 <div className="calendar-grid">32 {state.calendarDays?.map((day, index) => (33 <div34 key={index}35 className={`day ${day.isSelected ? 'selected' : ''} ${day.isToday ? 'today' : ''}`}36 onClick={() => handleDateSelect(day)}37 >38 {day.day}39 </div>40 ))}41 </div>42 </div>43 );44}
TypeScript Support
Calendar Controller is built with TypeScript and provides comprehensive type definitions to ensure type safety and improve developer experience.
Core Types
The key TypeScript interfaces and types that power Calendar Controller:
1// Configuration interface2interface CalendarOptions {3 locale?: string;4 firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6;5 minDate?: Date;6 maxDate?: Date;7 dateFormat?: string;8 initialSelectedDate?: Date;9 hideOtherMonthDays?: boolean;10 yearRangeSize?: number;11}1213// Calendar day representation14interface CalendarDate {15 date: Date;16 day: number;17 isCurrentMonth: boolean;18 isToday: boolean;19 isSelected: boolean;20 isDisabled: boolean;21 isRangeStart: boolean;22 isRangeEnd: boolean;23 isInRange: boolean;24}2526// Calendar month representation27interface CalendarMonth {28 month: number;29 monthName: string;30 year: number;31 isCurrentMonth: boolean;32 isSelected: boolean;33 isDisabled: boolean;34}3536// Calendar year representation37interface CalendarYear {38 year: number;39 isCurrentYear: boolean;40 isSelected: boolean;41 isDisabled: boolean;42}4344// Date range definition45interface DateRange {46 start: Date | null;47 end: Date | null;48}4950// View modes51type ViewMode = 'day' | 'month' | 'year';
Type-Safe Events
The event system is fully typed for better development experience:
1// Type-safe binding subscriptions2type DateSubscriptionHandler = (date: Date | null) => void;3type DateRangeSubscriptionHandler = (range: DateRange) => void;4type ViewModeSubscriptionHandler = (viewMode: ViewMode) => void;5type CalendarDaysHandler = (days: CalendarDate[]) => void;6type CalendarMonthsHandler = (months: CalendarMonth[]) => void;7type CalendarYearsHandler = (years: CalendarYear[]) => void;89// Type-safe binding subscriptions10const unsub1: () => void = calendar.bindings.selectedDate.subscribe((date) => {11 // date is typed as Date | null12 if (date) {13 console.log('Selected:', date.toLocaleDateString());14 }15});1617const unsub2: () => void = calendar.bindings.selectedDateRange.subscribe((range) => {18 // range is typed as DateRange with start and end19 if (range.start && range.end) {20 console.log('Range:', range.start, 'to', range.end);21 }22});2324const unsub3: () => void = calendar.bindings.calendarDays.subscribe((days) => {25 // days is typed as CalendarDate[]26 days.forEach(day => {27 // TypeScript provides intellisense for all properties28 if (day.isToday) {29 console.log('Today is day', day.day);30 }31 });32});3334// TypeScript ensures you're using bindings correctly35calendar.bindings.viewMode.subscribe((mode) => {36 // mode is typed as 'day' | 'month' | 'year'37 if (mode === 'day') {38 // TypeScript knows this is a valid condition39 }40});
Advanced TypeScript Integration
Working with Calendar Controller bindings in TypeScript:
1// Working with Calendar Controller bindings in TypeScript2import { CalendarController, CalendarDate } from '@uplink-protocol/calendar-controller';34const calendar = CalendarController();56// Type-safe binding access7const renderCalendar = (): void => {8 const days: CalendarDate[] = calendar.bindings.calendarDays.current;910 days.forEach(day => {11 // TypeScript provides intellisense for all properties12 const classNames = [13 day.isCurrentMonth ? 'current-month' : 'other-month',14 day.isToday ? 'today' : '',15 day.isSelected ? 'selected' : '',16 day.isDisabled ? 'disabled' : ''17 ].filter(Boolean).join(' ');1819 // Render your UI with the day data20 });21};2223// Type-safe view mode handling24const handleViewModeChange = (mode: 'day' | 'month' | 'year'): void => {25 calendar.methods.setViewMode(mode);26 // TypeScript ensures mode is valid27};
Type-Safe Configuration
Configure the Calendar Controller with TypeScript validation:
1import { CalendarController, CalendarOptions } from '@uplink-protocol/calendar-controller';23// Configuration with type checking4const options: CalendarOptions = {5 locale: 'en-US',6 firstDayOfWeek: 1, // Monday (TypeScript ensures this is 0-6)7 dateFormat: 'MM/DD/YYYY', // TypeScript ensures this is a valid format8 minDate: new Date(2025, 0, 1),9 maxDate: new Date(2025, 11, 31),10 yearRangeSize: 12 // TypeScript ensures this is a number11};1213// TypeScript will catch errors at compile time14const calendar = CalendarController(options);1516// Type inference works throughout the API17const currentDay = calendar.bindings.selectedDate.current; // Inferred as Date | null18const viewMode = calendar.bindings.viewMode.current; // Inferred as ViewMode1920// Methods are type-checked21calendar.methods.selectDate(2025, 4, 15); // TypeScript validates parameters22calendar.methods.setViewMode('month'); // TypeScript ensures valid view mode
Architecture
Calendar Controller is built using a service-oriented architecture with a reactive state management system, providing a robust foundation for complex calendar management.
Core Components
- CCCalendarController
The main entry point that initializes services and exposes the public API
- CSCalendarService
Core calendar operations and state management
- DSDateSelectionService
Handles single and range date selection logic
- DVDateValidationService
Enforces date constraints and validation rules
- DFDateFormattingService
Provides consistent date formatting and internationalization
- NSNavigationService
Manages calendar navigation between months, years, and view modes
- VSViewStateService
Maintains current view state and mode switching
- EMEventManagerService
Handles event dispatch and subscription management
State Management Concepts
Services
Internal state containers with getters/setters and subscription capabilities that manage different aspects of the calendar
Bindings
Public reactive properties exposed to consumers that automatically update when the underlying state changes
Methods
Public functions for interacting with the calendar that provide a clean API for calendar operations
Reactive Update Flow
- 1.User action triggers a method call
- 2.Method updates service state
- 3.Service notifies subscribers about state change
- 4.Binding callbacks are triggered with new values
- 5.UI components react to binding changes
Architecture Diagram
User Interface Layer
Public API Layer
Calendar Controller
Service Layer
Implementation Example
1// Example of how reactive updates flow through the system2import { CalendarController } from '@uplink-protocol/calendar-controller';34// Initialize calendar controller with config5const calendar = CalendarController({6 firstDayOfWeek: 1, // Monday7 dateFormat: 'MM/DD/YYYY',8 locale: 'en-US'9});1011// 1. Subscribe to calendar data changes in the UI12calendar.bindings.calendarDays.subscribe((days) => {13 console.log('Calendar days updated:', days);14 // Update UI components with new calendar grid15});1617calendar.bindings.selectedDate.subscribe((date) => {18 console.log('Selected date changed:', date);19 // Update UI to show selected date20});2122// 2. User navigates to next month via the public API23calendar.methods.nextMonth();2425// Internal flow (simplified):26// - nextMonth() method is called on CalendarController27// - CalendarController delegates to NavigationService.nextMonth()28// - NavigationService updates internal state29// - NavigationService notifies CalendarService of the change30// - CalendarService regenerates calendar days for new month31// - CalendarService updates its calendarDays state32// - CalendarService notifies subscribers (the binding)33// - The binding callback is triggered with the new values34// - UI component reacts to the binding change3536// 3. User selects a date37calendar.methods.selectDate(2025, 4, 15); // May 15, 20253839// Additional automatic steps:40// - DateValidationService is notified to validate the date41// - If validation passes, DateSelectionService updates selectedDate42// - ViewStateService may update view mode if needed43// - UI components subscribed to selectedDate update accordingly
Architecture Benefits
- ✓Separation of Concerns - Each service handles a specific aspect of calendar management
- ✓Testability - Services can be tested in isolation with minimal mocking
- ✓Extensibility - New capabilities can be added by extending existing services or adding new ones
- ✓Framework Agnostic - The core architecture isn't tied to any UI framework
Advanced Usage Notes
- •Services maintain immutable state internally to prevent side effects
- •Bindings provide a read-only view of state to prevent direct mutations
- •Method calls are the only way to update state, ensuring all updates go through proper validation
- •Event-based communication between services reduces tight coupling
Design Patterns
Service Locator
Used to register and locate specialized services within the controller, enabling loose coupling and testability.
Observer Pattern
Used for the reactive binding system, allowing components to subscribe to state changes without tight coupling.
Factory Pattern
CalendarController() factory function creates and configures all necessary services and their dependencies.
Strategy Pattern
Used for validation rules and date formatting, allowing for different strategies to be swapped in.
Immutable State
All state updates create new state objects rather than mutating existing ones, improving predictability.
Integration Flow
1// 1. Import and initialize2import { CalendarController } from '@uplink-protocol/calendar-controller';3const calendar = CalendarController();45// 2. Set up reactive bindings6const unsubscriptions = [7 calendar.bindings.calendarDays.subscribe(handleDaysChange),8 calendar.bindings.selectedDate.subscribe(handleDateSelection),9 calendar.bindings.viewMode.subscribe(handleViewModeChange),10 calendar.bindings.monthName.subscribe(handleMonthChange),11 calendar.bindings.currentYear.subscribe(handleYearChange)12];1314// 3. Access current state15const days = calendar.bindings.calendarDays.current;16const monthName = calendar.bindings.monthName.current;17const currentYear = calendar.bindings.currentYear.current;1819// 4. Render your UI with the data20function renderCalendar() {21 // Your UI rendering code using days, monthName, currentYear22}2324// 5. Handle user interactions25function handlePrevMonthClick() {26 calendar.methods.prevMonth();27 // UI updates automatically via bindings28}2930function handleDateClick(day) {31 if (!day.isDisabled && day.date) {32 calendar.methods.selectDate(33 day.date.getFullYear(),34 day.date.getMonth(),35 day.date.getDate()36 );37 // UI updates automatically via bindings38 }39}4041// 6. Clean up when component unmounts42function cleanup() {43 unsubscriptions.forEach(unsub => unsub());44}