diff --git a/HISTORY.md b/HISTORY.md index cc097f1d..141227e7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -8,6 +8,11 @@ http://visjs.org - Added German (de) locale. Thanks @Tooa. +### Timeline + +- Fixed #1215: inconsistent types of properties `start` and `end` in callback + functions `onMove`, `onMoving`, `onAdd`. + ## 2015-08-28, version 4.8.0 diff --git a/dist/vis.js b/dist/vis.js index 76dfffa0..9195cc57 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 4.8.1-SNAPSHOT - * @date 2015-08-28 + * @date 2015-09-04 * * @license * Copyright (C) 2011-2015 Almende B.V, http://almende.com @@ -16734,7 +16734,7 @@ return /******/ (function(modules) { // webpackBootstrap item: dragLeftItem, initialX: event.center.x, dragLeft: true, - data: util.extend({}, item.data) // clone the items data + data: this._cloneItemData(item.data) }; this.touchParams.itemProps = [props]; @@ -16743,7 +16743,7 @@ return /******/ (function(modules) { // webpackBootstrap item: dragRightItem, initialX: event.center.x, dragRight: true, - data: util.extend({}, item.data) // clone the items data + data: this._cloneItemData(item.data) }; this.touchParams.itemProps = [props]; @@ -16752,18 +16752,16 @@ return /******/ (function(modules) { // webpackBootstrap var baseGroupIndex = this._getGroupIndex(item.data.group); - this.touchParams.itemProps = this.getSelection().map(function (id) { + this.touchParams.itemProps = this.getSelection().map((function (id) { var item = me.items[id]; var groupIndex = me._getGroupIndex(item.data.group); - var props = { + return { item: item, initialX: event.center.x, groupOffset: baseGroupIndex - groupIndex, - data: util.extend({}, item.data) // clone the items data + data: this._cloneItemData(item.data) }; - - return props; - }); + }).bind(this)); } event.stopPropagation(); @@ -16805,14 +16803,14 @@ return /******/ (function(modules) { // webpackBootstrap var newItem = new RangeItem(itemData, this.conversion, this.options); newItem.id = id; // TODO: not so nice setting id afterwards - newItem.data = itemData; + newItem.data = this._cloneItemData(itemData); this._addItem(newItem); var props = { item: newItem, dragRight: true, initialX: event.center.x, - data: util.extend({}, itemData) + data: newItem.data }; this.touchParams.itemProps = [props]; @@ -16851,14 +16849,12 @@ return /******/ (function(modules) { // webpackBootstrap } // move - this.touchParams.itemProps.forEach(function (props) { - var newProps = {}; + this.touchParams.itemProps.forEach((function (props) { var current = me.body.util.toTime(event.center.x - xOffset); var initial = me.body.util.toTime(props.initialX - xOffset); - var offset = current - initial; - - var itemData = util.extend({}, props.item.data); // clone the data + var offset = current - initial; // ms + var itemData = this._cloneItemData(props.item.data); // clone the data if (props.item.editable === false) { return; } @@ -16871,6 +16867,7 @@ return /******/ (function(modules) { // webpackBootstrap if (itemData.start != undefined) { var initialStart = util.convert(props.data.start, 'Date'); var start = new Date(initialStart.valueOf() + offset); + // TODO: pass a Moment instead of a Date to snap(). (Breaking change) itemData.start = snap ? snap(start, scale, step) : start; } } else if (props.dragRight) { @@ -16878,6 +16875,7 @@ return /******/ (function(modules) { // webpackBootstrap if (itemData.end != undefined) { var initialEnd = util.convert(props.data.end, 'Date'); var end = new Date(initialEnd.valueOf() + offset); + // TODO: pass a Moment instead of a Date to snap(). (Breaking change) itemData.end = snap ? snap(end, scale, step) : end; } } else { @@ -16890,9 +16888,11 @@ return /******/ (function(modules) { // webpackBootstrap var initialEnd = util.convert(props.data.end, 'Date'); var duration = initialEnd.valueOf() - initialStart.valueOf(); + // TODO: pass a Moment instead of a Date to snap(). (Breaking change) itemData.start = snap ? snap(start, scale, step) : start; itemData.end = new Date(itemData.start.valueOf() + duration); } else { + // TODO: pass a Moment instead of a Date to snap(). (Breaking change) itemData.start = snap ? snap(start, scale, step) : start; } } @@ -16914,12 +16914,13 @@ return /******/ (function(modules) { // webpackBootstrap } // confirm moving the item + itemData = this._cloneItemData(itemData); // convert start and end to the correct type me.options.onMoving(itemData, function (itemData) { if (itemData) { props.item.setData(itemData); } }); - }); + }).bind(this)); this.stackDirty = true; // force re-stacking of all items next redraw this.body.emitter.emit('change'); @@ -16959,7 +16960,7 @@ return /******/ (function(modules) { // webpackBootstrap var itemProps = this.touchParams.itemProps; this.touchParams.itemProps = null; - itemProps.forEach(function (props) { + itemProps.forEach((function (props) { var id = props.item.id; var exists = me.itemsData.get(id, me.itemOptions) != null; @@ -16977,7 +16978,7 @@ return /******/ (function(modules) { // webpackBootstrap }); } else { // update existing item - var itemData = util.extend({}, props.item.data); // clone the data + var itemData = this._cloneItemData(props.item.data); // convert start and end to the correct type me.options.onMove(itemData, function (itemData) { if (itemData) { // apply changes @@ -16992,7 +16993,7 @@ return /******/ (function(modules) { // webpackBootstrap } }); } - }); + }).bind(this)); } }; @@ -17233,7 +17234,7 @@ return /******/ (function(modules) { // webpackBootstrap var scale = this.body.util.getScale(); var step = this.body.util.getStep(); - var newItem = { + var newItemData = { start: snap ? snap(start, scale, step) : start, content: 'new item' }; @@ -17241,18 +17242,19 @@ return /******/ (function(modules) { // webpackBootstrap // when default type is a range, add a default end date to the new item if (this.options.type === 'range') { var end = this.body.util.toTime(x + this.props.width / 5); - newItem.end = snap ? snap(end, scale, step) : end; + newItemData.end = snap ? snap(end, scale, step) : end; } - newItem[this.itemsData._fieldId] = util.randomUUID(); + newItemData[this.itemsData._fieldId] = util.randomUUID(); var group = this.groupFromTarget(event); if (group) { - newItem.group = group.groupId; + newItemData.group = group.groupId; } // execute async handler to customize (or cancel) adding an item - this.options.onAdd(newItem, function (item) { + newItemData = this._cloneItemData(newItemData); // convert start and end to the correct type + this.options.onAdd(newItemData, function (item) { if (item) { me.itemsData.getDataSet().add(item); // TODO: need to trigger a redraw? @@ -17419,6 +17421,29 @@ return /******/ (function(modules) { // webpackBootstrap return null; }; + /** + * Clone the data of an item, and "normalize" it: convert the start and end date + * to the type (Date, Moment, ...) configured in the DataSet. If not configured, + * start and end are converted to Date. + * @param {Object} itemData, typically `item.data` + * @return {Object} The cloned object + * @private + */ + ItemSet.prototype._cloneItemData = function (itemData) { + var clone = util.extend({}, itemData); + + // convert start and end date to the type (Date, Moment, ...) configured in the DataSet + var type = this.itemsData.getDataSet()._options.type; + if (clone.start != undefined) { + clone.start = util.convert(clone.start, type && type.start || 'Date'); + } + if (clone.end != undefined) { + clone.end = util.convert(clone.end, type && type.end || 'Date'); + } + + return clone; + }; + module.exports = ItemSet; /***/ }, @@ -43054,6 +43079,24 @@ return /******/ (function(modules) { // webpackBootstrap exports['en_EN'] = exports['en']; exports['en_US'] = exports['en']; + // German + exports['de'] = { + edit: 'Editieren', + del: 'Lösche Auswahl', + back: 'Zurück', + addNode: 'Knoten hinzufügen', + addEdge: 'Kante hinzufügen', + editNode: 'Knoten editieren', + editEdge: 'Kante editieren', + addDescription: 'Klicke auf eine freie Stelle, um einen neuen Knoten zu plazieren.', + edgeDescription: 'Klicke auf einen Knoten und ziehe die Kante zu einem anderen Knoten, um diese zu verbinden.', + editEdgeDescription: 'Klicke auf die Verbindungspunkte und ziehe diese auf einen Knoten, um sie zu verbinden.', + createEdgeError: 'Es ist nicht möglich, Kanten mit Clustern zu verbinden.', + deleteClusterError: 'Cluster können nicht gelöscht werden.', + editClusterError: 'Cluster können nicht editiert werden.' + }; + exports['de_DE'] = exports['de']; + // Spanish exports['es'] = { edit: 'Editar', diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index be30cf67..c4c11511 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -1160,7 +1160,7 @@ ItemSet.prototype._getGroupIndex = function(groupId) { if (groupId == this.groupIds[i]) return i; } -} +}; /** * Start dragging the selected events @@ -1174,8 +1174,8 @@ ItemSet.prototype._onDragStart = function (event) { if (item && item.selected) { - if (!this.options.editable.updateTime && - !this.options.editable.updateGroup && + if (!this.options.editable.updateTime && + !this.options.editable.updateGroup && !item.editable) { return; } @@ -1193,7 +1193,7 @@ ItemSet.prototype._onDragStart = function (event) { item: dragLeftItem, initialX: event.center.x, dragLeft: true, - data: util.extend({}, item.data) // clone the items data + data: this._cloneItemData(item.data) }; this.touchParams.itemProps = [props]; @@ -1203,7 +1203,7 @@ ItemSet.prototype._onDragStart = function (event) { item: dragRightItem, initialX: event.center.x, dragRight: true, - data: util.extend({}, item.data) // clone the items data + data: this._cloneItemData(item.data) }; this.touchParams.itemProps = [props]; @@ -1216,15 +1216,13 @@ ItemSet.prototype._onDragStart = function (event) { this.touchParams.itemProps = this.getSelection().map(function (id) { var item = me.items[id]; var groupIndex = me._getGroupIndex(item.data.group); - var props = { + return { item: item, initialX: event.center.x, groupOffset: baseGroupIndex-groupIndex, - data: util.extend({}, item.data) // clone the items data + data: this._cloneItemData(item.data) }; - - return props; - }); + }.bind(this)); } event.stopPropagation(); @@ -1267,14 +1265,14 @@ ItemSet.prototype._onDragStartAddItem = function (event) { var newItem = new RangeItem(itemData, this.conversion, this.options); newItem.id = id; // TODO: not so nice setting id afterwards - newItem.data = itemData; + newItem.data = this._cloneItemData(itemData); this._addItem(newItem); var props = { item: newItem, dragRight: true, initialX: event.center.x, - data: util.extend({}, itemData) + data: newItem.data }; this.touchParams.itemProps = [props]; @@ -1301,32 +1299,30 @@ ItemSet.prototype._onDrag = function (event) { var updateGroupAllowed = me.options.editable.updateGroup; var newGroupBase = null; if (updateGroupAllowed && selectedItem) { - if (selectedItem.data.group != undefined) { - // drag from one group to another - var group = me.groupFromTarget(event); - if (group) { - //we know the offset for all items, so the new group for all items - //will be relative to this one. - newGroupBase = this._getGroupIndex(group.groupId); - } + if (selectedItem.data.group != undefined) { + // drag from one group to another + var group = me.groupFromTarget(event); + if (group) { + //we know the offset for all items, so the new group for all items + //will be relative to this one. + newGroupBase = this._getGroupIndex(group.groupId); } + } } // move this.touchParams.itemProps.forEach(function (props) { - var newProps = {}; var current = me.body.util.toTime(event.center.x - xOffset); var initial = me.body.util.toTime(props.initialX - xOffset); - var offset = current - initial; - - var itemData = util.extend({}, props.item.data); // clone the data + var offset = current - initial; // ms + var itemData = this._cloneItemData(props.item.data); // clone the data if (props.item.editable === false) { return; } - var updateTimeAllowed = me.options.editable.updateTime || - props.item.editable === true; + var updateTimeAllowed = me.options.editable.updateTime || + props.item.editable === true; if (updateTimeAllowed) { if (props.dragLeft) { @@ -1334,6 +1330,7 @@ ItemSet.prototype._onDrag = function (event) { if (itemData.start != undefined) { var initialStart = util.convert(props.data.start, 'Date'); var start = new Date(initialStart.valueOf() + offset); + // TODO: pass a Moment instead of a Date to snap(). (Breaking change) itemData.start = snap ? snap(start, scale, step) : start; } } @@ -1342,6 +1339,7 @@ ItemSet.prototype._onDrag = function (event) { if (itemData.end != undefined) { var initialEnd = util.convert(props.data.end, 'Date'); var end = new Date(initialEnd.valueOf() + offset); + // TODO: pass a Moment instead of a Date to snap(). (Breaking change) itemData.end = snap ? snap(end, scale, step) : end; } } @@ -1355,18 +1353,20 @@ ItemSet.prototype._onDrag = function (event) { var initialEnd = util.convert(props.data.end, 'Date'); var duration = initialEnd.valueOf() - initialStart.valueOf(); + // TODO: pass a Moment instead of a Date to snap(). (Breaking change) itemData.start = snap ? snap(start, scale, step) : start; itemData.end = new Date(itemData.start.valueOf() + duration); } else { + // TODO: pass a Moment instead of a Date to snap(). (Breaking change) itemData.start = snap ? snap(start, scale, step) : start; } } } } - var updateGroupAllowed = me.options.editable.updateGroup || - props.item.editable === true; + var updateGroupAllowed = me.options.editable.updateGroup || + props.item.editable === true; if (updateGroupAllowed && (!props.dragLeft && !props.dragRight) && newGroupBase!=null) { if (itemData.group != undefined) { @@ -1381,12 +1381,13 @@ ItemSet.prototype._onDrag = function (event) { } // confirm moving the item + itemData = this._cloneItemData(itemData); // convert start and end to the correct type me.options.onMoving(itemData, function (itemData) { if (itemData) { props.item.setData(itemData); } }); - }); + }.bind(this)); this.stackDirty = true; // force re-stacking of all items next redraw this.body.emitter.emit('change'); @@ -1445,7 +1446,7 @@ ItemSet.prototype._onDragEnd = function (event) { } else { // update existing item - var itemData = util.extend({}, props.item.data); // clone the data + var itemData = this._cloneItemData(props.item.data); // convert start and end to the correct type me.options.onMove(itemData, function (itemData) { if (itemData) { // apply changes @@ -1461,7 +1462,7 @@ ItemSet.prototype._onDragEnd = function (event) { } }); } - }); + }.bind(this)); } }; @@ -1708,7 +1709,7 @@ ItemSet.prototype._onAddItem = function (event) { var scale = this.body.util.getScale(); var step = this.body.util.getStep(); - var newItem = { + var newItemData = { start: snap ? snap(start, scale, step) : start, content: 'new item' }; @@ -1716,18 +1717,19 @@ ItemSet.prototype._onAddItem = function (event) { // when default type is a range, add a default end date to the new item if (this.options.type === 'range') { var end = this.body.util.toTime(x + this.props.width / 5); - newItem.end = snap ? snap(end, scale, step) : end; + newItemData.end = snap ? snap(end, scale, step) : end; } - newItem[this.itemsData._fieldId] = util.randomUUID(); + newItemData[this.itemsData._fieldId] = util.randomUUID(); var group = this.groupFromTarget(event); if (group) { - newItem.group = group.groupId; + newItemData.group = group.groupId; } // execute async handler to customize (or cancel) adding an item - this.options.onAdd(newItem, function (item) { + newItemData = this._cloneItemData(newItemData); // convert start and end to the correct type + this.options.onAdd(newItemData, function (item) { if (item) { me.itemsData.getDataSet().add(item); // TODO: need to trigger a redraw? @@ -1901,4 +1903,27 @@ ItemSet.itemSetFromTarget = function(event) { return null; }; +/** + * Clone the data of an item, and "normalize" it: convert the start and end date + * to the type (Date, Moment, ...) configured in the DataSet. If not configured, + * start and end are converted to Date. + * @param {Object} itemData, typically `item.data` + * @return {Object} The cloned object + * @private + */ +ItemSet.prototype._cloneItemData = function (itemData) { + var clone = util.extend({}, itemData); + + // convert start and end date to the type (Date, Moment, ...) configured in the DataSet + var type = this.itemsData.getDataSet()._options.type; + if (clone.start != undefined) { + clone.start = util.convert(clone.start, type && type.start || 'Date'); + } + if (clone.end != undefined) { + clone.end = util.convert(clone.end , type && type.end || 'Date'); + } + + return clone; +}; + module.exports = ItemSet; diff --git a/test/timeline_groups.html b/test/timeline_groups.html index aac1c2ca..4df1b433 100644 --- a/test/timeline_groups.html +++ b/test/timeline_groups.html @@ -71,7 +71,9 @@ } // create a dataset with items - var items = new vis.DataSet(); + var items = new vis.DataSet({ + type: {start: 'Moment', end: 'Moment'} + }); for (var i = 0; i < itemCount; i++) { var start = now.clone().add(Math.random() * 200, 'hours'); var end = Math.random() > 0.5 ? start.clone().add(24, 'hours') : undefined; @@ -117,6 +119,7 @@ }, onMove: function (item, callback) { + console.log('onMove', item) if (confirm('Do you really want to move the item to\n' + 'start: ' + item.start + '\n' + 'end: ' + item.end + '?')) {