/*! * 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([ "sap/ui/core/InvisibleText", "sap/base/Log", "./Slider", "./SliderTooltip", "./SliderUtilities", "./RangeSliderRenderer", "sap/ui/thirdparty/jquery" ], function( InvisibleText, log, Slider, SliderTooltip, SliderUtilities, RangeSliderRenderer, jQuery ) { "use strict"; /** * Constructor for a new RangeSlider. * * @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 * Represents a numerical interval and two handles to select a sub-range within it. *

Overview

* The purpose of the control is to enable visual selection of sub-ranges within a given interval. *

Notes:

* * *

Usage

* The most common usecase is to select and move sub-ranges on a continuous numerical scale. * *

Responsive Behavior

* You can move the currently selected range by clicking on it and dragging it along the interval. * @extends sap.m.Slider * * @author SAP SE * @version 1.60.23 * * @constructor * @public * @since 1.38 * @alias sap.m.RangeSlider * @see {@link fiori:https://experience.sap.com/fiori-design-web/range-slider/ Range Slider} * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel */ var RangeSlider = Slider.extend("sap.m.RangeSlider", /** @lends sap.m.RangeSlider.prototype */ { metadata: { library: "sap.m", properties: { /** * Current second value of the slider. (Position of the second handle.) * * Note: If the value is not in the valid range (between min and max) it will be changed to be in the valid range. * If it is smaller than value it will be set to the same value. */ value2: {type: "float", group: "Data", defaultValue: 100}, /** * Determines the currently selected range on the slider. * * If the value is lower/higher than the allowed minimum/maximum, a warning message will be output to the console. */ range: {type: "float[]", group: "Data", defaultValue: [0,100]} }, designtime: "sap/m/designtime/RangeSlider.designtime" } }); RangeSlider.prototype.init = function () { var oStartLabel, oEndLabel, oRangeLabel; Slider.prototype.init.call(this, arguments); // Do not execute "_adjustRangeValue" before all initial setters are finished. // As max, min, range, value and value2 are dependent on each other, // we should be sure that at the first run they are set properly and then to be validated. this._bInitialRangeChecks = true; this._bRTL = sap.ui.getCore().getConfiguration().getRTL(); // the initial focus range which should be used this._aInitialFocusRange = this.getRange(); this._oResourceBundle = sap.ui.getCore().getLibraryResourceBundle('sap.m'); this._ariaUpdateDelay = []; oStartLabel = new InvisibleText({ text: this._oResourceBundle.getText("RANGE_SLIDER_LEFT_HANDLE") }); oEndLabel = new InvisibleText({ text: this._oResourceBundle.getText("RANGE_SLIDER_RIGHT_HANDLE") }); oRangeLabel = new InvisibleText({ text: this._oResourceBundle.getText("RANGE_SLIDER_RANGE_HANDLE") }); // clear Slider's tooltip this.destroyAggregation("_handlesLabels", true); this.addAggregation("_handlesLabels", oStartLabel); this.addAggregation("_handlesLabels", oEndLabel); this.addAggregation("_handlesLabels", oRangeLabel); this._mHandleTooltip = { start: { handle: null, // Handle is provided by the renderer, available onAfterRendering tooltip: null, label: oStartLabel }, end: { handle: null, // Handle is provided by the renderer, available onAfterRendering tooltip: null, label: oEndLabel } }; }; RangeSlider.prototype.exit = function () { this._oResourceBundle = null; this._aInitialFocusRange = null; this._liveChangeLastValue = null; this._mHandleTooltip.start.handle = null; this._mHandleTooltip.start.tooltip = null; this._mHandleTooltip.start.label = null; this._mHandleTooltip.end.handle = null; this._mHandleTooltip.end.tooltip = null; this._mHandleTooltip.end.label = null; this._ariaUpdateDelay = null; }; RangeSlider.prototype.onBeforeRendering = function () { var aRange = this.getRange(); if (this.getShowAdvancedTooltip()) { this.initAndSyncTooltips(["leftTooltip", "rightTooltip"]); this._storeTooltipsMetadata(); } // At this point it's certain that all setters are executed and values of // min, max, value, value2 and range are set properly and are not using the Default values. // It's important however to keep the slider values within the boundaries defined by min and max. // Executing once again the range setter would adjust values accordingly. It should not matter if we do: // this.setRange(aRange) OR this.setValue(fValue) && this.setValue2(fValue2). // Note: this.getRange() is intended to have the same value as [this.getValue(), this.getValue2()] this._bInitialRangeChecks = false; // We need the decimal precision in order to be able to set the correct values. // It is well known that JavaScript has issues with handling floating point values. // E.g. 0.0001 + 0.0002 = 0.00029999999999999998 this._iDecimalPrecision = this.getDecimalPrecisionOfNumber(this.getStep()); this.setRange(aRange); this._validateProperties(); // set the correct scale aggregation, if needed this._syncScaleUsage(); }; RangeSlider.prototype.onAfterRendering = function () { Slider.prototype.onAfterRendering.apply(this, arguments); var aRange = this.getRange(); this._mHandleTooltip.start.handle = this.getDomRef("handle1"); this._mHandleTooltip.end.handle = this.getDomRef("handle2"); this._recalculateStyles(); // Setting the handles to the Start and the End points of the provided or the default range this._updateHandle(this._mHandleTooltip.start.handle, aRange[0]); this._updateHandle(this._mHandleTooltip.end.handle, aRange[1]); //Swap tooltips so when range is with reversed values e.g. [12, 1] //to have properly updated tooltips if (this.getShowAdvancedTooltip() && (aRange[0] > aRange[1])) { this._swapTooltips(aRange); } }; RangeSlider.prototype._storeTooltipsMetadata = function () { var aTooltips = this.getUsedTooltips(); // Attach tooltips if (!this._mHandleTooltip.start.tooltip) { this._mHandleTooltip.start.tooltip = aTooltips[0]; } if (!this._mHandleTooltip.end.tooltip) { this._mHandleTooltip.end.tooltip = aTooltips[1]; } this._mHandleTooltip.bTooltipsSwapped = false; // Reset tooltips swapping }; /** * Recalculates the progress range and updates the progress indicator styles. * @private */ RangeSlider.prototype._recalculateRange = function () { var aHandlesLeftOffset, sStart, sEnd, oProgressIndicator, sSide = this._bRTL ? "right" : "left"; aHandlesLeftOffset = [ parseFloat(this._mHandleTooltip.start.handle.style[sSide]), parseFloat(this._mHandleTooltip.end.handle.style[sSide]) ]; sStart = Math.min.apply(Math, aHandlesLeftOffset) + "%"; sEnd = (100 - Math.max.apply(Math, aHandlesLeftOffset)) + "%"; oProgressIndicator = this.getDomRef("progress"); if (this._bRTL) { oProgressIndicator.style.left = sEnd; oProgressIndicator.style.right = sStart; } else { oProgressIndicator.style.left = sStart; oProgressIndicator.style.right = sEnd; } }; /** * Gets the closest to the oEvent x coordinate handle dom element. * @param {jQuery.Event} oEvent The event object * @returns {HTMLElement} The handle, from which the event comes from. * @private * @override */ RangeSlider.prototype.getClosestHandleDomRef = function (oEvent) { var oHandle1 = this._mHandleTooltip.start.handle, oHandle2 = this._mHandleTooltip.end.handle, fPageXCalc = Math.abs(oEvent.pageX - oHandle1.offsetLeft - this._fSliderPaddingLeft - this._fSliderOffsetLeft), fClientXCalc = Math.abs(oEvent.clientX - oHandle2.offsetLeft - this._fSliderPaddingLeft - this._fSliderOffsetLeft); return fPageXCalc > fClientXCalc ? oHandle2 : oHandle1; }; RangeSlider.prototype._getIndexOfHandle = function (oHandle) { if (oHandle && oHandle.getAttribute && oHandle.getAttribute("data-range-val") === "start") { return 0; } else if (oHandle && oHandle.getAttribute && oHandle.getAttribute("data-range-val") === "end") { return 1; } else { return -1; } }; /** * Gets a handle corresponding to a tooltip * @param {sap.m.SliderTooltipBase} oTooltip Slider/Range slider tooltip * @sap-restricted sap.m.SliderTooltipContainer.js * @returns {HTMLElement} The handle, from which the tooltip is responsible. * @private */ RangeSlider.prototype._getHandleForTooltip = function (oTooltip) { var oHandle = oTooltip === this._mHandleTooltip.start.tooltip ? this._mHandleTooltip.start.handle : this._mHandleTooltip.end.handle; return oHandle; }; /** * Updates the handle with the given new value and recalculates the progress indicator * @param {HTMLElement} oHandle The handle that should be updated * @param {float} fValue The new value of the handle * @private */ RangeSlider.prototype._updateHandle = function (oHandle, fValue) { var oTooltip = (this._mHandleTooltip.start.handle === oHandle) ? this._mHandleTooltip.start.tooltip : this._mHandleTooltip.end.tooltip, aRange = this.getRange(), iIndex = this._getIndexOfHandle(oHandle), fPercentVal = this._getPercentOfValue(fValue); aRange[iIndex] = fValue; this._updateRangePropertyDependencies(aRange); this._updateHandleDom(oHandle, aRange, iIndex, fValue, fPercentVal); if (this.getShowAdvancedTooltip()) { this._updateTooltipContent(oTooltip, fValue); this._adjustTooltipsContainer(); } this._recalculateRange(); }; RangeSlider.prototype._updateHandleDom = function (oHandle, aRange, iIndex, sValue, fPercentVal) { var bMergedRanges, sCssClass = this.getRenderer().CSS_CLASS, oFormInput = this.getDomRef("input"); if (!!this.getName()) { oFormInput.setAttribute(oHandle.getAttribute("data-range-val"), this.toFixed(aRange[iIndex], this._iDecimalPrecision)); oFormInput.setAttribute("value", this.getValue()); } if (this._bRTL) { oHandle.style.right = fPercentVal + "%"; } else { oHandle.style.left = fPercentVal + "%"; } if (this.getShowHandleTooltip() && !this.getShowAdvancedTooltip()) { oHandle.title = this._formatValueByCustomElement(sValue); } bMergedRanges = aRange[0] === aRange[1]; this.$("handle1").toggleClass(sCssClass + "HandleOverlap", bMergedRanges); this.$("handle2").toggleClass(sCssClass + "HandleOverlap", bMergedRanges); // ARIA updates. Delay the update to prevent multiple updates- for example holding the arrow key. // We need only the latest state clearTimeout(this._ariaUpdateDelay[iIndex]); this._ariaUpdateDelay[iIndex] = setTimeout(this["_updateHandleAria"].bind(this, oHandle, sValue), 100); }; RangeSlider.prototype._updateHandleAria = function (oHandle, sValue) { var aRange = this.getRange(), oProgressHandle = this.getDomRef("progress"), fNormalizedValue = this.toFixed(sValue, this._iDecimalPrecision), sScaleLabel = this._formatValueByCustomElement(fNormalizedValue); aRange[0] = this.toFixed(aRange[0], this._iDecimalPrecision); aRange[1] = this.toFixed(aRange[1], this._iDecimalPrecision); this._updateHandlesAriaLabels(); this._updateHandleAriaAttributeValues(oHandle, sValue, sScaleLabel); if (oProgressHandle) { oProgressHandle.setAttribute("aria-valuenow", aRange.join("-")); oProgressHandle.setAttribute("aria-valuetext", this._oResourceBundle.getText('RANGE_SLIDER_RANGE_ANNOUNCEMENT', aRange.map(this._formatValueByCustomElement, this))); } }; /** * Updates handles' ARIA Labels. * When handles are swapped, the corresponding labels should be swapped too. So the state would be always acurate * * @private */ RangeSlider.prototype._updateHandlesAriaLabels = function () { var aRange = this.getRange(), oTempLabel = this._mHandleTooltip.start.label; if ((aRange[0] > aRange[1] && !this._mHandleTooltip.bAriaHandlesSwapped) || (aRange[0] < aRange[1] && this._mHandleTooltip.bAriaHandlesSwapped)) { this._mHandleTooltip.start.label = this._mHandleTooltip.end.label; this._mHandleTooltip.end.label = oTempLabel; if (this._mHandleTooltip.start.handle) { this._mHandleTooltip.start.handle.setAttribute("aria-labelledby", this._mHandleTooltip.start.label.getId()); } if (this._mHandleTooltip.end.handle) { this._mHandleTooltip.end.handle.setAttribute("aria-labelledby", this._mHandleTooltip.end.label.getId()); } this._mHandleTooltip.bAriaHandlesSwapped = !this._mHandleTooltip.bAriaHandlesSwapped; } }; /** * Updates the handle's tooltip value * @param {Object} oTooltip The tooltip object. * @param {float} fNewValue The new value * @private */ RangeSlider.prototype._updateTooltipContent = function (oTooltip, fNewValue) { var sNewValue = this.toFixed(fNewValue, this._iDecimalPrecision); oTooltip.setValue(parseFloat(sNewValue)); }; RangeSlider.prototype._swapTooltips = function (aRange) { var oTempTooltip = this._mHandleTooltip.start.tooltip; if ((aRange[0] >= aRange[1] && !this._mHandleTooltip.bTooltipsSwapped) || (aRange[0] <= aRange[1] && this._mHandleTooltip.bTooltipsSwapped)) { this._mHandleTooltip.start.tooltip = this._mHandleTooltip.end.tooltip; this._mHandleTooltip.end.tooltip = oTempTooltip; this._updateTooltipContent(this._mHandleTooltip.start.tooltip, aRange[0]); this._updateTooltipContent(this._mHandleTooltip.end.tooltip, aRange[1]); // After swapping tooltips, also some accessibility properties should be updated if (this.getInputsAsTooltips()) { this._mHandleTooltip.start.handle.setAttribute("aria-controls", this._mHandleTooltip.start.tooltip.getId()); this._mHandleTooltip.end.handle.setAttribute("aria-controls", this._mHandleTooltip.end.tooltip.getId()); } this._mHandleTooltip.bTooltipsSwapped = !this._mHandleTooltip.bTooltipsSwapped; } }; RangeSlider.prototype._adjustTooltipsContainer = function () { var oTooltipsContainer = this.getAggregation("_tooltipContainer"); if (!oTooltipsContainer.getDomRef()) { return; } oTooltipsContainer.repositionTooltips(this.getMin(), this.getMax()); this._swapTooltips(this.getRange()); }; /** * Gets the tooltips that should be shown. * Returns custom tooltips if provided and more than 1 else default tooltips * * @protected * @override * @returns {sap.m.SliderTooltipBase[]} SliderTooltipBase instances. */ RangeSlider.prototype.getUsedTooltips = function () { var aCustomTooltips = this.getCustomTooltips(), aDefaultTooltips = this.getAggregation("_defaultTooltips") || []; return aCustomTooltips.length > 1 ? aCustomTooltips : aDefaultTooltips; }; /** * Handles changes in Tooltip Inputs * @param {jQuery.Event} oEvent The event object * @private * @override */ RangeSlider.prototype.handleTooltipChange = function (oEvent) { this.updateTooltipsPositionAndState(oEvent.getSource(), Number(oEvent.getParameter("value"))); }; /** * Updates values of RangeSlider and repositions tooltips. * * @param {string} oTooltip Tooltip to be changed * @param {float} fValue New value of the RangeSlider * @sap-restricted sap.m.SliderTooltipBase * @private */ RangeSlider.prototype.updateTooltipsPositionAndState = function (oTooltip, fValue) { var oHandle, oActiveTooltip, bTooltipsInitialPositionTouched = this._mHandleTooltip.bTooltipsSwapped; fValue = this._adjustRangeValue(fValue); oHandle = this._mHandleTooltip.start.tooltip === oTooltip ? this._mHandleTooltip.start.handle : this._mHandleTooltip.end.handle; this._updateHandle(oHandle, fValue); // When tooltips are swapped, we should put the focus to the corresponding visual representation if (bTooltipsInitialPositionTouched !== this._mHandleTooltip.bTooltipsSwapped) { oActiveTooltip = this._mHandleTooltip.start.tooltip !== oTooltip ? this._mHandleTooltip.start.tooltip : this._mHandleTooltip.end.tooltip; oActiveTooltip.focus(); } this._fireChangeAndLiveChange({ range: this.getRange() }); this.updateAdvancedTooltipDom(); }; RangeSlider.prototype._updateDOMAfterSetters = function (fValue, aRange, iHandleIndex) { var fPercentOfValue, oHandle; if (this.getDomRef()) { fPercentOfValue = this._getPercentOfValue(fValue); oHandle = iHandleIndex === 1 ? this._mHandleTooltip.end : this._mHandleTooltip.start; this._updateHandleDom(oHandle.handle, aRange, iHandleIndex, fValue, fPercentOfValue); if (this.getShowAdvancedTooltip()) { this._updateTooltipContent(oHandle.tooltip, fValue); } return true; } return false; }; RangeSlider.prototype.setRange = function (aRange) { aRange = aRange.map(this._adjustRangeValue, this); this._updateRangePropertyDependencies(aRange); if (this._updateDOMAfterSetters(aRange[0], aRange, 0) && this._updateDOMAfterSetters(aRange[1], aRange, 1)) { this._recalculateRange(); } return this; }; RangeSlider.prototype.setStep = function (fStep) { //Log warning in case fStep is not valid this._validateProperties(); //Get the new decimal precision this._iDecimalPrecision = this.getDecimalPrecisionOfNumber(fStep); // invalidate as setStep is not called regularly return this.setProperty("step", fStep); }; RangeSlider.prototype.setValue = function (fValue) { var aRange = this.getRange(); // validate the new value before arithmetic calculations if (typeof fValue !== "number" || !isFinite(fValue)) { return this; } fValue = this._adjustRangeValue(fValue); aRange[0] = fValue; this._updateRangePropertyDependencies(aRange); if (this._updateDOMAfterSetters(aRange[0], aRange, 0)) { this._recalculateRange(); } return this; }; RangeSlider.prototype.setValue2 = function (fValue) { var aRange = this.getRange(); fValue = this._adjustRangeValue(fValue); aRange[1] = fValue; this._updateRangePropertyDependencies(aRange); if (this._updateDOMAfterSetters(aRange[1], aRange, 1)) { this._recalculateRange(); } return this; }; RangeSlider.prototype._updateRangePropertyDependencies = function (aRange) { var aRangeCopy = Array.isArray(aRange) ? aRange.slice() : [], iDecimal = this._iDecimalPrecision ? this._iDecimalPrecision : 0, fNewValue = Number(aRangeCopy[0].toFixed(iDecimal)), fNewValue2 = Number(aRangeCopy[1].toFixed(iDecimal)); if (this.getProperty("value") !== fNewValue) { this.setProperty("value", fNewValue, true); aRangeCopy[0] = fNewValue; } if (this.getProperty("value2") !== fNewValue2) { this.setProperty("value2", fNewValue2, true); aRangeCopy[1] = fNewValue2; } this.setProperty("range", aRangeCopy, true); }; /** * Calculates the value for the handle of the RangeSlider based on the passed raw pageX event coordinates. * This will be later transformed to % of the total width of the RangeSlider in order for the handle to be * updated accordingly. * @param {float} fValue The new raw value of the handle. * @returns {float} The new calculated value of the handle. * @private */ RangeSlider.prototype._calculateHandlePosition = function (fValue) { var fMax = this.getMax(), fMin = this.getMin(), fNewValue; fNewValue = ((fValue - this._fSliderPaddingLeft - this._fSliderOffsetLeft) / this._fSliderWidth) * (fMax - fMin) + fMin; // RTL mirror if (this._bRTL) { fNewValue = this._convertValueToRtlMode(fNewValue); } return this._adjustRangeValue(fNewValue); }; /** * Checks and adjusts value according to Min and Max properties and checks RTL mode * * @param {float} fValue Value to be checked and adjusted * @returns {float} Adjusted value * @private */ RangeSlider.prototype._adjustRangeValue = function (fValue) { var fMax = this.getMax(), fMin = this.getMin(), fStep = this.getStep(), fModStepVal; if (this._bInitialRangeChecks) { return fValue; } fModStepVal = Math.abs((fValue - fMin) % fStep); if (fModStepVal !== 0 /* division with remainder */) { // snap the new value to the nearest step fValue = fModStepVal * 2 >= fStep ? fValue + fStep - fModStepVal : fValue - fModStepVal; } if (fValue < fMin) { log.warning("Warning: " + "Min value (" + fValue + ") not in the range: [" + fMin + "," + fMax + "]", this); fValue = fMin; } else if (fValue > fMax) { log.warning("Warning: " + "Max value (" + fValue + ") not in the range: [" + fMin + "," + fMax + "]", this); fValue = fMax; } return fValue; }; /** * Handle the touchstart event happening on the range slider. * @param {jQuery.Event} oEvent The event object. * @private * @override */ RangeSlider.prototype.ontouchstart = function (oEvent) { var oTouch = oEvent.targetTouches[0], CSS_CLASS = this.getRenderer().CSS_CLASS, sEventNamespace = "." + CSS_CLASS, fMinValue, fMaxValue, fValue, aHandles, aRange, iHandleIndex, fHandlesDistance, oFocusItem, fTotalNumberOfValues, fPercentOfHandle, fHandleValue, fHandleHalfWidth; if (!this.getEnabled()) { return; } // mark the event for components that needs to know if the event was handled oEvent.setMarked(); // Should be prevent as in Safari while dragging the handle everything else gets selection. // As part of the RangeSlider, Inputs in the tooltips should be excluded if (oEvent.target.className.indexOf("sapMInput") === -1) { oEvent.preventDefault(); } // we need to recalculate the styles since something may have changed // the screen size between touches. this._recalculateStyles(); if (["number", "text"].indexOf(oEvent.target.type) > -1) { return; } fValue = this._calculateHandlePosition(oTouch.pageX); aRange = this.getRange(); aHandles = [this._mHandleTooltip.start.handle, this._mHandleTooltip.end.handle]; iHandleIndex = this._getIndexOfHandle(oEvent.target); fHandlesDistance = aHandles.reduce(function (fAccumulation, oHandle) { return Math.abs(fAccumulation - oHandle.offsetLeft); }, 0); fMinValue = Math.min.apply(Math, aRange); fMaxValue = Math.max.apply(Math, aRange); // half width of a handle (both are equal) fHandleHalfWidth = this.$("handle1").outerWidth() / 2; // total number of possible values fTotalNumberOfValues = Math.abs(this.getMin()) + Math.abs(this.getMax()); // percents that half a handle takes from the width of the scale fPercentOfHandle = ((fHandleHalfWidth * 100) / this.$("inner").outerWidth()); // number of values that takes half a handle fHandleValue = (fPercentOfHandle / 100) * fTotalNumberOfValues; // if the click is outside the range or distance between handles is below the threshold - update the closest slider handle if (fValue < fMinValue || fValue < fMinValue + fHandleValue || fValue > fMaxValue || fValue > (fMaxValue - fHandleValue) || fHandlesDistance <= SliderUtilities.CONSTANTS.RANGE_MOVEMENT_THRESHOLD) { aHandles = [this.getClosestHandleDomRef(oTouch)]; this._updateHandle(aHandles[0], fValue); // _updateHandle would update the range and the check for change event fire would fail in _ontouchend this._fireChangeAndLiveChange({range: this.getRange()}); } else if (iHandleIndex !== -1) { // Determine if the press event is on certain handle aHandles = [this.getDomRef(iHandleIndex === 0 ? "handle1" : "handle2")]; } // registers event listeners jQuery(document) .on("touchend" + sEventNamespace + " touchcancel" + sEventNamespace + " mouseup" + sEventNamespace, this._ontouchend.bind(this, aHandles)) .on("touchmove" + sEventNamespace + (oEvent.originalEvent.type !== "touchstart" ? " mousemove" + sEventNamespace : ""), this._ontouchmove.bind(this, fValue, this.getRange(), aHandles)); // adds pressed state aHandles.map(function (oHandle) { if (oHandle.className.indexOf(CSS_CLASS + "HandlePressed") === -1) { oHandle.className += " " + CSS_CLASS + "HandlePressed"; } }); oFocusItem = aHandles.length === 1 ? aHandles[0] : this.getDomRef("progress"); setTimeout(oFocusItem["focus"].bind(oFocusItem), 0); }; /** * Handle the touchmove event happening on the slider. * @param {Int} [fInitialPointerPosition] Mouse pointer's initial position * @param {Int} [aInitialRange] Initial range array * @param {HTMLElement} [aHandles] The handle that should be updated * @param {jQuery.Event} oEvent The event object. * @private * @override */ RangeSlider.prototype._ontouchmove = function (fInitialPointerPosition, aInitialRange, aHandles, oEvent) { var fOffset, bRangesEquality, bRangeInBoudaries, bRangeOnBoudaries, iPageX = oEvent.targetTouches ? oEvent.targetTouches[0].pageX : oEvent.pageX, fMax = this.getMax(), fMin = this.getMin(), aRange = [], aRangeTemp = []; // note: prevent native document scrolling oEvent.preventDefault(); // mark the event for components that needs to know if the event was handled oEvent.setMarked(); // suppress the emulated mouse event from touch interfaces if (oEvent.isMarked("delayedMouseEvent") || !this.getEnabled() || // detect which mouse button caused the event and only process the standard click // (this is usually the left button, oEvent.button === 0 for standard click) // note: if the current event is a touch event oEvent.button property will be not defined oEvent.button) { return; } //calculation of the new range based on the mouse position fOffset = this._calculateHandlePosition(iPageX) - fInitialPointerPosition; for (var i = 0; i < aInitialRange.length; i++) { aRange[i] = aInitialRange[i] + fOffset; } aRangeTemp = this._getNormalizedRange(this.getRange(), aInitialRange, aHandles); //check if the current range is equal to the new one bRangesEquality = aRange.every(function (fValue, iIndex) {return fValue === aRangeTemp[iIndex];}); bRangeInBoudaries = aRange.every(function (fValue) {return (fValue >= fMin && fValue <= fMax );}); bRangeOnBoudaries = aRangeTemp.indexOf(fMin) > -1 || aRangeTemp.indexOf(fMax) > -1; if (!bRangesEquality) { //check the need to update the handle depending of number of the selected handles and the handles position if ((aHandles.length === 1) || bRangeInBoudaries || !bRangeOnBoudaries) { aHandles.map(function (oHandle) {this._updateHandle(oHandle, aInitialRange[this._getIndexOfHandle(oHandle)] + fOffset);}, this); } // adjust container if advanced tooltips have to be shown this.getShowAdvancedTooltip() && this._adjustTooltipsContainer(); aRangeTemp = this._getNormalizedRange(this.getRange(), aInitialRange, aHandles); } this._triggerLiveChange(); this.setRange(aRangeTemp); }; /** * Updates values of the advanced tooltips. * * @param {string} sNewValue The new value * @protected */ RangeSlider.prototype.updateAdvancedTooltipDom = function () { this.getAggregation("_tooltipContainer").repositionTooltips(this.getMin(), this.getMax()); }; RangeSlider.prototype._triggerLiveChange = function () { var bFireLiveChange, aRange = this.getRange(); this._liveChangeLastValue = this._liveChangeLastValue || []; bFireLiveChange = aRange.some(function (fValue, index) { return fValue !== this._liveChangeLastValue[index]; }, this); if (bFireLiveChange) { this._liveChangeLastValue = aRange.slice(); //Save a copy, not a reference this.fireLiveChange({range: aRange}); } }; /** * Get the range normalized in the boundaries. * @param {Array} aRange range value * @param {Array} aInitialRange last range values * @param {HTMLElement} [aHandles] The handles of the slider * @returns {number} The normalized range * @private * @override */ RangeSlider.prototype._getNormalizedRange = function (aRange, aInitialRange, aHandles) { var fMax = this.getMax(), fMin = this.getMin(), iSelectedRange = Math.abs(aInitialRange[0] - aInitialRange[1]), aRangeNormalized = [], i, iOtherElementIndex; for (i = 0; i < aRange.length; i++) { aRangeNormalized[i] = (aRange[i] < fMin ? fMin : aRange[i]); aRangeNormalized[i] = (aRange[i] > fMax ? fMax : aRangeNormalized[i]); if (aHandles.length === 2) { if (aRangeNormalized[0] == fMin) { aRangeNormalized[1] = aRangeNormalized[0] + iSelectedRange; } else { iOtherElementIndex = Math.abs(i - 1); aRangeNormalized[iOtherElementIndex] = (aRangeNormalized[i] <= fMin ? aRangeNormalized[i] + iSelectedRange : aRangeNormalized[iOtherElementIndex]); aRangeNormalized[iOtherElementIndex] = (aRangeNormalized[i] >= fMax ? aRangeNormalized[i] - iSelectedRange : aRangeNormalized[iOtherElementIndex]); } } } return aRangeNormalized; }; /** * Handle the touchend event happening on the slider. * @param {HTMLElement} aHandle The handle that should be updated * @param {jQuery.Event} oEvent The event object. * @private * @override */ RangeSlider.prototype._ontouchend = function (aHandle, oEvent) { var aNewRange = this.getRange(), sCSSClass = this.getRenderer().CSS_CLASS; // mark the event for components that needs to know if the event was handled oEvent.setMarked(); aHandle && aHandle.map(function (oHandle) { oHandle.className = oHandle.className.replace(new RegExp(" ?" + sCSSClass + "HandlePressed", "gi"), ""); }); jQuery(document).off("." + sCSSClass); this._recalculateRange(); if (this._aInitialFocusRange[0] !== aNewRange[0] || this._aInitialFocusRange[1] !== aNewRange[1]) { this._aInitialFocusRange = Array.prototype.slice.call(aNewRange); this.fireChange({ range: aNewRange }); } if (this.getShowAdvancedTooltip()) { this._updateTooltipContent(this._mHandleTooltip.start.tooltip, aNewRange[0]); this._updateTooltipContent(this._mHandleTooltip.end.tooltip, aNewRange[1]); } }; /** * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onfocusin = function (oEvent) { var oTooltipsContainer = this.getAggregation("_tooltipContainer"); if (this.getShowAdvancedTooltip()) { oTooltipsContainer.show(this); this._adjustTooltipsContainer(); } // remember the initial focus range so when esc key is pressed we can return to it if (!(document.activeElement === this.getFocusDomRef())) { this._aInitialFocusRange = this.getRange(); } }; RangeSlider.prototype.getFocusDomRef = function() { return this.getDomRef("progress"); }; /* ----------------------------------------------------------- */ /* Keyboard handling */ /* ----------------------------------------------------------- */ /** * Moves handle / entire slider by specified offset * * @param {float} fOffset Default value: 1. Increase or decrease value by provided offset * @param {HTMLElement} oHandle DOM reference to the handle * @private */ RangeSlider.prototype._updateSliderValues = function (fOffset, oHandle) { var aRange = this.getRange(), fMax = this.getMax(), fMin = this.getMin(), fRangeMax = Math.max.apply(null, aRange), fRangeMin = Math.min.apply(null, aRange), iIndex = this._getIndexOfHandle(oHandle), iOffsetSign = fOffset < 0 ? -1 : 1, aHandles = iIndex > -1 ? [oHandle] : [this._mHandleTooltip.start.handle, this._mHandleTooltip.end.handle]; // If this is a single handle, both values should be equal if (aHandles.length === 1) { fRangeMin = fRangeMax = aRange[iIndex]; } // Check the boundaries and recalculate the offset if exceeding if (fRangeMax + fOffset > fMax) { fOffset = iOffsetSign * (Math.abs(fMax) - Math.abs(fRangeMax)); } else if (fRangeMin + fOffset < fMin) { fOffset = iOffsetSign * (Math.abs(fRangeMin) - Math.abs(fMin)); } aHandles.map(function (oCurHandle) { this._updateHandle(oCurHandle, aRange[this._getIndexOfHandle(oCurHandle)] + fOffset); }, this); }; RangeSlider.prototype.onkeydown = function (oEvent) { var bFocusableTooltip = this.getInputsAsTooltips(), bShowAdvancedTooltips = this.getShowAdvancedTooltip(), bF2Pressed = oEvent.keyCode === SliderUtilities.CONSTANTS.F2_KEYCODE, bStartTooltipFocused = (oEvent.target === this._mHandleTooltip.start.handle), bTargetIsHandle = jQuery(oEvent.target).hasClass(SliderUtilities.CONSTANTS.HANDLE_CLASS); if (bF2Pressed && bShowAdvancedTooltips && bFocusableTooltip && bTargetIsHandle) { this._mHandleTooltip[bStartTooltipFocused ? "start" : "end"].tooltip.focus(); } }; /** * Handles the sapincrease event when right arrow or up arrow is pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsapincrease = function (oEvent) { // note: prevent document scrolling when arrow keys are pressed oEvent.preventDefault(); // mark the event for components that needs to know if the event was handled oEvent.setMarked(); if (this.getEnabled()) { this._updateSliderValues(this.getStep(), oEvent.target); this._fireChangeAndLiveChange({range: this.getRange()}); } }; /** * Handles the onsapplus event when "+" is pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsapplus = RangeSlider.prototype.onsapincrease; /** * Handles the sapincreasemodifiers event when Ctrl + right arrow or up arrow are pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsapincreasemodifiers = function (oEvent) { if (oEvent.altKey) { return; } // note: prevent document scrolling when arrow keys are pressed oEvent.preventDefault(); oEvent.stopPropagation(); // mark the event for components that needs to know if the event was handled oEvent.setMarked(); if (this.getEnabled()) { this._updateSliderValues(this._getLongStep(), oEvent.target); this._fireChangeAndLiveChange({range: this.getRange()}); } }; /** * Handles the sappageup event when page up is pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsappageup = RangeSlider.prototype.onsapincreasemodifiers; /** * Handles the sapdecrease event when left arrow or down arrow are pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsapdecrease = function (oEvent) { // note: prevent document scrolling when arrow keys are pressed oEvent.preventDefault(); // mark the event for components that needs to know if the event was handled oEvent.setMarked(); if (this.getEnabled()) { this._updateSliderValues(-1 * this.getStep(), oEvent.target); this._fireChangeAndLiveChange({range: this.getRange()}); } }; /** * Handles the sapminus event when "-" is pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsapminus = RangeSlider.prototype.onsapdecrease; /** * Handles the sapdecreasemodifiers event when Ctrl + left or Ctrl + down keys are pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsapdecreasemodifiers = function (oEvent) { if (oEvent.altKey) { return; } // note: prevent document scrolling when arrow keys are pressed oEvent.preventDefault(); oEvent.stopPropagation(); // mark the event for components that needs to know if the event was handled oEvent.setMarked(); if (this.getEnabled()) { this._updateSliderValues(-1 * this._getLongStep(), oEvent.target); this._fireChangeAndLiveChange({range: this.getRange()}); } }; /** * Handles the sappagedown event when when page down is pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsappagedown = RangeSlider.prototype.onsapdecreasemodifiers; /** * Handles the saphome event when home key is pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsaphome = function (oEvent) { var iHandleIndex = 0, fRangeValue, oHandle, fMin; // mark the event for components that needs to know if the event was handled oEvent.setMarked(); // note: prevent document scrolling when Home key is pressed oEvent.preventDefault(); iHandleIndex = this._getIndexOfHandle(oEvent.target); fRangeValue = this.getRange()[iHandleIndex]; fMin = this.getMin(); if (this.getEnabled() && (fRangeValue !== fMin)) { oHandle = (iHandleIndex === 1 ? this._mHandleTooltip.end : this._mHandleTooltip.start); this._updateHandle(oHandle.handle, fMin); this._fireChangeAndLiveChange({range: this.getRange()}); } }; /** * Handles the sapend event when the End key pressed. * * @param {jQuery.Event} oEvent The event object. * @override */ RangeSlider.prototype.onsapend = function (oEvent) { // mark the event for components that needs to know if the event was handled oEvent.setMarked(); // note: prevent document scrolling when End key is pressed oEvent.preventDefault(); if (this.getEnabled()) { this._updateSliderValues(this.getMax(), oEvent.target); this._fireChangeAndLiveChange({range: this.getRange()}); } }; /** * Handles the sapescape event when escape key is pressed. * @override */ RangeSlider.prototype.onsapescape = function () { // reset the slider back to the value // which it had when it got the focus this.setRange(this._aInitialFocusRange); this._fireChangeAndLiveChange({range: this.getRange()}); }; return RangeSlider; });