Calendar Layout Component
The calendar layout component renders entities of your modules as events in a calendar (month, week, day, year and
list views). Each event type in options.events maps one date field of one module to events: a module appointments
with a dateRange field becomes one event type, a module employees with a birthdate field another. The user can
switch event types on and off in a sidebar, click an event to open the entity, drag and resize events to change the
date, and click an empty day to create a new entity.
Like every other component it lives in a column of a layout:
{ "type": "calendar", "options": { "events": [ { "identifier": "appointments", "module": "appointments", "field": "period", "recipes": { "title": "this.title" } } ] }}This is the smallest useful calendar: one event type, all other options at their defaults. identifier, module and
field should always be set. The title recipe tells the calendar which text to print on the event
(see Event titles and recipes).
Calendar options
Section titled “Calendar options”All options are set in options of the component. Option names are snake_case.
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
events | array<EventType> | yes* | none | The event types shown in the calendar, see Event types. *Without events the component falls back to the legacy mode, see Legacy mode. |
available_views | array<string> | no | ["year","month","week","day","list"] | Views the user can switch between. The view switcher is hidden when only one view is listed. Besides the five names above, resourceWeek and resourceMonth (see Resource view) and the keys of custom_views are accepted; any other value is passed to FullCalendar as a view name. |
default_view | string | no | "month" | View that is shown first. Same values as available_views. A view the user has picked before is remembered in the browser (per module of the surrounding layout) and takes precedence. |
custom_views | object | no | {} | Additional FullCalendar view definitions, keyed by view name. Add the key to available_views to offer it, or set it as default_view. |
editable | boolean | no | true | Whether events can be moved and resized by dragging. false makes the whole calendar read-only for dates. Can be overridden per event type. |
height | string or number | no | "auto" | Height of the calendar grid, e.g. "50vh", 600 or "auto" (grows with the content). |
week_numbers | boolean | no | false | Show calendar week numbers in the day grid views. |
nav_buttons | boolean | no | true | Show the navigation buttons (previous, today, next, previous/next year, …). The title and the view switcher stay visible. |
filter_sidebar | boolean | no | true | Show the sidebar button that lets the user toggle event types, filter them and create an iCalendar link. With false all event types are always visible. |
fetch_buffer_units | number | no | length of the active range | How far beyond the visible range events are loaded, counted in units of the current view (months in month view, weeks in week view, days in day view). Default: the active range of the current view is loaded once more before and after, so the next click on “next” needs no new request. 0 loads no extra range. |
The component also accepts the options every layout element has, such as hidden_from_frontend, show_in and
recipes (see Layout overview).
Event types
Section titled “Event types”Every entry in options.events is one event type. The calendar loads the entities of module that fall into the
visible date range and draws one event per entity.
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
identifier | string | yes | Unique name of the event type within this calendar. Used as key for the sidebar selection and as translation key calendar.<identifier> for the label in the sidebar. | |
module | string | yes | Identifier of the module whose entities become events. If the module or the field does not exist in the frontend (for example because the user may not see the module), the event type is silently skipped. | |
field | string | yes | Identifier of the date field of that module. Supported field types: date, datetime, dateRange. For dateRange the start and end of the range are the start and end of the event. | |
color | string (CSS color) | no | FullCalendar default; #1890ff for the sidebar checkbox | Background color of the events and of the sidebar checkbox. |
text_color | string (CSS color) | no | FullCalendar default | Text color of the events. |
icon | string | no | Icon name shown at the right edge of the event. | |
title | string | no | empty | Static title for all events of this type. Normally you use recipes.title instead. |
quick_create | boolean | no | false | Allow creating an entity of this module by clicking an empty day. See Interactions. |
editable | boolean | no | the calendar’s editable (true) | Whether events of this type can be moved and resized. |
pre_filters | array<PreFilter> | no | Restricts which entities are loaded, see Filters and permissions. | |
recurrence | string or object | no | Repeat every event of this type, e.g. "yearly". See Recurring events. Usually set with a recipe. | |
recurrence_field | string | no | Identifier of a field of the module that holds the repeat frequency. Needed so that repeating entities are found regardless of the visible range, see Recurring events. | |
recipes | object<string, Recipe> | no | Recipes that compute event attributes per entity, see Event titles and recipes. |
Event titles and recipes
Section titled “Event titles and recipes”recipes maps an event attribute to a recipe. Each recipe is evaluated once per entity. Inside the
recipe, this is the entity (all fields of the module are available, relations as far as they are loaded in the
module’s table response). The entity of the surrounding layout, if there is one, is available as this._context.
The result of the recipes overrides the static value of the same name in the event type. These attributes are read by the calendar:
| Attribute | Type | Meaning |
|---|---|---|
title | string | Text shown on the event. |
color | string | Background color. |
text_color | string | Text color. |
icon | string | Icon name. |
html | string | HTML that replaces the whole content of the event. Make sure it contains no unescaped user input. |
start_time | string (HH:mm) | For date fields: start time of the event on that day. |
end_time | string (HH:mm) | For date fields: end time of the event on that day. |
full_day | boolean | Force the event to be an all-day event. |
display | string | FullCalendar display mode, default block. |
editable | boolean | Whether this single event can be moved or resized. |
recurrence | string or object | Repeat rule, see below. |
exdate | array<string> | Dates on which a recurring event is skipped. |
Example: show the name of the employee next to a fixed text and color holidays by status:
{ "identifier": "holidays", "module": "holidays", "field": "period", "recipes": { "title": "'Holiday: ' + this.employee.name", "color": "this.approved ? '#52c41a' : '#faad14'" }}Event types without a title recipe or title option show events without text, so set one.
Recurring events
Section titled “Recurring events”Set recurrence to a frequency string ("daily", "weekly", "monthly", "yearly") to repeat an event forever, starting
at the date of the entity. For more control pass an rrule object, for example
{"freq": "weekly", "interval": 2, "byweekday": ["MO", "WE"], "until": "2027-12-31"}; dtstart is set from the entity
automatically. exdate lists dates to skip.
Usually every entity brings its own frequency, stored in a field. Combine both options:
{ "identifier": "recurring_tasks", "module": "tasks", "field": "due", "recurrence_field": "repeat", "recipes": { "title": "this.name", "recurrence": "this.repeat" }}recurrence_field is required for this to work correctly. A repeating entity may start long before the visible range,
so the calendar must load it regardless of its date. It does so by also loading every entity whose recurrence_field
has one of the values daily, weekly, monthly, quarterly or yearly and whose date is not after the visible
range. Entities without a value in recurrence_field are loaded only when their own date is in the range. Without
recurrence_field, a repeating entity is loaded only when its first date lies in the loaded range, so later
repetitions do not appear once you navigate away from it.
The built-in view names are year, month, week, day and list. They are used in available_views and
default_view. For a view with other settings, define it in custom_views and use its key as view name:
{ "type": "calendar", "options": { "available_views": ["next7days", "month"], "default_view": "next7days", "custom_views": { "next7days": { "type": "list", "duration": {"days": 7} } }, "events": [] }}The calendar uses the language of the user. The month names, weekday names and week start follow the locale.
Resource view (experimental)
Section titled “Resource view (experimental)”The views resourceWeek and resourceMonth show a timeline with one row per entity of another module (for example per
employee or room). They are driven by two options that are read by the component but are not part of the published
layout types, so treat them as advanced and test them in your system:
options.resource:{ "module": "<module identifier>", "groupBy": "<field>", "pre_filters": [...] }selects the entities that become rows. The row title is thebrezel_nameof the entity; the row id is the value ofgroupBy, or the entityidwhengroupByis not set.- On every event type,
groupByis the field of the event entity whose value picks the row.
These views use the FullCalendar scheduler. The frontend needs a scheduler license key (VITE_SCHEDULER_LICENSE_KEY at
build time of the SPA); without it FullCalendar shows a license notice.
Interactions
Section titled “Interactions”- Click on an event opens the entity in a drawer at the bottom of the screen, in the module’s normal show layout.
The drawer’s layout context contains the date range of the clicked event in
calendar.rangeand, for a repeated event, the rule incalendar.recurrence. - Drag or resize an event (if
editable) writes the new date into the configuredfieldof the entity with a normal update request. Fordatefields the new start day is saved, fordatetimethe new start, fordateRangestart, end and the full-day flag. While saving, the event is shown semi-transparent; on failure an error toast appears and the value is not changed. For a repeated event, the date of the entity itself is moved by the amount you dragged. - Click on an empty day opens a drawer with the create form of the module, with the clicked date prefilled into
field. This only works if at least one event type hasquick_create: true. If several have it, the drawer offers a selection of the type to create. Without anyquick_create, clicking on a day does nothing. - Click on the day number or the week number in the month view switches to the day or week view.
- Sidebar (
filter_sidebar): per event type a checkbox to show or hide it (remembered in the browser), plus a filter button that opens the normal filter UI of the module; these filters are added to the date range andpre_filtersand apply to this user only. A button creates a personal iCalendar link for the currently selected event types.
Filters and permissions
Section titled “Filters and permissions”pre_filters use the same format as in other components such as resource_table: a list of conditions that all must
hold, or a list of lists (outer list OR, inner list AND). A condition has a column (column or field), an operator
(=, !=, <, <=, >, >=, in, not in, like, is, is not) and either a value or a recipe that is
evaluated in the browser. A condition with a recipe that returns nothing (or an empty list) is dropped. An optional
if recipe removes the condition when it returns false. In the recipe, this is the entity of the surrounding layout
and user() the current user, as in other layouts.
"pre_filters": [ {"column": "employee.id", "operator": "=", "recipe": "user().id"}]Filters only decide what is shown. The calendar loads entities with the same requests as the table of the module and
saves with the same update requests as the entity form, so the normal module permissions and
policies apply: users only see entities they may read, drag and drop fails with an error for
entities they may not update, and creating needs create permission on the module. Do not rely on pre_filters to
hide data from a user, use policies for that.
Complete example
Section titled “Complete example”A system with a module appointments (fields: title string, period dateRange, status select with confirmed
and tentative, owner relation to users) and a module employees with a birthdate date field and a repeat field.
The layout puts a full width calendar into a tab:
{ "tabs": [ { "identifier": "calendar", "rows": [ { "cols": [ { "span": 24, "components": [ { "type": "calendar", "options": { "default_view": "week", "available_views": ["day", "week", "month", "list"], "week_numbers": true, "height": "75vh", "events": [ { "identifier": "appointments", "module": "appointments", "field": "period", "color": "#1890ff", "icon": "team", "quick_create": true, "recipes": { "title": "this.title", "color": "this.status == 'confirmed' ? '#1890ff' : '#faad14'" } }, { "identifier": "my_appointments", "module": "appointments", "field": "period", "color": "#722ed1", "editable": false, "pre_filters": [ {"column": "owner.id", "operator": "=", "recipe": "user().id"} ], "recipes": { "title": "this.title" } }, { "identifier": "birthdays", "module": "employees", "field": "birthdate", "color": "#4adeff", "text_color": "rgba(0, 0, 0, 0.85)", "editable": false, "recurrence_field": "repeat", "recipes": { "title": "'Birthday: ' + this.first_name + ' ' + this.family_name", "recurrence": "this.repeat" } } ] } } ] } ] } ] } ]}To label the sidebar entries, add translations for the event type identifiers, for example under calendar.appointments,
calendar.my_appointments and calendar.birthdays in your translation files.
For the birthdays to repeat, each employee needs repeat set to yearly.
Legacy mode
Section titled “Legacy mode”If a calendar component has no events option, it works in the older mode of the built-in Calendar page: event types
come from the field options show_in_calendar, calendar_color, calendar_title, calendar_recurrence,
quick_create_in_calendar and create_in_calendar of the date fields, and a “Create” button is shown above the
calendar. New layouts should use events.
Troubleshooting
Section titled “Troubleshooting”- The calendar is empty. Check that
moduleandfieldare spelled exactly like the identifiers in the module, that the field is of typedate,datetimeordateRange, and that your user may read the module. The sidebar shows only event types that were set up successfully. - Events have no text. Add a
titlerecipe. - Repeating events disappear when navigating. Set
recurrence_field, see Recurring events. - Option has no effect. Options are
snake_case(default_view, notdefaultView).