v7.3.0

Upgrade guides for Scheduler v4.0.0+

Scheduler v4.0.0

Be sure to also check out the news in Grid.

Dropped support for Edge 18 and older

We are not actively removing the fixes we have in place yet, but moving forward we will no longer add new fixes for versions of Edge <= 18. If you are using an old version of Edge, we strongly encourage you to update to a new blink based version.

Existing fixes are planned to be removed in version 5.0.

New scheduler.lite.umd.js bundle to use with Angular

Bryntum Scheduler is delivered with a new UMD package without polyfills. This was done to avoid conflicts with any external Promise polyfills, such as ZoneAwarePromise from zone.js for Angular applications. It fixes this runtime error:

Zone.js has detected that ZoneAwarePromise (window|global).Promise has been overwritten.
Most likely cause is that a Promise polyfill has been loaded after Zone.js 
(Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)

We recommend that all your Angular applications should be upgraded to use the new scheduler.lite.umd.js bundle. Also, there should be no polyfills imported in app/src/polyfills.ts after import 'zone.js/dist/zone';. We have updated all our Angular examples to use this new bundle. Make sure to update all usages of scheduler.umd.js to scheduler.lite.umd.js within your application to avoid Bundle included twice runtime error.

Old code:

import { Button, WidgetHelper } from 'bryntum-scheduler/scheduler.umd.js';

New code:

import { Button, WidgetHelper } from 'bryntum-scheduler/scheduler.lite.umd.js';

If you wish to have IE/Edge compatibility, please follow the instructions in app/src/polyfills.ts.

Project holding stores

Behind the scenes, Scheduler has been brought in line with Scheduler Pro and Gantt and now always uses a project to hold its stores. The project also has a calculation engine, which is used to normalize dates and durations as described in "Async date manipulation below".

For basic usage, this difference should not be noticeable. Scheduler still accepts a CrudManager, separate stores or inline data the same way as before. But in addition, you now also have the option of assigning it a project:

const project = new ProjectModel({
    eventStore      : { /*...*/ },
    resourceStore   : { /*...*/ },
    dependencyStore : { /*...*/ }
})

new Scheduler({
    project
})

The stores available on the project and affected by it are:

  • EventStore
  • ResourceStore
  • AssignmentStore
  • DependencyStore

Advanced - Standalone stores

For advanced use cases, for example using standalone stores (not linked to a Scheduler), it is worth mentioning that the stores are no longer directly linked to each other. They are instead linked to the project.

For example this old code:

const resourceStore = new ResourceStore();

const eventStore = new EventStore({
    resourceStore,
    /*...*/
});

const assignments = eventStore.getAssignmentsForEvent(myEvent);

Now requires the stores to be part of a project:

const resourceStore = new ResourceStore();

const eventStore = new EventStore({
    /*...*/
});

// Link the stores using a project
const project = new ProjectModel({
    resourceStore,
    eventStore
})

const assignments = eventStore.getAssignmentsForEvent(myEvent);

Please note that if using the stores with a Scheduler, they are automatically linked using a project.

Always using an AssignmentStore

In addition to always having a behind the scenes project, Scheduler now also always has an AssignmentStore. Please note that it still supports single assignment using resourceId, see the next section for more information.

The decision to always use an AssignmentStore was taken based on: A) It allows us to simplify the core code. There used to be many places where single assignment was treated one way and multi assignment another. Now there is a single code path in these places instead. B) It forces us to support multi assignment in all features. Support for multi assignment was added after the 1.0 release and not all features supported it earlier.

In most scenarios, this change should not be noticeable.

Single assignment

As mentioned above, Scheduler always has an AssignmentStore but it still supports single assignment the old way, using resourceId:

new Scheduler({
    events    : [
        { id : 1, resourceId : 'r1', /*...*/ },
        { id : 2, resourceId : 'r1', /*...*/ }
    ],
    resources : [
        { id : 'r1' },
        { id : 'r2' }
    ]
});

For the code above Scheduler will automatically create an AssignmentStore and populate it with the required records. Pseudo code for the result of the above:

behindTheScenesAssignmentStore.data = [
    { eventId : 1, resourceId : 'r1' },
    { eventId : 2, resourceId : 'r1' }
];

For most apps using single assignment, this should happen automatically and not be noticeable.

Please note that it is not supported to mix the resourceId approach while simultaneously using multi assignment.

Event selection

The Event Selection API has been adapted to better support multi assignment. Previously its functions and accessors would either return events or assignments, depending on if an AssignmentStore was used or not. This has now been normalized in such way that selectedEvents now always returns event records and the new selectedAssignments always returns assignments.

If you relied on selectedEvents returning assignments, you will have to adapt your code.

Affected APIs:

  • selectedEvents, added selectedAssignments
  • isEventSelected(), added isAssignmentSelected()
  • selectEvent(), added selectAssignment() and select() that accepts either or
  • deselectEvent(), added deselectAssignment() and deselect() that accepts either or
  • selectEvents(), added selectAssignments()
  • deselectEvents(), added deselectAssignments()
  • Added an assignmentSelectionChange event

Async date manipulation

As mentioned in the "Project holding stores" section, date manipulation is now handled using a calculation engine. This matches how it works in Scheduler Pro and Gantt, keeping our API more consistent between products. The biggest impact this has on Scheduler is that date manipulation turns async. Consider this old code:

// Event record must be inside a Project's Store to participate in normalization
const [event] = myScheduler.eventStore.add({
    startDate : new Date(2020, 5, 22),
    duration  : 2,
    endDate   : new Date(2020, 5, 24)
});

event.duration = 4;

console.log(event.endDate);

The console would show that endDate is now the 26th, since the calculation happens in sync. With the new approach, the calculation is now async and thus the endDate is not yet calculated when it is logged to the console.

Assigning 4 to duration is now considered a proposed change, and all proposals are calculated when project changes are committed. The project is committed automatically on a buffer, but can also be committed manually if you need the result of the calculations right away. The above code would then be:

event.duration = 4;

await event.project.commitAsync();

console.log(event.endDate);

When using the Scheduler UI, this change should not be noticeable. Scheduler automatically redraws events when the calculations are finished.

Loading and adding events

The async calculation of dates and durations also applies to when loading or adding events. Consider this old code:

const [event] = eventStore.add({ startDate : '2020-09-10', endDate : '2020-09-14' });
console.assert(event.duration === 4); // ok

With the new async approach, duration is not yet calculated. Instead calculation is scheduled to happen later. To calculate it directly, you can use the approach described in the previous section:

const [event] = eventStore.add({ startDate : '2020-09-10', endDate : '2020-09-14' });

await eventStore.project.commitAsync();

console.assert(event.duration === 4); // ok

The same principles apply for loading data into the store:

eventStore.data = [my_dataset];

// calculations are not yet done

await eventStore.project.commitAsync();

// now calculations are done, data is ready for further processing

To simplify this a bit, we have added two new functions that performs the commit internally, addAsync() and loadDataAsync(). Example of their usage, loadDataAsync() as an alternative to store.data = ... first:

await eventStore.loadDataAsync([my_dataset]);

// calculations are done, data is ready for further processing

And addAsync() would be used like this:

await eventStore.addAsync({ startDate : '2020-09-10', endDate : '2020-09-14' });

// duration is calculated and available

Changed event triggering timing

The stores that are part of the project (EventStore, ResourceStore, AssignmentStore and DependencyStore) has had the timing of their event triggering for certain events modified. The change was made to assure async date calculations are finished when the event is triggered. This affects the following events:

  • add
  • remove
  • removeAll
  • change
  • refresh

This change should lead to better backward compatibility with the old Scheduler. Consider the following scenario:

const scheduler = new Scheduler({
    events : [/*...*/],

    listeners : {
        add({ records }) {
            console.assert(records[0].endDate)
        }
    }
});

scheduler.eventStore.add({ startDate : '2020-09-09', duration : 1 });

Without the change, the add event would be triggered immediately on the call to add(). Since date calculations are now async (as described in section "Async date manipulation" above), the endDate would not yet be available.

With the change, the event is triggered after calculations has finished, making endDate available in the listener.

It is still possible to catch the events at the earlier stage, in case you do not care about waiting for data to be in a calculated state. To achieve that, listen for addBeforeCommit, removeBeforeCommit and so on instead.

DependencyStore moved to Scheduler

Previously the dependencies feature held its own store, and Scheduler referenced it. Now Scheduler (or rather the project) holds the store directly. In most cases, this should not matter. But if you were actively defining the store for the feature you might need to change your code:

Old code:

const scheduler = new Scheduler({
    features : {
        dependencies : {
            store : myDependencyStore
        }
    }
})

New code:

const scheduler = new Scheduler({
    features : {
        dependencies : true
    },

    dependencyStore : myDependencyStore
})

WidgetHelper and BryntumWidgetAdapter

Registration of Widget classes for subsequent resolution through the type property of config objects has been simplified. The "Adapter" concept has been removed. Widget classes now register themselves with the Widget base class which is where they can also be looked up. When creating a custom widget, implement the static get type property to return the type name. And at the end simply call MyWidgetClass.initClass() to have the new Widget class register itself.

Old code:

class MyWidget extends Widget {

}

BryntumWidgetAdapterRegister.register('mywidget', MyWidget);

New code:

class MyWidget extends Widget {
    static get type() {
        return 'mywidget';
    }
}

MyWidget.initClass();

Fewer widgets are auto imported with the removal of the Adapter which imported a base set of Widgets. This means that when not using a bundle, application code must import the widgets it uses. It also means that custom builds may be smaller by including only what is used.

If your code previously had:

import 'lib/Core/adapter/widget/BryntumWidgetAdapter.js';
import WidgetHelper from 'lib/Core/helper/WidgetHelper.js';

WidgetHelper.append([
        {
            type : 'button',
            text : 'Click'
        }
    ], /* ... */
);

It should now instead not import the adapter, and instead directly import the button:

import WidgetHelper from 'lib/Core/helper/WidgetHelper.js';
import 'lib/Core/widget/Button.js';

WidgetHelper.append([
    {
        type : 'button',
        text : 'Click'
    }
], /* ... */
);

Recurring Events

The method by which occurrences of recurring events are collected for rendering has changed.

Prior to 4.0.0, after any EventStore or EventModel mutation (load, filter, record change etc), the EventStore was scanned for instances of events with a recurrenceRule. For each one found, a sequence of occurrences which fell within the Scheduler's current TimeAxis date range was generated and these were inserted into the EventStore.

This could result in difficulty in managing the store content since "ephemeral" occurrences were mixed in with concrete event definitions and EventStore would fire add events for these ephemeral occurrences which apps had to have code to ignore.

From 4.0.0, there is no RecurringEvents feature anymore (its base class RecurringTimeSpans is also removed as no longer needed). There is an enableRecurringEvents boolean config on the Scheduler instead. Occurrences of recurring events are provided on a "just in time" basis by a new EventStore API which must now be used when interrogating an EventStore.

Old code:

// Define a new Model extending TimeSpan class with RecurringTimeSpan mixin which adds recurrence support
class MyTimeRange extends RecurringTimeSpan(TimeSpan) {}

// Define a new store extending the Store class with RecurringTimeSpansMixin mixin to add recurrence support to the store.
// This store will contain time ranges.
class MyTimeRangeStore extends RecurringTimeSpansMixin(Store) {
    static get defaultConfig() {
        return {
            // use our new MyTimeRange model
            modelClass : MyTimeRange
        };
    }
}

// instantiate store for time ranges using our new classes
const timeRangeStore = new MyTimeRangeStore();

new Scheduler({
    //...
    features : {
        // enable recurrence support for events
        recurringEvents    : true,
        // we use time ranges feature
        timeRanges         : {
            store : timeRangeStore
        },
        // and bind recurringTimeSpans to the store timeRanges use
        // which makes time ranges supporting recurrence
        recurringTimeSpans : {
            store : timeRangeStore
        }
    }
})

New code:

// Define a new Model extending TimeSpan class with RecurringTimeSpan mixin which adds recurrence support
class MyTimeRange extends RecurringTimeSpan(TimeSpan) {}

// Define a new store extending the Store class with RecurringTimeSpansMixin mixin to add recurrence support to the store.
// This store will contain time ranges.
class MyTimeRangeStore extends RecurringTimeSpansMixin(Store) {
    static get defaultConfig() {
        return {
            // use our new MyTimeRange model
            modelClass : MyTimeRange
        };
    }
}

// instantiate store for time ranges using our new classes
const timeRangeStore = new MyTimeRangeStore();

// recurringEvents and recurringTimeSpans feature definitions are no longer needed - so we removed them
new Scheduler({
    // ...
    // enable recurrence support for events
    enableRecurringEvents : true,
    // time ranges feature
    timeRanges : {
        store : timeRangeStore
    }
})

EventStore.getEvents is a multipurpose event gathering method which can be asked to return events which match a set of criteria including a date range and a resource. By default, if the requested date range contains occurrences of a recurring event, those occurrences are returned in the result array.

myEventStore.getEvents({
    resourceRecord : myResourceRecord,
    startDate      : myScheduler.startDate,
    endDate        : myScheduler.endDate
});

Occurrences are not present in the store's data collection.

To directly access occurrences of a recurring event which intersect a date range, use:

recurringEvent.getOccurrencesForDateRange(startDate, endDate);

The endDate argument is optional if the occurrence for one date is required. This method always returns an array. Note that it may be empty if no occurrences intersect the date range.

Convert an occurrence to an exception

To programmatically convert an occurrence to be a single exception to its owner's sequence use:

myOccurrence.beginBatch();
myOccurrence.startDate = DateHelper.add(myOccurrence.startDate, 1, 'day');
myOccurrence.name = 'Postponed to next day';
myOccurrence.recurrence = null; // This means it does NOT become a new recurring base event.
myOccurrence.endBatch();

Or as a single call to set():

myOccurrence.set({
    startDate  : DateHelper.add(myOccurrence.startDate, 1, 'day'),
    name       : 'Postponed to next day',
    recurrence : null // This means it does NOT become a new recurring base event.
});

That will cause that event to be inserted into the store as a concrete event definition, firing an add event as would be expected, and will add an exceptionDates to its owning recurring event.

When syncing this change back to the server, the exceptionDates array for the modified recurring event now contains the exception dates correctly serialized into string form using the dateFormat defined for the field. The system-supplied default value for this is 'YYYY-MM-DDTHH:mm:ssZ'

Convert an occurrence to a new recurring event sequence

To programmatically convert an occurrence to be the start of a new recurring sequence, use:

myOccurrence.beginBatch();
myOccurrence.startDate = DateHelper.set(myOccurrence.startDate, 'hour', 14);
myOccurrence.name = 'Moved to 2pm from here on';
myOccurrence.endBatch();

That will cause that event to be inserted into the store as a concrete recurring event definition, firing an add event as would be expected, and will terminate the previous recurring owner of that occurrence on the day before the new event.

Customizing the event editor feature

Customizing the event editor is even simpler now. No multiple configs to change all the provided fields in the system-supplied event editor popup.

The items config of the Container class is now object-based with the property name being the ref of the resulting child item, and the value being its config object. An items value from configuration is merged into any items object that a class defines for itself. Child widget order is preserved by default, but adding a weight property to a widget's config means that lower values (lighter) are to the top while higher values (heavier) are at the bottom.

These new capabilities mean that you can specify one items config for the EventEdit feature to change or add any field in the editor.

So to change an aspect of a provided field use:

const scheduler = new Scheduler({
    features : {
        eventEdit : {
            // Merged with the provided items
            // So we can override any config of any provided field.
            items : {
                resourceField : {
                    label : 'Machine'
                }
            }
        }
    }
});

To add to the provided field set use:

const scheduler = new Scheduler({
    features : {
        eventEdit : {
            items : {
                owner : {
                    weight : -100, // Will sort above system-supplied fields which are weight 100 to 800
                    type   : 'usercombo',
                    name   : 'owner',
                    label  : 'Owner'
                },
                agreement : {
                    weight : 1000, // Will sort below system-supplied fields which are weight 100 to 800
                    type   : 'checkbox',
                    name   : 'agreement',
                    label  : 'Agree to terms'
                }
            }
        }
    }
});

Context menu

Base class

TimeSpanRecordContextMenuBase class was deprecated. Common functionality was moved to ContextMenuBase class, and functionality related to TimeAxis was moved to TimeSpanMenuBase class.

HeaderContextMenu feature for TimeAxis

HeaderContextMenu feature was replaced by TimeAxisHeaderMenu. TimeAxisHeaderMenu extends HeaderMenu to provide TimeAxis specific header menu items. extraItems config was turned into items which is a named object:

Old code:

const scheduler = new Scheduler({
    features : {
        headerContextMenu : {
            extraItems : [
                {
                    text : 'Extra',
                    onItem() { /*...*/ }
                }
            ],

            processItems({ items }) {
                items.push({
                    text : 'Cool action',
                    onItem() { /*...*/ }
                })
            }
        }
    }
});

New code:

const scheduler = new Scheduler({
    features : {
        timeAxisHeaderMenu : {
            items : {
                myExtraItem : {
                    text : 'Extra',
                    onItem() { /*...*/ }
                }
            },

            processItems({ items }) {
                items.coolItem = {
                    text : 'Cool action',
                    onItem() { /*...*/ }
                }
            }
        }
    }
});

Disable menu for locked grid

To disable header/cell context menu for locked grid, but leave it for normal grid please follow this way:

Old code:

const scheduler = new Scheduler({
    features : {
        contextMenu : {
            processHeaderItems : ({ column }) => column instanceof TimeAxisColumn,
            processCellItems   : () => false
        }
    }
});

New code:

const scheduler = new Scheduler({
    features : {
        headerMenu : false, // it doesn't turn off context menu for time axis column header now
        cellMenu   : false
    }
});

There is no "Context" in naming

We got rid of "Context" in EventContextMenu and ScheduleContextMenu features to follow the same naming standards in all products. So now they are called EventMenu and ScheduleMenu correspondingly.

Context menu events

Some of Scheduler's events provided by EventContextMenu and ScheduleContextMenu features were replaced by EventMenu and ScheduleMenu features correspondingly. Here is the list of changes:

  • eventContextMenuBeforeShow -> eventMenuBeforeShow;
  • eventContextMenuShow -> eventMenuShow;
  • eventContextMenuItem -> eventMenuItem;
  • scheduleContextMenuBeforeShow -> scheduleMenuBeforeShow;
  • scheduleContextMenuShow -> scheduleMenuShow;
  • scheduleContextMenuItem -> scheduleMenuItem;

For example:

Old code:

const scheduler = new Scheduler({
    listeners : {
        eventContextMenuBeforeShow    : () => { /*...*/ },
        eventContextMenuShow          : () => { /*...*/ },
        eventContextMenuItem          : () => { /*...*/ },
        scheduleContextMenuBeforeShow : () => { /*...*/ },
        scheduleContextMenuShow       : () => { /*...*/ },
        scheduleContextMenuItem       : () => { /*...*/ }
    }
});

New code:

const scheduler = new Scheduler({
    listeners : {
        eventMenuBeforeShow    : () => { /*...*/ },
        eventMenuShow          : () => { /*...*/ },
        eventMenuItem          : () => { /*...*/ },
        scheduleMenuBeforeShow : () => { /*...*/ },
        scheduleMenuShow       : () => { /*...*/ },
        scheduleMenuItem       : () => { /*...*/ }
    }
});

Event element id changes

The id attribute on event elements was removed. It used a cryptic generated value similar to id="scheduler-57-r2-x", which is no longer needed internally. If you need to retrieve the element for a specific event we recommend using scheduler.getElementFromEventRecord():

const element = scheduler.getElementFromEventRecord(eventStore.first);

Alternatively, elements has event id and assignment id in their dataset, allowing you to use them as CSS selectors:

const element = scheduler.element.querySelector('[data-event-id="someId"]');

Following this simplification, the getResourceRecordFromDomId() and getEventRecordFromDomId() functions has been removed. To lookup an event record from a DOM element, please use resolveEventRecord, and to lookup the resource from a specific DOM element, please use resolveResourceRecord.

Model Field Inheritance

Fields defined in a derived class Model that coincide by name with a field declared in a super class will now only override those field config properties specified by the derived class. This allows derived classes to adjust a field definition in one way, say to change the default value and still inherit other field properties, such as a convert function.

For example:

    class ModelOne extends Model {
    static get fields() {
        return [
            { name : 'barcode', convert : v => String(v) }
        ];
    }
}

class ModelTwo extends ModelOne {
    static get fields() {
        return [
            { name : 'barcode', defaultValue : 'ABC123' }
        ];
    }
}

In previous releases, ModelTwo would have had to redefine the convert config for the barcode field.

Renamed CSS themes

The Default, Light and Dark themes were renamed to Classic, Classic-Light and Classic-Dark. This change highlights the fact that they are variations of the same theme, and that it is not the default theme (Stockholm is our default theme since version 2.0).

If you are using one of these themes, you will have to adjust your css import to match the new name.

Old code:

<link rel="stylesheet" href="build/grid.dark.css" id="bryntum-theme">

New code:

<link rel="stylesheet" href="build/grid.classic-dark.css" id="bryntum-theme">

If you have theme specific selectors in your code/CSS you have to adjust those also:

Old code:

.b-theme-dark {
/*...*/
}

New code:

.b-theme-classic-dark {
/*...*/
}

Scheduler v4.0.4

Deprecated legacy functions on DependencyModel

The getSourceEvent() and getTargetEvent() functions in DependencyModel was deprecated in favor of the fromEvent and toEvent getters. They will be removed in 5.0. If you use them, the following snippets shows how to update your code:

Old code:

const
    sourceEvent = dependency.getSourceEvent(),
    targetEvent = dependency.getTargetEvent();

New code:

const
    sourceEvent = dependency.fromEvent,
    targetEvent = dependency.toEvent;

Setting resourceMargin and barMargin per row

Two new fields called resourceMargin and barMargin was added to ResourceModel. When set, they will take precedence over the resourceMargin and barMargin values configured on Scheduler. This allows you to control them on a per row basis.

As all fields, they can be supplied as data:

[
  {
    "id": "resource1",
    "name": "Resource 1",
    "resourceMargin": 20,
    "barMargin": 5
  },
  {
    "id": "resource2",
    "name": "Resource 2"
  }
]

"resource1" above will use resourceMargin : 20 and barMargin : 5, "resource2" will use the value configured on Scheduler.

Values can also be changed at runtime, the UI will reflect the changes:

scheduler.resourceStore.first.resourceMargin = 20;

If you need some additional runtime logic, subclass ResourceMargin and define a getter for the field. Something similar to:

class MyResource extends ResourceModel {
    get resourceMargin() {
        let margin;

        // Complex logic to determine margin

        return margin;
    }
}

Scheduler v4.0.8

Added short responses support to Crud Manager

Crud Manager default behaviour has been changed to allow short sync responses that include only server-side changes. Previously it was mandatory to mention all updated and removed records in the response to confirm the changes are applied. With this release the Crud Manager automatically confirms changes of all updated/removed records mentioned in the corresponding sync request. If your code relies on the old behavior please use supportShortSyncResponse to enable it back. To revert to the old behavior please set supportShortSyncResponse config to false:

new Scheduler({
    /*...*/
    crudManager : {
        // disable simlified responses support
        supportShortSyncResponse : false,
        /*...*/
    }
});

Scheduler v4.1.0

Be sure to also check out the news in Grid.

Dropped support for RequireJS

In version 4.1.0 we have removed the RequireJS demo from the project, since it is an outdated technology. We instead recommend using modern ES6 module imports which is supported in all modern browsers (Chrome, FireFox, Safari, Edge (chromium)).

You could easily upgrade old application code to use new technology.

Old code:

index.html

<script data-main="scripts/app" src="scripts/require.js"></script>

scripts/app.js

requirejs.config({
    paths : {
        scheduler : '../../../build/scheduler.umd'
    }
});

requirejs(['scheduler'], function(bryntum) {
    new bryntum.Scheduler({
        // config here
    });
});

New code:

index.html

<script type="module" src="scripts/app.js"></script>

scripts/app.js

import { Scheduler } from '../../../build/scheduler.module.js';

new Scheduler({
    // config here
});

Dropped support for passing TimeRanges feature store from the feature to Crud manager

Value of TimeRanges feature store config is no longer passed to Crud Manager instance. Instead please use timeRangeStore config on the project. That will register the store both on Crud Manager and the feature.

Old code:

new Scheduler({
    features : {
        timeRanges : {
            store : myStore
        }
    },
    crudManager : {
        /*...*/
    }
});

New code:

new Scheduler({
    project : {
        timeRangesStore : myStore
    },
    features : {
        timeRanges : true
    },
    crudManager : {
        /*...*/
    }
});

CrudManager API changes

CrudManager commit was deprecated in favor of acceptChanges. CrudManager commitCrudStores was deprecated in favor of acceptChanges. CrudManager reject was deprecated in favor of revertChanges. CrudManager rejectCrudStores was deprecated in favor of revertChanges.

Old code:

scheduler.crudManager.commit();
scheduler.crudManager.commitCrudStores();
scheduler.crudManager.reject();
scheduler.crudManager.rejectCrudStores();

New code:

scheduler.crudManager.acceptChanges();
scheduler.crudManager.revertChanges();

CSS encoding

Bryntum now uses Dart SASS to compile CSS from SCSS for the themes. It outputs CSS encoded with UTF-8. The encoding is specified at the top of the CSS file as a @charset tag:

@charset "UTF-8";

It is important that this tag is preserved in the CSS used on page, to guarantee that font icons render as intended.

Minified CSS instead uses a byte order mark to specify encoding, which although invisible to the eye should be preserved in the file used on page.

If you use a custom build process that includes our CSS and icons are not rendered correctly in all browsers, the issue is most likely caused by missing encoding info. Try adding it back or serving the CSS with correct encoding specified in the HTTP header.

DependencyCreation events

DependencyCreation events were all missing useful params about the state of the creation state as you drag and drop to setup a link between two events. The data param is now deprecated and will be removed in a future major version.

Old code:

scheduler.on({
    beforedependencycreatedrag({ data }) {
        // Use undocumented params in data
    }
});

New code:

scheduler.on({
    dependencycreatedrop({ source, target, dependency }) {
        // Documented access to source event, target event, and the created dependency
    }
});

Frameworks examples

Frameworks examples were moved to examples/frameworks folder:

Framework Examples folder
Angular examples/frameworks/angular
Ionic examples/frameworks/ionic
React examples/frameworks/react
Vue v2 examples/frameworks/vue
Vue v3 examples/frameworks/vue-3

Scheduler v4.1.1

Be sure to also check out the news in Grid.

Scheduler event drag events

The context param of the Scheduler beforeEventDrag, eventDragStart, eventDrag, eventDragAbort events was deprecated and will be removed in 5.0. eventRecords and assignmentRecords are available on the top level of the arguments.

Scheduler time axis header events

  • The events fired by Scheduler.column.TimeAxisColumn (timeaxisheaderclick, timeaxisheadercontextmenu, timeaxisheaderdblclick) were deprecated and should instead be listened to on the owning Scheduler / Gantt component.
scheduler.on({
  timeAxisHeaderClick : event => { /*...*/ }
})

Wrapper dependencies

In order to support installing trial package alias for wrappers they now require adding @bryntum/scheduler dependency to application's package.json file.

Example for licensed version for Angular framework:

{
  "dependencies": {
    "@bryntum/scheduler": "4.1.1",
    "@bryntum/scheduler-angular": "4.1.1"
  }
}

Example for trial version for Angular framework:

{
  "dependencies": {
    "@bryntum/scheduler": "npm:@bryntum/scheduler-trial@4.1.1",
    "@bryntum/scheduler-angular": "4.1.1"
  }
}

Scheduler v4.1.3

EventDrag dragTipTemplate renamed to tooltipTemplate

Simple renaming in the EventDrag feature, see this demonstrated in the updated "tooltips" demo:

Old code:

features : {
     eventDrag : {
        dragTipTemplate : ({ eventRecord, startDate }) => {
            return 'foo'
        }
    }
}

New code:

features : {
    eventDrag : {
        tooltipTemplate : ({ eventRecord, startDate }) => {
            return 'foo'
        }
    }
}

EventDrag dragTipTemplate / tooltipTemplate deprecated params

The startText, endText, startClockHtml, endClockHtml, dragData params of the dragTipTemplate / tooltipTemplate have been deprecated and will be removed in 5.0.

Scheduler v4.1.4

eventKeyDown & eventKeyUp event signatures changed

Simple renaming of the event params fired by Scheduler when pressing a key with events selected. The event always passes an array of eventRecords and assignmentRecord:

Old code:

new Scheduler({
    listeners : {
        eventKeyDown : ({ eventRecord, assignmentRecord }) => { /*...*/ }
    }
})

New code:

new Scheduler({
    listeners : {
        eventKeyDown : ({ eventRecords, assignmentRecords }) => { /*...*/ }
    }
})

Scheduler v4.2.0

EventDragCreate

The EventDragCreate feature now operates on a live instance of a new EventModel which is in the EventStore on a provisional basis until either confirmed or cancelled by the EventEdit UI.

The end times are manipulated live, and the UI performs appropriate updates. This is in contrast to having a proxy element dragged in place where an event may be placed if inserted.

If you have your own implementation of an event editor that is shown when drag create ends, this change might affect its behaviour. If you need to know if the event being edited is the result of a drag create operation (or a double click on the timeline), you can inspect the new isCreating flag of the event record.

The newEventRecord param of the dragCreateEnd event fired by Scheduler was renamed to eventRecord and will be removed in 5.0.

Dependencies can now be created by dropping anywhere on the event bar element

In previous versions you had to hit a specific side circle element to define it. The new behavior allows you to setup a dependency by dropping anywhere on the task bar. This creates a dependency with the default type, as defined by the defaultValue of the DependencyModel type field.

If you prefer the old behavior, simply set allowDropOnEventBar to false on the feature config:

const scheduler = new Scheduler({
    features : {
        dependencies : {
            allowDropOnEventBar : false
        }
    }
});

Scheduler v4.3.0

Buttons with menus shows a caret down arrow icon

Button has a new config menuIcon that specifies an icon to show when the button has a menu. It defaults to show a caret down arrow icon. If you are using buttons with menus and want to get rid of this icon (for example if you are already displaying a hamburger icon), set menuIcon : null on the button.

Old code:

const button = new Button({
    icon : 'fa fa-bars',
    menu : [
        { text : 'First item'},
        { text : 'Second item'}
    ]
})

New code:

const button = new Button({
    icon     : 'fa fa-bars',
    menuIcon : null,
    menu     : [
        { text : 'First item'},
        { text : 'Second item'}
    ]
})

Buttons menuIconCls deprecated

Buttons menuIconCls was deprecated in favor of the menuIcon config mentioned above. It will be removed in 5.0. Please replace any usages:

Old code:

const button = new Button({
    menuIconCls : 'fa fa-bars',
    menu        : [
        { text : 'First item'},
        { text : 'Second item'}
    ]
})

New code:

const button = new Button({
    menuIcon : 'fa fa-bars',
    menu     : [
        { text : 'First item'},
        { text : 'Second item'}
    ]
})

Scheduler v4.3.3

EventTooltips now hide by default on scroll

Previously they realigned on scroll. This change was done to boost scrolling performance, since realigning the tooltip has negative impact on that. To restore the old behaviour, configure the feature with scrollAction : 'realign'.

Old code:

new Scheduler({
    feature : {
        eventTooltip : true
    }
})

New code:

new Scheduler({
    feature : {
        eventTooltip : {
            scrollAction : 'realign'
        }
    }
})

Scheduler v4.3.7

PdfExport feature

beforeExport Scheduler event has been renamed to beforePdfExport and export event has been renamed to pdfExport respectively. Please modify your code to match the new names.

Old code:

scheduler.on({
    beforeExport({ config }) { /*...*/ },

    export({ config }) { /*...*/ }
})

New code:

scheduler.on({
    beforePdfExport({ config }) {
        const { orientation } = config;
        /*...*/
    },

    pdfExport({ config }) {
        const { orientation } = config;
        /*...*/
    }
});

Also the events signature had deprecated properties copied from config section which have been removed in this release.

Contents