From cdba4c1491d318d378fb856810cfdcb7298ef09e Mon Sep 17 00:00:00 2001 From: yotamberk Date: Sun, 1 Jan 2017 11:40:07 +0200 Subject: [PATCH 1/9] Fix: Fixes error in React example when adding a ranged item (#2521) * Fix redraw order * Fix error when option is not defined * Allow template labels * Fix react example --- examples/timeline/other/usingReact.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/timeline/other/usingReact.html b/examples/timeline/other/usingReact.html index f6d1e1f7..ba7bb49f 100644 --- a/examples/timeline/other/usingReact.html +++ b/examples/timeline/other/usingReact.html @@ -88,10 +88,12 @@ end: new Date(1000*60*60*24 + (new Date()).valueOf()), editable: true, template: function (item, element) { + if (!item) { return } ReactDOM.unmountComponentAtNode(element); return ReactDOM.render(, element); }, groupTemplate: function (group, element) { + if (!item) { return } ReactDOM.unmountComponentAtNode(element); return ReactDOM.render(, element); } From cb5cfc085892728d9b3452f15a0d874eca74fdaf Mon Sep 17 00:00:00 2001 From: Lewis B Date: Tue, 3 Jan 2017 21:48:20 +1000 Subject: [PATCH 2/9] Fixed deleting item with id 0 (#2530) --- lib/DataSet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DataSet.js b/lib/DataSet.js index 84d3f8c6..790a39e9 100644 --- a/lib/DataSet.js +++ b/lib/DataSet.js @@ -679,7 +679,7 @@ DataSet.prototype.remove = function (id, senderId) { item = this._remove(ids[i]); if (item) { itemId = item[this._fieldId]; - if (itemId) { + if (itemId != undefined) { removedIds.push(itemId); removedItems.push(item); } From d3d1dc7ecc758f32f6c50a228ae61cdad6b4d342 Mon Sep 17 00:00:00 2001 From: Lewis B Date: Wed, 4 Jan 2017 05:47:15 +1000 Subject: [PATCH 3/9] Fixed Timeline.setGroups for Array (#2529) --- lib/timeline/Timeline.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index 4ff51e5b..e27359e3 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -271,13 +271,11 @@ Timeline.prototype.setGroups = function(groups) { newDataSet = null; } else { - var filter = { - filter : function(group){ - return group.visible !== false; - } + var filter = function(group) { + return group.visible !== false; } if (groups instanceof DataSet || groups instanceof DataView) { - newDataSet = new DataView(groups,filter); + newDataSet = new DataView(groups,{filter: filter}); } else { // turn an array into a dataset From 723f91a1ac7be9c43177eebba4be75ac1ff96c09 Mon Sep 17 00:00:00 2001 From: Jim O'Brien Date: Tue, 3 Jan 2017 19:47:53 +0000 Subject: [PATCH 4/9] Don't pass non-string values to Date.parse (#2534) Fixes: #2490 (on the correct branch this time) --- lib/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.js b/lib/util.js index 5a3ac76c..31ca9bb7 100644 --- a/lib/util.js +++ b/lib/util.js @@ -409,7 +409,7 @@ exports.convert = function (object, type) { case 'number': case 'Number': - if (!isNaN(Date.parse(object))) { + if (exports.isString(object) && !isNaN(Date.parse(object))) { return moment(object).valueOf(); } else { return Number(object.valueOf()); From 70710f430bbad40cf34cb5c4ad3a21051cfda62a Mon Sep 17 00:00:00 2001 From: Zachariah Brown Date: Tue, 3 Jan 2017 16:00:18 -0500 Subject: [PATCH 5/9] Fixes removed event oldData items. (#2535) --- lib/DataView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DataView.js b/lib/DataView.js index 59aa2ffc..04725868 100644 --- a/lib/DataView.js +++ b/lib/DataView.js @@ -108,7 +108,7 @@ DataView.prototype.refresh = function () { id = oldIds[i]; if (!newIds[id]) { removedIds.push(id); - removedItems.push(this._data[id]); + removedItems.push(this._data._data[id]); delete this._ids[id]; } } From 3a4b8c4cb6b2e623e649e6cf3b3585b53f589b15 Mon Sep 17 00:00:00 2001 From: Lewis B Date: Thu, 5 Jan 2017 05:42:26 +1000 Subject: [PATCH 6/9] Fixed hover events for HTML elements (#2539) --- lib/timeline/component/ItemSet.js | 50 ++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 16e7c48d..022e4f18 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -1875,6 +1875,13 @@ ItemSet.prototype._onMouseOver = function (event) { var item = this.itemFromTarget(event); if (!item) return; + // Item we just left + var related = this.itemFromRelatedTarget(event); + if (item === related) { + // We haven't changed item, just element in the item + return; + } + if (item.getTitle()) { if (item.popup == null) { item.setPopup(new Popup(this.body.dom.root)); @@ -1897,6 +1904,13 @@ ItemSet.prototype._onMouseOut = function (event) { var item = this.itemFromTarget(event); if (!item) return; + // Item we are going to + var related = this.itemFromRelatedTarget(event); + if (item === related) { + // We aren't changing item, just element in the item + return; + } + if (item.popup != null) { item.popup.hide(); } @@ -2118,6 +2132,24 @@ ItemSet._getItemRange = function(itemsData) { } }; +/** + * Find an item from an element: + * searches for the attribute 'timeline-item' in the element's tree + * @param {HTMLElement} element + * @return {Item | null} item + */ +ItemSet.prototype.itemFromElement = function(element) { + var cur = element; + while (cur) { + if (cur.hasOwnProperty('timeline-item')) { + return cur['timeline-item']; + } + cur = cur.parentNode; + } + + return null; +}; + /** * Find an item from an event target: * searches for the attribute 'timeline-item' in the event target's element tree @@ -2125,15 +2157,17 @@ ItemSet._getItemRange = function(itemsData) { * @return {Item | null} item */ ItemSet.prototype.itemFromTarget = function(event) { - var target = event.target; - while (target) { - if (target.hasOwnProperty('timeline-item')) { - return target['timeline-item']; - } - target = target.parentNode; - } + return this.itemFromElement(event.target); +}; - return null; +/** + * Find an item from an event's related target: + * searches for the attribute 'timeline-item' in the related target's element tree + * @param {Event} event + * @return {Item | null} item + */ +ItemSet.prototype.itemFromRelatedTarget = function(event) { + return this.itemFromElement(event.relatedTarget); }; /** From 4d3157443fd1e89e9dfa77e5b25cd3011d2cd772 Mon Sep 17 00:00:00 2001 From: yotamberk Date: Wed, 4 Jan 2017 21:46:58 +0200 Subject: [PATCH 7/9] Fix small bug when subgroups start overlap without subgroupStack (#2527) * Fix redraw order * Fix error when option is not defined * Allow template labels * Fix boolean types bug * Add subgroup stacking * Almost finish subgroup stacking * Play with examples for subgroup order * Fix stacked subgroups * Fix subgroup stacking * Add stackSubgroups option * Fix example * Add docs * Fix onRemove item subgroups recalculate * Return subgroup example and add stackSubgroup example * Split stackSubgroup example to subgroup/html and expectedVsActualTimesItems.html * Fix small bug when subgroups start overlap --- lib/timeline/Stack.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/timeline/Stack.js b/lib/timeline/Stack.js index ab921893..1893c07e 100644 --- a/lib/timeline/Stack.js +++ b/lib/timeline/Stack.js @@ -177,7 +177,7 @@ exports.collision = function(a, b, margin, rtl) { */ exports.collisionByTimes = function(a, b) { return ( - (a.start < b.start && a.end > b.start && a.top < (b.top + b.height) && (a.top + a.height) > b.top ) || - (b.start < a.start && b.end > a.start && b.top < (a.top + a.height) && (b.top + b.height) > a.top ) + (a.start <= b.start && a.end >= b.start && a.top < (b.top + b.height) && (a.top + a.height) > b.top ) || + (b.start <= a.start && b.end >= a.start && b.top < (a.top + a.height) && (b.top + b.height) > a.top ) ) } \ No newline at end of file From e27de4f8687e76e4d5427f9e52cc8b3ecfed326e Mon Sep 17 00:00:00 2001 From: Lewis B Date: Thu, 5 Jan 2017 18:59:05 +1000 Subject: [PATCH 8/9] Fixed tool-tip surviving after item deleted (#2545) --- lib/shared/Popup.js | 7 +++++++ lib/timeline/component/ItemSet.js | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/shared/Popup.js b/lib/shared/Popup.js index 43d4537b..eafd94b7 100644 --- a/lib/shared/Popup.js +++ b/lib/shared/Popup.js @@ -93,6 +93,13 @@ class Popup { this.hidden = true; this.frame.style.visibility = "hidden"; } + + /** + * Remove the popup window + */ + destroy() { + this.frame.parentNode.removeChild(this.frame); // Remove element from DOM + } } export default Popup; diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 022e4f18..07c0ce74 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -869,7 +869,8 @@ ItemSet.prototype.getGroups = function() { */ ItemSet.prototype.removeItem = function(id) { var item = this.itemsData.get(id), - dataset = this.itemsData.getDataSet(); + dataset = this.itemsData.getDataSet(), + itemObj = this.items[id]; if (item) { // confirm deletion @@ -878,6 +879,12 @@ ItemSet.prototype.removeItem = function(id) { // remove by id here, it is possible that an item has no id defined // itself, so better not delete by the item itself dataset.remove(id); + + // Remove it's popup + if (itemObj.popup) { + itemObj.popup.destroy(); + itemObj.popup = null; + } } }); } From 8645404f4d43bb203ddc3837957ac96c2dd6a172 Mon Sep 17 00:00:00 2001 From: Lewis B Date: Thu, 5 Jan 2017 18:59:31 +1000 Subject: [PATCH 9/9] Added followMouse & overflowMethod to tooltip options (#2544) - Also updated docs and examples for this - Fixed the positioning of the tooltip if groups was enabled - Removed missing parameters from Popup docstring --- docs/timeline/index.html | 24 +++++++++++ examples/timeline/items/tooltip.html | 35 ++++++++++++++++ lib/shared/Popup.js | 63 +++++++++++++++++++--------- lib/timeline/component/ItemSet.js | 31 ++++++++++++-- lib/timeline/optionsTimeline.js | 9 ++++ 5 files changed, 138 insertions(+), 24 deletions(-) diff --git a/docs/timeline/index.html b/docs/timeline/index.html index ce8df517..2f905929 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -1096,6 +1096,30 @@ function (option, path) { + + tooltip + Object + Object + Specify how the tooltip is positioned. + + + tooltip.followMouse + boolean + false + If true, tooltips will follow the mouse as they move around in the item. + + + + tooltip.overflowMethod + String + 'flip' + + Set how the tooltip should act if it is about to overflow out of the timeline.
+ Choose from 'cap' and 'flip'.
+ If it is set to 'cap', the tooltip will just cap its position to inside to timeline.
+ While if it is set to 'flip', the position of the tooltip will flip around the cursor so that a corner is at the cursor, and the rest of it is visible.
+ + tooltipOnItemUpdateTime Object/Boolean diff --git a/examples/timeline/items/tooltip.html b/examples/timeline/items/tooltip.html index 3e767838..38701586 100644 --- a/examples/timeline/items/tooltip.html +++ b/examples/timeline/items/tooltip.html @@ -24,6 +24,20 @@
+

+ The example below has the tooltip follow the mouse. +

+ +
+ +

+ The example below has the tooltip overflow set to 'cap'. Compare this to the one above, + to see how they differ. For the best results, move the cursor to the top right, + where the tool-tip is going to overflow out of the timeline. +

+ +
+ diff --git a/lib/shared/Popup.js b/lib/shared/Popup.js index eafd94b7..c1351e84 100644 --- a/lib/shared/Popup.js +++ b/lib/shared/Popup.js @@ -1,15 +1,12 @@ /** * Popup is a class to create a popup window with some text - * @param {Element} container The container object. - * @param {Number} [x] - * @param {Number} [y] - * @param {String} [text] - * @param {Object} [style] An object containing borderColor, - * backgroundColor, etc. + * @param {Element} container The container object. + * @param {string} overflowMethod How the popup should act to overflowing ('flip' or 'cap') */ class Popup { - constructor(container) { + constructor(container, overflowMethod) { this.container = container; + this.overflowMethod = overflowMethod || 'cap'; this.x = 0; this.y = 0; @@ -60,20 +57,46 @@ class Popup { var maxHeight = this.frame.parentNode.clientHeight; var maxWidth = this.frame.parentNode.clientWidth; - var top = (this.y - height); - if (top + height + this.padding > maxHeight) { - top = maxHeight - height - this.padding; - } - if (top < this.padding) { - top = this.padding; - } + var left = 0, top = 0; - var left = this.x; - if (left + width + this.padding > maxWidth) { - left = maxWidth - width - this.padding; - } - if (left < this.padding) { - left = this.padding; + if (this.overflowMethod == 'flip') { + var isLeft = false, isTop = true; // Where around the position it's located + + if (this.y - height < this.padding) { + isTop = false; + } + + if (this.x + width > maxWidth - this.padding) { + isLeft = true; + } + + if (isLeft) { + left = this.x - width; + } else { + left = this.x; + } + + if (isTop) { + top = this.y - height; + } else { + top = this.y; + } + } else { + top = (this.y - height); + if (top + height + this.padding > maxHeight) { + top = maxHeight - height - this.padding; + } + if (top < this.padding) { + top = this.padding; + } + + left = this.x; + if (left + width + this.padding > maxWidth) { + left = maxWidth - width - this.padding; + } + if (left < this.padding) { + left = this.padding; + } } this.frame.style.left = left + "px"; diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index 07c0ce74..948f5758 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -95,6 +95,11 @@ function ItemSet(body, options) { axis: 20 }, + tooltip: { + followMouse: false, + overflowMethod: 'flip' + }, + tooltipOnItemUpdateTime: false }; @@ -250,6 +255,7 @@ ItemSet.prototype._create = function(){ this.body.dom.centerContainer.addEventListener('mouseover', this._onMouseOver.bind(this)); this.body.dom.centerContainer.addEventListener('mouseout', this._onMouseOut.bind(this)); + this.body.dom.centerContainer.addEventListener('mousemove', this._onMouseMove.bind(this)); // attach to the DOM this.show(); @@ -325,7 +331,7 @@ ItemSet.prototype.setOptions = function(options) { var fields = [ 'type', 'rtl', 'align', 'order', 'stack', 'stackSubgroups', 'selectable', 'multiselect', 'itemsAlwaysDraggable', 'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate', - 'hide', 'snap', 'groupOrderSwap', 'tooltipOnItemUpdateTime' + 'hide', 'snap', 'groupOrderSwap', 'tooltip', 'tooltipOnItemUpdateTime' ]; util.selectiveExtend(fields, this.options, options); @@ -1891,13 +1897,13 @@ ItemSet.prototype._onMouseOver = function (event) { if (item.getTitle()) { if (item.popup == null) { - item.setPopup(new Popup(this.body.dom.root)); + item.setPopup(new Popup(this.body.dom.root, this.options.tooltip.overflowMethod || 'flip')); } var container = this.body.dom.centerContainer; item.popup.setPosition( - event.clientX - util.getAbsoluteLeft(container), - event.clientY - util.getAbsoluteTop(container) + event.clientX - util.getAbsoluteLeft(container) + container.offsetLeft, + event.clientY - util.getAbsoluteTop(container) + container.offsetTop ); item.popup.show(); } @@ -1927,6 +1933,23 @@ ItemSet.prototype._onMouseOut = function (event) { event: util.elementsCensor(event) }); }; +ItemSet.prototype._onMouseMove = function (event) { + var item = this.itemFromTarget(event); + if (!item) return; + + if (this.options.tooltip.followMouse) { + if (item.popup) { + if (!item.popup.hidden) { + var container = this.body.dom.centerContainer; + item.popup.setPosition( + event.clientX - util.getAbsoluteLeft(container) + container.offsetLeft, + event.clientY - util.getAbsoluteTop(container) + container.offsetTop + ); + item.popup.show(); // Redraw + } + } + } +}; /** diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index 517a0e00..6251bcac 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -131,6 +131,11 @@ let allOptions = { template: {'function': 'function'}, groupTemplate: {'function': 'function'}, visibleFrameTemplate: {string, 'function': 'function'}, + tooltip: { + followMouse: { 'boolean': bool }, + overflowMethod: { 'string': ['cap', 'flip'] }, + __type__: {object} + }, tooltipOnItemUpdateTime: { template: {'function': 'function'}, __type__: { 'boolean': bool, object} @@ -230,6 +235,10 @@ let configureOptions = { // scale: ['millisecond', 'second', 'minute', 'hour', 'weekday', 'day', 'month', 'year'], // step: [1, 1, 10, 1] //}, + tooltip: { + followMouse: false, + overflowMethod: 'flip' + }, tooltipOnItemUpdateTime: false, type: ['box', 'point', 'range', 'background'], width: '100%',