Premium support for our pure JavaScript UI components


Post by era »

I'm are using the react wrapper for bryntum scheduler which imports the UMD version. It's works in Chrome, Edge and firefox but not IE11. The scheduler along with resources is shown, but no events or the timeranges...

Simplified code how I load the events:

class MyScheduler extends Component {
	scheduler = React.createRef();
	eventStore = new EventStore();
	
componentDidMount() {
	this.eventStore.data = [...this.props.calendarData.calendar.events];
}

render() {
	<BryntumScheduler
		ref={this.scheduler}
		[...] // Start dates and etc
		assignments={this.props.calendarData.calendar.assignments}
		resources={this.props.calendarData.calendar.resources}
		timeRanges={[
					{
						id: "rangeId",
						startDate: 'exampleStartDate',
						endDate: exampleEndDate,
						cls: 'test-class'
					}]}
		eventStore={this.eventStore}
	/>
}

Is there anything I'm missing(with the limited code I provided). From what I can see the div with class "b-sch-foreground-canvas" is empty in IE11.


Post by mats »

Any errors in the console? Do you have a test case we can inspect?


Post by era »

No errors in the console. I'm gonna see if I can upload some code. Am I implementing the eventstore the correct way in my previos code example?

Is this the correct wrapper to use?

/**
 *- React Scheduler wrapper
 */
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

// we import scheduler.umd for IE11 compatibility only. If you don't use IE import:
// import { Scheduler, ObjectHelper, Widget } from 'bryntum-scheduler';
import { Scheduler, ObjectHelper, Widget } from 'bryntum-scheduler/scheduler.umd';

// Defines a React component that wraps Bryntum Scheduler
class BryntumScheduler extends Component {

/**
 * @deprecated in favor of schedulerInstance
 */
get schedulerEngine() {
	console.warn('schedulerEngine is deprecated. Use schedulerInstance instead.')
	return this.schedulerInstance;
}

// defaults for scheduler. Feel free to adjust it
static defaultProps = {};

featureRe = /Feature$/;

/* #region Features */
features = [
	'cellEditFeature',
	'cellTooltipFeature',
	'columnDragToolbarFeature',
	'columnLinesFeature',
	'columnPickerFeature',
	'columnReorderFeature',
	'columnResizeFeature',
	'contextMenuFeature',
	'dependenciesFeature',
	'dependencyEditFeature',
	'eventContextMenuFeature',
	'eventDragFeature',
	'eventDragCreateFeature',
	'eventDragSelectFeature',
	'eventEditFeature',
	'eventFilterFeature',
	'eventResizeFeature',
	'eventTooltipFeature',
	'filterBarFeature',
	'filterFeature',
	'groupFeature',
	'groupSummaryFeature',
	'headerContextMenuFeature',
	'headerZoomFeature',
	'labelsFeature',
	'nonWorkingTimeFeature',
	'panFeature',
	'pdfExportFeature',
	'quickFindFeature',
	'recurringEventsFeature',
	'recurringTimeSpansFeature',
	'regionResizeFeature',
	'resourceTimeRangesFeature',
	'rowReorderFeature',
	'scheduleContextMenuFeature',
	'scheduleTooltipFeature',
	'searchFeature',
	'simpleEventEditFeature',
	'sortFeature',
	'stripeFeature',
	'summaryFeature',
	'timeRangesFeature',
	'treeFeature'
];
/* #endregion */

/* #region Configs */
configs = [
	'allowOverlap',
	'animateRemovingRows',
	'assignments',
	'assignmentStore',
	'autoAdjustTimeAxis',
	'autoHeight',
	'barMargin',
	'columnLines',
	'columns',
	'contextMenuTriggerEvent',
	'createEventOnDblClick',
	'crudManager',
	'defaultResourceImageName',
	'dependencyStore',
	'disableGridRowModelWarning',
	'displayDateFormat',
	'emptyText',
	'enableDeleteKey',
	'enableEventAnimations',
	'enableTextSelection',
	'endDate',
	'endParamName',
	'eventBarTextField',
	'eventBodyTemplate',
	'eventColor',
	'eventLayout',
	'eventRenderer',
	'events',
	'eventSelectionDisabled',
	'eventStyle',
	'eventStore',
	'fillLastColumn',
	'fullRowRefresh',
	'fillTicks',
	'hasVisibleEvents',
	'hideHeaders',
	'horizontalEventSorterFn',
	'loadMask',
	'longPressTime',
	'maintainSelectionOnDatasetChange',
	'managedEventSizing',
	'maxHeight',
	'maxWidth',
	'maxZoomLevel',
	'milestoneAlign',
	'milestoneCharWidth',
	'milestoneLayoutMode',
	'minZoomLevel',
	'mode',
	'height',
	'minHeight',
	'minWidth',
	'multiEventSelect',
	'partner',
	'passStartEndParameters',
	'readOnly',
	'removeUnassignedEvent',
	'resizeToFitIncludesHeader',
	'resourceColumns',
	'resourceImagePath',
	'resourceMargin',
	'resources',
	'resourceStore',
	'resourceTimeRanges',
	'responsiveLevels',
	'rowHeight',
	'scrollLeft',
	'scrollTop',
	'selectedEvents',
	'selectionMode',
	'showDirty',
	'showRemoveRowInContextMenu',
	'snap',
	'snapRelativeToEventStartDate',
	'startDate',
	'startParamName',
	'subGridConfigs',
	'tickWidth',
	'timeRanges',
	'timeResolution',
	'triggerSelectionChangeOnRemove',
	'useInitialAnimation',
	'viewportCenterDate',
	'viewPreset',
	'weekStartDay',
	'width',
	'workingTime',
	'zoomOnMouseWheel',
	'zoomOnTimeAxisDoubleClick',
	'zoomLevel'
];
/* #endregion */

state = {
	portals: new Set(),
	generation: 0
};

releaseReactCell(cellElement) {
	const
		{ state } = this,
		cellElementData = cellElement._domData;

	// Cell already has a react component in it, remove
	if (cellElementData.reactPortal) {
		state.portals.delete(cellElementData.reactPortal);

		this.setState({
			portals: state.portals,
			generation: state.generation + 1
		});

		cellElementData.reactPortal = null;
	}
}

// React component rendered to DOM, render scheduler to it
componentDidMount() {
	const
		{ props } = this,
		config = {
			// Use this element as our encapsulating element
			adopt: this.el,
			callOnFunctions: true,
			features: {},

			// Hook called by engine when requesting a cell editor
			processCellEditor: ({ editor, field }) => {
				// String etc handled by feature, only care about fns returning React components here
				if (typeof editor !== 'function') {
					return;
				}

				// Wrap React editor in an empty widget, to match expectations from CellEdit/Editor and make alignment
				// etc. work out of the box
				const wrapperWidget = new Widget({
					name: field // For editor to be hooked up to field correctly
				});

				// Ref for accessing the React editor later
				wrapperWidget.reactRef = React.createRef();

				// column.editor is expected to be a function returning a React component (can be JSX). Function is
				// called with the ref from above, it has to be used as the ref for the editor to wire things up
				const reactComponent = editor(wrapperWidget.reactRef);
				if (reactComponent.$$typeof !== Symbol.for('react.element')) {
					throw new Error('Expect a React element');
				}

				let editorValidityChecked = false;

				// Add getter/setter for value on the wrapper, relaying to getValue()/setValue() on the React editor
				Object.defineProperty(wrapperWidget, 'value', {
					enumerable: true,
					get: function () {
						return wrapperWidget.reactRef.current.getValue();
					},
					set: function (value) {
						const component = wrapperWidget.reactRef.current;

						if (!editorValidityChecked) {
							const misses = ['setValue', 'getValue', 'isValid', 'focus'].filter(fn => !(fn in component));

							if (misses.length) {
								throw new Error(`
                                    Missing function${misses.length > 1 ? 's' : ''} ${misses.join(', ')} in ${component.constructor.name}.
                                    Cell editors must implement setValue, getValue, isValid and focus
                                `);
							}

							editorValidityChecked = true;
						}

						const context = wrapperWidget.owner.cellEditorContext;
						component.setValue(value, context);
					}
				});

				// Add getter for isValid to the wrapper, mapping to isValid() on the React editor
				Object.defineProperty(wrapperWidget, 'isValid', {
					enumerable: true,
					get: function () {
						return wrapperWidget.reactRef.current.isValid();
					}
				});

				// Override widgets focus handling, relaying it to focus() on the React editor
				wrapperWidget.focus = () => {
					wrapperWidget.reactRef.current.focus && wrapperWidget.reactRef.current.focus();
				};

				// Create a portal, making the React editor belong to the React tree although displayed in a Widget
				const portal = ReactDOM.createPortal(reactComponent, wrapperWidget.element);
				wrapperWidget.reactPortal = portal;

				const { state } = this;
				// Store portal in state to let React keep track of it (inserted into the Grid component)
				state.portals.add(portal);
				this.setState({
					portals: state.portals,
					generation: state.generation + 1
				});

				return { editor: wrapperWidget };
			},

			// Hook called by engine when rendering cells, creates portals for JSX supplied by renderers
			processCellContent: ({ cellContent, cellElement, cellElementData, record }) => {
				let shouldSetContent = cellContent != null;

				// Release any existing React component
				this.releaseReactCell(cellElement);

				// Detect React component
				if (cellContent && cellContent.$$typeof === Symbol.for('react.element')) {
					// Excluding special rows for now to keep renderers simpler
					if (!record.meta.specialRow) {
						// Clear any non-react content
						const firstChild = cellElement.firstChild;
						if (!cellElementData.reactPortal && firstChild) {
							firstChild.data = '';
						}

						// Create a portal, belonging to the existing React tree but render in a cell
						const portal = ReactDOM.createPortal(cellContent, cellElement);
						cellElementData.reactPortal = portal;

						const { state } = this;
						// Store in state for React to not loose track of the portals
						state.portals.add(portal);
						this.setState({
							portals: state.portals,
							generation: state.generation + 1
						});
					}
					shouldSetContent = false;
				}

				return shouldSetContent;
			}
		};

	// relay properties with names matching this.featureRe to features
	this.features.forEach(featureName => {
		if (featureName in props) {
			config.features[featureName.replace(this.featureRe, '')] = props[featureName];
		}
	});

	// Handle config (relaying all props except those used for features to scheduler)
	Object.keys(props).forEach(propName => {
		if (!propName.match(this.featureRe) && undefined !== props[propName]) {
			if (propName === 'features') {
				console.warn('Passing "features" object as prop is not supported. Set features one-by-one with "Feature" suffix, for example "timeRangesFeature".');
			}
			else {
				config[propName] = props[propName];
			}
		}
	});

	// console.log(config);

	// Create the actual scheduler, used as engine for the wrapper
	const engine = this.schedulerInstance = props.schedulerClass ? new props.schedulerClass(config) : new Scheduler(config);

	// Release any contained React components when a row is removed
	engine.rowManager.on({
		removeRow: ({ row }) => row.cells.forEach(cell => this.releaseReactCell(cell))
	});

	// Make stores easier to access
	this.resourceStore = engine.resourceStore;
	this.eventStore = engine.eventStore;

	// Map all features from schedulerInstance to scheduler to simplify calls
	Object.keys(engine.features).forEach(key => {
		const featureName = key + 'Feature';
		if (!this[featureName]) {
			this[featureName] = engine.features[key];
		}
	});

	// Shortcut to set syncDataOnLoad on the stores
	if (props.syncDataOnLoad) {
		engine.resourceStore.syncDataOnLoad = true;
		engine.eventStore.syncDataOnLoad = true;
	}

	if (config.events) {
		this.lastEvents = config.events.slice();
	}

	if (config.resources) {
		this.lastResources = config.resources.slice();
	}
}

// React component removed, destroy engine
componentWillUnmount() {
	this.schedulerInstance.destroy();
}

// Component about to be updated, from changing a prop using state. React to it depending on what changed and
// prevent react from re-rendering our component.
shouldComponentUpdate(nextProps, nextState) {
	const
		{ props, schedulerInstance: engine } = this,
		// These props are ignored or has special handling below
		excludeProps = [
			'adapter',
			'children',
			'columns',
			'events',
			'eventsVersion',
			'features', // #445 React: Scheduler crashes when features object passed as prop
			'listeners', // #9114 prevents the crash when listeners are changed at runtime
			'ref',
			'resources',
			'resourcesVersion',
			'timeRanges',
			...this.features
		];

	// Reflect configuration changes. Since most scheduler configs are reactive the scheduler will update automatically
	Object.keys(props).forEach(propName => {
		// Only apply if prop has changed
		if (!excludeProps.includes(propName) && !ObjectHelper.isEqual(props[propName], nextProps[propName])) {
			engine[propName] = nextProps[propName];
		}
	});

	if (
		// resourceVersion used to flag that data has changed
		nextProps.resourcesVersion !== props.resourcesVersion ||
		// if not use do deep equality check against previous dataset
		// TODO: Might be costly for large datasets
		(!('resourcesVersion' in nextProps) && !('resourcesVersion' in props) && !ObjectHelper.isDeeplyEqual(this.lastResources, nextProps.resources))) {
		engine.resources = nextProps.resources;
		this.lastResources = nextProps.resources && nextProps.resources.slice();
	}

	if (
		// eventVersion used to flag that data has changed
		nextProps.eventsVersion !== props.eventsVersion ||
		// if not use do deep equality check against previous dataset
		// TODO: Might be costly for large datasets
		(!('eventsVersion' in nextProps) && !('eventsVersion' in props) && !ObjectHelper.isDeeplyEqual(this.lastEvents, nextProps.events))) {
		engine.events = nextProps.events;
		this.lastEvents = nextProps.events && nextProps.events.slice();
	}

	// Reflect feature config changes
	this.features.forEach(featureName => {
		const currentProp = props[featureName],
			nextProp = nextProps[featureName];

		if (featureName in props && !ObjectHelper.isDeeplyEqual(currentProp, nextProp)) {
			engine.features[featureName.replace(this.featureRe, '')].setConfig(nextProp);
		}
	});

	// Reflect JSX cell changes
	return (nextState.generation !== this.state.generation);
}

render() {
	return <div className={'b-react-scheduler-container'} ref={el => this.el = el}>{this.state.portals}</div>;
} // eo function render

} // eo class BryntumScheduler

export default BryntumScheduler;

// eof

Post by pmiklashevich »

The wrapper you attached looks a bit old. Could you please download the latest sources from the Customer zone and update the Bryntum lib? I've checked online react demo which uses the wrapper in IE11. Looks good.
https://www.bryntum.com/examples/scheduler/react/javascript/animations/build/index.html

Снимок экрана 2020-11-24 в 14.04.15.png
Снимок экрана 2020-11-24 в 14.04.15.png (497.8 KiB) Viewed 2463 times

So, please:

  • try to upgrade the lib
  • try to get one of our examples up and running
  • apply minimal changes to one of our demos to reproduce your issue

If you see the bug, please zip it up and attach here, so we have something to inspect.

Thanks!

Pavlo Miklashevych
Sr. Frontend Developer


Post by era »

Is this the correct wrapper to use?

import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";
import _inherits from "@babel/runtime/helpers/esm/inherits";
import _possibleConstructorReturn from "@babel/runtime/helpers/esm/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/esm/getPrototypeOf";

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () { })); return true; } catch (e) { return false; } }

/**
 *- React Scheduler wrapper
 */
import React, { Component } from 'react';
import ReactDOM from 'react-dom'; // we import scheduler.umd for IE11 compatibility only. If you don't use IE import:
// import { Scheduler, ObjectHelper, Widget } from 'bryntum-scheduler';

import { Scheduler, ObjectHelper, Widget } from 'bryntum-scheduler/scheduler.umd'; // Defines a React component that wraps Bryntum Scheduler

var BryntumScheduler = /*#__PURE__*/function (_Component) {
	_inherits(BryntumScheduler, _Component);

var _super = _createSuper(BryntumScheduler);

function BryntumScheduler() {
	var _this;

	_classCallCheck(this, BryntumScheduler);

	for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
		args[_key] = arguments[_key];
	}

	_this = _super.call.apply(_super, [this].concat(args));
	_this.featureRe = /Feature$/;
	_this.features = ['cellEditFeature', 'cellTooltipFeature', 'columnDragToolbarFeature', 'columnLinesFeature', 'columnPickerFeature', 'columnReorderFeature', 'columnResizeFeature', 'contextMenuFeature', 'dependenciesFeature', 'dependencyEditFeature', 'eventContextMenuFeature', 'eventDragFeature', 'eventDragCreateFeature', 'eventDragSelectFeature', 'eventEditFeature', 'eventFilterFeature', 'eventResizeFeature', 'eventTooltipFeature', 'filterBarFeature', 'filterFeature', 'groupFeature', 'groupSummaryFeature', 'headerContextMenuFeature', 'headerZoomFeature', 'labelsFeature', 'nonWorkingTimeFeature', 'panFeature', 'pdfExportFeature', 'quickFindFeature', 'recurringEventsFeature', 'recurringTimeSpansFeature', 'regionResizeFeature', 'resourceTimeRangesFeature', 'rowReorderFeature', 'scheduleContextMenuFeature', 'scheduleTooltipFeature', 'searchFeature', 'simpleEventEditFeature', 'sortFeature', 'stripeFeature', 'summaryFeature', 'timeRangesFeature', 'treeFeature'];
	_this.configs = ['allowOverlap', 'animateRemovingRows', 'assignments', 'assignmentStore', 'autoAdjustTimeAxis', 'autoHeight', 'barMargin', 'columnLines', 'columns', 'contextMenuTriggerEvent', 'createEventOnDblClick', 'crudManager', 'defaultResourceImageName', 'dependencyStore', 'disableGridRowModelWarning', 'displayDateFormat', 'emptyText', 'enableDeleteKey', 'enableEventAnimations', 'enableTextSelection', 'endDate', 'endParamName', 'eventBarTextField', 'eventBodyTemplate', 'eventColor', 'eventLayout', 'eventRenderer', 'events', 'eventSelectionDisabled', 'eventStyle', 'eventStore', 'fillLastColumn', 'fullRowRefresh', 'fillTicks', 'hasVisibleEvents', 'hideHeaders', 'horizontalEventSorterFn', 'loadMask', 'longPressTime', 'maintainSelectionOnDatasetChange', 'managedEventSizing', 'maxHeight', 'maxWidth', 'maxZoomLevel', 'milestoneAlign', 'milestoneCharWidth', 'milestoneLayoutMode', 'minZoomLevel', 'mode', 'height', 'minHeight', 'minWidth', 'multiEventSelect', 'partner', 'passStartEndParameters', 'readOnly', 'removeUnassignedEvent', 'resizeToFitIncludesHeader', 'resourceColumns', 'resourceImagePath', 'resourceMargin', 'resources', 'resourceStore', 'resourceTimeRanges', 'responsiveLevels', 'rowHeight', 'scrollLeft', 'scrollTop', 'selectedEvents', 'selectionMode', 'showDirty', 'showRemoveRowInContextMenu', 'snap', 'snapRelativeToEventStartDate', 'startDate', 'startParamName', 'subGridConfigs', 'tickWidth', 'timeRanges', 'timeResolution', 'triggerSelectionChangeOnRemove', 'useInitialAnimation', 'viewportCenterDate', 'viewPreset', 'weekStartDay', 'width', 'workingTime', 'zoomOnMouseWheel', 'zoomOnTimeAxisDoubleClick', 'zoomLevel'];
	_this.state = {
		portals: new Set(),
		generation: 0
	};
	return _this;
}

_createClass(BryntumScheduler, [{
	key: "releaseReactCell",
	value: function releaseReactCell(cellElement) {
		var state = this.state,
			cellElementData = cellElement._domData; // Cell already has a react component in it, remove

		if (cellElementData.reactPortal) {
			state.portals.delete(cellElementData.reactPortal);
			this.setState({
				portals: state.portals,
				generation: state.generation + 1
			});
			cellElementData.reactPortal = null;
		}
	} // React component rendered to DOM, render scheduler to it

}, {
	key: "componentDidMount",
	value: function componentDidMount() {
		var _this2 = this;

		var props = this.props,
			config = {
				// Use this element as our encapsulating element
				adopt: this.el,
				callOnFunctions: true,
				features: {},
				// Hook called by engine when requesting a cell editor
				processCellEditor: function processCellEditor(_ref) {
					var editor = _ref.editor,
						field = _ref.field;

					// String etc handled by feature, only care about fns returning React components here
					if (typeof editor !== 'function') {
						return;
					} // Wrap React editor in an empty widget, to match expectations from CellEdit/Editor and make alignment
					// etc. work out of the box


					var wrapperWidget = new Widget({
						name: field // For editor to be hooked up to field correctly

					}); // Ref for accessing the React editor later

					wrapperWidget.reactRef = React.createRef(); // column.editor is expected to be a function returning a React component (can be JSX). Function is
					// called with the ref from above, it has to be used as the ref for the editor to wire things up

					var reactComponent = editor(wrapperWidget.reactRef);

					if (reactComponent.$$typeof !== Symbol.for('react.element')) {
						throw new Error('Expect a React element');
					}

					var editorValidityChecked = false; // Add getter/setter for value on the wrapper, relaying to getValue()/setValue() on the React editor

					Object.defineProperty(wrapperWidget, 'value', {
						enumerable: true,
						get: function get() {
							return wrapperWidget.reactRef.current.getValue();
						},
						set: function set(value) {
							var component = wrapperWidget.reactRef.current;

							if (!editorValidityChecked) {
								var misses = ['setValue', 'getValue', 'isValid', 'focus'].filter(function (fn) {
									return !(fn in component);
								});

								if (misses.length) {
									throw new Error("\n                                        Missing function".concat(misses.length > 1 ? 's' : '', " ").concat(misses.join(', '), " in ").concat(component.constructor.name, ".\n                                        Cell editors must implement setValue, getValue, isValid and focus\n                                    "));
								}

								editorValidityChecked = true;
							}

							var context = wrapperWidget.owner.cellEditorContext;
							component.setValue(value, context);
						}
					}); // Add getter for isValid to the wrapper, mapping to isValid() on the React editor

					Object.defineProperty(wrapperWidget, 'isValid', {
						enumerable: true,
						get: function get() {
							return wrapperWidget.reactRef.current.isValid();
						}
					}); // Override widgets focus handling, relaying it to focus() on the React editor

					wrapperWidget.focus = function () {
						wrapperWidget.reactRef.current.focus && wrapperWidget.reactRef.current.focus();
					}; // Create a portal, making the React editor belong to the React tree although displayed in a Widget


					var portal = ReactDOM.createPortal(reactComponent, wrapperWidget.element);
					wrapperWidget.reactPortal = portal;
					var state = _this2.state; // Store portal in state to let React keep track of it (inserted into the Grid component)

					state.portals.add(portal);

					_this2.setState({
						portals: state.portals,
						generation: state.generation + 1
					});

					return {
						editor: wrapperWidget
					};
				},
				// Hook called by engine when rendering cells, creates portals for JSX supplied by renderers
				processCellContent: function processCellContent(_ref2) {
					var cellContent = _ref2.cellContent,
						cellElement = _ref2.cellElement,
						cellElementData = _ref2.cellElementData,
						record = _ref2.record;
					var shouldSetContent = cellContent != null; // Release any existing React component

					_this2.releaseReactCell(cellElement); // Detect React component


					if (cellContent && cellContent.$$typeof === Symbol.for('react.element')) {
						// Excluding special rows for now to keep renderers simpler
						if (!record.meta.specialRow) {
							// Clear any non-react content
							var firstChild = cellElement.firstChild;

							if (!cellElementData.reactPortal && firstChild) {
								firstChild.data = '';
							} // Create a portal, belonging to the existing React tree but render in a cell


							var portal = ReactDOM.createPortal(cellContent, cellElement);
							cellElementData.reactPortal = portal;
							var state = _this2.state; // Store in state for React to not loose track of the portals

							state.portals.add(portal);

							_this2.setState({
								portals: state.portals,
								generation: state.generation + 1
							});
						}

						shouldSetContent = false;
					}

					return shouldSetContent;
				}
			}; // relay properties with names matching this.featureRe to features

		this.features.forEach(function (featureName) {
			if (featureName in props) {
				config.features[featureName.replace(_this2.featureRe, '')] = props[featureName];
			}
		}); // Handle config (relaying all props except those used for features to scheduler)

		Object.keys(props).forEach(function (propName) {
			if (!propName.match(_this2.featureRe) && undefined !== props[propName]) {
				if (propName === 'features') {
					console.warn('Passing "features" object as prop is not supported. Set features one-by-one with "Feature" suffix, for example "timeRangesFeature".');
				} else {
					config[propName] = props[propName];
				}
			}
		}); // console.log(config);
		// Create the actual scheduler, used as engine for the wrapper

		var engine = this.schedulerInstance = props.schedulerClass ? new props.schedulerClass(config) : new Scheduler(config); // Release any contained React components when a row is removed

		engine.rowManager.on({
			removeRow: function removeRow(_ref3) {
				var row = _ref3.row;
				return row.cells.forEach(function (cell) {
					return _this2.releaseReactCell(cell);
				});
			}
		}); // Make stores easier to access

		this.resourceStore = engine.resourceStore;
		this.eventStore = engine.eventStore; // Map all features from schedulerInstance to scheduler to simplify calls

		Object.keys(engine.features).forEach(function (key) {
			var featureName = key + 'Feature';

			if (!_this2[featureName]) {
				_this2[featureName] = engine.features[key];
			}
		}); // Shortcut to set syncDataOnLoad on the stores

		if (props.syncDataOnLoad) {
			engine.resourceStore.syncDataOnLoad = true;
			engine.eventStore.syncDataOnLoad = true;
		}

		if (config.events) {
			this.lastEvents = config.events.slice();
		}

		if (config.resources) {
			this.lastResources = config.resources.slice();
		}
	} // React component removed, destroy engine

}, {
	key: "componentWillUnmount",
	value: function componentWillUnmount() {
		this.schedulerInstance.destroy();
	} // Component about to be updated, from changing a prop using state. React to it depending on what changed and
	// prevent react from re-rendering our component.

}, {
	key: "shouldComponentUpdate",
	value: function shouldComponentUpdate(nextProps, nextState) {
		var _this3 = this;

		var props = this.props,
			engine = this.schedulerInstance,
			excludeProps = ['adapter', 'children', 'columns', 'events', 'eventsVersion', 'features', // #445 React: Scheduler crashes when features object passed as prop
				'listeners', // #9114 prevents the crash when listeners are changed at runtime
				'ref', 'resources', 'resourcesVersion', 'timeRanges'].concat(_toConsumableArray(this.features)); // Reflect configuration changes. Since most scheduler configs are reactive the scheduler will update automatically

		Object.keys(props).forEach(function (propName) {
			// Only apply if prop has changed
			if (!excludeProps.includes(propName) && !ObjectHelper.isEqual(props[propName], nextProps[propName])) {
				engine[propName] = nextProps[propName];
			}
		});

		if ( // resourceVersion used to flag that data has changed
			nextProps.resourcesVersion !== props.resourcesVersion || // if not use do deep equality check against previous dataset
			// TODO: Might be costly for large datasets
			!('resourcesVersion' in nextProps) && !('resourcesVersion' in props) && !ObjectHelper.isDeeplyEqual(this.lastResources, nextProps.resources)) {
			engine.resources = nextProps.resources;
			this.lastResources = nextProps.resources && nextProps.resources.slice();
		}

		if ( // eventVersion used to flag that data has changed
			nextProps.eventsVersion !== props.eventsVersion || // if not use do deep equality check against previous dataset
			// TODO: Might be costly for large datasets
			!('eventsVersion' in nextProps) && !('eventsVersion' in props) && !ObjectHelper.isDeeplyEqual(this.lastEvents, nextProps.events)) {
			engine.events = nextProps.events;
			this.lastEvents = nextProps.events && nextProps.events.slice();
		} // Reflect feature config changes


		this.features.forEach(function (featureName) {
			var currentProp = props[featureName],
				nextProp = nextProps[featureName];

			if (featureName in props && !ObjectHelper.isDeeplyEqual(currentProp, nextProp)) {
				engine.features[featureName.replace(_this3.featureRe, '')].setConfig(nextProp);
			}
		}); // Reflect JSX cell changes

		return nextState.generation !== this.state.generation;
	}
}, {
	key: "render",
	value: function render() {
		var _this4 = this;

		return /*#__PURE__*/React.createElement("div", {
			className: 'b-react-scheduler-container',
			ref: function ref(el) {
				return _this4.el = el;
			}
		}, this.state.portals);
	} // eo function render

}, {
	key: "schedulerEngine",

	/**
	 * @deprecated in favor of schedulerInstance
	 */
	get: function get() {
		console.warn('schedulerEngine is deprecated. Use schedulerInstance instead.');
		return this.schedulerInstance;
	} // defaults for scheduler. Feel free to adjust it

}]);

return BryntumScheduler;
}(Component); // eo class BryntumScheduler


BryntumScheduler.defaultProps = {
	viewPreset: 'hourAndDay',
	barMargin: 2
};
export default BryntumScheduler; // eof

I'm importing the resources like this(bryntum is liked to the build folder):

import { Scheduler, EventStore, StringHelper } from 'bryntum-scheduler/scheduler.umd';
import BryntumScheduler from '../../components/Scheduler/BryntumReactWrapper';
import 'bryntum-scheduler/scheduler.stockholm.css';

Can't figure out why it doesn't work in our build for IE11. I have tried to put the entire scheduler-3.1.8 in the root folder of our react project and exluded the folder from transpiling in webpack(just like you would with the node_modules folder).

I don't know if it's related, but when I hover over certain elements outside the scheduler in our app(like our navigation), sometimes the scheduler logs an error: "Object doesn't support property or method 'closest', scheduler.umd.js (63350,9)". It's wierd since we have the polyfill in our app and the scheduler.umd.js seems to contain the polyfill as well...


Post by era »

I tried logging the event inside a custom event renderer

eventRenderer={this.eventRenderer}

But the event doesnt even get logged here in IE11


Post by era »

Solved it. Added some additional polyfills.

Still not sure what wrapper I should use. Maybe if I used the correct one I wouldn't need additional polyfills? Weird thing is the scheduler keeps throwing error: "Object doesn't support property or method 'closest', scheduler.umd.js (63350,9)" when hovering on certain elements outside the scheduler.


Post by pmiklashevich »

Hello,

Please submit a runnable testcase that shows the error. For that please download the latest sources from the customer zone (it will contain the newest wrapper). Then get one of our example up and running. Apply minimal changes to it to reproduce the error. Then zip it up and attach here.

Best,
Pavel

Pavlo Miklashevych
Sr. Frontend Developer


Post Reply