UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

233 lines (211 loc) 10.1 kB
/*! * OpenUI5 * (c) Copyright 2026 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/Element' ], function (library, SliderUtilities, Element) { "use strict"; /** * Constructor for a new <code>ResponsiveScale</code>. * * @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 * <strong><i>Overview</i></strong> * * A {@link sap.m.Slider} element represents a scale with tickmarks and labels. * The purpose of the element is to decouple the scale logic from other controls i.e. Slider / RangeSlider * * * The most important properties of the ResponsiveScale are: * <ul> * <li> tickmarksBetweenLabels - Puts a label on every N-th tickmark.</li> * </ul> * * * @extends sap.ui.core.Element * * @author SAP SE * @version 1.146.0 * * @constructor * @public * @since 1.46 * @alias sap.m.ResponsiveScale */ var Scale = Element.extend("sap.m.ResponsiveScale", { metadata: { interfaces: [ "sap.m.IScale" ], library: "sap.m", properties: { /** * Put a label on every N-th tickmark. */ tickmarksBetweenLabels: {type: "int", group: "Appearance", defaultValue: 0} } } }); /** * How many tickmarks could be placed on the axis/scale? * * * @param {float} fSize - Size of the scale. This is the distance between the start and end point i.e. 0..100 * @param {float} fStep - The step walking from start to end. * @param {int} iTickmarksThreshold - Limits the number of tickmarks. * * @public * @returns {number} The max number of possible tickmarks */ Scale.prototype.calcNumberOfTickmarks = function (fSize, fStep, iTickmarksThreshold) { var iMaxPossible = Math.floor(fSize / fStep) + 1; //How many tickmarks would be there if we show one for each step? iMaxPossible = iTickmarksThreshold && (iMaxPossible > iTickmarksThreshold) ? this._runStepsOptimization(iTickmarksThreshold, iMaxPossible - 1) : iMaxPossible; return Math.floor(iMaxPossible); }; /** * This method is run when there's a risk for the ratio (maxPossibleTickmarks / customNumberOfTickmarksProvided) * would not be an integer. * The result of this method still might not be an integer, but at least there's a try to optimize the space, * so the tickmarks would look like in the best case. * * @param {int} iTickmarksCount The number of tickmarks * @param {int} iMaxPossibleTickmarks The maximum number of tickmarks * @returns {int} Yhe optimized number of tickmarks * @private */ Scale.prototype._runStepsOptimization = function (iTickmarksCount, iMaxPossibleTickmarks) { var iStepsCount = iMaxPossibleTickmarks / iTickmarksCount; // Loop until the iStepsCount is an integer. // That way tickmarks would be distributed in the best way while ((iTickmarksCount > 1) && (iStepsCount % 1 !== 0)) { iTickmarksCount--; iStepsCount = iMaxPossibleTickmarks / iTickmarksCount; } return iTickmarksCount; }; /** * Computes which tickmarks with labels should get hidden. * Returns a flagged array with the indices of those tickmarks: * <code>[0: undefined, 1: true, 2: undefined, 3: true, 4: undefined, 5: true, 6: undefined]</code> * * The algorithm to determine which labels should get hidden is: * 1) Check if there's enough space for all the labels. Which means that the distance between labels * should not be less than labels' width. That width is defined in {_CONSTANTS.TICKMARKS.MIN_SIZE.WITH_LABEL}. * 2) Start walking from both edges of the scale to the center and remove every second <b>visible</b> label. * 3) Move the pointer to the next starting visible node and repeat from 1) if there's still not enough space. * * <b>Note:</b> The starting point is always an integer which is on power of 2. For example: 1, 2, 4, 8, 16, 32, etc. * The implementation of 2) (the walk step/jump) is also an integer which is on power of 2. For example if we start from position * <b>1</b> (the second element in the array), we'll visit the following nodes: 1, 3, 5, 7, 9, etc. * Then if we move the starting pointer to the next visible node, its index (it's 2) is integer on power of 2, * the jump would become <b>4</b>: 2, 6, 10, 14, 18, 22, etc. Remember that we want to remove every second <b>visible</b> * node and 1, 3, 5, 7, 9 are already invisible. So, if we continue in that manner, we'll always move the starting * pointer to the next integer which is on power of 2 and the jump step would be the next integer which is on power of 2. * For example: * * Initial array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] * #1: Start: 1, Jump: 2: [0, X, 2, X, 4, X, 6, X, 8, X, 10, X, 12, X, 14, X, 16] * #2: Start: 2, Jump: 4: [0, X, X, X, 4, X, X, X, 8, X, X, X, 12, X, X, X, 16] * #3: Start: 4, Jump: 8: [0, X, X, X, X, X, X, X, 8, X, X, X, X, X, X, X, 16] * #4: Start: 8, Jump: 16: [0, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, 16] * * @param {int} iScaleWidth - Width in Px of the scale * @param {int} iTotalLabelsCount - Number of tickmarks with labels * @param {float} fOffsetLeftPx - The distance in Px between the first and the second label * @param {int} iLabelsMinDistance - The minimum distance between two labels * @returns {Array} A flagged array with the indices of the hidden tickmarks * @private */ Scale.prototype.getHiddenTickmarksLabels = function (iScaleWidth, iTotalLabelsCount, fOffsetLeftPx, iLabelsMinDistance) { var iJumpStep, iCurPos, aHiddenLabelsIndices = new Array(iTotalLabelsCount), // How many labels should get hidden, so there would be enough space. // There's min distance design size limits of {TICKMARKS.MIN_SIZE.WITH_LABEL} between the labels iStartPosition = Math.ceil(1 / (fOffsetLeftPx / iLabelsMinDistance)), fnCalcJumpStep = function (iPos) { // iPos^2 === iPos * iPos === iPos << 1 return iPos << 1; }; if (iStartPosition === 1) { return aHiddenLabelsIndices; } // The code bellow, with all the bitwise operators, just finds the next higher integer which is power of 2. // There are easier ways to achieve this, but not in constant time. // The starting position must be always an integer which is power of 2: 1, 2, 4, 8, 16, 32 iStartPosition--; iStartPosition |= iStartPosition >> 1; iStartPosition |= iStartPosition >> 2; iStartPosition |= iStartPosition >> 4; iStartPosition |= iStartPosition >> 8; iStartPosition |= iStartPosition >> 16; iStartPosition++; // Already found the next higher integer, but we need the previous one iStartPosition = iStartPosition >> 1; // This is the step to walk over the items. // It's a function of iStartPosition which is equal to iStartPosition^2 iJumpStep = fnCalcJumpStep(iStartPosition); while (iStartPosition) { iCurPos = iStartPosition; // Go from the current position to the middle of array while (iCurPos < ((iTotalLabelsCount / 2) + iJumpStep)) { aHiddenLabelsIndices[iCurPos] = true; // Go from left to right aHiddenLabelsIndices[iTotalLabelsCount - iCurPos - 1] = true; // Go from right to left. // Increase with the jump step iCurPos += iJumpStep; } // Decrease to the lower position until it gets 0. // Using just (iStartPosition / 2) won't work as (iStartPosition === 1) should be also considered. iStartPosition = iStartPosition >> 1; iJumpStep = fnCalcJumpStep(iStartPosition); } return aHiddenLabelsIndices; }; /** * Shows/hides tickmarks when some size limits are met. * Implements responsiveness of the tickmarks. * * @param {jQuery.Event} oEvent The event object passed. * @private * @ui5-restricted sap.m.Slider */ Scale.prototype.handleResize = function (oEvent) { var aLabelsInDOM, fOffsetLeftPct, fOffsetLeftPx, aHiddenLabels, oSiblingTickmark, $oSlider = oEvent.control.$(), aTickmarksInDOM = $oSlider.find(".sapMSliderTick"), iScaleWidth = $oSlider.find(".sapMSliderTickmarks").width(), bShowTickmarks = (iScaleWidth / aTickmarksInDOM.length) >= SliderUtilities.CONSTANTS.TICKMARKS.MIN_SIZE.SMALL; //Small tickmarks should get hidden if their width is less than _SliderUtilities.CONSTANTS.TICKMARKS.MIN_SIZE.SMALL aTickmarksInDOM.css("visibility", bShowTickmarks ? '' /* visible */ : 'hidden'); // Tickmarks with labels responsiveness aLabelsInDOM = $oSlider.find(".sapMSliderTickLabel"); // The distance between the first and second label in % of Scale's width fOffsetLeftPct = parseFloat(aLabelsInDOM[1].style.left); // Convert to PX fOffsetLeftPx = iScaleWidth * fOffsetLeftPct / 100; // Get which labels should become hidden aHiddenLabels = this.getHiddenTickmarksLabels(iScaleWidth, aLabelsInDOM.length, fOffsetLeftPx, SliderUtilities.CONSTANTS.TICKMARKS.MIN_SIZE.WITH_LABEL); aLabelsInDOM.each(function (iIndex, oElem) { // All the labels are positioned prior the corresponding tickmark, except for the last label. // That's why we're using the previousSibling property oSiblingTickmark = oElem.nextSibling || oElem.previousSibling || {style: {visibility: null}}; // As tickmarks are separated from the lables, we should ensure that if a label is visible, // the corresponding tickmark should be visible too and vice versa. if (aHiddenLabels[iIndex] && !bShowTickmarks) { oElem.style.display = "none"; oSiblingTickmark.style.visibility = 'hidden'; //visible- inherit from CSS } else { oElem.style.display = ""; //inline-block- inherit from CSS oSiblingTickmark.style.visibility = ''; //visible- inherit from CSS } }); }; return Scale; });