Logic

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:

npm
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:

typescript
1import {
2 CalendarController,
3 CalendarOptions,
4 CalendarDate,
5 DateRange
6} from '@uplink-protocol/calendar-controller';
7
8// Types are automatically available
9const options: CalendarOptions = {
10 firstDayOfWeek: 1,
11 dateFormat: 'MM/DD/YYYY'
12};
13
14const calendar = CalendarController(options);

Then install the integration package for your framework:

npm
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-indexed
3calendar.methods.selectDate(2025, 4, 21); // May 21, 2025
4
5// Enable range selection mode
6calendar.methods.setRangeSelectionMode(true);
7
8// Select range start and end
9calendar.methods.selectDate(2025, 4, 10); // Start date
10calendar.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 configuration
2const calendar = CalendarController({
3 locale: 'fr-FR',
4 firstDayOfWeek: 1 // Monday
5});
6
7// Access localized month and weekday names
8const 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 views
2const days = calendar.bindings.calendarDays.current; // Day view
3const months = calendar.bindings.calendarMonths.current; // Month view
4const years = calendar.bindings.calendarYears.current; // Year view
5
6// Navigate between views via method selection
7calendar.methods.selectMonth(3, 2025); // Switch to April 2025
8calendar.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 constraints
2const calendar = CalendarController({
3 minDate: new Date(2025, 0, 1), // January 1, 2025
4 maxDate: new Date(2025, 11, 31), // December 31, 2025
5 disabledDates: [
6 new Date(2025, 6, 4), // July 4th disabled
7 new Date(2025, 11, 25) // Christmas disabled
8 ]
9});
10
11// Check if a date is disabled
12const isDisabled = calendar.methods.isDateDisabled(new Date(2025, 6, 4));

Event System

  • Reactive event subscription
  • Comprehensive event coverage
  • Subscription cleanup handling
1// Subscribe to reactive bindings
2const unsubSelectedDate = calendar.bindings.selectedDate.subscribe((date) => {
3 console.log('Selected date:', date);
4});
5
6const unsubCalendarDays = calendar.bindings.calendarDays.subscribe((days) => {
7 console.log('Calendar updated:', days.length, 'days');
8});
9
10const unsubDateRange = calendar.bindings.selectedDateRange.subscribe((range) => {
11 console.log('Range:', range.startDate, 'to', range.endDate);
12});
13
14// Clean up subscriptions
15unsubSelectedDate.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 DateRange
6} from '@uplink-protocol/calendar-controller';
7
8// Type-safe configuration
9const options: CalendarOptions = {
10 firstDayOfWeek: 1,
11 dateFormat: 'MM/DD/YYYY',
12 initialSelectedDate: new Date()
13};
14
15const 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
Remember that Calendar Controller provides only the logical layer. You'll need to implement your own UI components that consume this logic.

Initialization

Create a new calendar controller with default or custom configuration:

1import { CalendarController } from '@uplink-protocol/calendar-controller';
2
3// Create with default settings
4const calendar = CalendarController();
5
6// Or with custom configuration
7const customCalendar = CalendarController({
8 firstDayOfWeek: 0, // Sunday (0) as first day of week
9 hideOtherMonthDays: false, // Show days from adjacent months
10 minDate: new Date(2023, 0, 1), // Jan 1, 2023 as earliest selectable date
11 maxDate: new Date(2025, 11, 31), // Dec 31, 2025 as latest selectable date
12 locale: 'en-US', // Use US English formatting
13 selectionMode: 'single' // Allow only single date selection
14});

Date Selection

Select dates with full validation against any constraints you've set:

1// Select a specific date using methods
2calendar.methods.selectDate(2025, 4, 21); // May 21, 2025 (year, month, day)
3
4// Get the currently selected date from bindings
5const selectedDate = calendar.bindings.selectedDate.current;
6
7// Set range selection mode and select a range
8calendar.methods.setRangeSelectionMode(true);
9calendar.methods.selectDate(2025, 4, 10); // Start date
10calendar.methods.selectDate(2025, 4, 15); // End date
11
12// Get selected range
13const selectedRange = calendar.bindings.selectedDateRange.current;
14
15// Clear selection
16calendar.methods.clearSelection();

Calendar Navigation

Navigate through the calendar with intuitive methods:

1// Move to next/previous month
2calendar.methods.nextMonth();
3calendar.methods.prevMonth();
4
5// Move to next/previous year
6calendar.methods.nextYear();
7calendar.methods.prevYear();
8
9// Jump to today
10calendar.methods.goToToday();
11
12// Navigate to a specific month and year
13calendar.methods.selectMonth(0, 2025); // January 2025
14calendar.methods.selectYear(2025); // Go to 2025
15
16// Switch view modes
17calendar.methods.setViewMode('month'); // Month view
18calendar.methods.setViewMode('year'); // Year view
19calendar.methods.setViewMode('day'); // Day view (default)

Rendering Data

Generate all the data needed to render your calendar UI:

1// Get days to render in the current day view
2const days = calendar.bindings.calendarDays.current;
3/* Example structure:
4[
5 {
6 date: Date(2025, 3, 30), // Date object for April 30
7 day: 30, // Day number
8 isCurrentMonth: false, // From previous month
9 isToday: false, // Not today
10 isSelected: false, // Not selected
11 isDisabled: false, // Can be selected
12 isRangeStart: false, // Not start of range
13 isRangeEnd: false, // Not end of range
14 isInRange: false // Not within a range
15 },
16 {
17 date: Date(2025, 4, 1), // Date object for May 1
18 day: 1, // Day number
19 isCurrentMonth: true, // From current month
20 // ...other properties
21 },
22 // ...more days
23]
24*/
25
26// Get months for month view
27const months = calendar.bindings.calendarMonths.current;
28
29// Get years for year view
30const years = calendar.bindings.calendarYears.current;
31
32// Get current month name and year
33const 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 changes
2const unsubscribeFromDateEvents = calendar.bindings.selectedDate.subscribe((selectedDate) => {
3 console.log('Date selected:', selectedDate);
4 // Update your UI here
5});
6
7// Subscribe to month/year navigation changes
8const unsubscribeFromMonthEvents = calendar.bindings.monthName.subscribe((monthName) => {
9 console.log('Current month:', monthName);
10});
11
12const unsubscribeFromYearEvents = calendar.bindings.currentYear.subscribe((year) => {
13 console.log('Current year:', year);
14});
15
16// Subscribe to view mode changes
17const unsubscribeFromViewModeEvents = calendar.bindings.viewMode.subscribe((viewMode) => {
18 console.log('View mode changed to:', viewMode); // 'day', 'month', or 'year'
19});
20
21// Don't forget to clean up subscriptions when done
22unsubscribeFromDateEvents();
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';
3
4function Calendar() {
5 const [calendar] = useState(() => CalendarController());
6 const [days, setDays] = useState([]);
7 const [monthName, setMonthName] = useState('');
8 const [currentYear, setCurrentYear] = useState('');
9
10 useEffect(() => {
11 // Set up subscriptions to reactive bindings
12 const subscriptions = [
13 calendar.bindings.calendarDays.subscribe(setDays),
14 calendar.bindings.monthName.subscribe(setMonthName),
15 calendar.bindings.currentYear.subscribe(setCurrentYear)
16 ];
17
18 // Clean up subscriptions
19 return () => {
20 subscriptions.forEach(sub => sub());
21 };
22 }, [calendar]);
23
24 // Navigation handlers
25 const handlePrevMonth = () => calendar.methods.prevMonth();
26 const handleNextMonth = () => calendar.methods.nextMonth();
27 const handleToday = () => calendar.methods.goToToday();
28
29 // Date selection handler
30 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 };
39
40 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>
48
49 <div className="calendar-grid">
50 {/* Calendar days */}
51 {days.map((day, index) => (
52 <div
53 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:

react
1import { useUplink } from "@uplink-protocol/react";
2import { CalendarController } from "@uplink-protocol/calendar-controller";
3
4function MyCalendar() {
5 const { state, methods } = useUplink(
6 () => CalendarController({
7 firstDayOfWeek: 1,
8 dateFormat: 'MM/DD/YYYY'
9 }),
10 { trackBindings: "all" }
11 );
12
13 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 };
22
23 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>
30
31 <div className="calendar-grid">
32 {state.calendarDays?.map((day, index) => (
33 <div
34 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 interface
2interface 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}
12
13// Calendar day representation
14interface 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}
25
26// Calendar month representation
27interface CalendarMonth {
28 month: number;
29 monthName: string;
30 year: number;
31 isCurrentMonth: boolean;
32 isSelected: boolean;
33 isDisabled: boolean;
34}
35
36// Calendar year representation
37interface CalendarYear {
38 year: number;
39 isCurrentYear: boolean;
40 isSelected: boolean;
41 isDisabled: boolean;
42}
43
44// Date range definition
45interface DateRange {
46 start: Date | null;
47 end: Date | null;
48}
49
50// View modes
51type ViewMode = 'day' | 'month' | 'year';

Type-Safe Events

The event system is fully typed for better development experience:

1// Type-safe binding subscriptions
2type 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;
8
9// Type-safe binding subscriptions
10const unsub1: () => void = calendar.bindings.selectedDate.subscribe((date) => {
11 // date is typed as Date | null
12 if (date) {
13 console.log('Selected:', date.toLocaleDateString());
14 }
15});
16
17const unsub2: () => void = calendar.bindings.selectedDateRange.subscribe((range) => {
18 // range is typed as DateRange with start and end
19 if (range.start && range.end) {
20 console.log('Range:', range.start, 'to', range.end);
21 }
22});
23
24const unsub3: () => void = calendar.bindings.calendarDays.subscribe((days) => {
25 // days is typed as CalendarDate[]
26 days.forEach(day => {
27 // TypeScript provides intellisense for all properties
28 if (day.isToday) {
29 console.log('Today is day', day.day);
30 }
31 });
32});
33
34// TypeScript ensures you're using bindings correctly
35calendar.bindings.viewMode.subscribe((mode) => {
36 // mode is typed as 'day' | 'month' | 'year'
37 if (mode === 'day') {
38 // TypeScript knows this is a valid condition
39 }
40});

Advanced TypeScript Integration

Working with Calendar Controller bindings in TypeScript:

1// Working with Calendar Controller bindings in TypeScript
2import { CalendarController, CalendarDate } from '@uplink-protocol/calendar-controller';
3
4const calendar = CalendarController();
5
6// Type-safe binding access
7const renderCalendar = (): void => {
8 const days: CalendarDate[] = calendar.bindings.calendarDays.current;
9
10 days.forEach(day => {
11 // TypeScript provides intellisense for all properties
12 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(' ');
18
19 // Render your UI with the day data
20 });
21};
22
23// Type-safe view mode handling
24const handleViewModeChange = (mode: 'day' | 'month' | 'year'): void => {
25 calendar.methods.setViewMode(mode);
26 // TypeScript ensures mode is valid
27};

Type-Safe Configuration

Configure the Calendar Controller with TypeScript validation:

1import { CalendarController, CalendarOptions } from '@uplink-protocol/calendar-controller';
2
3// Configuration with type checking
4const 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 format
8 minDate: new Date(2025, 0, 1),
9 maxDate: new Date(2025, 11, 31),
10 yearRangeSize: 12 // TypeScript ensures this is a number
11};
12
13// TypeScript will catch errors at compile time
14const calendar = CalendarController(options);
15
16// Type inference works throughout the API
17const currentDay = calendar.bindings.selectedDate.current; // Inferred as Date | null
18const viewMode = calendar.bindings.viewMode.current; // Inferred as ViewMode
19
20// Methods are type-checked
21calendar.methods.selectDate(2025, 4, 15); // TypeScript validates parameters
22calendar.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

  • CC
    CalendarController

    The main entry point that initializes services and exposes the public API

  • CS
    CalendarService

    Core calendar operations and state management

  • DS
    DateSelectionService

    Handles single and range date selection logic

  • DV
    DateValidationService

    Enforces date constraints and validation rules

  • DF
    DateFormattingService

    Provides consistent date formatting and internationalization

  • NS
    NavigationService

    Manages calendar navigation between months, years, and view modes

  • VS
    ViewStateService

    Maintains current view state and mode switching

  • EM
    EventManagerService

    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. 1.User action triggers a method call
  2. 2.Method updates service state
  3. 3.Service notifies subscribers about state change
  4. 4.Binding callbacks are triggered with new values
  5. 5.UI components react to binding changes

Architecture Diagram

User Interface Layer

React Components
Vue Components
Svelte Components

Public API Layer

Bindings (Reactive Properties)
Methods (Public Functions)

Calendar Controller

Orchestrates Services & Manages Calendar Lifecycle

Service Layer

CalendarService
DateSelectionService
DateValidationService
DateFormattingService
NavigationService
ViewStateService
EventManagerService
ConstraintsService

Implementation Example

reactive-updates.js
1// Example of how reactive updates flow through the system
2import { CalendarController } from '@uplink-protocol/calendar-controller';
3
4// Initialize calendar controller with config
5const calendar = CalendarController({
6 firstDayOfWeek: 1, // Monday
7 dateFormat: 'MM/DD/YYYY',
8 locale: 'en-US'
9});
10
11// 1. Subscribe to calendar data changes in the UI
12calendar.bindings.calendarDays.subscribe((days) => {
13 console.log('Calendar days updated:', days);
14 // Update UI components with new calendar grid
15});
16
17calendar.bindings.selectedDate.subscribe((date) => {
18 console.log('Selected date changed:', date);
19 // Update UI to show selected date
20});
21
22// 2. User navigates to next month via the public API
23calendar.methods.nextMonth();
24
25// Internal flow (simplified):
26// - nextMonth() method is called on CalendarController
27// - CalendarController delegates to NavigationService.nextMonth()
28// - NavigationService updates internal state
29// - NavigationService notifies CalendarService of the change
30// - CalendarService regenerates calendar days for new month
31// - CalendarService updates its calendarDays state
32// - CalendarService notifies subscribers (the binding)
33// - The binding callback is triggered with the new values
34// - UI component reacts to the binding change
35
36// 3. User selects a date
37calendar.methods.selectDate(2025, 4, 15); // May 15, 2025
38
39// Additional automatic steps:
40// - DateValidationService is notified to validate the date
41// - If validation passes, DateSelectionService updates selectedDate
42// - ViewStateService may update view mode if needed
43// - 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 initialize
2import { CalendarController } from '@uplink-protocol/calendar-controller';
3const calendar = CalendarController();
4
5// 2. Set up reactive bindings
6const 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];
13
14// 3. Access current state
15const days = calendar.bindings.calendarDays.current;
16const monthName = calendar.bindings.monthName.current;
17const currentYear = calendar.bindings.currentYear.current;
18
19// 4. Render your UI with the data
20function renderCalendar() {
21 // Your UI rendering code using days, monthName, currentYear
22}
23
24// 5. Handle user interactions
25function handlePrevMonthClick() {
26 calendar.methods.prevMonth();
27 // UI updates automatically via bindings
28}
29
30function 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 bindings
38 }
39}
40
41// 6. Clean up when component unmounts
42function cleanup() {
43 unsubscriptions.forEach(unsub => unsub());
44}

Share this page