Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dxChart - fix tooltip behavior on touch devices (T856700) #12718

Merged
merged 2 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 30 additions & 63 deletions js/viz/gauges/tracker.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
const eventsEngine = require('../../events/core/events_engine');
const Class = require('../../core/class');
const domAdapter = require('../../core/dom_adapter');
const ready = require('../../core/utils/ready_callbacks').add;
const wheelEvent = require('../../events/core/wheel');
const ready = require('../../core/utils/ready_callbacks').add;
const addNamespace = require('../../events/utils').addNamespace;
const pointerEvents = require('../../events/pointer');
const EVENT_NS = 'gauge-tooltip';

const TOOLTIP_HIDE_DELAY = 100;

const tooltipMouseEvents = {
'mouseover.gauge-tooltip': handleTooltipMouseOver,
'mouseout.gauge-tooltip': handleTooltipMouseOut
};

const tooltipMouseMoveEvents = {
'mousemove.gauge-tooltip': handleTooltipMouseMove
};

const tooltipMouseWheelEvents = {};
tooltipMouseWheelEvents[wheelEvent.name + '.gauge-tooltip'] = handleTooltipMouseWheel;

const tooltipTouchEvents = {
'touchstart.gauge-tooltip': handleTooltipTouchStart
};

const Tracker = Class.inherit({
ctor: function(parameters) {
///#DEBUG
Expand Down Expand Up @@ -65,6 +52,7 @@ const Tracker = Class.inherit({
const that = this;
that._dispose();
that.deactivate();
that._element.off('.' + EVENT_NS);
that._element.linkOff();
that._element = that._context = that._callbacks = null;
return that;
Expand Down Expand Up @@ -92,11 +80,15 @@ const Tracker = Class.inherit({

setTooltipState: function(state) {
const that = this;
let data;
that._element.off(tooltipMouseEvents).off(tooltipTouchEvents).off(tooltipMouseWheelEvents);
that._element.off('.' + EVENT_NS);
if(state) {
data = { tracker: that };
that._element.on(tooltipMouseEvents, data).on(tooltipTouchEvents, data).on(tooltipMouseWheelEvents, data);
const data = { tracker: that };
that._element
.on(addNamespace([pointerEvents.move], EVENT_NS), data, handleTooltipMouseOver)
.on(addNamespace([pointerEvents.out], EVENT_NS), data, handleTooltipMouseOut)
.on(addNamespace([pointerEvents.down], EVENT_NS), data, handleTooltipTouchStart)
.on(addNamespace([pointerEvents.up], EVENT_NS), data, handleTooltipTouchEnd)
.on(addNamespace([wheelEvent.name], EVENT_NS), data, handleTooltipMouseWheel);
}
return that;
},
Expand Down Expand Up @@ -136,74 +128,49 @@ const Tracker = Class.inherit({
}
});

function handleTooltipMouseOver(event) {
const tracker = event.data.tracker;
tracker._x = event.pageX;
tracker._y = event.pageY;
tracker._element.off(tooltipMouseMoveEvents).on(tooltipMouseMoveEvents, event.data);
tracker._showTooltip(event);
}
let active_touch_tooltip_tracker = null;

function handleTooltipMouseMove(event) {
const tracker = event.data.tracker;
///#DEBUG
Tracker._DEBUG_reset = function() {
active_touch_tooltip_tracker = null;
};
///#ENDDEBUG

function handleTooltipMouseOver(event) {
const tracker = event.data.tracker;
tracker._x = event.pageX;
tracker._y = event.pageY;
tracker._showTooltip(event);
}

function handleTooltipMouseOut(event) {
const tracker = event.data.tracker;
tracker._element.off(tooltipMouseMoveEvents);
tracker._hideTooltip(TOOLTIP_HIDE_DELAY);
event.data.tracker._hideTooltip(TOOLTIP_HIDE_DELAY);
}

function handleTooltipMouseWheel(event) {
event.data.tracker._hideTooltip();
}

let active_touch_tooltip_tracker = null;

///#DEBUG
Tracker._DEBUG_reset = function() {
active_touch_tooltip_tracker = null;
};
///#ENDDEBUG

function handleTooltipTouchStart(event) {
event.preventDefault();
let tracker = active_touch_tooltip_tracker;
if(tracker && tracker !== event.data.tracker) {
tracker._hideTooltip(TOOLTIP_HIDE_DELAY);
}
tracker = active_touch_tooltip_tracker = event.data.tracker;
tracker._showTooltip(event);
const tracker = active_touch_tooltip_tracker = event.data.tracker;
tracker._touch = true;
handleTooltipMouseOver(event);
}

function handleTooltipDocumentTouchStart() {
const tracker = active_touch_tooltip_tracker;
if(tracker) {
if(!tracker._touch) {
tracker._hideTooltip(TOOLTIP_HIDE_DELAY);
active_touch_tooltip_tracker = null;
}
tracker._touch = null;
}
function handleTooltipTouchEnd() {
active_touch_tooltip_tracker._touch = false;
}

function handleTooltipDocumentTouchEnd() {
function handleDocumentTooltipTouchStart(event) {
const tracker = active_touch_tooltip_tracker;
if(tracker) {
if(tracker && !tracker._touch) {
tracker._hideTooltip(TOOLTIP_HIDE_DELAY);
active_touch_tooltip_tracker = null;
}
}

ready(function() {
eventsEngine.subscribeGlobal(domAdapter.getDocument(), {
'touchstart.gauge-tooltip': handleTooltipDocumentTouchStart,
'touchend.gauge-tooltip': handleTooltipDocumentTouchEnd
});
eventsEngine.subscribeGlobal(domAdapter.getDocument(), addNamespace([pointerEvents.down], EVENT_NS), handleDocumentTooltipTouchStart);
});

module.exports = Tracker;
68 changes: 35 additions & 33 deletions testing/tests/DevExpress.viz.gauges/tracker.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const $ = require('jquery');
const noop = require('core/utils/common').noop;
const vizMocks = require('../../helpers/vizMocks.js');
const Tracker = require('viz/gauges/tracker');
const pointerEvents = require('events/pointer');

QUnit.module('Tracker', {
beforeEach: function() {
Expand Down Expand Up @@ -98,7 +99,7 @@ QUnit.test('"Show" is raised on mouseover after delay', function(assert) {
element.element['gauge-data-info'] = info; // emulate data attachment
this.onTooltipShow = sinon.spy(function() { return true; });

this.trigger('mouseover', element);
this.trigger(pointerEvents.move, element);

assert.strictEqual(this.onTooltipShow.callCount, 1);
assert.strictEqual(this.onTooltipShow.firstCall.args[0], target, 'target');
Expand All @@ -113,12 +114,12 @@ QUnit.test('"Show" is not raised until mousemove occurs', function(assert) {
element.element['gauge-data-target'] = target; // emulate data attachment
element.element['gauge-data-info'] = info; // emulate data attachment
this.onTooltipShow = sinon.spy(function() { return true; });
this.trigger('mouseover', element, 5, 5);
this.trigger(pointerEvents.move, element, 5, 5);

this.trigger('mousemove', element, 10, 5);
this.trigger('mousemove', element, 10, 20);
this.trigger('mousemove', element, 30, 10);
this.trigger('mousemove', element, 40, 5);
this.trigger(pointerEvents.move, element, 10, 5);
this.trigger(pointerEvents.move, element, 10, 20);
this.trigger(pointerEvents.move, element, 30, 10);
this.trigger(pointerEvents.move, element, 40, 5);

assert.strictEqual(this.onTooltipShow.callCount, 1);
});
Expand All @@ -131,12 +132,12 @@ QUnit.test('"Show" is raised when small mousemove occurs', function(assert) {
element.element['gauge-data-target'] = target; // emulate data attachment
element.element['gauge-data-info'] = info; // emulate data attachment
this.onTooltipShow = sinon.spy(function() { return true; });
this.trigger('mouseover', element, 5, 5);
this.trigger(pointerEvents.move, element, 5, 5);

this.trigger('mousemove', element, 8, 5);
this.trigger('mousemove', element, 8, 1);
this.trigger('mousemove', element, 4, 3);
this.trigger('mousemove', element, 7, 5);
this.trigger(pointerEvents.move, element, 8, 5);
this.trigger(pointerEvents.move, element, 8, 1);
this.trigger(pointerEvents.move, element, 4, 3);
this.trigger(pointerEvents.move, element, 7, 5);

assert.strictEqual(this.onTooltipShow.callCount, 1);
});
Expand All @@ -146,7 +147,7 @@ QUnit.test('"Hide" is raised on mousewheel without delay', function(assert) {
const element = this.renderer.path([], 'area');
this.tracker.attach(element);
this.onTooltipHide = sinon.spy(function() { return true; });
this.trigger('mouseover', element);
this.trigger(pointerEvents.move, element);

that.trigger('dxmousewheel', element);

Expand All @@ -159,11 +160,11 @@ QUnit.test('"Hide" is raised on mouseout after delay', function(assert) {
const element = this.renderer.path([], 'area');
this.tracker.attach(element);
this.onTooltipShow = sinon.spy(function() {
that.trigger('mouseout', element);
that.trigger(pointerEvents.out, element);
return true;
});
this.onTooltipHide = sinon.spy(function() { return true; });
this.trigger('mouseover', element);
this.trigger(pointerEvents.move, element);
this.clock.tick(this.tracker.TOOLTIP_HIDE_DELAY);

assert.strictEqual(this.tracker._DEBUG_hideTooltipTimeoutSet, 1, 'timeout is set');
Expand All @@ -176,23 +177,22 @@ QUnit.test('"Hide" is not raised if tooltip is not shown', function(assert) {
this.tracker.attach(element);
this.onTooltipHide = sinon.spy(function() { return true; });

this.trigger('mouseout', element);
this.trigger(pointerEvents.out, element);
this.clock.tick(this.tracker.TOOLTIP_HIDE_DELAY);

assert.strictEqual(this.tracker._DEBUG_hideTooltipTimeoutSet, 1, 'timeout is set');
assert.strictEqual(this.onTooltipHide.callCount, 0);
});

QUnit.test('"Hide" is not raised if mouseover occurs after mouseout', function(assert) {
const that = this;
const element = this.renderer.path([], 'area');
this.tracker.attach(element);

this.onTooltipHide = sinon.spy(function() { return true; });

this.trigger('mouseover', element);
that.trigger('mouseout', element);
that.trigger('mouseover', element);
this.trigger(pointerEvents.move, element);
this.trigger(pointerEvents.out, element);
this.trigger(pointerEvents.move, element);

this.clock.tick(this.tracker.TOOLTIP_HIDE_DELAY);

Expand Down Expand Up @@ -221,8 +221,8 @@ QUnit.test('"Show" is raised after delay on mouseover on other element if toolti
return true;
};

this.trigger('mouseover', element1);
this.trigger('mouseover', element2);
this.trigger(pointerEvents.move, element1);
this.trigger(pointerEvents.move, element2);
});

QUnit.test('"Hide" is raised after delay on mouseover then mouseout on other element if tooltip is shown', function(assert) {
Expand All @@ -233,9 +233,9 @@ QUnit.test('"Hide" is raised after delay on mouseover then mouseout on other ele
this.onTooltipHide = function() {
assert.strictEqual(this.tracker._DEBUG_hideTooltipTimeoutSet, 1, 'timeout is set');
};
this.trigger('mouseover', element1);
this.trigger('mouseover', element2);
this.trigger('mouseout', element2);
this.trigger(pointerEvents.move, element1);
this.trigger(pointerEvents.move, element2);
this.trigger(pointerEvents.out, element2);
this.clock.tick(this.tracker.TOOLTIP_HIDE_DELAY);
});

Expand All @@ -251,9 +251,9 @@ QUnit.test('"Show" is not raised on mouseout then mouseover if tooltip is shown'

return true;
};
this.trigger('mouseover', element);
this.trigger('mouseout', element);
this.trigger('mouseover', element);
this.trigger(pointerEvents.move, element);
this.trigger(pointerEvents.out, element);
this.trigger(pointerEvents.move, element);
assert.strictEqual(this.tracker._DEBUG_hideTooltipTimeoutCleared, 1, 'show timeout is cleared');
assert.strictEqual(this.tracker._DEBUG_hideTooltipTimeoutSet, 1, 'hide timeout is set');
assert.strictEqual(this.tracker._DEBUG_hideTooltipTimeoutCleared, 1, 'hide timeout is cleared');
Expand Down Expand Up @@ -294,21 +294,22 @@ QUnit.test('"Show" is raised on touchstart', function(assert) {
return true;
};

this.trigger('touchstart', element);
this.trigger(pointerEvents.down, element);
});

QUnit.test('"Hide" is raised on touchstart outside the element', function(assert) {
assert.expect(1);
const element = this.renderer.path([], 'area');
this.tracker.attach(element);
this.onTooltipShow = function() {
this.triggerDocument('touchstart');
this.trigger(pointerEvents.up, element);
this.triggerDocument(pointerEvents.down);
return true;
};
this.onTooltipHide = function() {
assert.strictEqual(this.tracker._DEBUG_hideTooltipTimeoutSet, 1, 'timeout is set');
};
this.trigger('touchstart', element);
this.trigger(pointerEvents.down, element);
this.clock.tick(this.tracker.TOOLTIP_HIDE_DELAY);
});

Expand All @@ -320,15 +321,16 @@ QUnit.test('"Hide" is raised after delay on touchstart then touchend on other el
element1.element['gauge-data-target'] = element2; // emulate data attachment
this.onTooltipShow = function() {
this.onTooltipShow = function() {
this.triggerDocument('touchend');
this.trigger(pointerEvents.up, element1);
return true;
};
this.trigger('touchstart', element2);
this.trigger(pointerEvents.up, element1);
this.trigger(pointerEvents.down, element2);
return true;
};
this.onTooltipHide = function() {
assert.strictEqual(this.tracker._DEBUG_hideTooltipTimeoutSet, 1, 'timeout is set');
};
this.trigger('touchstart', element1);
this.trigger(pointerEvents.down, element1);
this.clock.tick(this.tracker.TOOLTIP_HIDE_DELAY);
});