From 08d486a53fb2faea65de5c9f4ff805aa9201d276 Mon Sep 17 00:00:00 2001 From: jos Date: Tue, 10 Jun 2014 11:58:48 +0200 Subject: [PATCH] Items are selectable and editable largely --- src/timeline/Timeline.js | 135 +------------------- src/timeline/component/CurrentTime.js | 2 +- src/timeline/component/CustomTime.js | 2 +- src/timeline/component/ItemSet.js | 138 ++++++++++++++++++++- src/timeline/component/css/currenttime.css | 1 + src/timeline/component/css/customtime.css | 1 + src/timeline/component/css/itemset.css | 7 +- src/timeline/component/css/panel.css | 2 +- 8 files changed, 148 insertions(+), 140 deletions(-) diff --git a/src/timeline/Timeline.js b/src/timeline/Timeline.js index d646e632..a54a8a76 100644 --- a/src/timeline/Timeline.js +++ b/src/timeline/Timeline.js @@ -346,7 +346,6 @@ Timeline.prototype._create = function () { this.dom.right = document.createElement('div'); this.dom.top = document.createElement('div'); this.dom.bottom = document.createElement('div'); - this.dom.foregroundVertical = document.createElement('div'); this.dom.background.className = 'vispanel background'; this.dom.backgroundVertical.className = 'vispanel background vertical'; @@ -359,7 +358,6 @@ Timeline.prototype._create = function () { this.dom.left.className = 'content'; this.dom.center.className = 'content'; this.dom.right.className = 'content'; - this.dom.foregroundVertical.className = 'vispanel foreground vertical'; this.dom.root.appendChild(this.dom.background); this.dom.root.appendChild(this.dom.backgroundVertical); @@ -369,7 +367,6 @@ Timeline.prototype._create = function () { this.dom.root.appendChild(this.dom.rightContainer); this.dom.root.appendChild(this.dom.top); this.dom.root.appendChild(this.dom.bottom); - this.dom.root.appendChild(this.dom.foregroundVertical); this.dom.centerContainer.appendChild(this.dom.center); this.dom.leftContainer.appendChild(this.dom.left); @@ -389,7 +386,8 @@ Timeline.prototype._create = function () { var me = this; var events = [ - 'touch', 'pinch', 'tap', 'doubletap', 'hold', + 'pinch', + //'tap', 'doubletap', 'hold', // TODO: catching the events here disables selecting an item 'dragstart', 'drag', 'dragend', 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox ]; @@ -807,7 +805,6 @@ Timeline.prototype.redraw = function() { dom.centerContainer.style.height = props.centerContainer.height + 'px'; dom.leftContainer.style.height = props.leftContainer.height + 'px'; dom.rightContainer.style.height = props.rightContainer.height + 'px'; - dom.foregroundVertical.style.height = props.background.height + 'px'; dom.background.style.width = props.background.width + 'px'; dom.backgroundVertical.style.width = props.centerContainer.width + 'px'; @@ -815,7 +812,6 @@ Timeline.prototype.redraw = function() { dom.centerContainer.style.width = props.center.width + 'px'; dom.top.style.width = props.top.width + 'px'; dom.bottom.style.width = props.bottom.width + 'px'; - dom.foregroundVertical.style.width = props.centerContainer.width + 'px'; // reposition the panels dom.background.style.left = '0'; @@ -834,8 +830,6 @@ Timeline.prototype.redraw = function() { dom.top.style.top = '0'; dom.bottom.style.left = props.left.width + 'px'; dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px'; - dom.foregroundVertical.style.left = props.left.width + 'px'; - dom.foregroundVertical.style.top = '0'; // redraw all components this.components.forEach(function (component) { @@ -852,131 +846,6 @@ Timeline.prototype.repaint = function () { throw new Error('Function repaint is deprecated. Use redraw instead.'); }; -/** - * Handle selecting/deselecting an item when tapping it - * @param {Event} event - * @private - */ -// TODO: move this function to ItemSet -Timeline.prototype._onSelectItem = function (event) { - if (!this.options.selectable) return; - - var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey; - var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey; - if (ctrlKey || shiftKey) { - this._onMultiSelectItem(event); - return; - } - - var oldSelection = this.getSelection(); - - var item = ItemSet.itemFromTarget(event); - var selection = item ? [item.id] : []; - this.setSelection(selection); - - var newSelection = this.getSelection(); - - // emit a select event, - // except when old selection is empty and new selection is still empty - if (newSelection.length > 0 || oldSelection.length > 0) { - this.emit('select', { - items: this.getSelection() - }); - } - - event.stopPropagation(); -}; - -/** - * Handle creation and updates of an item on double tap - * @param event - * @private - */ -// TODO: move this function to ItemSet -Timeline.prototype._onAddItem = function (event) { - if (!this.options.selectable) return; - if (!this.options.editable.add) return; - - var me = this, - item = ItemSet.itemFromTarget(event); - - if (item) { - // update item - - // execute async handler to update the item (or cancel it) - var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset - this.options.onUpdate(itemData, function (itemData) { - if (itemData) { - me.itemsData.update(itemData); - } - }); - } - else { - // add item - var xAbs = vis.util.getAbsoluteLeft(this.contentPanel.frame); - var x = event.gesture.center.pageX - xAbs; - var newItem = { - start: this.timeAxis.snap(this._toTime(x)), - content: 'new item' - }; - - // when default type is a range, add a default end date to the new item - if (this.options.type === 'range' || this.options.type == 'rangeoverflow') { - newItem.end = this.timeAxis.snap(this._toTime(x + this.rootPanel.width / 5)); - } - - var id = util.randomUUID(); - newItem[this.itemsData.fieldId] = id; - - var group = ItemSet.groupFromTarget(event); - if (group) { - newItem.group = group.groupId; - } - - // execute async handler to customize (or cancel) adding an item - this.options.onAdd(newItem, function (item) { - if (item) { - me.itemsData.add(newItem); - // TODO: need to trigger a redraw? - } - }); - } -}; - -/** - * Handle selecting/deselecting multiple items when holding an item - * @param {Event} event - * @private - */ -// TODO: move this function to ItemSet -Timeline.prototype._onMultiSelectItem = function (event) { - if (!this.options.selectable) return; - - var selection, - item = ItemSet.itemFromTarget(event); - - if (item) { - // multi select items - selection = this.getSelection(); // current selection - var index = selection.indexOf(item.id); - if (index == -1) { - // item is not yet selected -> select it - selection.push(item.id); - } - else { - // item is already selected -> deselect it - selection.splice(index, 1); - } - this.setSelection(selection); - - this.emit('select', { - items: this.getSelection() - }); - - event.stopPropagation(); - } -}; - /** * Convert a position on screen (pixels) to a datetime * @param {int} x Position on the screen in pixels diff --git a/src/timeline/component/CurrentTime.js b/src/timeline/component/CurrentTime.js index ac824cb7..8707292a 100644 --- a/src/timeline/component/CurrentTime.js +++ b/src/timeline/component/CurrentTime.js @@ -37,7 +37,7 @@ CurrentTime.prototype._create = function _create () { */ CurrentTime.prototype.redraw = function redraw() { if (this.options.showCurrentTime) { - var parent = this.timeline.dom.foregroundVertical; + var parent = this.timeline.dom.backgroundVertical; if (this.bar.parentNode != parent) { // attach to the dom if (this.bar.parentNode) { diff --git a/src/timeline/component/CustomTime.js b/src/timeline/component/CustomTime.js index b95079cd..3ac61096 100644 --- a/src/timeline/component/CustomTime.js +++ b/src/timeline/component/CustomTime.js @@ -55,7 +55,7 @@ CustomTime.prototype._create = function _create () { */ CustomTime.prototype.redraw = function () { if (this.options.showCustomTime) { - var parent = this.timeline.dom.foregroundVertical; + var parent = this.timeline.dom.backgroundVertical; if (this.bar.parentNode != parent) { // attach to the dom if (this.bar.parentNode) { diff --git a/src/timeline/component/ItemSet.js b/src/timeline/component/ItemSet.js index 4126ef15..9ba812ee 100644 --- a/src/timeline/component/ItemSet.js +++ b/src/timeline/component/ItemSet.js @@ -111,10 +111,21 @@ ItemSet.prototype._create = function _create(){ this.hammer = Hammer(frame, { prevent_default: true }); + + // drag items when selected this.hammer.on('dragstart', this._onDragStart.bind(this)); this.hammer.on('drag', this._onDrag.bind(this)); this.hammer.on('dragend', this._onDragEnd.bind(this)); + // single select (or unselect) when tapping an item + this.hammer.on('tap', this._onSelectItem.bind(this)); + + // multi select when holding mouse/touch, or on ctrl+click + this.hammer.on('hold', this._onMultiSelectItem.bind(this)); + + // add item on doubletap + this.hammer.on('doubletap', this._onAddItem.bind(this)); + // attach to the DOM this.show(); }; @@ -187,7 +198,7 @@ ItemSet.prototype.show = function show() { // show axis with dots if (!this.dom.axis.parentNode) { - this.timeline.dom.foregroundVertical.appendChild(this.dom.axis); + this.timeline.dom.backgroundVertical.appendChild(this.dom.axis); } // show labelset containing labels @@ -1003,6 +1014,131 @@ ItemSet.prototype._onDragEnd = function (event) { } }; +/** + * Handle selecting/deselecting an item when tapping it + * @param {Event} event + * @private + */ +ItemSet.prototype._onSelectItem = function (event) { + if (!this.options.selectable) return; + + var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey; + var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey; + if (ctrlKey || shiftKey) { + this._onMultiSelectItem(event); + return; + } + + var oldSelection = this.getSelection(); + + var item = ItemSet.itemFromTarget(event); + var selection = item ? [item.id] : []; + this.setSelection(selection); + + var newSelection = this.getSelection(); + + // emit a select event, + // except when old selection is empty and new selection is still empty + if (newSelection.length > 0 || oldSelection.length > 0) { + this.timeline.emitter.emit('select', { + items: this.getSelection() + }); + } + + event.stopPropagation(); +}; + +/** + * Handle creation and updates of an item on double tap + * @param event + * @private + */ +ItemSet.prototype._onAddItem = function (event) { + if (!this.options.selectable) return; + if (!this.options.editable.add) return; + + var me = this, + snap = this.options.snap || null, + item = ItemSet.itemFromTarget(event); + + if (item) { + // update item + + // execute async handler to update the item (or cancel it) + var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset + this.options.onUpdate(itemData, function (itemData) { + if (itemData) { + me.itemsData.update(itemData); + } + }); + } + else { + // add item + var xAbs = vis.util.getAbsoluteLeft(this.dom.frame); + var x = event.gesture.center.pageX - xAbs; + var start = this._toTime(x); + var newItem = { + start: snap ? snap(start) : start, + content: 'new item' + }; + + // when default type is a range, add a default end date to the new item + if (this.options.type === 'range' || this.options.type == 'rangeoverflow') { + var end = this._toTime(x + this.props.width / 5); + newItem.end = snap ? snap(end) : end; + } + + var id = util.randomUUID(); + newItem[this.itemsData.fieldId] = id; + + var group = ItemSet.groupFromTarget(event); + if (group) { + newItem.group = group.groupId; + } + + // execute async handler to customize (or cancel) adding an item + this.options.onAdd(newItem, function (item) { + if (item) { + me.itemsData.add(newItem); + // TODO: need to trigger a redraw? + } + }); + } +}; + +/** + * Handle selecting/deselecting multiple items when holding an item + * @param {Event} event + * @private + */ +ItemSet.prototype._onMultiSelectItem = function (event) { + if (!this.options.selectable) return; + + var selection, + item = ItemSet.itemFromTarget(event); + + if (item) { + // multi select items + selection = this.getSelection(); // current selection + var index = selection.indexOf(item.id); + if (index == -1) { + // item is not yet selected -> select it + selection.push(item.id); + } + else { + // item is already selected -> deselect it + selection.splice(index, 1); + } + this.setSelection(selection); + + this.timeline.emitter.emit('select', { + items: this.getSelection() + }); + + event.stopPropagation(); + } +}; + /** * Find an item from an event target: * searches for the attribute 'timeline-item' in the event target's element tree diff --git a/src/timeline/component/css/currenttime.css b/src/timeline/component/css/currenttime.css index 20164cd8..093fc603 100644 --- a/src/timeline/component/css/currenttime.css +++ b/src/timeline/component/css/currenttime.css @@ -1,4 +1,5 @@ .vis.timeline .currenttime { background-color: #FF7F6E; width: 2px; + z-index: 1; } \ No newline at end of file diff --git a/src/timeline/component/css/customtime.css b/src/timeline/component/css/customtime.css index f5832e4b..c590485d 100644 --- a/src/timeline/component/css/customtime.css +++ b/src/timeline/component/css/customtime.css @@ -2,4 +2,5 @@ background-color: #6E94FF; width: 2px; cursor: move; + z-index: 1; } \ No newline at end of file diff --git a/src/timeline/component/css/itemset.css b/src/timeline/component/css/itemset.css index b9f5f54e..3acb7a88 100644 --- a/src/timeline/component/css/itemset.css +++ b/src/timeline/component/css/itemset.css @@ -12,14 +12,14 @@ /**/ } -.vis.timeline .background, -.vis.timeline .foreground { +.vis.timeline .itemset .background, +.vis.timeline .itemset .foreground { position: absolute; width: 100%; height: 100%; } -.vis.timeline .foreground { +.vis.timeline .itemset.foreground { overflow: hidden; } @@ -28,6 +28,7 @@ width: 100%; height: 0; left: 1px; + z-index: 1; } .vis.timeline .group { diff --git a/src/timeline/component/css/panel.css b/src/timeline/component/css/panel.css index a1064938..e8eba312 100644 --- a/src/timeline/component/css/panel.css +++ b/src/timeline/component/css/panel.css @@ -44,4 +44,4 @@ .vis.timeline .background { overflow: hidden; -} \ No newline at end of file +}