From b68030a5461f7a76620aa86eea63803b14963205 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sun, 19 Jul 2015 15:29:21 +0200 Subject: [PATCH 01/11] Group Dragging in Timeline --- lib/timeline/component/ItemSet.js | 55 +++++++++++++++++++++++++++++-- lib/timeline/optionsTimeline.js | 2 ++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index b6ac97ab..52d5ded3 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -34,7 +34,14 @@ function ItemSet(body, options) { }, align: 'auto', // alignment of box items stack: true, - groupOrder: null, + groupsDraggable: false, + groupOrder: function(a,b) { + if (a.order != undefined && b.order != undefined) { + return (a.order - b.order); + } else { + return 0; + } + }, selectable: true, multiselect: false, @@ -127,6 +134,7 @@ function ItemSet(body, options) { this.stackDirty = true; // if true, all items will be restacked on next redraw this.touchParams = {}; // stores properties while dragging + this.groupTouchParams = {}; // create the HTML DOM this._create(); @@ -209,6 +217,12 @@ ItemSet.prototype._create = function(){ // add item on doubletap this.hammer.on('doubletap', this._onAddItem.bind(this)); + this.groupHammer = new Hammer(this.body.dom.leftContainer); + this.groupHammer.on('panstart', this._onGroupDragStart.bind(this)); + this.groupHammer.on('panmove', this._onGroupDrag.bind(this)); + this.groupHammer.on('panend', this._onGroupDragEnd.bind(this)); + this.groupHammer.get('pan').set({threshold:5, direction:30}); + // attach to the DOM this.show(); }; @@ -280,7 +294,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','groupTemplate','hide', 'snap']; + var fields = ['type', 'align', 'order', 'stack', 'selectable', 'multiselect', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap', 'groupsDraggable']; util.selectiveExtend(fields, this.options, options); if ('orientation' in options) { @@ -1389,6 +1403,43 @@ ItemSet.prototype._onDragEnd = function (event) { } }; +ItemSet.prototype._onGroupDragStart = function (event) { + if (this.options.groupsDraggable) { + this.groupTouchParams.group = this.groupFromTarget(event); + if (this.groupTouchParams.group) { + event.stopPropagation(); + } + } +} + +ItemSet.prototype._onGroupDrag = function (event) { + if (this.options.groupsDraggable && this.groupTouchParams.group) { + event.stopPropagation(); + + // drag from one group to another + var group = this.groupFromTarget(event); + if (group && group != this.groupTouchParams.group) { + var groupsData = this.groupsData; + var draggedGroup = groupsData.get(this.groupTouchParams.group.groupId); + var targetGroup = groupsData.get(group.groupId); + + if (draggedGroup && targetGroup) { + var targetOrder = targetGroup.order; + targetGroup.order = draggedGroup.order; + groupsData.update(targetGroup); + draggedGroup.order = targetOrder; + groupsData.update(draggedGroup); + } + } + } +} + +ItemSet.prototype._onGroupDragEnd = function (event) { + if (this.options.groupsDraggable) { + this.body.emitter.emit('groupDragged'); + } +} + /** * Handle selecting/deselecting an item when tapping it * @param {Event} event diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index da4c4803..12644c2a 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -63,6 +63,7 @@ let allOptions = { __type__: {object} }, groupOrder: {string, 'function': 'function'}, + groupsDraggable: {boolean}, height: {string, number}, hiddenDates: {object, array}, locale:{string}, @@ -157,6 +158,7 @@ let configureOptions = { }, //groupOrder: {string, 'function': 'function'}, + groupsDraggable: false, height: '', //hiddenDates: {object, array}, locale: '', From 22822da00839463fddd6f886801b1f6f8c6fbffe Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Thu, 23 Jul 2015 08:50:25 +0200 Subject: [PATCH 02/11] group dragging works (on groups with same height) using a swap function --- lib/timeline/component/ItemSet.js | 170 +++++++++++++++++++++++++++--- lib/timeline/optionsTimeline.js | 8 +- 2 files changed, 161 insertions(+), 17 deletions(-) diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 52d5ded3..46af2ec9 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -35,13 +35,21 @@ function ItemSet(body, options) { align: 'auto', // alignment of box items stack: true, groupsDraggable: false, - groupOrder: function(a,b) { - if (a.order != undefined && b.order != undefined) { - return (a.order - b.order); - } else { - return 0; - } + moveBefore: function(draggedGroup, targetGroup, groups) { + var targetOrder = targetGroup.order; + targetGroup.order = draggedGroup.order; + draggedGroup.order = targetOrder; + groups.update(draggedGroup); + groups.update(targetGroup); }, +// groupOrder: function(a,b) { +// if (a.order != undefined && b.order != undefined) { +// return (a.order - b.order); +// } else { +// return 0; +// } +// }, + groupOrder: 'order', selectable: true, multiselect: false, @@ -53,6 +61,12 @@ function ItemSet(body, options) { remove: false }, + groupEditable: { + order: false, + add: false, + remove: false + }, + snap: TimeStep.snap, onAdd: function (item, callback) { @@ -294,7 +308,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', 'groupTemplate', 'hide', 'snap', 'groupsDraggable']; + var fields = ['type', 'align', 'order', 'stack', 'selectable', 'multiselect', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap', 'moveBefore']; util.selectiveExtend(fields, this.options, options); if ('orientation' in options) { @@ -337,6 +351,17 @@ ItemSet.prototype.setOptions = function(options) { util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable); } } + + if ('groupEditable' in options) { + if (typeof options.groupEditable === 'boolean') { + this.options.groupEditable.order = options.groupEditable; + this.options.groupEditable.add = options.groupEditable; + this.options.groupEditable.remove = options.groupEditable; + } + else if (typeof options.groupEditable === 'object') { + util.selectiveExtend(['order', 'add', 'remove'], this.options.groupEditable, options.groupEditable); + } + } // callback functions var addCallback = (function (name) { @@ -1404,38 +1429,151 @@ ItemSet.prototype._onDragEnd = function (event) { }; ItemSet.prototype._onGroupDragStart = function (event) { - if (this.options.groupsDraggable) { + if (this.options.groupEditable.order) { this.groupTouchParams.group = this.groupFromTarget(event); + if (this.groupTouchParams.group) { event.stopPropagation(); + + this.groupTouchParams.originalOrder = this.groupsData.getIds({ + order: this.options.groupOrder + }); } } } ItemSet.prototype._onGroupDrag = function (event) { - if (this.options.groupsDraggable && this.groupTouchParams.group) { + if (this.options.groupEditable.order && this.groupTouchParams.group) { event.stopPropagation(); // drag from one group to another var group = this.groupFromTarget(event); if (group && group != this.groupTouchParams.group) { var groupsData = this.groupsData; - var draggedGroup = groupsData.get(this.groupTouchParams.group.groupId); var targetGroup = groupsData.get(group.groupId); + var draggedGroup = groupsData.get(this.groupTouchParams.group.groupId); if (draggedGroup && targetGroup) { - var targetOrder = targetGroup.order; - targetGroup.order = draggedGroup.order; - groupsData.update(targetGroup); - draggedGroup.order = targetOrder; - groupsData.update(draggedGroup); + this.options.moveBefore(draggedGroup, targetGroup, this.groupsData); } + + var newOrder = this.groupsData.getIds({ + order: this.options.groupOrder + }); + + if (!util.equalArray(newOrder, this.groupTouchParams.originalOrder)) { + var groupsData = this.groupsData; + var origOrder = this.groupTouchParams.originalOrder; + var draggedId = this.groupTouchParams.group.groupId; + var numGroups = Math.min(origOrder.length, newOrder.length); + var curPos = 0; + var newOffset = 0; + var orgOffset = 0; + while (curPos < numGroups) { + // as long as the groups are where they should be step down along the groups order + while ((curPos+newOffset) < numGroups + && (curPos+orgOffset) < numGroups + && newOrder[curPos+newOffset] == origOrder[curPos+orgOffset]) { + curPos++; + } + + // all ok + if (curPos+newOffset >= numGroups) { + break; + } + + // not all ok -> since we start from the first group, did we reach the dragged group? + if (newOrder[curPos+newOffset] == draggedId) { + newOffset = 1; + continue; + } else if (origOrder[curPos+orgOffset] == draggedId) { + orgOffset = 1; + continue; + } else { + var slippedPosition = newOrder.indexOf(origOrder[curPos+orgOffset]) + var switchGroup = groupsData.get(newOrder[curPos+newOffset]); + var shouldBeGroup = groupsData.get(origOrder[curPos+orgOffset]); + this.options.moveBefore(switchGroup, shouldBeGroup, groupsData); + + var switchGroupId = newOrder[curPos+newOffset]; + newOrder[curPos+newOffset] = origOrder[curPos+orgOffset]; + newOrder[slippedPosition] = switchGroupId; + + curPos++; + } + } + } + } } } ItemSet.prototype._onGroupDragEnd = function (event) { - if (this.options.groupsDraggable) { + if (this.options.groupEditable.order && this.groupTouchParams.group) { + event.stopPropagation(); + + var newOrder = this.groupsData.getIds({ + order: this.options.groupOrder + }); + + if (!util.equalArray(newOrder, this.groupTouchParams.originalOrder)) { + var groupsData = this.groupsData; + var origOrder = this.groupTouchParams.originalOrder; + var draggedId = this.groupTouchParams.group.groupId; + var numGroups = Math.min(origOrder.length, newOrder.length); + var curPos = 0; + var newOffset = 0; + var orgOffset = 0; + while (curPos < numGroups) { + // as long as the groups are where they should be step down along the groups order + while ((curPos+newOffset) < numGroups + && (curPos+orgOffset) < numGroups + && newOrder[curPos+newOffset] == origOrder[curPos+orgOffset]) { + curPos++; + } + + // all ok + if (curPos+newOffset >= numGroups) { + break; + } + + // not all ok -> since we start from the first group, did we reach the dragged group? + if (newOrder[curPos+newOffset] == draggedId) { + newOffset = 1; + continue; + } else if (origOrder[curPos+orgOffset] == draggedId) { + orgOffset = 1; + continue; + } else { + var slippedPosition = newOrder.indexOf(origOrder[curPos+orgOffset]) + var switchGroup = groupsData.get(newOrder[curPos+newOffset]); + var shouldBeGroup = groupsData.get(origOrder[curPos+orgOffset]); + this.options.moveBefore(switchGroup, shouldBeGroup, groupsData); + + var switchGroupId = newOrder[curPos+newOffset]; + newOrder[curPos+newOffset] = origOrder[curPos+orgOffset]; + newOrder[slippedPosition] = switchGroupId; + + curPos++; + } + } + } + + +// // drag from one group to another +// var group = this.groupFromTarget(event); +// if (group && group != this.groupTouchParams.group) { +// var draggedGroup = groupsData.get(this.groupTouchParams.group.groupId); +// var targetGroup = groupsData.get(group.groupId); +// +// if (draggedGroup && targetGroup) { +// var targetOrder = targetGroup.order; +// targetGroup.order = draggedGroup.order; +// groupsData.update(targetGroup); +// draggedGroup.order = targetOrder; +// groupsData.update(draggedGroup); +// } +// } this.body.emitter.emit('groupDragged'); } } diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index 12644c2a..5d588d3b 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -63,7 +63,13 @@ let allOptions = { __type__: {object} }, groupOrder: {string, 'function': 'function'}, - groupsDraggable: {boolean}, + groupEditable: { + add: {boolean, 'undefined': 'undefined'}, + remove: {boolean, 'undefined': 'undefined'}, + order: {boolean, 'undefined': 'undefined'}, + __type__: {boolean, object} + }, + moveBefore: {'function': 'function'}, height: {string, number}, hiddenDates: {object, array}, locale:{string}, From 448411ee3904939cc3184b91cbbf8c4cf90a1693 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Fri, 24 Jul 2015 15:52:34 +0200 Subject: [PATCH 03/11] Dragging of Groups working now using a swap function --- lib/timeline/component/Group.js | 6 +- lib/timeline/component/ItemSet.js | 113 ++++++++---------------- lib/timeline/component/css/labelset.css | 4 + lib/timeline/optionsTimeline.js | 2 +- 4 files changed, 47 insertions(+), 78 deletions(-) diff --git a/lib/timeline/component/Group.js b/lib/timeline/component/Group.js index ae60270a..51761959 100644 --- a/lib/timeline/component/Group.js +++ b/lib/timeline/component/Group.js @@ -47,7 +47,11 @@ function Group (groupId, data, itemSet) { */ Group.prototype._create = function() { var label = document.createElement('div'); - label.className = 'vis-label'; + if (this.itemSet.options.groupEditable.order) { + label.className = 'vis-label draggable'; + } else { + label.className = 'vis-label'; + } this.dom.label = label; var inner = document.createElement('div'); diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 46af2ec9..6e628473 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -35,20 +35,13 @@ function ItemSet(body, options) { align: 'auto', // alignment of box items stack: true, groupsDraggable: false, - moveBefore: function(draggedGroup, targetGroup, groups) { + groupOrderSwap: function(draggedGroup, targetGroup, groups) { var targetOrder = targetGroup.order; targetGroup.order = draggedGroup.order; draggedGroup.order = targetOrder; groups.update(draggedGroup); groups.update(targetGroup); }, -// groupOrder: function(a,b) { -// if (a.order != undefined && b.order != undefined) { -// return (a.order - b.order); -// } else { -// return 0; -// } -// }, groupOrder: 'order', selectable: true, @@ -308,7 +301,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', 'groupTemplate', 'hide', 'snap', 'moveBefore']; + var fields = ['type', 'align', 'order', 'stack', 'selectable', 'multiselect', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap', 'groupOrderSwap']; util.selectiveExtend(fields, this.options, options); if ('orientation' in options) { @@ -1448,19 +1441,43 @@ ItemSet.prototype._onGroupDrag = function (event) { // drag from one group to another var group = this.groupFromTarget(event); + + // try to avoid toggling when groups differ in height + if (group && group.height != this.groupTouchParams.group.height) { + var movingUp = (group.top < this.groupTouchParams.group.top); + var clientY = event.center ? event.center.y : event.clientY; + var targetGroupTop = util.getAbsoluteTop(group.dom.foreground); + var draggedGroupHeight = this.groupTouchParams.group.height; + if (movingUp) { + // skip swapping the groups when the dragged group is not below clientY afterwards + if (targetGroupTop + draggedGroupHeight < clientY) { + return; + } + } else { + var targetGroupHeight = group.height; + // skip swapping the groups when the dragged group is not below clientY afterwards + if (targetGroupTop + targetGroupHeight - draggedGroupHeight > clientY) { + return; + } + } + } + if (group && group != this.groupTouchParams.group) { var groupsData = this.groupsData; var targetGroup = groupsData.get(group.groupId); var draggedGroup = groupsData.get(this.groupTouchParams.group.groupId); + // switch groups if (draggedGroup && targetGroup) { - this.options.moveBefore(draggedGroup, targetGroup, this.groupsData); + this.options.groupOrderSwap(draggedGroup, targetGroup, this.groupsData); } + // fetch current order of groups var newOrder = this.groupsData.getIds({ order: this.options.groupOrder }); + // in case of changes since _onGroupDragStart if (!util.equalArray(newOrder, this.groupTouchParams.originalOrder)) { var groupsData = this.groupsData; var origOrder = this.groupTouchParams.originalOrder; @@ -1482,18 +1499,24 @@ ItemSet.prototype._onGroupDrag = function (event) { break; } - // not all ok -> since we start from the first group, did we reach the dragged group? + // not all ok + // if dragged group was move upwards everything below should have an offset if (newOrder[curPos+newOffset] == draggedId) { newOffset = 1; continue; - } else if (origOrder[curPos+orgOffset] == draggedId) { + } + // if dragged group was move downwards everything above should have an offset + else if (origOrder[curPos+orgOffset] == draggedId) { orgOffset = 1; continue; - } else { + } + // found a group (apart from dragged group) that has the wrong position -> switch with the + // group at the position where other one should be, fix index arrays and continue + else { var slippedPosition = newOrder.indexOf(origOrder[curPos+orgOffset]) var switchGroup = groupsData.get(newOrder[curPos+newOffset]); var shouldBeGroup = groupsData.get(origOrder[curPos+orgOffset]); - this.options.moveBefore(switchGroup, shouldBeGroup, groupsData); + this.options.groupOrderSwap(switchGroup, shouldBeGroup, groupsData); var switchGroupId = newOrder[curPos+newOffset]; newOrder[curPos+newOffset] = origOrder[curPos+orgOffset]; @@ -1512,68 +1535,6 @@ ItemSet.prototype._onGroupDragEnd = function (event) { if (this.options.groupEditable.order && this.groupTouchParams.group) { event.stopPropagation(); - var newOrder = this.groupsData.getIds({ - order: this.options.groupOrder - }); - - if (!util.equalArray(newOrder, this.groupTouchParams.originalOrder)) { - var groupsData = this.groupsData; - var origOrder = this.groupTouchParams.originalOrder; - var draggedId = this.groupTouchParams.group.groupId; - var numGroups = Math.min(origOrder.length, newOrder.length); - var curPos = 0; - var newOffset = 0; - var orgOffset = 0; - while (curPos < numGroups) { - // as long as the groups are where they should be step down along the groups order - while ((curPos+newOffset) < numGroups - && (curPos+orgOffset) < numGroups - && newOrder[curPos+newOffset] == origOrder[curPos+orgOffset]) { - curPos++; - } - - // all ok - if (curPos+newOffset >= numGroups) { - break; - } - - // not all ok -> since we start from the first group, did we reach the dragged group? - if (newOrder[curPos+newOffset] == draggedId) { - newOffset = 1; - continue; - } else if (origOrder[curPos+orgOffset] == draggedId) { - orgOffset = 1; - continue; - } else { - var slippedPosition = newOrder.indexOf(origOrder[curPos+orgOffset]) - var switchGroup = groupsData.get(newOrder[curPos+newOffset]); - var shouldBeGroup = groupsData.get(origOrder[curPos+orgOffset]); - this.options.moveBefore(switchGroup, shouldBeGroup, groupsData); - - var switchGroupId = newOrder[curPos+newOffset]; - newOrder[curPos+newOffset] = origOrder[curPos+orgOffset]; - newOrder[slippedPosition] = switchGroupId; - - curPos++; - } - } - } - - -// // drag from one group to another -// var group = this.groupFromTarget(event); -// if (group && group != this.groupTouchParams.group) { -// var draggedGroup = groupsData.get(this.groupTouchParams.group.groupId); -// var targetGroup = groupsData.get(group.groupId); -// -// if (draggedGroup && targetGroup) { -// var targetOrder = targetGroup.order; -// targetGroup.order = draggedGroup.order; -// groupsData.update(targetGroup); -// draggedGroup.order = targetOrder; -// groupsData.update(draggedGroup); -// } -// } this.body.emitter.emit('groupDragged'); } } diff --git a/lib/timeline/component/css/labelset.css b/lib/timeline/component/css/labelset.css index c2690e97..3aeabd10 100644 --- a/lib/timeline/component/css/labelset.css +++ b/lib/timeline/component/css/labelset.css @@ -21,6 +21,10 @@ border-bottom: 1px solid #bfbfbf; } +.vis-labelset .vis-label.draggable { + cursor: pointer; +} + .vis-labelset .vis-label:last-child { border-bottom: none; } diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index 5d588d3b..ace9e714 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -69,7 +69,7 @@ let allOptions = { order: {boolean, 'undefined': 'undefined'}, __type__: {boolean, object} }, - moveBefore: {'function': 'function'}, + groupOrderSwap: {'function': 'function'}, height: {string, number}, hiddenDates: {object, array}, locale:{string}, From c2e7123037c92b5414a9ca642e9fc6692c9e6ffb Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Fri, 24 Jul 2015 15:57:37 +0200 Subject: [PATCH 04/11] removed unused option groupsDraggable --- lib/timeline/component/ItemSet.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 6e628473..642dbbd7 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -34,7 +34,6 @@ function ItemSet(body, options) { }, align: 'auto', // alignment of box items stack: true, - groupsDraggable: false, groupOrderSwap: function(draggedGroup, targetGroup, groups) { var targetOrder = targetGroup.order; targetGroup.order = draggedGroup.order; From 5ea64119357740d5396c1d3b3f587a75c2fbb622 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Mon, 27 Jul 2015 20:07:13 +0200 Subject: [PATCH 05/11] added event handlers for onAddGroup, onMoveGroup, onRemoveGroup --- lib/timeline/component/ItemSet.js | 65 ++++++++++++++++++++++++++++++- lib/timeline/optionsTimeline.js | 3 ++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 642dbbd7..6ce7f6f8 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -76,6 +76,15 @@ function ItemSet(body, options) { onMoving: function (item, callback) { callback(item); }, + onAddGroup: function (item, callback) { + callback(item); + }, + onMoveGroup: function (item, callback) { + callback(item); + }, + onRemoveGroup: function (item, callback) { + callback(item); + }, margin: { item: { @@ -365,7 +374,7 @@ ItemSet.prototype.setOptions = function(options) { this.options[name] = fn; } }).bind(this); - ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving'].forEach(addCallback); + ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving', 'onAddGroup', 'onMoveGroup', 'onRemoveGroup'].forEach(addCallback); // force the itemSet to refresh: options like orientation and margins may be changed this.markDirty(); @@ -1534,7 +1543,59 @@ ItemSet.prototype._onGroupDragEnd = function (event) { if (this.options.groupEditable.order && this.groupTouchParams.group) { event.stopPropagation(); - this.body.emitter.emit('groupDragged'); + // update existing group + var me = this; + var id = me.groupTouchParams.group.groupId; + var dataset = me.groupsData.getDataSet(); + var groupData = util.extend({}, dataset.get(id)); // clone the data + me.options.onMoveGroup(groupData, function (groupData) { + if (groupData) { + // apply changes + groupData[dataset._fieldId] = id; // ensure the group contains its id (can be undefined) + dataset.update(groupData); + } + else { + + // fetch current order of groups + var newOrder = dataset.getIds({ + order: me.options.groupOrder + }); + + // restore original order + if (!util.equalArray(newOrder, me.groupTouchParams.originalOrder)) { + var origOrder = me.groupTouchParams.originalOrder; + var numGroups = Math.min(origOrder.length, newOrder.length); + var curPos = 0; + while (curPos < numGroups) { + // as long as the groups are where they should be step down along the groups order + while (curPos < numGroups && newOrder[curPos] == origOrder[curPos]) { + curPos++; + } + + // all ok + if (curPos >= numGroups) { + break; + } + + // found a group that has the wrong position -> switch with the + // group at the position where other one should be, fix index arrays and continue + var slippedPosition = newOrder.indexOf(origOrder[curPos]) + var switchGroup = dataset.get(newOrder[curPos]); + var shouldBeGroup = dataset.get(origOrder[curPos]); + me.options.groupOrderSwap(switchGroup, shouldBeGroup, dataset); + + var switchGroupId = newOrder[curPos]; + newOrder[curPos] = origOrder[curPos]; + newOrder[slippedPosition] = switchGroupId; + + curPos++; + } + } + + } + + me.body.emitter.emit('groupDragged'); + }); } } diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index ace9e714..518e917a 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -97,6 +97,9 @@ let allOptions = { onMove: {'function': 'function'}, onMoving: {'function': 'function'}, onRemove: {'function': 'function'}, + onAddGroup: {'function': 'function'}, + onMoveGroup: {'function': 'function'}, + onRemoveGroup: {'function': 'function'}, order: {'function': 'function'}, orientation: { axis: {string,'undefined': 'undefined'}, From 584f2664145c205494a91a0b4d791411123984f2 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Thu, 30 Jul 2015 08:56:02 +0200 Subject: [PATCH 06/11] removed updating the groups in groupOrderSwap, started docs --- docs/timeline/index.html | 66 ++++++++++++++++++++++++++++++- lib/timeline/component/ItemSet.js | 16 +++++--- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/docs/timeline/index.html b/docs/timeline/index.html index 79487509..5eaf4a90 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -585,12 +585,50 @@ function (option, path) { + + groupEditable + boolean or Object + false + If true, the groups in the timeline can be manipulated. See also the callbacks onAddGroup, onMoveGroup, and onRemoveGroup. When groupEditable is an object, one can enable or disable individual manipulation actions. + The editing of groups follows the same principles as for items, see section Editing Items for a detailed explanation. + + + + groupEditable.add + boolean + false + If true, new groups can be created in the Timeline. For now adding new groups is done by the user. + + + groupEditable.remove + boolean + false + If true, groups can be deleted. For now removing new groups is done by the user. + + + groupEditable.order + boolean + false + If true, groups can be dragged to change their order. Only applicable when the Timeline has groups. + + groupOrder String or Function - none + 'order' Order the groups by a field name or custom sort function. - By default, groups are not ordered. + By default, groups are ordered by an attribute order (if set). + + + + + groupOrderSwap + Function + none + Swaps the positions of two groups. If groups have a custom order (via groupOrder) and groups are configured to be reorderable (via groupEditable.order), the user has to provide a function that swaps the positions of two given groups. + If this option is not set, the default implementation assumes that groups hold an attribute order which values are changed. The signature of the groupOrderWap function is: +
function groupOrderSwap(fromGroup: Object, toGroup: Object, groups: DataSet)
+ The first to arguments hold the groups of which the positions are to be swapped and the third argument holds the DataSet with all groups. @@ -728,6 +766,14 @@ function (option, path) { Callback function triggered when an item is about to be added: when the user double taps an empty space in the Timeline. See section Editing Items for more information. Only applicable when both options selectable and editable.add are set true. + + + onAddGroup + function + none + Callback function triggered when a group is about to be added. The signature and semantics are the same as for onAdd. + + onUpdate @@ -744,6 +790,14 @@ function (option, path) { Callback function triggered when an item has been moved: after the user has dragged the item to an other position. See section Editing Items for more information. Only applicable when both options selectable and editable.updateTime or editable.updateGroup are set true. + + + onMoveGroup + function + none + Callback function triggered when a group has been moved: after the user has dragged the group to an other position. The signature and semantics are the same as for onMove. + + onMoving @@ -760,6 +814,14 @@ function (option, path) { Callback function triggered when an item is about to be removed: when the user tapped the delete button on the top right of a selected item. See section Editing Items for more information. Only applicable when both options selectable and editable.remove are set true. + + + onRemoveGroup + function + none + Callback function triggered when a group is about to be removed. The signature and semantics are the same as for onRemove. + + order diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 6ce7f6f8..72b54f83 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -34,12 +34,10 @@ function ItemSet(body, options) { }, align: 'auto', // alignment of box items stack: true, - groupOrderSwap: function(draggedGroup, targetGroup, groups) { - var targetOrder = targetGroup.order; - targetGroup.order = draggedGroup.order; - draggedGroup.order = targetOrder; - groups.update(draggedGroup); - groups.update(targetGroup); + groupOrderSwap: function(fromGroup, toGroup, groups) { + var targetOrder = toGroup.order; + toGroup.order = fromGroup.order; + fromGroup.order = targetOrder; }, groupOrder: 'order', @@ -1478,6 +1476,8 @@ ItemSet.prototype._onGroupDrag = function (event) { // switch groups if (draggedGroup && targetGroup) { this.options.groupOrderSwap(draggedGroup, targetGroup, this.groupsData); + this.groupsData.update(draggedGroup); + this.groupsData.update(targetGroup); } // fetch current order of groups @@ -1525,6 +1525,8 @@ ItemSet.prototype._onGroupDrag = function (event) { var switchGroup = groupsData.get(newOrder[curPos+newOffset]); var shouldBeGroup = groupsData.get(origOrder[curPos+orgOffset]); this.options.groupOrderSwap(switchGroup, shouldBeGroup, groupsData); + groupsData.update(switchGroup); + groupsData.update(shouldBeGroup); var switchGroupId = newOrder[curPos+newOffset]; newOrder[curPos+newOffset] = origOrder[curPos+orgOffset]; @@ -1583,6 +1585,8 @@ ItemSet.prototype._onGroupDragEnd = function (event) { var switchGroup = dataset.get(newOrder[curPos]); var shouldBeGroup = dataset.get(origOrder[curPos]); me.options.groupOrderSwap(switchGroup, shouldBeGroup, dataset); + groupsData.update(switchGroup); + groupsData.update(shouldBeGroup); var switchGroupId = newOrder[curPos]; newOrder[curPos] = origOrder[curPos]; From 58e2157be6c7dd23941d2abbf3feeb0f06f07b01 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Thu, 30 Jul 2015 14:07:22 +0200 Subject: [PATCH 07/11] added groupEditable example --- examples/timeline/groups/groupsEditable.html | 295 +++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 examples/timeline/groups/groupsEditable.html diff --git a/examples/timeline/groups/groupsEditable.html b/examples/timeline/groups/groupsEditable.html new file mode 100644 index 00000000..f3a4ca01 --- /dev/null +++ b/examples/timeline/groups/groupsEditable.html @@ -0,0 +1,295 @@ + + + + Timeline | Editable Groups + + + + + + + + +

+ This example demonstrates editable groups (for now only reordering). +

+
+ + + + \ No newline at end of file From 6804cb5595ce1f79b5919b2e46da99bfc608b7c1 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Fri, 31 Jul 2015 12:02:17 +0200 Subject: [PATCH 08/11] changed docs for groupDragging --- docs/timeline/index.html | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/timeline/index.html b/docs/timeline/index.html index 5eaf4a90..30d741bf 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -603,13 +603,13 @@ function (option, path) { groupEditable.remove boolean false - If true, groups can be deleted. For now removing new groups is done by the user. + If true, groups can be deleted. For now removing groups is done by the user. groupEditable.order boolean false - If true, groups can be dragged to change their order. Only applicable when the Timeline has groups. + If true, groups can be dragged to change their order. Only applicable when the Timeline has groups. For this option to work properly the groupOrder and groupOrderSwap options have to be set as well. @@ -1318,6 +1318,15 @@ timeline.off('select', onSelect); + + groupDragged + + Passes the id of the dragged group. + + Fired after the dragging of a group is finished. + + + rangechange From 21d09a264298c49089da9710f74f7002535c9cc4 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Fri, 31 Jul 2015 12:02:43 +0200 Subject: [PATCH 09/11] finished new example for groupEditable --- examples/timeline/groups/groupsEditable.html | 478 ++++++++++--------- 1 file changed, 249 insertions(+), 229 deletions(-) diff --git a/examples/timeline/groups/groupsEditable.html b/examples/timeline/groups/groupsEditable.html index f3a4ca01..8fce1410 100644 --- a/examples/timeline/groups/groupsEditable.html +++ b/examples/timeline/groups/groupsEditable.html @@ -14,6 +14,12 @@ width: 100%; height: 300px; } + + .vis-item.openwheel { background-color: #B0E2FF; } + .vis-item.rally { background-color: #EAEAEA; } + .vis-item.motorcycle { background-color: #FA8072; } + .vis-item.touringcar { background-color: #B4EEB4; } + .vis-item.endurance { background-color: #FFFFCC; } @@ -29,240 +35,254 @@