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'},