# Upgrade guide for Scheduler Pro v4.0.0

Be sure to also check out the news in [Grid](#Grid/guides/upgrades/4.0.0.md) and 
[Scheduler](#Scheduler/guides/upgrades/4.0.0.md).

## Version in line with other Bryntum products

The version of Scheduler Pro was bumped from 1.0 -> 4.0 to bring it in line with Grid, Scheduler, Gantt and Calendar.
This will make it easier to determine which versions are compatible with each other.

## 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.

## Async data manipulation changes

To simplify async data manipulation a bit, we have added two new functions that performs the engine commit internally, 
`addAsync()` and  `loadDataAsync()`. Example of their usage, `loadDataAsync()` as an alternative to `store.data = ...` 
first:

```javascript
await eventStore.loadDataAsync([my_dataset]);

// calculations are done, data is ready for further processing
```

And `addAsync()` would be used like this:

```javascript
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:

```javascript
const scheduler = new SchedulerPro({
  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.

## 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:**
```javascript
class MyWidget extends Widget {

}
BryntumWidgetAdapterRegister.register('mywidget', MyWidget);
```

**New code:**
```javascript
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:

```javascript
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:

```javascript
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` `add` events would be fired for these ephemeral occurrences which apps had to have code
to ignore.

From 4.0.0, there is no `RecurringEvents` Feature. 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.

`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.

```javascript
myEventStore.getEvents({
    resourceRecord : myResourceRecord,
    startDate      : myScheduler.timeAxis.startDate,
    endDate        : myScheduler.timeAxis.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:

```javascript
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:

```javascript
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()`:

```javascript
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 `exceptionDate` 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` of the `EventModel`. 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:

```javascript
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.

## TaskEditor configs changes

`tabsConfig` and `extraItems` configs are deprecated and will be removed in version 5.0. TaskEditor tabs and widgets in
the tabs are configured in a single `items` config. 

**Old code:**
```javascript
const scheduler = new SchedulerPro({
    features : {
        taskEdit : {
            editorConfig : {
                extraItems : {
                    generaltab : [
                        { type : 'button', text : 'New Button' }
                    ]
                }
            },
            tabsConfig : {
                generaltab : { title : 'Common' },
                notestab : false,
                filestab : { type : 'custom_filestab' }
            }
        }
    }
});
```

**New code:**
```javascript
const scheduler = new SchedulerPro({
    features : {
        taskEdit : {
            items : {
                generalTab : {
                    title : 'Common',
                    items : {
                        newButton : { type : 'button', text : 'New Button' }
                    }
                },
                notesTab : false,
                filesTab : { type : 'custom_filestab' }
            }
        }
    }
});
```

## 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:
```javascript
    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 Grid 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:**
```html
<link rel="stylesheet" href="build/grid.dark.css" id="bryntum-theme">
```

**New code:**
```html
<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:**
```css
.b-theme-dark {
   /*...*/
}
```

**New code:**
```css
.b-theme-classic-dark {
   /*...*/
}
```


<p class="last-modified">Last modified on 2023-10-26 8:19:27</p>