Now.js Framework Documentation
EventCalendar
EventCalendar
EventCalendar is the calendar component used by Now.js applications for month, week, and day scheduling views. It is exposed as window.EventCalendar. When the Now.js core runtime is present, the bundle also registers the manager so it is available from Now.getManager('eventCalendar').
Files And Build
- Runtime bundle:
Now/dist/eventcalendar.min.css,Now/dist/eventcalendar.min.js - Source entry:
Now/entry-eventcalendar.js - Source implementation:
js/components/EventCalendar.js - Source styles:
Now/css/event-calendar.css - Rebuild command:
npm run build:eventcalendar
Features
- Month, week, and day views
- Auto locale resolution with framework i18n first, then Thai and English fallback strings
- Two scheduling semantics:
continuousandrecurring-slot - Multi-day spanning bars in month and week views
- Overlap column layout for timed events in week and day views
- API loading with nested response extraction via
eventDataPath - Event click API integration through
ResponseHandler - Optional month period picker with min/max bounds
- Keyboard navigation and framework event emission
- Auto discovery on DOM ready and supported SPA lifecycle events
Loading The Bundle
EventCalendar is designed to run with the Now.js core runtime. Load the core CSS and JS before the EventCalendar bundle.
Requirements
- Required for normal runtime:
Now/dist/now.core.min.css,Now/dist/now.core.min.js - Required for framework event emission:
EventManagerfrom the core bundle - Optional but recommended:
ModalorDialogManagerfor detail popups - Optional integrations:
ApiService,ResponseHandler, andI18nManager
Load Order
<link rel="stylesheet" href="Now/dist/now.core.min.css">
<link rel="stylesheet" href="Now/dist/eventcalendar.min.css">
<script src="Now/dist/now.core.min.js"></script>
<script src="Now/dist/eventcalendar.min.js"></script>EventCalendar.init() runs automatically after the script loads. Calendars are discovered on DOM ready and after route:changed, modal:shown, and page:loaded. Locale strings are refreshed again after locale:changed and i18n:updated.
If your application injects calendar markup outside those lifecycle events, call EventCalendar.discoverCalendars(container) after you insert the new DOM.
Basic Usage
Declarative HTML
<div
id="calendar"
data-event-calendar
data-view="month"
data-views="month,week,day"
data-locale="en"
data-first-day="1"
data-max-events="3"
data-show-period-picker="true"
data-api="api/calendar"
data-event-data-path="data"
></div>What this example does:
data-view="month"starts in month viewdata-first-day="1"uses Monday as the first day of the weekdata-apiloads the visible range from the serverdata-event-data-path="data"extracts events from a{ data: [...] }response
Expected result: the calendar renders the month view and requests the visible date range from api/calendar on first render.
JavaScript API
// Create or reuse the instance bound to #calendar.
const calendar = EventCalendar.create('#calendar', {
defaultView: 'month',
locale: 'en',
showPeriodPicker: true,
minDate: '2026-01',
maxDate: '2026-12',
onDateClick(date, instance) {
console.log('Date clicked', date, instance);
}
});
// Inject local events directly when you are not using config.api.
EventCalendar.setEvents(calendar, [
{
id: 'meeting-01',
title: 'Team Meeting',
start: '2026-04-21T09:00:00',
end: '2026-04-21T10:30:00',
allDay: false,
scheduleType: 'continuous',
color: '#4285F4'
},
{
id: 'booking-01',
title: 'Vehicle Reservation',
start: '2026-04-21T08:00:00',
end: '2026-04-23T17:00:00',
allDay: false,
scheduleType: 'continuous',
color: '#EA4335'
},
{
id: 'class-slot',
title: 'Daily Class 08:00 - 09:00',
start: '2026-04-21T08:00:00',
end: '2026-04-27T09:00:00',
startDate: '2026-04-21',
endDate: '2026-04-27',
slotStartTime: '08:00',
slotEndTime: '09:00',
scheduleType: 'recurring-slot',
color: '#34A853'
}
]);What this example does:
create()returns the existing instance if#calendarwas already initializedsetEvents()is the supported way to inject a local event array- The example mixes
continuousandrecurring-slotevents in one calendar
Expected result: the calendar renders a month view with a timed single-day meeting, a spanning booking, and a recurring daily slot.
Advanced Examples
API-Backed Calendar With Nested Response Data
<script>
window.App = {
calendar: {
handleEventClick(eventData, instance) {
console.log('Clicked event', eventData, instance);
}
}
};
</script>
<div
id="api-calendar"
data-event-calendar
data-view="month"
data-api="/api/calendar"
data-event-data-path="data.items"
data-on-event-click="App.calendar.handleEventClick"
></div>What this example does:
- Requests
/api/calendar?start=YYYY-MM-DD&end=YYYY-MM-DD - Extracts the event array from
response.data.items - Resolves the click callback from a global function path string
Recurring Daily Slot With Server-Driven Detail Actions
const bookingCalendar = EventCalendar.create('#booking-calendar', {
defaultView: 'week',
scheduleMode: 'continuous',
onEventClickApi: '/api/bookings/{id}'
});
EventCalendar.setEvents(bookingCalendar, [
{
id: 'slot-01',
title: 'Training Room 08:00 - 09:00',
start: '2026-04-21T08:00:00',
end: '2026-04-27T09:00:00',
startDate: '2026-04-21',
endDate: '2026-04-27',
slotStartTime: '08:00',
slotEndTime: '09:00',
scheduleType: 'recurring-slot',
color: '#34A853'
}
]);What this example does:
recurring-slotrenders one timed occurrence for each day in the inclusive date rangeonEventClickApirequests booking details when the event is clicked- If the response contains actions,
ResponseHandlerprocesses them before the built-in detail modal fallback is used
Configuration Options
| Option | Data attribute | Default | Description |
|---|---|---|---|
defaultView |
data-view |
'month' |
Initial view. Allowed values: 'month', 'week', 'day'. |
views |
data-views |
['month', 'week', 'day'] |
Enabled views. Declarative form is a comma-separated string. |
locale |
data-locale |
'auto' |
Locale preference. Uses the active i18n manager, the document language, or the browser locale when set to 'auto'. |
timezone |
- | 'local' |
Parsing mode for incoming dates. 'UTC' adjusts non-timezone strings to UTC semantics. |
scheduleMode |
data-schedule-mode |
'continuous' |
Fallback scheduling semantic when an event payload omits scheduleType. |
firstDayOfWeek |
data-first-day |
0 |
First weekday, where 0 is Sunday. |
maxEventsPerDay |
data-max-events |
3 |
Maximum inline month items and maximum visible spanning rows in standard layouts. |
showNavigation |
data-show-navigation |
true |
Show previous, next, and Today controls. |
showToday |
data-show-today |
true |
Show the Today button. |
showViewSwitcher |
data-show-view-switcher |
true |
Show month, week, and day switch buttons. |
showPeriodPicker |
data-show-period-picker |
false |
Show month and year dropdowns in month view only. |
api |
data-api |
null |
Event endpoint. When set, initial load and navigation request the visible date range from the API. |
apiMethod |
data-api-method |
'GET' |
HTTP method used by the fetch fallback. When ApiService is available, the component uses ApiService.get(url). |
eventDataPath |
data-event-data-path |
'data' |
Property path used to extract the event array from API responses. |
minDate |
data-min-date |
null |
Lower bound for navigation and picker options. Accepts Date, YYYY-MM, or YYYY-MM-DD. |
maxDate |
data-max-date |
null |
Upper bound for navigation and picker options. Accepts Date, YYYY-MM, or YYYY-MM-DD. |
yearRangeBefore |
data-year-range-before |
10 |
Picker year range before the current year when no minDate is set. |
yearRangeAfter |
data-year-range-after |
10 |
Picker year range after the current year when no maxDate is set. |
eventColors |
- | built-in palette | Fallback color palette used when an event payload omits color. |
onDateClick |
data-on-date-click |
null |
Callback function or global function path string invoked after a date click. |
onEventClick |
data-on-event-click |
null |
Callback function or global function path string invoked after an event click. |
onEventClickApi |
data-on-event-click-api |
null |
URL template for click-to-API flows, for example api/events/{id}. |
Event Payload
The component accepts plain event objects and normalizes them before rendering.
{
"id": "event-001",
"title": "Recurring Training",
"start": "2026-04-21T08:00:00",
"end": "2026-04-27T09:00:00",
"allDay": false,
"scheduleType": "recurring-slot",
"startDate": "2026-04-21",
"endDate": "2026-04-27",
"slotStartTime": "08:00",
"slotEndTime": "09:00",
"color": "#34A853",
"description": "Instructor-led class",
"location": "Room A"
}| Field | Type | Notes |
|---|---|---|
id |
string | Optional. Auto-generated if omitted. |
title |
string | Optional, falls back to 'Untitled'. |
start |
string|Date | Required for useful rendering. Used for parsing, sorting, and default range values. |
end |
string|Date | Optional. Defaults to the parsed start date when omitted. |
allDay |
boolean | For continuous events this defaults to true unless you explicitly send false. For timed bookings, send allDay: false. For recurring-slot, the component forces allDay to false. |
scheduleType / scheduleMode |
string | Supported values are continuous and recurring-slot. scheduleType on the event overrides the calendar-level scheduleMode. |
startDate / rangeStart |
string|Date | Optional inclusive range start used mainly for recurring-slot. |
endDate / rangeEnd |
string|Date | Optional inclusive range end used mainly for recurring-slot. |
slotStartTime |
string | HH:mm time for recurring daily slots. |
slotEndTime |
string | HH:mm time for recurring daily slots. |
color |
string | Event accent color. Falls back to the configured palette. |
description, location, category |
string | Optional metadata used by modal and detail flows. |
During normalization the original object is preserved internally as event.data. URL templates such as data-on-event-click-api="/api/bookings/{id}" can resolve placeholders from the normalized event object and, when nested lookup helpers are available, from the original payload as well.
Scheduling Semantics
continuous
- Default behavior
- Same-day timed events render as timed blocks when
allDay: falseis provided - Multi-day events render as spanning bars in month view and in the week all-day lane
- In week and day views, a multi-day timed event renders timed slices only on the start day and end day instead of duplicating the block in every day column
recurring-slot
- Represents the same daily time slot repeated across an inclusive date range
- Never renders in the month or week all-day lane
- Renders inline in month cells for each date in the range
- Renders timed occurrences for every day in week and day views using
slotStartTimeandslotEndTime
Rendering Notes
- Month view measures live DOM metrics to align spanning bars and inline items. The spacing is not based on a fixed hard-coded pixel formula.
- Week and day timed events use overlap columns so concurrent items appear side-by-side instead of stacking on top of each other.
- Under
480pxwidth, month view keeps a compact single-row spanning-bar lane. Hidden spanning bars are added to the+N moreindicator instead of disappearing. recurring-slotitems receive.ec-schedule-recurring-slot, which is styled with a dashed accent border by the default CSS.
API Response Handling
loadEvents() requests the visible date range using start and end query parameters in YYYY-MM-DD format.
The component can extract events from several response shapes:
- Direct array response
response.dataresponse.data.dataresponse.data.data.data- The property path defined by
eventDataPath
Example:
{
"success": true,
"data": [
{
"id": "booking-01",
"title": "Vehicle Reservation",
"start": "2026-04-21 08:00:00",
"end": "2026-04-23 17:00:00",
"allDay": false,
"scheduleType": "continuous",
"color": "#EA4335"
}
]
}Public JavaScript API
| Method | Parameters | Returns | Notes |
|---|---|---|---|
init(options) |
global defaults object | EventCalendar |
Merges global defaults, binds lifecycle handlers once, and scans the current DOM. |
discoverCalendars(container) |
Element or document |
void |
Scans container for [data-event-calendar] nodes and creates instances for them. |
create(element, options) |
selector or Element, optional config |
instance or null |
Reuses the existing instance if the element was already initialized. |
addEvent(calendar, event) |
instance or selector, event object | void |
Normalizes the new event, re-renders, and emits eventcalendar:eventAdd. |
removeEvent(calendar, eventId) |
instance or selector, string id | void |
Removes a matching event, re-renders, and emits eventcalendar:eventRemove. |
updateEvent(calendar, eventId, partialData) |
instance or selector, string id, partial event object | void |
Re-normalizes the merged event and emits eventcalendar:eventUpdate. |
getEvents(calendar, start, end) |
instance or selector, optional Date range |
array | When start and end are provided, returns events whose ranges overlap that window. |
setEvents(calendar, events) |
instance or selector, event array | void |
Replaces the local event collection and re-renders immediately. |
refreshEvents(calendar) |
instance or selector | void |
Reloads from config.api when an API endpoint is configured. |
getInstance(element) |
selector, Element, or instance |
instance or null |
Returns the bound instance or null if the element is not initialized. |
navigate(calendar, direction) |
instance or selector, -1 or 1 |
void |
Moves the current period backward or forward according to the active view. |
goToToday(calendar) |
instance or selector | void |
Jumps to the current date and emits eventcalendar:today. |
changeView(calendar, view) |
instance or selector, 'month', 'week', or 'day' |
void |
Switches view and re-renders the current data. If your API returns only the current visible range, call refreshEvents() after switching to a wider view. |
destroy(calendar) |
instance or selector | void |
Removes listeners, clears the DOM, unregisters the instance, and emits eventcalendar:destroy. |
Emitted Events
The component emits framework events through EventManager and also dispatches matching DOM CustomEvents on document.
| Event | detail payload |
|---|---|
eventcalendar:dateClick |
{ date, instance, element } |
eventcalendar:eventClick |
{ event, instance, element } |
eventcalendar:navigate |
{ date, direction, view, instance } |
eventcalendar:today |
{ date, view, instance } |
eventcalendar:viewChange |
{ view, date, instance } |
eventcalendar:periodChange |
{ date, year, month, view, instance } |
eventcalendar:eventAdd |
{ event, instance } |
eventcalendar:eventRemove |
{ eventId, instance } |
eventcalendar:eventUpdate |
{ event, instance } |
eventcalendar:destroy |
{ element } |
Example listener:
document.addEventListener('eventcalendar:eventClick', (event) => {
console.log('Clicked event payload', event.detail.event);
});Accessibility And Interaction
- The root calendar element uses
role="application"and becomes focusable withtabindex="0"when needed. - The current period label uses
role="status"andaria-live="polite"so screen readers announce date-range changes. - Navigation buttons and the Today button receive localized
aria-labelvalues. - Keyboard shortcuts are built in:
ArrowLeftandArrowRightnavigate,HomeorTgo to today, andM,W,Dswitch to enabled month, week, and day views.
ResponseHandler Integration
When onEventClickApi is set, the event click URL is created by replacing placeholders with event data.
<div
data-event-calendar
data-api="api/calendar"
data-on-event-click-api="api/calendar/{id}"
></div>Current behavior:
- If the API response contains actions,
ResponseHandlerprocesses them - If it does not contain actions, the component merges any returned
payload.datainto the clicked event and falls back to the built-in event detail modal - If
onEventClickis also configured, the callback still runs aftereventcalendar:eventClickis emitted
Gotchas And Best Practices
- Send
allDay: falsefor timedcontinuousevents. If you omit it, the event is treated as an all-day item. - Use
scheduleType: 'recurring-slot'only for daily repeating time slots across a date range. Usecontinuousfor true spanning reservations. - Load
ModalorDialogManagerif you want the built-in detail popup. Otherwise provide your ownonEventClickhandler. - If you enable
showPeriodPicker, also setminDateandmaxDatewhen the allowed booking range is limited. - If your application injects HTML outside the supported SPA lifecycle events, call
EventCalendar.discoverCalendars(container)yourself. - If you switch from a narrower API-loaded view to a wider view, call
EventCalendar.refreshEvents(calendar)to request the wider range. - After editing source code or styles, rebuild the shipped products with
npm run build:eventcalendar.
Styling Hooks
Useful CSS variables:
:root {
--ec-primary: #4285F4;
--ec-day-height: 120px;
--ec-event-height: 22px;
--ec-event-gap: 2px;
--ec-allday-label-width: 60px;
--ec-time-label-width: 60px;
}Useful state classes:
.ec-schedule-continuous.ec-schedule-recurring-slot.ec-continuation-slice.ec-event-start.ec-event-end.ec-today.ec-other-month