From dd324da2a28ec03ddab12de2c64062876e775b43 Mon Sep 17 00:00:00 2001 From: yotamberk Date: Sat, 29 Oct 2016 20:59:32 +0300 Subject: [PATCH] Drag and drop support for the timeline (#2238) * Add initial experiment * Register drag and drop events * Add initial drag and drop support * Add drag and drop support for all types of objects * Fix example * Clean up code and add comments * Fix example * remove font awesome * Fix example style * Add meta tag to example and add selected when dropped --- examples/timeline/other/drag&drop.html | 131 +++++++++++++++++++++++++ lib/timeline/Core.js | 40 ++++++++ lib/timeline/component/ItemSet.js | 38 +++++-- 3 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 examples/timeline/other/drag&drop.html diff --git a/examples/timeline/other/drag&drop.html b/examples/timeline/other/drag&drop.html new file mode 100644 index 00000000..81bcb1f1 --- /dev/null +++ b/examples/timeline/other/drag&drop.html @@ -0,0 +1,131 @@ + + + + + Timeline | Drag & Drop + + + + + + + + + + + +

Timeline Drag & Drop Example

+ +

For this to work, you will have to define your own 'dragstart' eventListener on each item in your list of items (make sure that any new item added to the list is attached to this eventListener 'dragstart' handler). This 'dragstart' handler must set dataTransfer - notice you can set the item's information as you want this way.

+ +
+
+

Items:

+ +
+ + + + + diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index a0352cae..4589568b 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -231,6 +231,46 @@ Core.prototype._create = function (container) { this.dom.left.parentNode.addEventListener('scroll', onMouseScrollSide.bind(this)); this.dom.right.parentNode.addEventListener('scroll', onMouseScrollSide.bind(this)); + var itemAddedToTimeline = false; + + function handleDragOver(event) { + if (event.preventDefault) { + event.preventDefault(); // Necessary. Allows us to drop. + } + + // make sure your target is a vis element + if (!event.target.className.includes('vis')) return; + + // make sure only one item is added every time you're over the timeline + if (itemAddedToTimeline) return; + + event.dataTransfer.dropEffect = 'move'; + itemAddedToTimeline = true; + return false; + } + + function handleDrop(event) { + // return when dropping non-vis items + try { + var itemData = JSON.parse(event.dataTransfer.getData("text/plain")) + if (!itemData.content) return + } catch (err) { + return false; + } + + itemAddedToTimeline = false; + event.center = { + x: event.x, + y: event.y + } + me.itemSet._onAddItem(event); + + return false; + } + + this.dom.center.addEventListener('dragover', handleDragOver.bind(this), false); + this.dom.center.addEventListener('drop', handleDrop.bind(this), false); + this.customTimes = []; // store state information needed for touch events diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index f2020b09..0bd8cb86 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -95,9 +95,7 @@ function ItemSet(body, options) { // options is shared by this ItemSet and all its items this.options = util.extend({}, this.defaultOptions); - if (options) { - this.options.rtl = options.rtl; // required to determine from the initial creation if rtl - } + this.options.rtl = options.rtl; // options for getting items from the DataSet with the correct type this.itemOptions = { @@ -320,7 +318,7 @@ ItemSet.prototype.setOptions = function(options) { // copy all options that we know var fields = ['type', 'rtl', 'align', 'order', 'stack', 'selectable', 'multiselect', 'itemsAlwaysDraggable', 'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap', 'groupOrderSwap']; util.selectiveExtend(fields, this.options, options); - + if ('orientation' in options) { if (typeof options.orientation === 'string') { this.options.orientation.item = options.orientation === 'top' ? 'top' : 'bottom'; @@ -1825,13 +1823,30 @@ ItemSet.prototype._onAddItem = function (event) { content: 'new item' }; - // 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); - newItemData.end = snap ? snap(end, scale, step) : end; - } + if (event.type == 'drop') { + var itemData = JSON.parse(event.dataTransfer.getData("text/plain")) + newItemData.content = itemData.content; // content is required + newItemData.type = itemData.type || 'box'; + newItemData[this.itemsData._fieldId] = itemData.id || util.randomUUID(); + + if (itemData.type == 'range' || (itemData.end && itemData.start)) { + + if (!itemData.end) { + var end = this.body.util.toTime(x + this.props.width / 5); + newItemData.end = snap ? snap(end, scale, step) : end; + } else { + newItemData.end = new Date(newItemData.start._i).getTime() + new Date(itemData.end).getTime() - new Date(itemData.start).getTime(); + } + } + } else { + newItemData[this.itemsData._fieldId] = util.randomUUID(); - newItemData[this.itemsData._fieldId] = util.randomUUID(); + // 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); + newItemData.end = snap ? snap(end, scale, step) : end; + } + } var group = this.groupFromTarget(event); if (group) { @@ -1843,6 +1858,9 @@ ItemSet.prototype._onAddItem = function (event) { this.options.onAdd(newItemData, function (item) { if (item) { me.itemsData.getDataSet().add(item); + if (event.type == 'drop') { + me.setSelection([item.id]); + } // TODO: need to trigger a redraw? } });