diff options
| -rw-r--r-- | addons/web_tour/static/src/js/tip.js | 89 | ||||
| -rw-r--r-- | addons/web_tour/static/src/js/tour_manager.js | 30 |
2 files changed, 89 insertions, 30 deletions
diff --git a/addons/web_tour/static/src/js/tip.js b/addons/web_tour/static/src/js/tip.js index d1c7ffaab08..c800bc31c1e 100644 --- a/addons/web_tour/static/src/js/tip.js +++ b/addons/web_tour/static/src/js/tip.js @@ -54,10 +54,17 @@ var Tip = Widget.extend({ this._onAncestorScroll = _.throttle(this._onAncestorScroll, 50); }, /** + * Attaches the tip to the provided $anchor and $altAnchor. + * $altAnchor is an alternative trigger that can consume the step. The tip is + * however only displayed on the $anchor. + * * @param {jQuery} $anchor the node on which the tip should be placed + * @param {jQuery} $altAnchor an alternative node that can consume the step + * @return {Promise} */ - attach_to: async function ($anchor) { - this._setupAnchor($anchor); + attach_to: function ($anchor, $altAnchor) { + this._setupAnchor($anchor, $altAnchor); + this.is_anchor_fixed_position = this.$anchor.css("position") === "fixed"; if (this.info.optional === "true") { @@ -128,14 +135,22 @@ var Tip = Widget.extend({ return this._super.apply(this, arguments); }, - update: function ($anchor) { + /** + * Updates the $anchor and $altAnchor the tip is attached to. + * $altAnchor is an alternative trigger that can consume the step. The tip is + * however only displayed on the $anchor. + * + * @param {jQuery} $anchor the node on which the tip should be placed + * @param {jQuery} $altAnchor an alternative node that can consume the step + */ + update: function ($anchor, $altAnchor) { // We unbind/rebind events on each update because we support widgets // detaching and re-attaching nodes to their DOM element without keeping // the initial event handlers, with said node being potential tip // anchors (e.g. FieldMonetary > input element). this._unbind_anchor_events(); if (!$anchor.is(this.$anchor)) { - this._setupAnchor($anchor); + this._setupAnchor($anchor, $altAnchor); } this._bind_anchor_events(); if (!this.$el) { @@ -152,12 +167,16 @@ var Tip = Widget.extend({ //-------------------------------------------------------------------------- /** - * @private - * @param {jQuery} $anchor + * Sets the $anchor and $altAnchor the tip is attached to. + * $altAnchor is an alternative trigger that can consume the step. The tip is + * however only displayed on the $anchor. + * + * @param {jQuery} $anchor the node on which the tip should be placed + * @param {jQuery} $altAnchor an alternative node that can consume the step */ - _setupAnchor: function ($anchor) { + _setupAnchor: function ($anchor, $altAnchor) { this.$anchor = $anchor; - this.$actualAnchor = this.$anchor; + this.$altAnchor = $altAnchor; this.$ideal_location = this._get_ideal_location(); this.$furtherIdealLocation = this._get_ideal_location(this.$ideal_location); }, @@ -214,11 +233,9 @@ var Tip = Widget.extend({ let $location; if (this.viewPortState === 'in') { this.$tooltip_content.html(this.info.content); - this.$actualAnchor = this.$anchor; $location = this.$ideal_location; } else { this.$tooltip_content.html(this.info.scrollContent); - this.$actualAnchor = this.$ideal_location; $location = this.$furtherIdealLocation; } // Update o_tooltip_parent class and tip DOM location. Note: @@ -320,37 +337,57 @@ var Tip = Widget.extend({ this.$el.addClass("o_animated"); }, _bind_anchor_events: function () { + // The consume_event taken for RunningTourActionHelper is the one of $anchor and not $altAnchor. this.consume_event = this.info.consumeEvent || Tip.getConsumeEventType(this.$anchor, this.info.run); - this.$consumeEventAnchor = this.$anchor; - if (this.consume_event === "drag") { + this.$consumeEventAnchors = this._getAnchorAndCreateEvent(this.consume_event, this.$anchor); + if (this.$altAnchor.length) { + const consumeEvent = this.info.consumeEvent || Tip.getConsumeEventType(this.$altAnchor, this.info.run); + this.$consumeEventAnchors = this.$consumeEventAnchors.add( + this._getAnchorAndCreateEvent(consumeEvent, this.$altAnchor) + ); + } + this.$anchor.on('mouseenter.anchor', () => this._to_info_mode()); + this.$anchor.on('mouseleave.anchor', () => this._to_bubble_mode()); + + this.$scrolableElement = this.$ideal_location.is('html,body') ? $(window) : this.$ideal_location; + this.$scrolableElement.on('scroll.Tip', () => this._onAncestorScroll()); + }, + /** + * Gets the anchor corresponding to the provided arguments and attaches the + * event to the $anchor in order to consume the step accordingly. + * + * @private + * @param {String} consumeEvent + * @param {jQuery} $anchor the node on which the tip should be placed + * @return {jQuery} + */ + _getAnchorAndCreateEvent: function(consumeEvent, $anchor) { + let $consumeEventAnchors = $anchor; + if (consumeEvent === "drag") { // jQuery-ui draggable triggers 'drag' events on the .ui-draggable element, // but the tip is attached to the .ui-draggable-handle element which may // be one of its children (or the element itself) - this.$consumeEventAnchor = this.$anchor.closest('.ui-draggable'); - } else if (this.consume_event === "input" && !this.$anchor.is('textarea, input')) { - this.$consumeEventAnchor = this.$anchor.closest("[contenteditable='true']"); - } else if (this.consume_event.includes('apply.daterangepicker')) { - this.$consumeEventAnchor = this.$anchor.parent().children('.o_field_date_range'); - } else if (this.consume_event === "sort") { + $consumeEventAnchors = $anchor.closest('.ui-draggable'); + } else if (consumeEvent === "input" && !$anchor.is('textarea, input')) { + $consumeEventAnchors = $anchor.closest("[contenteditable='true']"); + } else if (consumeEvent.includes('apply.daterangepicker')) { + $consumeEventAnchors = $anchor.parent().children('.o_field_date_range'); + } else if (consumeEvent === "sort") { // when an element is dragged inside a sortable container (with classname // 'ui-sortable'), jQuery triggers the 'sort' event on the container - this.$consumeEventAnchor = this.$anchor.closest('.ui-sortable'); + $consumeEventAnchors = $anchor.closest('.ui-sortable'); } - this.$consumeEventAnchor.on(this.consume_event + ".anchor", (function (e) { + $consumeEventAnchors.on(consumeEvent + ".anchor", (function (e) { if (e.type !== "mousedown" || e.which === 1) { // only left click this.trigger("tip_consumed"); this._unbind_anchor_events(); } }).bind(this)); - this.$anchor.on('mouseenter.anchor', () => this._to_info_mode()); - this.$anchor.on('mouseleave.anchor', () => this._to_bubble_mode()); - - this.$scrolableElement = this.$ideal_location.is('html,body') ? $(window) : this.$ideal_location; - this.$scrolableElement.on('scroll.Tip', () => this._onAncestorScroll()); + return $consumeEventAnchors; }, _unbind_anchor_events: function () { this.$anchor.off(".anchor"); - this.$consumeEventAnchor.off(".anchor"); + this.$consumeEventAnchors.off(".anchor"); this.$scrolableElement.off('.Tip'); }, _get_spaced_inverted_position: function (position) { diff --git a/addons/web_tour/static/src/js/tour_manager.js b/addons/web_tour/static/src/js/tour_manager.js index cc170864e64..90eb44e37e7 100644 --- a/addons/web_tour/static/src/js/tour_manager.js +++ b/addons/web_tour/static/src/js/tour_manager.js @@ -278,12 +278,23 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, { extra_trigger = get_first_visible_element($extra_trigger).length; } + var $visible_alt_trigger = $(); + if (tip.alt_trigger) { + var $alt_trigger; + if (tip.in_modal !== false && this.$modal_displayed.length) { + $alt_trigger = this.$modal_displayed.find(tip.alt_trigger); + } else { + $alt_trigger = get_jquery_element_from_selector(tip.alt_trigger); + } + $visible_alt_trigger = get_first_visible_element($alt_trigger); + } + var triggered = $visible_trigger.length && extra_trigger; if (triggered) { if (!tip.widget) { - this._activate_tip(tip, tour_name, $visible_trigger); + this._activate_tip(tip, tour_name, $visible_trigger, $visible_alt_trigger); } else { - tip.widget.update($visible_trigger); + tip.widget.update($visible_trigger, $visible_alt_trigger); } } else { if ($trigger.iframeContainer || ($extra_trigger && $extra_trigger.iframeContainer)) { @@ -317,7 +328,18 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, { } return !!triggered; }, - _activate_tip: function(tip, tour_name, $anchor) { + /** + * Activates the provided tip for the provided tour, $anchor and $alt_trigger. + * $alt_trigger is an alternative trigger that can consume the step. The tip is + * however only displayed on the $anchor. + * + * @param {Object} tip + * @param {String} tour_name + * @param {jQuery} $anchor + * @param {jQuery} $alt_trigger + * @private + */ + _activate_tip: function(tip, tour_name, $anchor, $alt_trigger) { var tour = this.tours[tour_name]; var tip_info = tip; if (tour.skip_link) { @@ -334,7 +356,7 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, { if (this.running_tour !== tour_name) { tip.widget.on('tip_consumed', this, this._consume_tip.bind(this, tip, tour_name)); } - tip.widget.attach_to($anchor).then(this._to_next_running_step.bind(this, tip, tour_name)); + tip.widget.attach_to($anchor, $alt_trigger).then(this._to_next_running_step.bind(this, tip, tour_name)); }, _deactivate_tip: function(tip) { if (tip && tip.widget) { |
