diff --git a/HISTORY.md b/HISTORY.md index 3d601afd..56385cf2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -15,8 +15,14 @@ http://visjs.org ### Timeline +- Implemented support for group templates (#996). Thanks @hansmaulwurf23. +- Fixed #1076: Fixed possible overlap of minor labels text on the TimeAxis. - Fixed #1001: First element of group style being cut. - Fixed #1071: HTML contents of a group not cleared when the contents is updated. +- Fixed #1033: Moved item data not updated in DataSet when using an asynchronous + `onMove` handler. +- Fixed #239: Do not zoom/move the window when the mouse is on the left panel + with group labels. ## 2015-07-03, version 4.4.0 diff --git a/docs/timeline/index.html b/docs/timeline/index.html index 0359e32c..a6665ec3 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -585,6 +585,13 @@ function (option, path) { + + groupTemplate + function + none + A template function used to generate the contents of the groups. The function is called by the Timeline with a groups data as argument, and must return HTML code as result. When the option groupTemplate is specified, the groups do not need to have a field content. See section Templates for a detailed explanation. + + height number or String @@ -1608,4 +1615,4 @@ var options = { - \ No newline at end of file + diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index 545039c3..060dffce 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -396,6 +396,16 @@ Core.prototype.getCustomTime = function(id) { return customTimes[0].getCustomTime(); }; +/** + * Retrieve meta information from an event. + * Should be overridden by classes extending Core + * @param {Event} event + * @return {Object} An object with related information. + */ +Core.prototype.getEventProperties = function (event) { + return { event: event }; +}; + /** * Add custom vertical bar * @param {Date | String | Number} [time] A Date, unix timestamp, or diff --git a/lib/timeline/Range.js b/lib/timeline/Range.js index 2fc5ef73..11c33093 100644 --- a/lib/timeline/Range.js +++ b/lib/timeline/Range.js @@ -360,9 +360,13 @@ Range.conversion = function (start, end, width, totalHidden) { Range.prototype._onDragStart = function(event) { this.deltaDifference = 0; this.previousDelta = 0; + // only allow dragging when configured as movable if (!this.options.moveable) return; + // only start dragging when the mouse is inside the current range + if (!this._isInsideRange(event)) return; + // refuse to drag when we where pinching to prevent the timeline make a jump // when releasing the fingers in opposite order from the touch screen if (!this.props.touch.allowDragging) return; @@ -382,6 +386,8 @@ Range.prototype._onDragStart = function(event) { * @private */ Range.prototype._onDrag = function (event) { + if (!this.props.touch.dragging) return; + // only allow dragging when configured as movable if (!this.options.moveable) return; @@ -433,6 +439,8 @@ Range.prototype._onDrag = function (event) { * @private */ Range.prototype._onDragEnd = function (event) { + if (!this.props.touch.dragging) return; + // only allow dragging when configured as movable if (!this.options.moveable) return; @@ -464,6 +472,9 @@ Range.prototype._onMouseWheel = function(event) { // only allow zooming when configured as zoomable and moveable if (!(this.options.zoomable && this.options.moveable)) return; + // only zoom when the mouse is inside the current range + if (!this._isInsideRange(event)) return; + // retrieve delta var delta = 0; if (event.wheelDelta) { /* IE/Opera. */ @@ -561,6 +572,23 @@ Range.prototype._onPinch = function (event) { this.endToFront = true; // revert to default }; +/** + * Test whether the mouse from a mouse event is inside the visible window, + * between the current start and end date + * @param {Object} event + * @return {boolean} Returns true when inside the visible window + * @private + */ +Range.prototype._isInsideRange = function(event) { + // calculate the time where the mouse is, check whether inside + // and no scroll action should happen. + var clientX = event.center ? event.center.x : event.clientX; + var x = clientX - util.getAbsoluteLeft(this.body.dom.centerContainer); + var time = this.body.util.toTime(x); + + return time >= this.start && time <= this.end; +}; + /** * Helper function to calculate the center date for zooming * @param {{x: Number, y: Number}} pointer diff --git a/lib/timeline/component/Group.js b/lib/timeline/component/Group.js index e286ff9f..42ed8621 100644 --- a/lib/timeline/component/Group.js +++ b/lib/timeline/component/Group.js @@ -81,8 +81,16 @@ Group.prototype._create = function() { */ Group.prototype.setData = function(data) { // update contents - var content = data && data.content; + var content; + if (this.itemSet.options && this.itemSet.options.groupTemplate) { + content = this.itemSet.options.groupTemplate(data); + } + else { + content = data && data.content; + } + if (content instanceof Element) { + this.dom.inner.appendChild(content); while (this.dom.inner.firstChild) { this.dom.inner.removeChild(this.dom.inner.firstChild); } diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 8509c021..b6ac97ab 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -46,7 +46,7 @@ function ItemSet(body, options) { remove: false }, - snap: TimeStep.snap, + snap: TimeStep.snap, onAdd: function (item, callback) { callback(item); @@ -280,7 +280,7 @@ ItemSet.prototype._create = function(){ ItemSet.prototype.setOptions = function(options) { if (options) { // copy all options that we know - var fields = ['type', 'align', 'order', 'stack', 'selectable', 'multiselect', 'groupOrder', 'dataAttributes', 'template','hide', 'snap']; + var fields = ['type', 'align', 'order', 'stack', 'selectable', 'multiselect', 'groupOrder', 'dataAttributes', 'template','groupTemplate','hide', 'snap']; util.selectiveExtend(fields, this.options, options); if ('orientation' in options) { @@ -1345,13 +1345,11 @@ ItemSet.prototype._onDragEnd = function (event) { if (this.touchParams.itemProps) { event.stopPropagation(); - // prepare a change set for the changed items - var changes = []; var me = this; var dataset = this.itemsData.getDataSet(); - var itemProps = this.touchParams.itemProps ; this.touchParams.itemProps = null; + itemProps.forEach(function (props) { var id = props.item.id; var exists = me.itemsData.get(id, me.itemOptions) != null; @@ -1376,7 +1374,7 @@ ItemSet.prototype._onDragEnd = function (event) { if (itemData) { // apply changes itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined) - changes.push(itemData); + dataset.update(itemData); } else { // restore original values @@ -1388,11 +1386,6 @@ ItemSet.prototype._onDragEnd = function (event) { }); } }); - - // apply the changes to the data (if there are changes) - if (changes.length) { - dataset.update(changes); - } } }; diff --git a/lib/timeline/component/TimeAxis.js b/lib/timeline/component/TimeAxis.js index 750eb54a..4c35ad4e 100644 --- a/lib/timeline/component/TimeAxis.js +++ b/lib/timeline/component/TimeAxis.js @@ -223,6 +223,7 @@ TimeAxis.prototype._repaintLabels = function () { var xPrev = 0; var width = 0; var prevLine; + var prevText; var xFirstMajorLabel = undefined; var max = 0; var className; @@ -241,9 +242,12 @@ TimeAxis.prototype._repaintLabels = function () { if (prevLine) { prevLine.style.width = width + 'px'; } + if (prevText) { + prevText.style.width = width + 'px'; + } if (this.options.showMinorLabels) { - this._repaintMinorText(x, step.getLabelMinor(), orientation, className); + prevText = this._repaintMinorText(x, step.getLabelMinor(), orientation, className); } if (isMajor && this.options.showMajorLabels) { @@ -290,6 +294,7 @@ TimeAxis.prototype._repaintLabels = function () { * @param {String} text * @param {String} orientation "top" or "bottom" (default) * @param {String} className + * @return {Element} Returns the HTML element of the created label * @private */ TimeAxis.prototype._repaintMinorText = function (x, text, orientation, className) { @@ -311,6 +316,8 @@ TimeAxis.prototype._repaintMinorText = function (x, text, orientation, className label.style.left = x + 'px'; label.className = 'vis-text vis-minor ' + className; //label.title = title; // TODO: this is a heavy operation + + return label; }; /** @@ -319,6 +326,7 @@ TimeAxis.prototype._repaintMinorText = function (x, text, orientation, className * @param {String} text * @param {String} orientation "top" or "bottom" (default) * @param {String} className + * @return {Element} Returns the HTML element of the created label * @private */ TimeAxis.prototype._repaintMajorText = function (x, text, orientation, className) { @@ -340,6 +348,8 @@ TimeAxis.prototype._repaintMajorText = function (x, text, orientation, className label.style.top = (orientation == 'top') ? '0' : (this.props.minorLabelHeight + 'px'); label.style.left = x + 'px'; + + return label; }; /** diff --git a/lib/timeline/component/css/timeaxis.css b/lib/timeline/component/css/timeaxis.css index efe8e90f..a9c72aa2 100644 --- a/lib/timeline/component/css/timeaxis.css +++ b/lib/timeline/component/css/timeaxis.css @@ -21,6 +21,9 @@ position: absolute; color: #4d4d4d; padding: 3px; + overflow: hidden; + box-sizing: border-box; + white-space: nowrap; } diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index 5666e76b..81585612 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -104,6 +104,7 @@ let allOptions = { snap: {'function': 'function', 'null': 'null'}, start: {date, number, string, moment}, template: {'function': 'function'}, + groupTemplate: {'function': 'function'}, timeAxis: { scale: {string,'undefined': 'undefined'}, step: {number,'undefined': 'undefined'},