diff --git a/HISTORY.md b/HISTORY.md index 63236e0b..920c7c01 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,7 +6,7 @@ http://visjs.org ### Timeline -- Items can be dragged and removed. +- Items can be dragged, added, and removed. - Implemented options `selectable`, `editable`. - Added events when dragging the custom time bar. diff --git a/src/timeline/Controller.js b/src/timeline/Controller.js index ed683220..d4760782 100644 --- a/src/timeline/Controller.js +++ b/src/timeline/Controller.js @@ -130,6 +130,8 @@ Controller.prototype.repaint = function repaint() { util.forEach(this.components, repaint); + this.emit('repaint'); + // immediately reflow when needed if (changed) { this.reflow(); @@ -171,6 +173,8 @@ Controller.prototype.reflow = function reflow() { util.forEach(this.components, reflow); + this.emit('reflow'); + // immediately repaint when needed if (resized) { this.repaint(); diff --git a/src/timeline/Timeline.js b/src/timeline/Timeline.js index 6fff7245..ee42f0df 100644 --- a/src/timeline/Timeline.js +++ b/src/timeline/Timeline.js @@ -438,26 +438,77 @@ Timeline.prototype._onMultiSelectItem = function (event) { var selection, item = ItemSet.itemFromTarget(event); - if (!item) { - // do nothing... - return; - } + 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.controller.emit('select', { + items: this.getSelection() + }); - 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); + event.stopPropagation(); } else { - // item is already selected -> deselect it - selection.splice(index, 1); + // create a new item + var xAbs = vis.util.getAbsoluteLeft(this.rootPanel.frame); + var x = event.gesture.center.pageX - xAbs; + var newItem = { + start: this.timeaxis.snap(this._toTime(x)), + content: 'new item' + }; + + var id = util.randomUUID(); + newItem[this.itemsData.fieldId] = id; + + var group = GroupSet.groupFromTarget(event); + if (group) { + newItem.group = group.groupId; + } + + // TODO: implement an async handler to customize adding an item + + this.itemsData.add(newItem); + + // select the created item after it is repainted + this.controller.once('repaint', function () { + this.setSelection([id]); + + this.controller.emit('select', { + items: this.getSelection() + }); + }.bind(this)); } - this.setSelection(selection); +}; - this.controller.emit('select', { - items: this.getSelection() - }); +/** + * Convert a position on screen (pixels) to a datetime + * @param {int} x Position on the screen in pixels + * @return {Date} time The datetime the corresponds with given position x + * @private + */ +Timeline.prototype._toTime = function _toTime(x) { + var conversion = this.range.conversion(this.content.width); + return new Date(x / conversion.scale + conversion.offset); +}; - event.stopPropagation(); +/** + * Convert a datetime (Date object) into a position on the screen + * @param {Date} time A date + * @return {int} x The position on the screen in pixels which corresponds + * with the given date. + * @private + */ +Timeline.prototype._toScreen = function _toScreen(time) { + var conversion = this.range.conversion(this.content.width); + return (time.valueOf() - conversion.offset) * conversion.scale; }; diff --git a/src/timeline/component/GroupSet.js b/src/timeline/component/GroupSet.js index db8907c4..2fa78523 100644 --- a/src/timeline/component/GroupSet.js +++ b/src/timeline/component/GroupSet.js @@ -216,6 +216,7 @@ GroupSet.prototype.repaint = function repaint() { if (!frame) { frame = document.createElement('div'); frame.className = 'groupset'; + frame['timeline-groupset'] = this; this.dom.frame = frame; var className = options.className; @@ -544,3 +545,36 @@ GroupSet.prototype._toQueue = function _toQueue(ids, action) { this.requestRepaint(); } }; + +/** + * Find the Group from an event target: + * searches for the attribute 'timeline-groupset' in the event target's element + * tree, then finds the right group in this groupset + * @param {Event} event + * @return {Group | null} group + */ +GroupSet.groupFromTarget = function groupFromTarget (event) { + var groupset, + target = event.target; + + while (target) { + if (target.hasOwnProperty('timeline-groupset')) { + groupset = target['timeline-groupset']; + break; + } + target = target.parentNode; + } + + if (groupset) { + for (var groupId in groupset.groups) { + if (groupset.groups.hasOwnProperty(groupId)) { + var group = groupset.groups[groupId]; + if (group.itemset && ItemSet.itemSetFromTarget(event) == group.itemset) { + return group; + } + } + } + } + + return null; +};