/*! * UI development toolkit for HTML5 (OpenUI5) * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ './library', './SliderUtilities', 'sap/ui/core/Control', 'sap/ui/core/Popup', './SliderTooltipContainerRenderer', "sap/ui/thirdparty/jquery" ], function( Library, SliderUtilities, Control, Popup, SliderTooltipContainerRenderer, jQuery ) { "use strict"; /** * Constructor for a new SliderTooltipCotainer. * * @param {string} [sId] ID for the new control, generated automatically if no ID is given * @param {object} [mSettings] Initial settings for the new control * * @class * A Popup based control helper for Slider and RangeSlider controls * It serves as container of Slider/RangeSlider tooltips which is placed inside the static area * * @extends sap.ui.core.Control * * @author SAP SE * @version 1.60.23 * * @constructor * @private * @since 1.54 * @alias sap.m.SliderTooltipContainer */ var SliderTooltipContainer = Control.extend("sap.m.SliderTooltipContainer", /** @lends sap.m.SliderTooltipContainer.prototype */ { metadata: { library: "sap.m", properties: { /** * Indicates whether the user can change values of tooltips. */ enabled: { type: "boolean", group: "Behavior", defaultValue: true }, /** * Defines the width of the control. */ width: { type: "sap.ui.core.CSSSize", group: "Appearance", defaultValue: "0px" } }, associations: { /** * Multiple Association for Tooltips * * @since 1.54 */ associatedTooltips: { type: "sap.m.SliderTooltipBase", multiple: true } } } }); SliderTooltipContainer.prototype.init = function () { this.oPopup = new Popup(); this.oPopup.setShadow(false); this.oPopup.setAutoClose(false); // scroll listener for updating tooltip container position this._scrollListener = this._getScrollListener(); // indicates that the tooltips has been closed after overflowing this._bClosedFromOverflow = false; // indicates whether RTL is switched on this._bRtl = sap.ui.getCore().getConfiguration().getRTL(); }; SliderTooltipContainer.prototype._handleTabNavigation = function (oEvent) { var bParentRangeSlider = this._oParentSlider instanceof sap.m.RangeSlider; oEvent.preventDefault(); this[bParentRangeSlider ? "_handleRangeSliderF2" : "_handleSliderF2"].apply(this, arguments); }; SliderTooltipContainer.prototype._handleSliderF2 = function () { this._oParentSlider.focus(); }; SliderTooltipContainer.prototype._handleRangeSliderF2 = function (oEvent) { var oHandle = this._oParentSlider._getHandleForTooltip(oEvent.srcControl); jQuery(oHandle).focus(); }; SliderTooltipContainer.prototype.onsaptabnext = SliderTooltipContainer.prototype._handleTabNavigation; SliderTooltipContainer.prototype.onsaptabprevious = SliderTooltipContainer.prototype._handleTabNavigation; SliderTooltipContainer.prototype.onkeydown = function (oEvent) { if (oEvent.keyCode === SliderUtilities.CONSTANTS.F2_KEYCODE) { this._handleTabNavigation(oEvent); } }; /** * Places the Container in the static area. * * @param {object} oControl This is the Slider to which the Container will be placed. * @public */ SliderTooltipContainer.prototype.show = function (oControl) { this.oPopup.setContent(this); this._$ParentSlider = oControl.$(); this._oParentSlider = oControl; this.oPopup.open(0, Popup.Dock.BeginTop, Popup.Dock.BeginTop, this._$ParentSlider, '0 -24', 'flip'); document.addEventListener("scroll", this._scrollListener, true); }; /** * Gets the reposition function of the tooltips. * @private */ SliderTooltipContainer.prototype._getScrollListener = function () { return function () { clearTimeout(this._scrollDebounce); this._scrollDebounce = setTimeout(this.repositionTooltips.bind(this), 0); }.bind(this); }; /** * Hides the SliderTooltipContainer. * @public */ SliderTooltipContainer.prototype.hide = function () { this.oPopup.close(); document.removeEventListener("scroll", this._scrollListener, true); }; /** * Repositions tooltips depending on the Slider/RangeSlider values. * @public */ SliderTooltipContainer.prototype.repositionTooltips = function () { var bParentRangeSlider = this._oParentSlider instanceof sap.m.RangeSlider, aTooltips = this._oParentSlider.getUsedTooltips(), // we are considering that both tooltips have the same rendering fTooltipHeight = this.getAssociatedTooltipsAsControls()[0].$().outerHeight(true); if (this.getDomRef()) { this[bParentRangeSlider ? "_positionRangeTooltips" : "_positionTooltip"].call(this, aTooltips, arguments[0], arguments[1]); this.getDomRef().style["top"] = (this._$ParentSlider.offset().top - fTooltipHeight) + "px"; this._handleOverflow(); } }; /** * Repositions tooltips depending on the Slider value. * @param {array} aTooltips Array representing Slider's tooltip aggregation. * @param {float} fMin Min property of the Slider. * @param {float} fMax Max property of the Slider. * @private */ SliderTooltipContainer.prototype._positionTooltip = function (aTooltips, fMin, fMax) { var sTooltipPosition = this._getTooltipPosition(aTooltips[0].getValue(), fMin, fMax), sAdjustProperty = this._bRtl ? "right" : "left"; if (sTooltipPosition) { this.getDomRef().children[0].style[sAdjustProperty] = sTooltipPosition; } }; SliderTooltipContainer.prototype._handleOverflow = function () { var oPopupRef = this.getDomRef(), oScrollableParentRef, bScrolledIntoView; if (oPopupRef) { oScrollableParentRef = SliderUtilities.getElementScrollableParent(this._$ParentSlider[0].parentNode); bScrolledIntoView = SliderUtilities.isScrolledIntoView(this._$ParentSlider[0], oScrollableParentRef); if (!bScrolledIntoView) { this._bClosedFromOverflow = true; this.hide(); } } }; /** * Repositions tooltips depending on the RangeSlider values. * @param {array} aTooltips Array representing RangeSlider's tooltip aggregation. * @param {float} fMin Min property of the RangeSlider. * @param {float} fMax Max property of the RangeSlider. * @private */ SliderTooltipContainer.prototype._positionRangeTooltips = function (aTooltips, fMin, fMax) { var bRtl = this._bRtl, sAdjustPropertyStart = bRtl ? "right" : "left", sAdjustPropertyEnd = bRtl ? "left" : "right", aRange = this._oParentSlider.getRange(), fStartPct = SliderUtilities.getPercentOfValue(aRange[0] > aRange[1] ? aRange[1] : aRange[0], fMin, fMax), fEndPct = SliderUtilities.getPercentOfValue(aRange[0] > aRange[1] ? aRange[0] : aRange[1], fMin, fMax), iTooltipWidth = this.getAssociatedTooltipsAsControls()[0].$().outerWidth(), iStartValue = SliderUtilities.getPercentOfValue(+(aTooltips[0].getValue()), fMin, fMax), iEndValue = SliderUtilities.getPercentOfValue(+(aTooltips[1].getValue()), fMin, fMax), $Progress = this._oParentSlider.$("progress"), $Container = this.$("container"), iSliderWidth = this._$ParentSlider.width(), bOverlapped = false, iTooltipInputWidth = iTooltipWidth - SliderUtilities.CONSTANTS.TOOLTIP_SIDE_PADDING, iCorrection = (((iTooltipInputWidth + SliderUtilities.CONSTANTS.CHARACTER_WIDTH_PX) / 2) / iSliderWidth) * 100, fSticking = fStartPct - (iCorrection) - (iCorrection * 2 - (fEndPct - fStartPct)) / 2, oCSSObject = { "min-width": (2 * iTooltipWidth) + (SliderUtilities.CONSTANTS.TOOLTIP_BORDER * 2) + "px" }, oSliderOffset; // calculate centered tooltips over handles oCSSObject[sAdjustPropertyStart] = "calc(" + iStartValue + "%" + " - " + ((iTooltipWidth / 2) - SliderUtilities.CONSTANTS.HANDLE_HALF_WIDTH) + "px)"; oCSSObject[sAdjustPropertyEnd] = "calc(" + (100 - iEndValue) + "% " + "- " + (iTooltipWidth - ((iTooltipWidth / 2) - SliderUtilities.CONSTANTS.HANDLE_HALF_WIDTH - SliderUtilities.CONSTANTS.TOOLTIP_BORDER)) + "px)"; // the tooltips are overlapped if ($Progress.outerWidth() <= (iTooltipWidth / 2) + (iTooltipWidth - SliderUtilities.CONSTANTS.HANDLE_HALF_WIDTH)) { oCSSObject[sAdjustPropertyStart] = "calc(" + fSticking + "%" + " + " + SliderUtilities.CONSTANTS.HANDLE_HALF_WIDTH + "px)"; bOverlapped = true; } // update Container's css so we can check edge cases later $Container.css(oCSSObject); // save Slider's offset after initial position is applied oSliderOffset = this._$ParentSlider.offset(); // tooltip sticked to the end if (($Container.offset().left + $Container.outerWidth()) > (oSliderOffset.left + this._$ParentSlider.outerWidth())) { oCSSObject = this[bRtl ? "_getStickedToStart" : "_getStickedToEnd"].call(this, oCSSObject, sAdjustPropertyStart, sAdjustPropertyEnd, bOverlapped); } // tooltip sticked to the left if (($Container.offset().left <= oSliderOffset.left)) { oCSSObject = this[bRtl ? "_getStickedToEnd" : "_getStickedToStart"].call(this, oCSSObject, sAdjustPropertyStart, sAdjustPropertyEnd, bOverlapped); } // apply finaly position $Container.css(oCSSObject); }; SliderTooltipContainer.prototype._getStickedToStart = function (oCSSObject, sAdjustPropertyStart) { oCSSObject[sAdjustPropertyStart] = "0"; return oCSSObject; }; SliderTooltipContainer.prototype._getStickedToEnd = function (oCSSObject, sAdjustPropertyStart, sAdjustPropertyEnd, bOverlapped) { var iTooltipWidth = this.getAssociatedTooltipsAsControls()[0].$().outerWidth(); oCSSObject[sAdjustPropertyEnd] = "calc(0% - " + (2 * SliderUtilities.CONSTANTS.HANDLE_HALF_WIDTH) + "px)"; // left attached to the right(end) if (bOverlapped) { oCSSObject[sAdjustPropertyStart] = "calc(100% - " + (iTooltipWidth + (iTooltipWidth - 2 * SliderUtilities.CONSTANTS.HANDLE_HALF_WIDTH)) + "px)"; } return oCSSObject; }; /** * Gets Slider's tooltip position. * * @param {float} fValue * @param {float} fMin Min property of the Slider/RangeSlider. * @param {float} fMax Max property of the Slider/RangeSlider. * @private * @return {String} */ SliderTooltipContainer.prototype._getTooltipPosition = function (fTooltipValue, fMin, fMax) { var fPerValue = SliderUtilities.getPercentOfValue(+(fTooltipValue), fMin, fMax), iTooltipWidth = this.getAssociatedTooltipsAsControls()[0].$().outerWidth(), iSliderWidth = this._$ParentSlider.outerWidth(), fSidePaddingPercent = (100 * SliderUtilities.CONSTANTS.SLIDER_SIDE_PADDING) / iSliderWidth, fTooltipPercent = ((100 * iTooltipWidth) / iSliderWidth); if (fPerValue + fSidePaddingPercent < (fTooltipPercent / 2)) { // attached to the left corner return "0"; } else if (fPerValue - fSidePaddingPercent > 100 - (fTooltipPercent / 2)) { // attached to the right corner return "calc(100% - " + (iTooltipWidth - (SliderUtilities.CONSTANTS.HANDLE_HALF_WIDTH * 2)) + "px)"; } else { // normal centered tooltip return "calc(" + fPerValue + "% - " + ((iTooltipWidth / 2) - SliderUtilities.CONSTANTS.HANDLE_HALF_WIDTH) + "px)"; } }; /** * Sets the width of the SliderTooltipContainer. * @param {sap.ui.core.CSSSize} sWidth The width of the SliderTooltipContainer as CSS size. * @returns {sap.m.SliderTooltipContainer} Pointer to the control instance to allow method chaining. * @public */ SliderTooltipContainer.prototype.setWidth = function (sWidth) { if (this.getDomRef()) { this.$().width(sWidth); } return this.setProperty("width", sWidth, true); }; SliderTooltipContainer.prototype.getAssociatedTooltipsAsControls = function () { var aAssociatedTooltips = this.getAssociation("associatedTooltips") || []; return aAssociatedTooltips.map(function(sTooltipId) { return sap.ui.getCore().byId(sTooltipId); }); }; SliderTooltipContainer.prototype.onmouseout = function (oEvent) { var bSliderFocused = jQuery.contains(this._oParentSlider.getDomRef(), document.activeElement), bContainerFocused = jQuery.contains(this.getDomRef(), document.activeElement), bToSlider = jQuery.contains(this._oParentSlider.getDomRef(), oEvent.toElement), bToTooltipContainer = jQuery.contains(this.getDomRef(), oEvent.toElement); if (bSliderFocused || bContainerFocused || bToSlider || bToTooltipContainer) { return; } this.hide(); }; SliderTooltipContainer.prototype.onfocusout = function (oEvent) { if (jQuery.contains(this._$ParentSlider[0], oEvent.relatedTarget) || jQuery.contains(this.getDomRef(), oEvent.relatedTarget)) { return; } if (this._bClosedFromOverflow) { this._oParentSlider.focus(); this._bClosedFromOverflow = false; } this.hide(); }; SliderTooltipContainer.prototype.onBeforeRendering = function () { this._bRtl = sap.ui.getCore().getConfiguration().getRTL(); }; SliderTooltipContainer.prototype.exit = function () { this._oParentSlider = null; this._$ParentSlider = null; document.removeEventListener("scroll", this._scrollListener, true); }; return SliderTooltipContainer; });