diff --git a/docs/timeline/index.html b/docs/timeline/index.html
index 14e71561..09acb325 100644
--- a/docs/timeline/index.html
+++ b/docs/timeline/index.html
@@ -946,6 +946,29 @@ function (option, path) {
Callback function triggered when a group is about to be removed. The signature and semantics are the same as for onRemove.
+
+
+
onTimeout
+
Object
+
Object
+
Specify timeline bailing options when a specified timeout is reached.
+
+
+
onTimeout.timeoutMs
+
number
+
none
+
Number of milliseconds until the callback function should be called.
+ The callback will not be called if the timeline gets drawn completely before the timeoutMs limit.
+
+
+
+
onTimeout.callback
+
function
+
none
+
+ A callback function called when timeoutMs milliseconds pass and the timeline has yet to be fully drawn initially.
+
+
onUpdate
diff --git a/examples/timeline/other/onTimeout.html b/examples/timeline/other/onTimeout.html
new file mode 100644
index 00000000..7f77822d
--- /dev/null
+++ b/examples/timeline/other/onTimeout.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+ Timeline | onTimeout example
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js
index d3652641..dee44004 100644
--- a/lib/timeline/Core.js
+++ b/lib/timeline/Core.js
@@ -969,7 +969,7 @@ Core.prototype._redraw = function() {
props.border.top - props.border.bottom, 0);
}
dom.center.style.top = offset + 'px';
-
+
// show shadows when vertical scrolling is available
var visibilityTop = props.scrollTop == 0 ? 'hidden' : '';
var visibilityBottom = props.scrollTop == props.scrollTopMin ? 'hidden' : '';
diff --git a/lib/timeline/Stack.js b/lib/timeline/Stack.js
index b25179b4..71e92277 100644
--- a/lib/timeline/Stack.js
+++ b/lib/timeline/Stack.js
@@ -35,8 +35,11 @@ exports.orderByEnd = function(items) {
* @param {boolean} [force=false]
* If true, all items will be repositioned. If false (default), only
* items having a top===null will be re-stacked
+ * @param {function} shouldBailItemsRedrawFunction
+ * bailing function
+ * @return {boolean} shouldBail
*/
-exports.stack = function(items, margin, force) {
+exports.stack = function(items, margin, force, shouldBailItemsRedrawFunction) {
if (force) {
// reset top position of all items
for (var i = 0; i < items.length; i++) {
@@ -50,6 +53,7 @@ exports.stack = function(items, margin, force) {
if (item.stack && item.top === null) {
// initialize top position
item.top = margin.axis;
+ var shouldBail = false;
do {
// TODO: optimize checking for overlap. when there is a gap without items,
@@ -57,6 +61,10 @@ exports.stack = function(items, margin, force) {
var collidingItem = null;
for (var j = 0, jj = items.length; j < jj; j++) {
var other = items[j];
+ shouldBail = shouldBailItemsRedrawFunction() || false;
+
+ if (shouldBail) { return true; }
+
if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item, other.options.rtl)) {
collidingItem = other;
break;
@@ -70,6 +78,7 @@ exports.stack = function(items, margin, force) {
} while (collidingItem);
}
}
+ return shouldBail;
};
/**
diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js
index ba83f36f..2c2f5b22 100644
--- a/lib/timeline/Timeline.js
+++ b/lib/timeline/Timeline.js
@@ -28,6 +28,9 @@ var Validator = require('../shared/Validator').default;
*/
function Timeline (container, items, groups, options) {
+ this.initTime = new Date();
+ this.itemsDone = false;
+
if (!(this instanceof Timeline)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
@@ -78,6 +81,7 @@ function Timeline (container, items, groups, options) {
this.options.rollingMode = options && options.rollingMode;
this.options.onInitialDrawComplete = options && options.onInitialDrawComplete;
+ this.options.onTimeout = options && options.onTimeout;
this.options.loadingScreenTemplate = options && options.loadingScreenTemplate;
// Prepare loading screen
@@ -202,6 +206,7 @@ function Timeline (container, items, groups, options) {
if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end) || me.options.rollingMode)) {
me.initialDrawDone = true;
+ me.itemSet.initialDrawDone = true;
me.dom.root.style.visibility = 'visible';
me.dom.loadingScreen.parentNode.removeChild(me.dom.loadingScreen);
if (me.options.onInitialDrawComplete) {
@@ -212,6 +217,10 @@ function Timeline (container, items, groups, options) {
}
});
+ this.on('destroyTimeline', () => {
+ me.destroy()
+ });
+
// apply options
if (options) {
this.setOptions(options);
@@ -285,6 +294,8 @@ Timeline.prototype.setOptions = function (options) {
* @param {vis.DataSet | Array | null} items
*/
Timeline.prototype.setItems = function(items) {
+ this.itemsDone = false;
+
// convert to type DataSet when needed
var newDataSet;
if (!items) {
diff --git a/lib/timeline/component/Group.js b/lib/timeline/component/Group.js
index 14e76d10..8b748735 100644
--- a/lib/timeline/component/Group.js
+++ b/lib/timeline/component/Group.js
@@ -13,6 +13,7 @@ function Group (groupId, data, itemSet) {
this.subgroupStack = {};
this.subgroupStackAll = false;
this.doInnerStack = false;
+ this.shouldBailStackItems = false;
this.subgroupIndex = 0;
this.subgroupOrderer = data && data.subgroupOrder;
this.itemSet = itemSet;
@@ -262,6 +263,35 @@ Group.prototype._calculateGroupSizeAndPosition = function() {
this.width = offsetWidth;
}
+Group.prototype._shouldBailItemsRedraw = function() {
+ var me = this;
+ var timeoutOptions = this.itemSet.options.onTimeout;
+ var bailOptions = {
+ relativeBailingTime: this.itemSet.itemsSettingTime,
+ bailTimeMs: timeoutOptions && timeoutOptions.timeoutMs,
+ userBailFunction: timeoutOptions && timeoutOptions.callback,
+ shouldBailStackItems: this.shouldBailStackItems
+ };
+ var bail = null;
+ if (!this.itemSet.initialDrawDone) {
+ if (bailOptions.shouldBailStackItems) { return true; }
+ if (Math.abs(new Date() - new Date(bailOptions.relativeBailingTime)) > bailOptions.bailTimeMs) {
+ if (bailOptions.userBailFunction && this.itemSet.userContinueNotBail == null) {
+ bailOptions.userBailFunction(function(didUserContinue) {
+ me.itemSet.userContinueNotBail = didUserContinue;
+ bail = !didUserContinue;
+ })
+ } else if (me.itemSet.userContinueNotBail == false) {
+ bail = true;
+ } else {
+ bail = false;
+ }
+ }
+ }
+
+ return bail;
+}
+
Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, range) {
var restack = forceRestack || this.stackDirty || this.isVisible && !lastIsVisible;
@@ -270,6 +300,7 @@ Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, ran
var visibleSubgroups = {};
var subgroup = null;
+
if (typeof this.itemSet.options.order === 'function') {
// a custom order function
// brute force restack of all items
@@ -321,7 +352,7 @@ Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, ran
var customOrderedItems = this.orderedItems.byStart.slice().sort(function (a, b) {
return me.itemSet.options.order(a.data, b.data);
});
- stack.stack(customOrderedItems, margin, true /* restack=true */);
+ this.shouldBailStackItems = stack.stack(customOrderedItems, margin, true, this._shouldBailItemsRedraw.bind(this));
}
this.visibleItems = this._updateItemsInRange(this.orderedItems, this.visibleItems, range);
@@ -339,7 +370,7 @@ Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, ran
}
else {
// TODO: ugly way to access options...
- stack.stack(this.visibleItems, margin, true /* restack=true */);
+ this.shouldBailStackItems = stack.stack(this.visibleItems, margin, true, this._shouldBailItemsRedraw.bind(this));
}
} else {
// no stacking
@@ -347,6 +378,9 @@ Group.prototype._redrawItems = function(forceRestack, lastIsVisible, margin, ran
}
}
+ if (this.shouldBailStackItems) {
+ this.itemSet.body.emitter.emit('destroyTimeline')
+ }
this.stackDirty = false;
}
}
diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js
index 00586826..bb0e4014 100644
--- a/lib/timeline/component/ItemSet.js
+++ b/lib/timeline/component/ItemSet.js
@@ -115,12 +115,13 @@ function ItemSet(body, options) {
// options is shared by this ItemSet and all its items
this.options = util.extend({}, this.defaultOptions);
this.options.rtl = options.rtl;
-
+ this.options.onTimeout = options.onTimeout;
+
// options for getting items from the DataSet with the correct type
this.itemOptions = {
type: {start: 'Date', end: 'Date'}
};
-
+
this.conversion = {
toScreen: body.util.toScreen,
toTime: body.util.toTime
@@ -128,11 +129,14 @@ function ItemSet(body, options) {
this.dom = {};
this.props = {};
this.hammer = null;
-
+
var me = this;
this.itemsData = null; // DataSet
this.groupsData = null; // DataSet
-
+ this.itemsSettingTime = null;
+ this.initialItemSetDrawn = false;
+ this.userContinueNotBail = null;
+
// listeners for the DataSet of the items
this.itemListeners = {
'add': function (event, params, senderId) { // eslint-disable-line no-unused-vars
@@ -367,7 +371,7 @@ ItemSet.prototype.setOptions = function(options) {
var fields = [
'type', 'rtl', 'align', 'order', 'stack', 'stackSubgroups', 'selectable', 'multiselect',
'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate',
- 'hide', 'snap', 'groupOrderSwap', 'showTooltips', 'tooltip', 'tooltipOnItemUpdateTime'
+ 'hide', 'snap', 'groupOrderSwap', 'showTooltips', 'tooltip', 'tooltipOnItemUpdateTime', 'onTimeout'
];
util.selectiveExtend(fields, this.options, options);
@@ -688,7 +692,6 @@ ItemSet.prototype.redraw = function() {
this.lastStackSubgroups = options.stackSubgroups;
this.props.lastWidth = this.props.width;
-
var firstGroup = this._firstGroup();
var firstMargin = {
item: margin.item,
@@ -834,6 +837,7 @@ ItemSet.prototype.getLabelSet = function() {
* @param {vis.DataSet | null} items
*/
ItemSet.prototype.setItems = function(items) {
+ this.itemsSettingTime = new Date();
var me = this,
ids,
oldItemsData = this.itemsData;
diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js
index e13e6ae6..ab7d147b 100644
--- a/lib/timeline/optionsTimeline.js
+++ b/lib/timeline/optionsTimeline.js
@@ -31,6 +31,11 @@ let allOptions = {
offset: {number,'undefined': 'undefined'},
__type__: {object}
},
+ onTimeout: {
+ timeoutMs: {number},
+ callback: {'function': 'function'},
+ __type__: {object}
+ },
verticalScroll: { 'boolean': bool, 'undefined': 'undefined'},
horizontalScroll: { 'boolean': bool, 'undefined': 'undefined'},
autoResize: { 'boolean': bool},