v7.3.0
SupportExamplesFree Trial

Calendar editor

Introduction

Bryntum Scheduler Pro and Gantt ship with a built-in calendar editor - a special widget visualizing a calendar's data and allowing a user to edit it.

This demo shows the editor, click Edit project calendar button to start editing:

const schedulerPro = new SchedulerPro({ appendTo : targetElement, flex : '1 0 100%', // Project contains all the data and is responsible for correct scheduling project : { calendar : 1, calendars : [{ id : 1, name : 'Project Calendar', unspecifiedTimeIsWorking : true, intervals : [ { recurrentStartDate : 'on Sat', recurrentEndDate : 'on Mon', isWorking : false }, { name : 'Day-off', startDate : '2023-12-13', endDate : '2023-12-14', isWorking : false } ] }], events : [{ id : 1, name : 'Write docs', expanded : true, children : [ { id : 2, name : 'Proof-read docs', startDate : '2023-12-12', duration : 3 }, { id : 3, name : 'Release docs', startDate : '2023-12-19', duration : 5 } ] }], resources : [ { id : 1, name : 'Albert' }, { id : 2, name : 'Bill' } ], assignments : [ { event : 2, resource : 1 }, { event : 3, resource : 2 } ] }, startDate : new Date(2023, 11, 11), endDate : new Date(2023, 11, 31), height : 250, tbar : [{ type : 'button', icon : 'fa fa-calendar', text : 'Edit Project Calendar', async onAction() { // wait till project finished its calculations await schedulerPro.project.commitAsync(); // scheduler might be destroyed during the commit if (schedulerPro.isDestroyed) { return; } let editor = this.calendarEditor; // create calendar editor if (!editor) { this.calendarEditor = editor = new CalendarEditor({ owner : schedulerPro, // don't show it right away autoShow : false, modal : true }); } // edit project calendar editor.calendar = schedulerPro.project.calendar; editor.activeDate = schedulerPro.visibleDateRange.startDate; editor.show(); } }], columns : [ { field : 'name', text : 'Name' } ] });

Please note: Some data cases are unsupported by the calendar editor, please check the "Checking that your data is ready for the Calendar editor" chapter for details.

Under the hood

The widget is implemented with the CalendarEditor class. It extends Popup so any corresponding configs work for it:

const editor = new CalendarEditor({
    // don't display the editor right now
    autoShow  : false,
    // use it as a modal dialog
    modal     : true,
    // clicking outside the editor closes it
    autoClose : true
});

And additionally the class has a few new APIs specific to its purposes:

  • calendar - config to load a calendar for editing.
  • activeDate - date to display on the widget's "General" tab (read "Widget Structure" for more details).
  • save - method to save changes programmatically.

For example:

// create calendar editor
const editor = new CalendarEditor({
    modal    : true,
    // don't show it right away
    autoShow : false
});

// Show calendar editor for the project calendar
editor.calendar   = project.calendar;
editor.activeDate = new Date(2023, 11, 7);
editor.show();

Widget structure

The widget is a tab panel that has three tabs: "General", "Exceptions" and "Weeks".

Tab ref Type Text Weight
generalTab Container General 100
exceptionTab CalendarEditorExceptionTab Exceptions 200
weekTab CalendarEditorWeekTab Weeks 300

Besides that there is a bottom toolbar with two Save and Cancel buttons having (ref : saveButton, weight : 200) and (ref : cancelButton, weight : 300) respectively.

General tab

The "General" tab has fields allowing to change the calendar name, parent and specify whether its whole time is working by default.

Besides that the tab allows inspecting certain day ranges with a date picker. The picker highlights days affected by the calendar with background and color badges. The meaning of the background and the badges is explained in a legend placed to the left of the picker.

The widget to the right of the picker displays the selected day availability information (whether the day is working and why). Like here for example user selected Jan 1st 2019 and the panel reported that it's a day off owing to the "X-Mas & New Year!" exception:

When selecting a date or a date range the widget opens a special context menu allowing to add an exception or a week override for the range.

Weeks tab

The "Weeks" tab allows managing weeks - calendar intervals that specify regular working schedule. An example of such interval could be a typical work week of 8AM to 5PM work days, Monday through Friday.

The tab has a grid of weeks and an editor-panel in the left and right parts respectively. Adding (or removing) weeks can be done with buttons on the grid toolbar:

After clicking "Add week" the editor-panel displays the newly added record data:

To make a day working one should toggle the slide control next to the day name. That will add the default availability to it:

The time values can be changed in the fields. And ranges can be added/removed with the buttons next to the fields.

Note: A working schedule might change in the middle of the project. In that case one can add a week override - a week having "To" and "From" dates provided. Then the specified working schedule will be applied for the provided date range only. For example here we setup 08AM till 12PM working time, Monday through Friday for the period from Dec 7th 2023 till Dec 31st 2023:

Please note A calendar that doesn't allow working any time by default must have at least one week providing some availability.

Exceptions tab

The "Exception" tab allows managing exceptions - calendar intervals that specify changes to the regular working schedule. An example of such intervals could be a vacation, day-off or a public holiday.

Adding a vacation

First switch to the "General" tab. Then select the vacation range on the date picker widget (use [SHIFT + left click] to select a range). This will display a context menu (it disappears after few seconds but you can open it any time by right clicking the date picker). Click "Add exception" menu entry:

After doing that the widget will make a new exception and switch to the "Exceptions" tab where you can specify its name.

And that's basically it. The selected range is made non-working. You can check that by switching back to the "General" tab and inspecting the dates there.

Adding a recurring holiday

As an example of adding a regular holiday let's register Christmas and New Year holidays starting from Dec 24th till Jan 2nd. Switch to the "General" tab and click Dec 24th of any year on the date picker widget. And then holding [SHIFT] key click the ensuing Jan 1st date and click "Add exception" in the appeared context menu:

After doing that the widget will make a new exception and switch to the "Exceptions" tab where you can specify "Christmas and New Year" as its name:

At this point we've got a static non-working period for the selected dates but we want it to repeat every year. To configure that click "Repeat" slide control:

In the appeared "Frequency" field select "Yearly" to specify that it should repeat every year. And make sure the "On" checkbox is selected and the field next to it has 24 December value:

That makes the exception repeat every year on Dec 24th and last till Dec 25th 00:00. But we want the exception end to repeat every year on Jan 2nd. To configure that click "Repeat end" slide control. The "Frequency" field already has "Yearly" selected. So just make sure the "On" checkbox is selected and the field next to it has 2 January value:

After doing that the exception start and end dates will be calculated based on the provided recurrence settings. And we no longer need "From" and "To" dates since they limit the recurrence to run in that concrete range only. So let's scroll up and clear the fields:

Now if you switch back to the "General" tab and inspect the holiday dates for different years you will see that they are treated as non-working and repeat every year.

Exception dates and recurrence combination

Here is a table that explains how the exception start, end dates and recurrence settings work together:

Start date End date Start
recurrence
End
recurrence
Meaning
Yes - - - All dates starting from the provided start date.
- Yes - - All dates till the provided end date.
Yes Yes - - All dates starting from the provided start date and till the provided end dates.
Yes Yes Yes - Effective start dates are calculated based on the start recurrence settings. The dates are collected in the provided start/end dates range only. Effective end dates are calculated as the ends of the collected start dates.
Yes Yes Yes Yes Effective start and end dates are calculated based on the start and end recurrence settings respectively. The dates are collected in the provided start/end dates range only.

The above fields are responsible for collecting dates only. When the dates are found they are treated as working or not based on the exception availability setting.

Checking that your data is ready for the Calendar editor

Later JS library (we use under the hood for calendars) syntax is quite rich which is good. But the downside is it's tricky to cover all its possible configurations in UI. Thus the calendar editor at the moment supports only the most common cases used by the calendars.

You can check if your calendar data is compatible with the new editor. In order to do that please call calendarManagerStorevalidateAllRecordsForCalendarEditor method after the data is loaded:

calendarManagerStore.validateAllRecordsForCalendarEditor();

That will report found errors to the browser console. If nothing is reported the data is good.

For instance the calendar editor does not support so called composite schedules (rules looking like {rule1} and {rule2} or {rule1} except {rule2}). Let's say we have a "Working hours" calendar with the following unsupported rules:

{
    "recurrentStartDate" : "at 8:00 except on Saturday and Sunday",
    "recurrentEndDate"   : "at 17:00 except on Saturday and Sunday",
    "isWorking"          : true
},

So if we trigger calendars validation:

// validate all calendars in the store
calendarManagerStore.validateAllRecordsForCalendarEditor();

That will dump a warning in the browser console:

  CalendarEditorStore: The "Working hours" calendar interval cannot be handled. Please copy its data and
  report to  https://bryntum.com/forum :
{
  "id": "_generatedModelClass_9ce06c8d-df47-4d4d-921b-3b51fd806e98",
  "recurrentStartDate": "at 8:00 except on Saturday and Sunday",
  "recurrentEndDate": "at 17:00 except on Saturday and Sunday",
  "isWorking": true,
  "availability": null
}

In that exact case the rules can be easily changed to a non-composite look:

{
    "recurrentStartDate" : "every weekday at 8:00",
    "recurrentEndDate"   : "every weekday at 17:00",
    "isWorking"          : true
}

And other similar "composite cases" could require breaking an interval into multiple ones with simple rules. Or you could just try making them manually with the calendar editor.

As always if you face a case you cannot implement report us: https://bryntum.com/forum

Contents