What's new in Scheduler Pro v6.0.0+
Scheduler Pro v6.0.0
Calendar editor widget
The release introduces the calendar editor - a special widget visualizing and allowing to edit a calendar's data. Click Edit project calendar button to see the widget in action:
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' } ] }); The widget used is a CalendarEditor. It extends the standard Popup, any corresponding configs works for it too:
const editor = new CalendarEditor({
// display the editor right now
autoShow : true,
calendar : calendarToEdit,
// use it as a modal dialog
modal : true,
// clicking outside the editor closes it
autoClose : true
});
And additionally the class has some extra APIs specific to its purposes:
- calendar - a config to load a calendar for editing.
- activeDate - a date to display on the widget's "General" tab date picker.
For more details please check the API docs and this new guide.
CalendarField support for the calendar editor
CalendarField has been changed to support the new calendar editor. The field has a new trigger to open the editor for the selected calendar.
const project = new ProjectModel({ startDate : '2020-01-02', events : [ { id : 1, name : 'Write docs', startDate : '2020-01-02', endDate : '2020-01-05' } ], calendars : [ { id : 'general', name : '24 hour calendar', intervals : [ { recurrentStartDate : 'on Sat', recurrentEndDate : 'on Mon', isWorking : false } ], expanded : true, children : [ { id : 'business', name : 'Business hours (8am - 5pm)', intervals : [ { recurrentStartDate : 'every weekday at 12:00', recurrentEndDate : 'every weekday at 13:00', isWorking : false }, { recurrentStartDate : 'every weekday at 17:00', recurrentEndDate : 'every weekday at 08:00', isWorking : false } ] }, { id : 'nightshift', name : 'Night shift (10pm - 6am)', intervals : [ { recurrentStartDate : 'every weekday at 6:00', recurrentEndDate : 'every weekday at 22:00', isWorking : false } ] } ] } ] }); const eventRecord = project.eventStore.getById(1); const calendarField = new CalendarField({ appendTo : targetElement, width : 250, store : project.calendarManagerStore, // edit "Write docs" event calendar value : eventRecord.calendar, calendarConsumerRecord : eventRecord }); There is a new calendarConsumerRecord config on the field that must be provided to use the calendar editor. The config should be set to the record "owning" the edited calendar. So it could be either a resource, an event or a project:
new CalendarField({
value : event1.calendar,
calendarConsumerRecord : event1
})
The field has got a new calendarEditor config to customize the calendar editor widget:
new CalendarField({
value : event1.calendar,
calendarConsumerRecord : event1,
calendarEditor : {
// display editor centered
centered : true
}
})
The config can also be set to false to opt out both the new editor and its trigger displaying:
new CalendarField({
value : event1.calendar,
// disable new calendar editor
calendarEditor : false
})
The field is used in the task editor and resource calendar column widgets so please check the new behavior in our demos ("resource-non-working-time" demo for instance shows both of them).
Scheduler Pro v6.0.2
ResourceHistogram and ResourceUtilization views API changes
ResourceHistogram and ResourceUtilization views have got two new hooks providing better control over the rendered DOM elements.
The first hook is getBarDOMConfig. It's a function which is meant to return a bar DOM configuration object - the object that configures the bar RECT element.
new ResourceHistogram({
// Let's add left & right margins to bars
getBarDOMConfig(series, domConfig) {
// margin size is 10% of the bar width
const xMargin = 0.1 * domConfig.width;
// adjust the bar x-coordinate
domConfig.x += xMargin;
// reduce the bar width respectively
domConfig.width -= 2 * xMargin;
// return the edited domConfig
return domConfig;
},
...
})
Another one is getBarTextDOMConfig - a function which is meant to return a DOM configuration object for text elements:
new ResourceHistogram({
getBarTextDOMConfig(domConfig, datum, index) {
// Place text at the top of the "effort" bar
// so calculate y-position in percents
domConfig.y = `${100 * (1 - datum.effort / this.topValue)}%`;
// also let's laid the text lines horizontally
domConfig.style = 'writing-mode: lr';
return domConfig;
},
...
})
For more details on the functions please check their docs:
ResourceHistogramResourceUtilization
This release also deprecates getRectClass config in favor of new getBarClass config to make naming more consistent. The old config will be removed in the next major release so please change getRectClass in your code to getBarClass before that.
Scheduler Pro v6.0.5
Dependency creation just for parent events
The Dependency feature has a new config that only applies when using nested events: allowCreateOnlyParent. Set it to true to allow dependency creation only for parent events. Normally the nested event container inside parent events cannot be scrolled when using dependencies, but by enabling this setting and limiting to where dependencies can be drawn scrolling will be enabled.
new Scheduler({
...
features : {
nestedEvents : true,
dependencies : {
allowCreateOnlyParent : true
}
},
...
});
SchedulerPro v6.1.0
Remote paging, sorting and filtering added to Store
The ability to sort, filter and paginate records remotely was previously only available when using an AjaxStore configured with a readUrl. Now, it is possible to use the built-in functionality and UI controls when using a regular Store.
3 new configs has been added to Store:
Turning any of these functions on is a simple matter of setting the config to true, and then implementing a way of providing the correct data to the Store. For that, the requestData function needs to be implemented. Here is a simple example using paging, sorting and filtering:
const store = new Store({
remotePaging : true,
remoteFilter : true,
remoteSort : true,
requestData({ filters, sorters, page, pageSize }){
let filteredRecords = [...allRecords];
filters?.forEach(filter => {
const { field, operator, value, caseSensitive } = filter;
if(operator === '='){
filteredRecords = filteredRecords.filter(r => r[field] === value);
}
else {
/// ... implement other filter operators
}
});
sorters?.forEach(sorter => filteredRecords.sort((a,b) => {
const { field, ascending } = sorter;
if (!ascending) {
([b, a] = [a, b]);
}
return a[field] > b[field] ? 1 : (a[field] < b[field] ? -1 : 0)
}));
const start = (page - 1) * pageSize;
const data = filteredRecords.splice(start, start + pageSize);
return {
data,
total : filteredRecords.length
}
}
});
For a more detailed explanation please see the updated Store guide. There is also a new Grid demo that demonstrates the new functionality.
SchedulerPro v6.1.3
New keepTime modes for StartDateField and EndDateField classes
The StartDateField and EndDateField classes processing of time values differs from the standard DateField behavior. So it was decided to make separate modes for them:
-
StartDateFieldkeepTimeconfig has got a newsodmode (default for the field). When the mode is enabled the field adjusts its value time portion to start of the working day. The adjustment is skipped when loading a record value to the field. -
EndDateFieldkeepTimeconfig has got a neweodmode (default for the field). When the mode is enabled the field adjusts its value time portion to end of the working day. The adjustment is skipped when loading a record value to the field.
Scheduler Pro v6.1.4
Introducing thin trial NPM packages
Starting with the 6.1.4 release, there are now trial versions of thin NPM packages available on our NPM server. These can be used to evaluate combining multiple products before purchasing a license.
Please check the combining multiple products guides for the trial packages list and installation tips:
New is{{ClassName}} boolean property
Classes extending Base have got new is{{ClassName}} boolean property indicating instances of the corresponding class or subclasses of thereof. The properties can be used for checking types without need fo importing the corresponding types just to do instanceof check.
For example EventModel class is automatically decorated with isEventModel property returning true. And EventSegmentModel has isEventSegmentModel as true respectively. Then the following code can be used for checking the corresponding types:
eventRenderer({ eventRecord }) {
// if that's a segment
if (eventRecord.isEventSegmentModel) {
// ...render a segment
}
// that's an event
else {
...
}
},
ProjectModel and EventSegmentModel classes has got new properties
ProjectModel class has got new segmentModelClass property allowing to override the class used for representing a segment:
// a custom class for a segment
class MySegment extends EventSegmentModel {
static fields = [
// a custom segment field
{ name : 'responsiblePerson' }
];
}
new SchedulerPro({
project : {
// tell the project to use the new class for segments
segmentModelClass : MySegment
}
})
EventSegmentModel class has got new segmentIndex property providing zero-based index of the segment.
SchedulerPro v6.1.7
New scrollAction config in CellEditing
The scrollAction config specifies what action should be taken if an active cell editor is scrolled out of view.
It may be set to 'cancel', 'complete' or null.
The default value is null which means the edit is preserved.
New roundedSplit config introduced in EventSegments feature
The roundedSplit config allows user, when is set to false, to split events using the exact clicked date. When the config value is true (default) the feature uses a rounded date.
new SchedulerPro({
...
features : {
eventSegments : {
// split at the exact date user clicks in UI
roundedSplit : false
}
}
})
SchedulerPro v6.1.8
rtl config of Widget
The rtl config property of Widgets is now public. By default Widgets conform to the writing direction of the element they are rendered to. But with the now public rtl property, you can force a widget, and all descendant widgets to lay out from right to left.
Duration and lag conversion hooks
This release introduces new public methods allowing to override duration units conversion easier.
Event model has got a couple of new methods:
- convertDurationGen method that basically converts the provided duration value from one time unit to another. The method has to be a generator to embed into the rest of the Engine code.
- canConvertDuration method that indicates whether the event can perform the requested duration conversion. The method is used when requesting a conversion in a synchronous way and since the data is loaded asynchronously there could be cases when a conversion is not possible yet. By default the method checks that the project has loaded the rates needed for conversion.
And dependency model has got new convertLagGen method responsible for converting the dependency lag. Our codebase has no code triggering lag conversion from a synchronous context. So we haven't added canConvertLag similar to mentioned canConvertDuration.
Usage example
In the old version of the Gantt made for Ext JS each calendar keeps own conversion rates. The approach was quite controversial and caused a lot of questions. So it was decided to change that in the new scheduling Engine. But if you migrate from the old version and want to stick to that approach you can use the following code:
// Make calendars capable of converting durations.
// This will add "hoursPerDay", "daysPerWeek" and "daysPerMonth" fields to MyCalendarModel model
class MyCalendarModel extends DurationConverterMixin.derive(CalendarModel) {}
class MyDependencyModel extends DependencyModel {
* convertLagGen(duration, fromUnit, toUnit) {
// use the dependency calendar as lag converter
const converter = yield this.$.calendar;
return yield* converter.$convertDuration(duration, fromUnit, toUnit);
}
}
class MyEventModel extends EventModel {
* convertDurationGen(duration, fromUnit, toUnit) {
// use the event calendar as duration converter
const converter = yield this.$.effectiveCalendar;
return yield* converter.$convertDuration(duration, fromUnit, toUnit);
}
// Override to check that the event calendar is ready for converting
canConvertDuration(duration, fromUnit, toUnit) {
// sanitize provided units ("d", "days" -> "day", "ms" -> "millisecond" etc)
toUnit = DateHelper.normalizeUnit(toUnit);
fromUnit = DateHelper.normalizeUnit(fromUnit);
const calendar = this.effectiveCalendar;
// can convert duration if its numeric and
return typeof duration === 'number' &&
// the event calendar is resolved and has needed conversion rates
calendar?.unitsInMs?.[ fromUnit ] && calendar.unitsInMs[ toUnit ];
}
}
new SchedulerPro({
project : {
// tell the project to use own customized models
calendarModelClass : MyCalendarModel,
eventModelClass : MyEventModel,
dependencyModelClass : MyDependencyModel,
...
},
...
})
SchedulerPro v6.2.0
New respectStoreFilters and assignmentFilterFn config on the ResourceHistogram view
ResourceHistogram class has got new configs giving more control on data filtering when collecting resource allocation:
- respectStoreFilters boolean config that allows to specify whether exiting event and assignment store filters should be taken into account or not:
new ResourceHistogram({
// skip filtered out events/assignments
respectStoreFilters : true,
...
});
// Filter event store to include only event w/ id === 777
// That will result the histogram displays only that event allocation
project.eventStore.filter(event => event.id === 777);
The config is false by default so the filters are not taken into account.
- assignmentFilterFn config accepts a function that specifies whether the provided assignment allocation should be included or not:
new ResourceHistogram({
// custom filtering function
* assignmentFilterFn(assignment) {
// get the assignment event
const event = yield assignment.$.event;
// include only allocation of events having 'Meeting' type
return event && event.kind === 'Meeting';
},
...
});
SchedulerPro v6.2.4
New recurrentDatesTimeZone field on calendar intervals
CalendarIntervalModel has got a new field specifying a timezone in which recurrentStartDate and recurrentEndDate rules are provided.
Until this release time info of the rules were always treated as specified in the local machine time zone. But that could result inconsistencies when backend provided dates in some other timezone. The new field should help in such cases.
Here is for example a dataset using the new field:
{
"calendars" : [
{
"id" : "general",
"name" : "General",
"intervals" : [
{
// weekends are days off starting Sat 00:00 GMT+6 and finishing Mon 00:00 GMT+6
"recurrentStartDate" : "on Sat",
"recurrentEndDate" : "on Mon",
// specify we use GMT+6 for recurrent*Date rules
"recurrentDatesTimeZone" : "+06:00",
"isWorking" : false
}
]
}
],
"resources" : [
{ "id" : "r1", "name" : "Worker #1" }
],
"assignments" : [
{ "id" : "a1", "resource" : "r1", "event" : 11 }
],
"events" : [
{
"id" : 11,
"name" : "Build a skyscraper",
// event start is specified in GMT+6 timezone
"startDate" : "2017-01-16T00:00+06:00",
"duration" : 3,
"durationUnit" : "hour"
}
]
}
Loading of the above dataset on a machine with GMT timezone will adjust the non-working interval six hours back to Fri 18:00 - Sun 18:00 and the event will start 2017-01-15T18:00 accordingly.
SchedulerPro v6.3.0
New Chart module
Bryntum products now ship with a new Chart module, which makes it easier to integrate Chart.js-based charts (see www.chartjs.org) with our products. The new module can be combined with all our products, by using the thin bundles / packages.
Using charts with your applications:
If you are using a vanilla JavaScript application, you need to import the chart thin bundle to enable support for the Charts feature:
import 'PATH_TO_DISTRIBUTION_FOLDER/build/thin/chart.module.thin.js';
For framework-based applications, the Bryntum npm repository provides the @bryntum/chart-thin package, along with chart component wrapper packages for major frameworks:
Angular:
import '@bryntum/chart-thin'
import { BryntumChart } from '@bryntum/chart-angular-thin'
React:
import '@bryntum/chart-thin'
import { BryntumChart } from '@bryntum/chart-react-thin'
Vue 3:
import '@bryntum/chart-thin'
import { BryntumChart } from '@bryntum/chart-vue-3-thin'
Streamed PDF export
The PdfExport feature now supports exporting large datasets by streaming data to the export server using a WebSocket connection. This allows for more efficient handling of large datasets during export operations, improving performance and reducing memory usage