From b70c990b6a9b1f082399a25bc8440aaccb060f64 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Thu, 23 Jun 2016 14:42:37 +0200 Subject: [PATCH] [IMP] web_tour: tip location Choosing where to place a tip is a difficult problem. If placed in the body element, the tip have to be repositionned on each scroll event which is performance-consuming and induces bugs (firefox scroll event, tips which is over an element while the anchor is under, ...). If placed in the anchor parent which scrolls, all the above problems are solved but when the tip opens it cannot overflow its parent boundaries, which is not possible in many contexts (modals, ...). The solution here is to change the tip location when it opens/closes. When closed, it is placed in the anchor parent which scrolls. This allows to not have to reposition the tip on each scroll. The tip is also naturally hidden when its anchor is not visible on the screen. When opened, the tip is placed in the body, where it can be placed on the top of anything in the DOM. The transition from one state to another is not noticable as the position does not have to be computed when the tip is moved in the DOM and the animation is purely CSS. Note: however, this commit does not solve the firefox bug of closing the tip when the window scrolls. Here, the temporary solution is to let the tip be wrongly position if the user scrolls when a tip is opened (this only occurs when using the mouse wheel anyway). --- addons/web_tour/static/src/js/tip.js | 71 +++++++++++++++++-- addons/web_tour/static/src/js/tour_manager.js | 4 +- addons/web_tour/static/src/less/tip.less | 4 ++ 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/addons/web_tour/static/src/js/tip.js b/addons/web_tour/static/src/js/tip.js index a505a692255..23db51babcf 100644 --- a/addons/web_tour/static/src/js/tip.js +++ b/addons/web_tour/static/src/js/tip.js @@ -11,7 +11,6 @@ return Widget.extend({ mouseleave: "_to_bubble_mode", }, /** - * @param {$anchor} [JQuery] the node on which the tip should be placed * @param {info} [Object] description of the tip, containing the following keys: * - content [String] the html content of the tip * - event_handlers [Object] description of optional event handlers to bind to the tip: @@ -24,9 +23,8 @@ return Widget.extend({ * - overlay [Object] x and y values for the number of pixels the mouseout detection area * overlaps the opened tip, default {x: 50, y: 50} */ - init: function(parent, $anchor, info) { + init: function(parent, info) { this._super(parent); - this.$anchor = $anchor; this.info = _.defaults(info, { position: "right", width: 270, @@ -36,6 +34,24 @@ return Widget.extend({ y: 50, }, }); + this.position = { + top: "50%", + left: "50%", + }; + }, + /** + * @param {$anchor} [JQuery] the node on which the tip should be placed + */ + attach_to: function ($anchor) { + this.$anchor = $anchor; + this.$ideal_location = this._get_ideal_location(); + + var position = this.$ideal_location.css("position"); + if (position === "static") { + this.$ideal_location.addClass("o_tooltip_parent"); + } + + return this.appendTo(this.$ideal_location); }, start: function() { this.$tooltip_overlay = this.$(".o_tooltip_overlay"); @@ -59,30 +75,58 @@ return Widget.extend({ this._reposition(); this.$el.css("opacity", 1); - core.bus.on('scroll resize', this, function() { + core.bus.on("resize", this, function () { if (this.tip_opened) { this._to_bubble_mode(true); } this._reposition(); }); + this.$el.on("transitionend oTransitionEnd webkitTransitionEnd", (function () { + if (!this.tip_opened && this.$el.parent()[0] === document.body) { + this.$el.detach(); + this.$el.css(this.position); + this.$el.appendTo(this.$ideal_location); + } + }).bind(this)); + return this._super.apply(this, arguments); }, destroy: function () { this._unbind_anchor_events(); clearTimeout(this.timerIn); clearTimeout(this.timerOut); + + // Do not remove the parent class if it contains other tooltips + if (this.$ideal_location.children(".o_tooltip").not(this.$el[0]).length === 0) { + this.$ideal_location.removeClass("o_tooltip_parent"); + } + return this._super.apply(this, arguments); }, - update: function($anchor) { + update: function ($anchor) { if (!$anchor.is(this.$anchor)) { this._unbind_anchor_events(); this.$anchor = $anchor; + this.$ideal_location = this._get_ideal_location(); + if (this.$el.parent()[0] !== document.body) { + this.$el.appendTo(this.$ideal_location); + } this._bind_anchor_events(); } this._reposition(); }, - _reposition: function() { + _get_ideal_location: function () { + var $location = this.$anchor; + var o; + do { + $location = $location.parent(); + o = $location.css("overflow"); + } while ((o === "visible" || o === "hidden") && $location[0] !== document.body); + + return $location; + }, + _reposition: function () { if (this.tip_opened) return; this.$el.removeClass("o_animated"); @@ -101,6 +145,8 @@ return Widget.extend({ left: -Math.min((this.info.position === "right" ? this.info.space : this.info.overlay.x), offset.left), }); + this.position = this.$el.position(); + this.$el.addClass("o_animated"); }, _bind_anchor_events: function () { @@ -135,6 +181,9 @@ return Widget.extend({ this.timerOut = undefined; return; } + if (this.tip_opened) { + return; + } if (force === true) { this._build_info_mode(); @@ -149,6 +198,13 @@ return Widget.extend({ this.tip_opened = true; var offset = this.$el.offset(); + + if (this.$el.parent()[0] !== document.body) { + this.$el.detach(); + this.$el.css(offset); + this.$el.appendTo(document.body); + } + var mbLeft = 0; var mbTop = 0; var overflow = false; @@ -180,6 +236,9 @@ return Widget.extend({ this.timerIn = undefined; return; } + if (!this.tip_opened) { + return; + } if (force === true) { this._build_bubble_mode(); diff --git a/addons/web_tour/static/src/js/tour_manager.js b/addons/web_tour/static/src/js/tour_manager.js index 7e1a4c54006..d19b27b4888 100644 --- a/addons/web_tour/static/src/js/tour_manager.js +++ b/addons/web_tour/static/src/js/tour_manager.js @@ -125,9 +125,9 @@ return core.Class.extend({ }], }); } - tip.widget = new Tip(this, $anchor, tip_info); - tip.widget.appendTo(document.body); + tip.widget = new Tip(this, tip_info); tip.widget.on('tip_consumed', this, this._consume_tip.bind(this, tip, tour_name)); + tip.widget.attach_to($anchor); if (this.running_tour === tour_name) { clearTimeout(this.running_tour_timeout); diff --git a/addons/web_tour/static/src/less/tip.less b/addons/web_tour/static/src/less/tip.less index 4dca388b074..a9145b97121 100644 --- a/addons/web_tour/static/src/less/tip.less +++ b/addons/web_tour/static/src/less/tip.less @@ -1,4 +1,8 @@ +.o_tooltip_parent { + position: relative!important; +} + .o_tooltip { @o-tip-background-color: lighten(@odoo-brand-primary, 5%); @o-tip-size: 26px;