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 callmapFields()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();