diff --git a/HISTORY.md b/HISTORY.md index 2f200915..34342e3a 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -19,6 +19,7 @@ http://visjs.org - Implemented animated range change for functions `fit`, `focus`, `setSelection`, and `setWindow`. - Implemented functions `setCurrentTime(date)` and `getCurrentTime()`. +- Implemented a new callback function `onMoving(item, callback)`. - Fixed the `change` event sometimes being fired twice on IE10. - Fixed canceling moving an item to another group did not move the item back to the original group. diff --git a/docs/timeline.html b/docs/timeline.html index 7f556d14..4230caaa 100644 --- a/docs/timeline.html +++ b/docs/timeline.html @@ -1030,6 +1030,7 @@ var options = {
  • onAdd(item, callback) Fired when a new item is about to be added. If not implemented, the item will be added with default text contents.
  • onUpdate(item, callback) Fired when an item is about to be updated. This function typically has to show a dialog where the user change the item. If not implemented, nothing happens.
  • onMove(item, callback) Fired when an item has been moved. If not implemented, the move action will be accepted.
  • +
  • onMoving(item, callback) Fired repeatedly while an item is being moved (dragged). Can be used to adjust the items start, end, and/or group to allowed regions.
  • onRemove(item, callback) Fired when an item is about to be deleted. If not implemented, the item will be always removed.
  • diff --git a/examples/timeline/08_edit_items.html b/examples/timeline/08_edit_items.html index b41726b7..9948098b 100644 --- a/examples/timeline/08_edit_items.html +++ b/examples/timeline/08_edit_items.html @@ -6,6 +6,7 @@ @@ -13,79 +14,93 @@ -
    -

    -
    +

    + This example shows how to use callback functions onAdd, onMove, onMoving, onUpdate, and onRemove. The onMoving function updates an item while dragging, and can be used to prevent the item from being drawn at disallowed or infeasible timeslots. In this example, the items cannot be moved outside of the month April 2013. The other callback functions are called after an add, move, update, or remove action has taken place, and can be used to cancel these actions. +

    - + \ No newline at end of file diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 93f84370..1ce2de5c 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -47,6 +47,7 @@ function ItemSet(body, options) { onMove: function (item, callback) { callback(item); }, + onMoving: null, onRemove: function (item, callback) { callback(item); }, @@ -303,7 +304,7 @@ ItemSet.prototype.setOptions = function(options) { this.options[name] = fn; } }).bind(this); - ['onAdd', 'onUpdate', 'onRemove', 'onMove'].forEach(addCallback); + ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving'].forEach(addCallback); // force the itemSet to refresh: options like orientation and margins may be changed this.markDirty(); @@ -1094,28 +1095,44 @@ ItemSet.prototype._onDragStart = function (event) { */ ItemSet.prototype._onDrag = function (event) { if (this.touchParams.itemProps) { - var range = this.body.range, - snap = this.body.util.snap || null, - deltaX = event.gesture.deltaX, - scale = (this.props.width / (range.end - range.start)), - offset = deltaX / scale; + var me = this; + var range = this.body.range; + var snap = this.body.util.snap || null; + var deltaX = event.gesture.deltaX; + var scale = (this.props.width / (range.end - range.start)); + var offset = deltaX / scale; // move this.touchParams.itemProps.forEach(function (props) { + var newProps = {}; + if ('start' in props) { var start = new Date(props.start + offset); - props.item.data.start = snap ? snap(start) : start; + newProps.start = snap ? snap(start) : start; } if ('end' in props) { var end = new Date(props.end + offset); - props.item.data.end = snap ? snap(end) : end; + newProps.end = snap ? snap(end) : end; } if ('group' in props) { // drag from one group to another var group = ItemSet.groupFromTarget(event); - _moveToGroup(props.item, group); + newProps.group = group && group.groupId; + } + + if (me.options.onMoving) { + var itemData = util.extend({}, props.item.data, newProps); + + me.options.onMoving(itemData, function (itemData) { + if (itemData) { + me._updateItemProps(props.item, itemData); + } + }); + } + else { + me._updateItemProps(props.item, newProps); } }); @@ -1128,13 +1145,28 @@ ItemSet.prototype._onDrag = function (event) { } }; +/** + * Update an items properties + * @param {Item} item + * @param {Object} props Can contain properties start, end, and group. + * @private + */ +ItemSet.prototype._updateItemProps = function(item, props) { + if ('start' in props) item.data.start = props.start; + if ('end' in props) item.data.end = props.end; + if ('group' in props && item.data.group != props.group) { + this._moveToGroup(item, props.group) + } +}; + /** * Move an item to another group * @param {Item} item - * @param {Group} group + * @param {String | Number} groupId * @private */ -function _moveToGroup (item, group) { +ItemSet.prototype._moveToGroup = function(item, groupId) { + var group = this.groups[groupId]; if (group && group.groupId != item.data.group) { var oldGroup = item.parent; oldGroup.remove(item); @@ -1144,7 +1176,7 @@ function _moveToGroup (item, group) { item.data.group = group.groupId; } -} +}; /** * End of dragging selected items @@ -1190,12 +1222,7 @@ ItemSet.prototype._onDragEnd = function (event) { } else { // restore original values - if ('start' in props) props.item.data.start = props.start; - if ('end' in props) props.item.data.end = props.end; - if ('group' in props && props.item.data.group != props.group) { - var group = me.groups[props.group]; - _moveToGroup(props.item, group); - } + me._updateItemProps(props.item, props); me.stackDirty = true; // force re-stacking of all items next redraw me.body.emitter.emit('change'); diff --git a/test/timeline_groups.html b/test/timeline_groups.html index edd98eb0..ad4e5edb 100644 --- a/test/timeline_groups.html +++ b/test/timeline_groups.html @@ -57,7 +57,7 @@ // create a dataset with items var items = new vis.DataSet(); for (var i = 0; i < itemCount; i++) { - var start = now.clone().add('hours', Math.random() * 200); + var start = now.clone().add(Math.random() * 200, 'hours'); var group = Math.floor(Math.random() * groupCount); items.add({ id: i, @@ -80,6 +80,57 @@ updateTime: true, updateGroup: true }, + + + onAdd: function (item, callback) { + item.content = prompt('Enter text content for new item:', item.content); + if (item.content != null) { + callback(item); // send back adjusted new item + } + else { + callback(null); // cancel item creation + } + }, + + onMove: function (item, callback) { + if (confirm('Do you really want to move the item to\n' + + 'start: ' + item.start + '\n' + + 'end: ' + item.end + '?')) { + callback(item); // send back item as confirmation (can be changed) + } + else { + callback(null); // cancel editing item + } + }, + + onMoving: function (item, callback) { + var min = moment().minutes(0).seconds(0).milliseconds(0).add(2, 'day').toDate(); + if (item.start < min) { + item.start = min; + } + + callback(item); // send back item as confirmation (can be changed) + }, + + onUpdate: function (item, callback) { + item.content = prompt('Edit items text:', item.content); + if (item.content != null) { + callback(item); // send back adjusted item + } + else { + callback(null); // cancel updating the item + } + }, + + onRemove: function (item, callback) { + if (confirm('Remove item ' + item.content + '?')) { + callback(item); // confirm deletion + } + else { + callback(null); // cancel deletion + } + }, + //stack: false, //height: 200, groupOrder: 'content'