Milk Admin

CalendarBuilder

The CalendarBuilder provides a powerful, database-driven calendar system with automatic query filtering, event mapping, and extensive customization options. It extends GetDataBuilder, inheriting all query manipulation capabilities.

Key Features

  • Database Integration: Automatically queries events from your database with proper date filtering
  • Multiple Views: Monthly and weekly calendar views with automatic view type tracking
  • Query Manipulation: Full access to GetDataBuilder methods (where, orderBy, join, etc.)
  • Field Mapping: Map your database fields to calendar properties with support for callable functions
  • AJAX Support: Built-in AJAX navigation with automatic type and filter persistence
  • Localization: Multi-language support using PHP's IntlDateFormatter
  • Multi-day Events: Automatic handling of events spanning multiple days
  • Responsive Design: Bootstrap-based responsive calendar grid
  • Customization: Extensive styling and behavior options

Quick Start

Basic Calendar

use Builders\CalendarBuilder;

// Simple calendar with default settings
$calendar = CalendarBuilder::create($eventModel)
    ->setMonthYear()  // Auto-detects from request or uses current month
    ->render();

echo $calendar;

Complete Example from Events Module

$calendar = CalendarBuilder::create($this->model, 'calendar_events')
    ->mapFields([
        'id' => 'id',
        'title' => 'title',
        'start_datetime' => 'start_datetime',
        'end_datetime' => 'end_datetime',
        'class' => function($event, $event_raw) {
            return $event_raw->event_class;
        }
    ])
    ->setMonthYear()
    ->setHeaderTitle('Events Calendar')
    ->setHeaderIcon('bi-calendar-event')
    ->setHeaderColor('primary')
    ->onAppointmentClick('?page=events&action=edit&id=%id%')
    ->onEmptyDateClick('?page=events&action=new&date=%date%')
    ->render();

Database Query Manipulation

CalendarBuilder extends GetDataBuilder, providing full access to query manipulation methods. The builder automatically filters events for the selected month, but you can add additional conditions.

Basic Query Filtering

$calendar = CalendarBuilder::create($eventModel)
    ->where('status = ?', ['active'])
    ->where('user_id = ?', [$userId])
    ->orderBy('start_datetime', 'asc')
    ->setMonthYear()
    ->render();

Advanced Query with Joins

$calendar = CalendarBuilder::create($eventModel)
    // Join with users table
    ->join('LEFT JOIN #__users u ON u.id = t.user_id')

    // Filter by category
    ->where('t.category_id = ?', [$categoryId])

    // Only show public or user's own events
    ->where('(t.visibility = ? OR t.user_id = ?)', ['public', $userId], 'OR')

    // Order by priority then date
    ->orderBy('t.priority', 'desc')
    ->orderBy('t.start_datetime', 'asc')

    ->setMonthYear()
    ->render();

Query with Search Filter

$search = $_GET['search'] ?? '';

$calendar = CalendarBuilder::create($eventModel)
    ->setMonthYear();

// Add search filter if provided
if (!empty($search)) {
    $calendar->where(
        '(title LIKE ? OR description LIKE ?)',
        ['%' . $search . '%', '%' . $search . '%']
    );
}

echo $calendar->render();

Field Mapping

Map your database fields to calendar event properties. Supports both string field names and callable functions for dynamic values.

Basic Field Mapping

$calendar = CalendarBuilder::create($eventModel)
    ->mapFields([
        'id' => 'event_id',
        'title' => 'event_name',
        'start_datetime' => 'start_date',
        'end_datetime' => 'end_date',
        'class' => 'css_class'
    ])
    ->setMonthYear()
    ->render();

Advanced Mapping with Callable Functions

$calendar = CalendarBuilder::create($eventModel)
    ->mapFields([
        'id' => 'id',
        'title' => function($event, $event_raw) {
            // Custom title with user name
            return $event_raw->title . ' (' . $event_raw->user_name . ')';
        },
        'start_datetime' => 'start_datetime',
        'end_datetime' => 'end_datetime',
        'class' => function($event, $event_raw) {
            // Dynamic CSS class based on priority
            if ($event_raw->priority == 'high') {
                return 'event-danger';
            } elseif ($event_raw->priority == 'medium') {
                return 'event-warning';
            }
            return 'event-primary';
        }
    ])
    ->setMonthYear()
    ->render();

Header Customization

Header Styling

$calendar = CalendarBuilder::create($eventModel)
    ->setHeaderTitle('Team Calendar')
    ->setHeaderIcon('bi-calendar-check')
    ->setHeaderColor('success')  // primary, secondary, success, danger, warning, info, light, dark
    ->setMonthYear()
    ->render();

Hide Header Completely

$calendar = CalendarBuilder::create($eventModel)
    ->setShowHeader(false)
    ->setCompact(true)
    ->setMonthYear()
    ->render();

Graphic Styling

CalendarBuilder provides comprehensive CSS class customization methods similar to TableBuilder and ListBuilder. You can customize the appearance of every calendar element.

Quick Color Theme

$calendar = CalendarBuilder::create($eventModel)
    ->calendarColor('success')  // Applies coordinated green theme
    ->setMonthYear()
    ->render();

// Available colors: primary, secondary, success, danger, warning, info, light, dark
// Also supports aliases: blue, green, red, yellow, cyan, gray, black, white

Individual Element Styling

$calendar = CalendarBuilder::create($eventModel)
    ->containerClass('shadow-lg rounded-4 border border-2 border-success')
    ->gridClass('border-0')
    ->weekdaysClass('bg-success text-white fw-bold')
    ->daysGridClass('gap-2')
    ->dayClass('border border-success bg-light')
    ->dayNumberClass('text-success fw-bold')
    ->appointmentContainerClass('gap-2')
    ->appointmentClass('rounded-pill shadow-sm')
    ->setMonthYear()
    ->render();

Custom Attributes

$calendar = CalendarBuilder::create($eventModel)
    // Add custom data attributes
    ->addCalendarAttr('container', 'data-theme', 'dark')
    ->addCalendarAttr('container', 'class', 'custom-calendar')
    ->addCalendarAttr('day', 'data-tooltip', 'enabled')
    ->setMonthYear()
    ->render();

Combined Theme Example

// Elegant theme with shadows
$calendar = CalendarBuilder::create($eventModel)
    ->calendarColor('warning')
    ->containerClass('shadow-lg')
    ->weekdaysClass('bg-warning bg-opacity-25 text-dark fw-semibold border-bottom border-warning')
    ->dayClass('shadow-sm')
    ->dayNumberClass('badge bg-warning bg-opacity-10 text-dark')
    ->setMonthYear()
    ->render();

// Dark theme
$calendar = CalendarBuilder::create($eventModel)
    ->calendarColor('dark')
    ->containerClass('border border-secondary')
    ->gridClass('bg-dark')
    ->weekdaysClass('bg-secondary text-light')
    ->daysGridClass('bg-dark')
    ->dayClass('bg-dark text-light border-secondary')
    ->dayNumberClass('text-warning fw-bold')
    ->setMonthYear()
    ->render();

// Minimalista theme
$calendar = CalendarBuilder::create($eventModel)
    ->containerClass('border-0')
    ->gridClass('border-0 shadow-none')
    ->weekdaysClass('bg-white border-bottom text-secondary text-uppercase small')
    ->daysGridClass('gap-1 bg-white')
    ->dayClass('border-0 rounded-3')
    ->dayNumberClass('small text-secondary')
    ->setMonthYear()
    ->render();

Using with Plugin Directly

// You can also pass calendar_attrs directly to the plugin
$calendar = Get::themePlugin('Calendar', [
    'month' => $month,
    'year' => $year,
    'header_color' => 'success',
    'calendar_attrs' => [
        'container' => ['class' => 'shadow-lg rounded-4'],
        'weekdays' => ['class' => 'bg-success text-white fw-bold'],
        'day' => ['class' => 'border border-success'],
        'day-number' => ['class' => 'text-success fw-bold']
    ]
]);

Navigation Controls

Customize Navigation Elements

$calendar = CalendarBuilder::create($eventModel)
    // Control which navigation elements are shown
    ->setShowYearMonthSelect(true)   // Show year/month dropdowns
    ->setShowPrevNextButtons(true)   // Show prev/next arrows
    ->setShowTodayButton(false)      // Hide today button
    ->setMonthYear()
    ->render();

Set Date Range Limits

$calendar = CalendarBuilder::create($eventModel)
    // Limit navigation from 2024 to 2026
    ->setDateRange(2024, 2026)
    ->setMonthYear()
    ->render();

// Or set specific month ranges
$calendar = CalendarBuilder::create($eventModel)
    // From June 2024 to November 2026
    ->setDateRange(2024, 2026, 6, 11)
    ->setMonthYear()
    ->render();

// Set only minimum date
$calendar = CalendarBuilder::create($eventModel)
    ->setMinDate(2024, 1)  // From January 2024
    ->setMonthYear()
    ->render();

// Set only maximum date
$calendar = CalendarBuilder::create($eventModel)
    ->setMaxDate(2026, 12)  // Until December 2026
    ->setMonthYear()
    ->render();

Display Options

Calendar Views

CalendarBuilder supports both monthly and weekly views. The view type is automatically tracked and persisted during AJAX navigation.

Monthly View (Default)

$calendar = CalendarBuilder::create($eventModel)
    ->useMonthlyView()  // or ->setViewType('monthly')
    ->setMonthYear()
    ->render();

Weekly View

$calendar = CalendarBuilder::create($eventModel)
    ->useWeeklyView()  // or ->setViewType('weekly')
    ->setWeeklyHours(8, 20)  // Show hours from 8:00 to 20:00
    ->setWeeklyHourHeight(60)  // 60px per hour
    ->setMonthYear()
    ->render();

// Or use the all-in-one method
$calendar = CalendarBuilder::create($eventModel)
    ->useWeeklyView()
    ->setWeeklyViewSettings(8, 20, 60)  // start, end, height
    ->setMonthYear()
    ->render();

Compact Mode

$calendar = CalendarBuilder::create($eventModel)
    ->setCompact(true)  // Smaller calendar, no event details shown
    ->setHighlightDaysWithAppointments(true)  // Add visual indicator for days with events
    ->setMonthYear()
    ->render();

Locale Settings

// Italian calendar
$calendar = CalendarBuilder::create($eventModel)
    ->setLocale('it_IT')
    ->setMonthYear()
    ->render();

// French calendar
$calendar = CalendarBuilder::create($eventModel)
    ->setLocale('fr_FR')
    ->setMonthYear()
    ->render();

// German calendar
$calendar = CalendarBuilder::create($eventModel)
    ->setLocale('de_DE')
    ->setMonthYear()
    ->render();

Click Handlers

Configure URLs for different click events. Use placeholders %id% for event ID and %date% for date in Y-m-d format.

Event Click Handler

$calendar = CalendarBuilder::create($eventModel)
    // When user clicks an event, open edit form
    ->onAppointmentClick('?page=events&action=edit&id=%id%')
    ->setMonthYear()
    ->render();

Empty Date Click Handler

$calendar = CalendarBuilder::create($eventModel)
    // When user clicks an empty date, open new event form with pre-filled date
    ->onEmptyDateClick('?page=events&action=new&date=%date%')
    ->setMonthYear()
    ->render();

Date with Appointments Click Handler

$calendar = CalendarBuilder::create($eventModel)
    // When user clicks a date with events, show list of all events for that day
    ->onDateWithAppointmentsClick('?page=events&action=list&date=%date%', 'fetch')
    ->setMonthYear()
    ->render();

// Use 'link' mode for regular navigation instead of AJAX
$calendar = CalendarBuilder::create($eventModel)
    ->onDateWithAppointmentsClick('?page=events&action=list&date=%date%', 'link')
    ->setMonthYear()
    ->render();

Complete Click Handlers Example

$calendar = CalendarBuilder::create($eventModel)
    ->onAppointmentClick('?page=events&action=edit&id=%id%')
    ->onEmptyDateClick('?page=events&action=new&date=%date%')
    ->onDateWithAppointmentsClick('?page=events&action=daily&date=%date%', 'fetch')
    ->setMonthYear()
    ->render();

Custom Cell Renderer

For advanced customization, provide a custom function to render each day cell. The renderer receives all day information and returns custom HTML.

Custom Cell Renderer Example

$calendar = CalendarBuilder::create($eventModel)
    ->setCustomCellRenderer(function(
        int $day,
        int $month,
        int $year,
        bool $otherMonth,
        string $date,
        bool $isToday,
        array $appointments,
        $calendar
    ) {
        $classes = ['calendar-day'];

        if ($otherMonth) {
            $classes[] = 'other-month';
        }

        if ($isToday) {
            $classes[] = 'today';
        }

        $html = '<div class="' . implode(' ', $classes) . '">';

        // Custom day number with badge
        if ($isToday) {
            $html .= '<div class="day-number" style="background: #0d6efd; color: white; border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; font-weight: bold;">';
        } else {
            $html .= '<div class="day-number">';
        }
        $html .= $day;
        $html .= '</div>';

        // Show event count badge
        if (!empty($appointments)) {
            $count = count($appointments);
            $badgeColor = $count > 3 ? 'danger' : ($count > 1 ? 'warning' : 'success');
            $html .= '<span class="badge bg-' . $badgeColor . '" style="font-size: 0.7rem;">';
            $html .= $count . ' event' . ($count > 1 ? 's' : '');
            $html .= '</span>';
        }

        $html .= '</div>';
        return $html;
    })
    ->setMonthYear()
    ->render();

AJAX Implementation

CalendarBuilder automatically handles AJAX requests for navigation. The calendar automatically tracks and persists the view type (monthly/weekly) and any filters through hidden form fields during AJAX updates.

Hidden Form Fields

The calendar form includes two hidden fields that are automatically managed:

  • type: Stores the current view type ('monthly' or 'weekly')
  • filters: Stores custom filter data as a string

Basic AJAX Handler

// In your controller
#[RequestAction('home')]
public function calendarView()
{
    $calendar_id = 'my_calendar';
    $calendar_params = $_REQUEST[$calendar_id] ?? [];

    $month = (int)($calendar_params['month'] ?? date('n'));
    $year = (int)($calendar_params['year'] ?? date('Y'));
    $type = $calendar_params['type'] ?? 'monthly';  // Get persisted view type

    $calendar = CalendarBuilder::create($this->model, $calendar_id)
        ->setViewType($type)  // Restore view type
        ->setMonthYear($month, $year)
        ->setHeaderTitle('My Calendar')
        ->render();

    // Handle AJAX request
    if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
        strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {

        Response::json([
            'success' => true,
            'html' => $calendar
        ]);
    }

    // Full page render
    Response::render(__DIR__ . '/Views/calendar.php', [
        'calendar' => $calendar
    ]);
}

AJAX with Custom Filters

// AJAX handler with filter persistence
#[RequestAction('home')]
public function calendarView()
{
    $calendar_id = 'my_calendar';
    $calendar_params = $_REQUEST[$calendar_id] ?? [];

    $month = (int)($calendar_params['month'] ?? date('n'));
    $year = (int)($calendar_params['year'] ?? date('Y'));
    $type = $calendar_params['type'] ?? 'monthly';
    $filters = $calendar_params['filters'] ?? '';  // Get persisted filters

    // Parse filters if needed
    $category = $_GET['category'] ?? '';

    $calendar = CalendarBuilder::create($this->model, $calendar_id)
        ->setViewType($type)
        ->setMonthYear($month, $year);

    // Apply filters
    if ($category) {
        $calendar->where('category = ?', [$category])
                 ->setFilters($category);  // Store filter for persistence
    }

    $html = $calendar->render();

    // Handle AJAX request
    if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
        strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {

        Response::json([
            'success' => true,
            'html' => $html
        ]);
    }

    // Full page render
    Response::render(__DIR__ . '/Views/calendar.php', [
        'calendar' => $html
    ]);
}

Complete Real-World Example

This example shows a fully-featured calendar from the Events module with filtering, custom mapping, and click handlers:

class EventsController extends AbstractController
{
    #[RequestAction('home')]
    public function eventsCalendar()
    {
        $calendar_id = 'calendar_events';
        $calendar_params = $_REQUEST[$calendar_id] ?? [];

        // Get month/year from request
        $month = (int)($calendar_params['month'] ?? $_REQUEST['month'] ?? date('n'));
        $year = (int)($calendar_params['year'] ?? $_REQUEST['year'] ?? date('Y'));

        // Build calendar with all customizations
        $calendar = CalendarBuilder::create($this->model, $calendar_id)
            // Field mapping with callable for dynamic class
            ->mapFields([
                'id' => 'id',
                'title' => 'title',
                'start_datetime' => 'start_datetime',
                'end_datetime' => 'end_datetime',
                'class' => function($event, $event_raw) {
                    return $event_raw->event_class;
                }
            ])

            // Date and locale
            ->setMonthYear($month, $year)
            ->setLocale('it_IT')

            // Header customization
            ->setHeaderTitle('Events Calendar')
            ->setHeaderIcon('bi-calendar-event')
            ->setHeaderColor('primary')

            // Click handlers
            ->onAppointmentClick('?page=' . $this->page . '&action=edit&id=%id%')
            ->onEmptyDateClick('?page=' . $this->page . '&action=edit&date=%date%')

            // Optional: Set date range
            // ->setDateRange(2024, 2026)

            ->render();

        // Handle AJAX request
        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
            strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {

            Response::json([
                'success' => true,
                'html' => $calendar
            ]);
        }

        // Full page render
        Response::render(__DIR__ . '/Views/calendar_page.php', [
            'calendar' => $calendar,
            'html' => $calendar,
            ...$this->getCommonData()
        ]);
    }
}

Method Reference

Query Methods (Inherited from GetDataBuilder)

Method Parameters Description
where() $condition, $params, $logic Add WHERE condition to query
orderBy() $field, $direction Add ORDER BY clause
join() $join_clause Add JOIN clause
limit() $limit Set query LIMIT (note: calendar removes limit automatically)

Calendar-Specific Methods

Method Parameters Description
setMonth() int $month Set display month (1-12)
setYear() int $year Set display year
setMonthYear() ?int $month, ?int $year Set both month and year (auto-detects from request if null)
setLocale() string $locale Set locale for date formatting (e.g., 'it_IT', 'fr_FR')
setCalendarId() string $id Set calendar ID for AJAX requests
mapFields() array $mappings Map database fields to event properties

View Type Methods

Method Parameters Description
setViewType() string $type Set view type: 'monthly' or 'weekly'
useMonthlyView() - Shortcut for setViewType('monthly')
useWeeklyView() - Shortcut for setViewType('weekly')
setWeeklyViewSettings() int $hourStart, int $hourEnd, int $hourHeight Configure weekly view (start hour, end hour, px per hour)
setWeeklyHours() int $start, int $end Set hour range for weekly view (0-23, 1-24)
setWeeklyHourHeight() int $height Set hour cell height in pixels (min 40px)
setFilters() string $filters Set custom filters string (persisted in AJAX)
getViewType() - Get current view type

Header Methods

Method Parameters Description
setHeaderTitle() string $title Set header title
setHeaderIcon() string $icon Set header icon (Bootstrap Icons class)
setHeaderColor() string $color Set header color (primary, success, danger, etc.)
setShowHeader() bool $show Show/hide header completely

Navigation Methods

Method Parameters Description
setShowYearMonthSelect() bool $show Show/hide year and month dropdown selectors
setShowPrevNextButtons() bool $show Show/hide previous/next month buttons
setShowTodayButton() bool $show Show/hide today button
setMinDate() int $year, ?int $month Set minimum navigable date
setMaxDate() int $year, ?int $month Set maximum navigable date
setDateRange() int $minYear, int $maxYear, ?int $minMonth, ?int $maxMonth Set both min and max navigable dates

Display Methods

Method Parameters Description
setCompact() bool $compact Enable/disable compact mode
setHighlightDaysWithAppointments() bool $highlight Add special styling to days with events
setCustomCellRenderer() callable $renderer Set custom function to render day cells

Styling Methods

Method Parameters Description
calendarColor() string $color Apply coordinated color theme (primary, success, danger, etc.)
containerClass() string $classes Set CSS classes for calendar container
gridClass() string $classes Set CSS classes for calendar grid
weekdaysClass() string $classes Set CSS classes for weekdays header
daysGridClass() string $classes Set CSS classes for days grid container
dayClass() string $classes Set CSS classes for individual day cells
dayNumberClass() string $classes Set CSS classes for day numbers
appointmentContainerClass() string $classes Set CSS classes for appointments container
appointmentClass() string $classes Set CSS classes for appointment items
setCalendarAttrs() array $attrs Set all calendar attributes at once
addCalendarAttr() string $element, string $key, string $value Add single attribute to a calendar element

Click Handler Methods

Method Parameters Description
onAppointmentClick() string $url Set URL for event clicks (use %id% placeholder)
onEmptyDateClick() string $url Set URL for empty date clicks (use %date% placeholder)
onDateWithAppointmentsClick() string $url, string $mode Set URL and mode ('fetch' or 'link') for dates with events

Rendering Methods

Method Parameters Description
render() - Generate and return calendar HTML
__toString() - Magic method - allows using calendar as string

Best Practices

  • Use Auto-Detection: Call setMonthYear() without parameters to auto-detect from request
  • Set Calendar ID: Always provide a unique calendar ID when using multiple calendars on the same page
  • Map Fields Properly: Ensure your database has datetime fields for start_datetime and end_datetime
  • Call mapFields Before setMonthYear: setMonthYear() applies the month filter using the mapped date fields, so call mapFields() first if your columns are named differently (e.g. data)
  • Use Callable Mapping: For dynamic event classes or titles, use callable functions in mapFields()
  • Filter Efficiently: Add WHERE conditions before calling setMonthYear() for better performance
  • Handle AJAX: Always check for AJAX requests and return JSON for smooth navigation
  • Set Date Ranges: Limit navigation range to prevent unnecessary database queries
  • Localization: Set locale to match your application's language
  • Test Multi-day Events: Ensure your start_datetime and end_datetime fields can span multiple days

Common Patterns

Calendar with User Filter

$userId = Get::currentUserId();

$calendar = CalendarBuilder::create($eventModel)
    ->where('user_id = ? OR visibility = ?', [$userId, 'public'])
    ->setMonthYear()
    ->setHeaderTitle('My Events')
    ->render();

Calendar with Category Filter

$category = $_GET['category'] ?? null;

$calendar = CalendarBuilder::create($eventModel)
    ->setMonthYear();

if ($category) {
    $calendar->where('category = ?', [$category]);
}

echo $calendar->render();

Compact Sidebar Calendar

$calendar = CalendarBuilder::create($eventModel)
    ->setMonthYear()
    ->setCompact(true)
    ->setHighlightDaysWithAppointments(true)
    ->setShowYearMonthSelect(false)
    ->setShowTodayButton(false)
    ->setHeaderTitle('Quick View')
    ->setHeaderColor('secondary')
    ->onDateWithAppointmentsClick('?page=events&date=%date%', 'link')
    ->render();
Loading...