UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,302 lines (1,152 loc) 41 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([ "./TimePickerInternals", "./TimePickerClock", "./ToggleButton", "./SegmentedButton", "./SegmentedButtonItem", "./TimePickerClocksRenderer", "./ToggleButtonRenderer", "sap/ui/core/Renderer", "sap/ui/events/KeyCodes", 'sap/ui/Device', 'sap/ui/core/library', "sap/ui/thirdparty/jquery", 'sap/ui/core/date/UI5Date' ], function( TimePickerInternals, TimePickerClock, ToggleButton, SegmentedButton, SegmentedButtonItem, TimePickerClocksRenderer, ToggleButtonRenderer, Renderer, KeyCodes, Device, coreLibrary, jQuery, UI5Date ) { "use strict"; var TYPE_COOLDOWN_DELAY = 1000; // Cooldown delay; 0 = disabled cooldown // shortcut for sap.ui.core.TextDirection var TextDirection = coreLibrary.TextDirection; /** * Constructor for a new <code>TimePickerClocks</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 * A picker clocks container control used inside the {@link sap.m.TimePicker}. * If you use the control standalone, please call the {@link #prepareForOpen} method before opening or displaying it. * @extends sap.ui.core.Control * * @author SAP SE * @version 1.146.0 * * @constructor * @public * @since 1.90 * @alias sap.m.TimePickerClocks */ var TimePickerClocks = TimePickerInternals.extend("sap.m.TimePickerClocks", /** @lends sap.m.TimePickerClocks.prototype */ { metadata : { properties: { /** * When set to <code>true</code>, the clock will be displayed without the animation. */ skipAnimation: {type: "boolean", group: "Misc", defaultValue: false} }, aggregations: { /** * Holds the inner buttons. */ _buttons: { type: "sap.m.internal.ToggleSpinButton", multiple: true, visibility: "hidden" }, /** * Holds the inner clocks. */ _clocks: { type: "sap.m.TimePickerClock", multiple: true, visibility: "hidden" } } }, renderer: TimePickerClocksRenderer }); /********************************************************************************************************* * ToggleSpinButton Control and Renderer*/ var ToggleSpinButtonRenderer = Renderer.extend(ToggleButtonRenderer); ToggleSpinButtonRenderer.apiVersion = 2; /** * Renders the HTML for the given control, using the provided * {@link sap.ui.core.RenderManager}. * * @param {sap.ui.core.RenderManager} oRm * the RenderManager that can be used for writing to * the Render-Output-Buffer * @param {sap.ui.core.Control} oButton * the button to be rendered */ ToggleSpinButtonRenderer.render = function(oRm, oButton) { // get control properties var sButtonId = oButton.getId(), sType = oButton.getType(), bEnabled = oButton.getEnabled(), sWidth = oButton.getWidth(), sTooltip = oButton._getTooltip(), sText = oButton._getText(), sTextDir = oButton.getTextDirection(), bRenderBDI = (sTextDir === TextDirection.Inherit); // start button tag oRm.openStart("div", oButton); oRm.class("sapMBtnBase"); oRm.class("sapMBtn"); //ARIA attributes var mAccProps = this.generateAccProps(oButton); mAccProps["pressed"] = null; mAccProps["valuemin"] = oButton.getMin(); mAccProps["valuemax"] = oButton.getMax(); mAccProps["valuenow"] = parseInt(oButton.getText()); mAccProps["label"] = sTooltip; mAccProps["valuetext"] = parseInt(oButton.getText()).toString() + " " + sTooltip; mAccProps["role"] = "spinbutton"; oRm.accessibilityState(oButton, mAccProps); // check if the button is disabled if (!bEnabled) { oRm.attr("disabled", "disabled"); oRm.class("sapMBtnDisabled"); } // set user defined width if (sWidth != "" || sWidth.toLowerCase() === "auto") { oRm.style("width", sWidth); oRm.style("min-width", "2.25rem"); } // set tooltip if (sTooltip) { oRm.attr("title", sTooltip); } // set tabindex oRm.attr("tabindex", "0"); // close button tag oRm.openEnd(); // start inner button tag oRm.openStart("span", sButtonId + "-inner"); // button style class oRm.class("sapMBtnInner"); // check if button is hoverable if (oButton._isHoverable()) { oRm.class("sapMBtnHoverable"); } // check if button is focusable (not disabled) if (bEnabled) { oRm.class("sapMFocusable"); } if (sText) { oRm.class("sapMBtnText"); } if (oButton.getPressed()) { oRm.class("sapMToggleBtnPressed"); } // set button specific styles if (sType !== "") { // set button specific styles oRm.class("sapMBtn" + sType); } // close inner button tag oRm.openEnd(); // write button text if (sText) { oRm.openStart("span", sButtonId + "-content"); oRm.class("sapMBtnContent"); // check if textDirection property is not set to default "Inherit" and add "dir" attribute if (sTextDir !== TextDirection.Inherit) { oRm.attr("dir", sTextDir.toLowerCase()); } oRm.openEnd(); if (bRenderBDI) { oRm.openStart("bdi", sButtonId + "-BDI-content"); oRm.openEnd(); } oRm.text(sText); if (bRenderBDI) { oRm.close("bdi"); } oRm.close("span"); } // end inner button tag oRm.close("span"); // add tooltip if available if (sTooltip) { oRm.openStart("span", sButtonId + "-tooltip"); oRm.class("sapUiInvisibleText"); oRm.openEnd(); oRm.text(sTooltip); oRm.close("span"); } // end button tag oRm.close("div"); }; var ToggleSpinButton = ToggleButton.extend("sap.m.internal.ToggleSpinButton", { metadata: { library: "sap.m", properties: { label: {type : "string", defaultValue : ""}, min: {type: "int", defaultValue: 0}, max: {type: "int", defaultValue: 0} } }, renderer: ToggleSpinButtonRenderer }); /********************************************************************************************************/ /** * Initializes the control. * * @public */ TimePickerClocks.prototype.init = function() { TimePickerInternals.prototype.init.apply(this, arguments); this._performInitialFocus = false; }; /** * After rendering. * * @private */ TimePickerClocks.prototype.onAfterRendering = function() { if (!this._clickAttached) { this._attachClickEvent(); } this._clockConstraints = this._getClocksConstraints(); if (this._performInitialFocus) { this._focusActiveButton(); } }; /** * Keyup event handler - used to handle Ctrl and Space keys releasing. * * @param {object} oEvent keydown event * @private */ TimePickerClocks.prototype.onkeyup = function(oEvent) { var iKey = oEvent.which || oEvent.keyCode; if (iKey === KeyCodes.CONTROL) { oEvent.preventDefault(); if (this._clockIndexes.H === this._getActiveClockIndex() && this.getSupport2400() && this._ctrlKeyDown === 1) { this._getActiveClock()._toggle2400(true)._markToggleAsSelected(false); } this._ctrlKeyDown = 0; // 0 = Ctrl is released, 1 = Ctrl is pressed, 2 = Ctrl key down flag must be reset due to value change } else if ( iKey === KeyCodes.SPACE) { this._spaceKeyDown = false; } }; /** * Keydown event handler - used to handle entering of numbers to set as value of currently selected clock. * * @param {object} oEvent keydown event * @private */ TimePickerClocks.prototype.onkeydown = function(oEvent) { var iKey = oEvent.which || oEvent.keyCode, iChar = oEvent.key, aClocks = this.getAggregation("_clocks"), oClock = this._getActiveClock(), iActiveClock = this._getActiveClockIndex(), bSupport2400 = this.getSupport2400(), aNumbersAndColon = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":"], oAmPm, iHoursVisible, iValue, sBuffer = "", iBuffer, iIndex, sIndex, iMatching = 0, iValueMatching = -1, bEventTargetOverButtons = oEvent.srcControl && oEvent.srcControl.getMetadata().getName().indexOf("ToggleSpinButton") === -1 ? false : true; if (this._clockIndexes.H === iActiveClock && bSupport2400 && iKey === KeyCodes.CONTROL && !this._ctrlKeyDown) { oEvent.preventDefault(); this._ctrlKeyDown = 1; // 0 = Ctrl is released, 1 = Ctrl is pressed, 2 = Ctrl key down flag must be reset due to value change oClock._toggle2400(true)._markToggleAsSelected(true); } else if (iKey === KeyCodes.ENTER) { // check if the ENTER is pressed over the hours/minutes/seconds Buttons and return if it is not if (!bEventTargetOverButtons) { return; } // otherwise close the popover and accept the selected time if (typeof this._parentAcceptCallback === "function") { this._parentAcceptCallback(); } } else if ((iKey === KeyCodes.ARROW_UP || iKey === KeyCodes.ARROW_DOWN) && !oEvent.altKey && !oEvent.metaKey) { // Arrows up/down increase/decrease currently active clock oClock && oClock.getEnabled() && oClock.modifyValue(iKey === KeyCodes.ARROW_UP); oEvent.preventDefault(); } else if ((iKey === KeyCodes.PAGE_UP || iKey === KeyCodes.PAGE_DOWN) && !oEvent.altKey && !oEvent.metaKey) { oEvent.preventDefault(); if (!oEvent.shiftKey && !oEvent.ctrlKey) { // Hours oClock = this._getHoursClock(); } else if (oEvent.shiftKey && !oEvent.ctrlKey) { // Minutes oClock = this._getMinutesClock(); } else { // Seconds oClock = this._getSecondsClock(); } oClock && oClock.getEnabled() && oClock.modifyValue(iKey === KeyCodes.PAGE_UP); oClock && this._switchClock(this._getClockIndex(oClock), true); } else if (iKey === KeyCodes.P || iKey === KeyCodes.A) { // AM/PM oEvent.preventDefault(); oAmPm = this._getFormatButton(); if (oAmPm) { oAmPm.setSelectedKey(iKey === KeyCodes.P ? "pm" : "am"); oAmPm.fireSelectionChange(); } } else if (iKey === KeyCodes.SPACE && !this._spaceKeyDown) { // check if the SPACE is pressed over the hours/minutes/seconds Buttons and return if it is not if (!bEventTargetOverButtons) { return; } oEvent.preventDefault(); this._spaceKeyDown = true; iValue = oClock.getSelectedValue(); iHoursVisible = oClock._get24HoursVisible() ? 24 : 0; if (this._clockIndexes.H === iActiveClock && bSupport2400 && iValue !== iHoursVisible && (iValue === 24 || iValue === 0)) { oClock.setSelectedValue(iHoursVisible); } this._kbdBuffer = ""; this._resetCooldown(true); setTimeout(function() { this._switchNextClock(true, true); }.bind(this), 0); } else if (aNumbersAndColon.indexOf(iChar) !== -1) { // direct number enter this._exactMatch = null; this._resetCooldown(true); if (iChar === ":") { this._kbdBuffer = ""; this._resetCooldown(true); this._switchNextClock(true, true); } else if (this._clockConstraints[iActiveClock]) { sBuffer = this._kbdBuffer + iChar; iBuffer = parseInt(sBuffer); if (this._clockConstraints[iActiveClock].step === 1) { // when the step=1, there is "direct" approach - while typing, the exact value is selected if (iBuffer > this._clockConstraints[iActiveClock].max) { // value accumulated in the buffer (old entry + new entry) is greater than the clock maximum value, // so assign old entry to the current clock and then switch to the next clock, and add new entry as an old value aClocks[iActiveClock].setSelectedValue(parseInt(this._kbdBuffer)); this._switchNextClock(false, true); this._kbdBuffer = iChar; iActiveClock = this._getActiveClockIndex(); aClocks[iActiveClock].setSelectedValue(parseInt(iChar)); this._resetCooldown(true); } else { // value is less than clock's max value, so add new entry to the buffer this._kbdBuffer = sBuffer; aClocks[iActiveClock].setSelectedValue(parseInt(this._kbdBuffer)); if (this._kbdBuffer.length === 2 || parseInt(this._kbdBuffer + "0") > this._clockConstraints[iActiveClock].max) { // if buffer length is 2, or buffer value + one more (any) number is greater than clock's max value // there is no place for more entry - just set buffer as a value, and switch to the next clock this._resetCooldown(this._kbdBuffer.length === 2 ? false : true); this._kbdBuffer = ""; this._switchNextClock(false, true); } } } else { // when the step is > 1, while typing, the exact match is searched, otherwise the first value that starts with entered value, is being selected // find matches for (iIndex = this._clockConstraints[iActiveClock].min; iIndex <= this._clockConstraints[iActiveClock].max; iIndex++) { if (iIndex % this._clockConstraints[iActiveClock].step === 0) { sIndex = iIndex.toString(); if (sBuffer === sIndex.substr(0, sBuffer.length) || iBuffer === iIndex) { iMatching++; iValueMatching = iMatching === 1 ? iIndex : -1; if (iBuffer === iIndex) { this._exactMatch = iIndex; } } } } if (iMatching === 1) { // only one item is matching aClocks[iActiveClock].setSelectedValue(iValueMatching); this._exactMatch = null; this._kbdBuffer = ""; this._resetCooldown(true); this._switchNextClock(false, true); } else if (sBuffer.length === 2) { // no matches, but 2 numbers are entered, start again this._exactMatch = null; this._kbdBuffer = ""; this._resetCooldown(true); } else { // no match, add last number to buffer this._kbdBuffer = sBuffer; } } } } }; /** * Sets the value of the <code>TimePickerClocks</code> container. * * @param {string} sValue The value of the <code>TimePickerClocks</code> * @returns {this} Pointer to the control instance to allow method chaining * @public */ TimePickerClocks.prototype.setValue = function (sValue) { var oHoursClock = this._getHoursClock(), sFormat = this._getValueFormatPattern(), iIndexOfHH = sFormat.indexOf("HH"), iIndexOfH = sFormat.indexOf("H"), bHoursClockValueIs24 = oHoursClock && oHoursClock.getSelectedValue() === 24, bHoursValueIs24 = TimePickerInternals._isHoursValue24(sValue, iIndexOfHH, iIndexOfH), oDate; if (bHoursClockValueIs24 && this._isFormatSupport24() && !bHoursValueIs24) { sValue = TimePickerInternals._replaceZeroHoursWith24(sValue, iIndexOfHH, iIndexOfH); } sValue = this.validateProperty("value", sValue); this.setProperty("value", sValue, true); // no rerendering // convert to date object if (sValue) { oDate = this._parseValue(bHoursValueIs24 ? TimePickerInternals._replace24HoursWithZero(sValue, iIndexOfHH, iIndexOfH) : sValue); } if (oDate) { this._setTimeValues(oDate, bHoursValueIs24); } return this; }; /** * Gets the time values from the clocks, as a date object. * * @returns {Date|module:sap/ui/core/date/UI5Date} A date instance * @public */ TimePickerClocks.prototype.getTimeValues = function() { var oHoursClock = this._getHoursClock(), oMinutesClock = this._getMinutesClock(), oSecondsClock = this._getSecondsClock(), oFormatButton = this._getFormatButton(), iHours = null, sAmpm = null, oDateValue = UI5Date.getInstance(); if (oHoursClock) { iHours = parseInt(oHoursClock.getSelectedValue()); } if (oFormatButton) { sAmpm = oFormatButton.getSelectedKey(); } if (sAmpm === "am" && iHours === 12) { iHours = 0; } else if (sAmpm === "pm" && iHours !== 12) { iHours += 12; } if (iHours !== null) { oDateValue.setHours(iHours.toString()); } if (oMinutesClock) { oDateValue.setMinutes(oMinutesClock.getSelectedValue()); } if (oSecondsClock) { oDateValue.setSeconds(oSecondsClock.getSelectedValue()); } return oDateValue; }; /** * Prepare the control for opening. * If there are already clock and button objects created, set their appearance-related properties. * * @returns {this} Pointer to the control instance to allow method chaining * @public */ TimePickerClocks.prototype.prepareForOpen = function() { const aClocks = this.getAggregation("_clocks"), aButtons = this.getAggregation("_buttons"); this.setSkipAnimation(true); if (aClocks.length) { this._activeClock = 0; aClocks.forEach((oClock, iIndex) => { oClock.setSkipAnimation(iIndex === 0).setFadeOut(false).setFadeIn(iIndex === 0); aButtons[iIndex].setPressed(iIndex === 0); }); } this._performInitialFocus = true; return this; }; /* * PRIVATE API */ /** * Attaches click events to the clocks control. * * @private */ TimePickerClocks.prototype._attachClickEvent = function() { var oElement = this.getDomRef(); oElement.addEventListener("click", jQuery.proxy(this._focusActiveButton, this), false); this._clickAttached = true; }; /** * Returns focus to the recently focused input in order to keep entering of numbers. * * @param {Event} oEvent the click event * @private */ TimePickerClocks.prototype._focusActiveButton = function(oEvent) { var aButtons = this.getAggregation("_buttons"), iActiveClock = this._getActiveClockIndex(), oAmPmButton = this._getFormatButton(); // Don't refocus if click originated from AM/PM button if (oEvent && oAmPmButton && oEvent.target) { var oAmPmDomRef = oAmPmButton.getDomRef(); if (oAmPmDomRef && oAmPmDomRef.contains(oEvent.target)) { return; } } aButtons && aButtons[iActiveClock] && aButtons[iActiveClock].focus(); this._performInitialFocus = false; }; /** * Displays the first available clock and focus its buttom. * @private */ TimePickerClocks.prototype._showFirstClock = function() { var aButtons = this.getAggregation("_buttons"), iActiveClock = 0; if (aButtons && aButtons[iActiveClock]) { aButtons[iActiveClock].setPressed(true); this._switchClock(iActiveClock, true); aButtons[iActiveClock].focus(); this._activeClock = iActiveClock; } }; /** * An instance of a callback that is called after accepting the selected value. * * @private */ TimePickerClocks.prototype._setAcceptCallback = function(oCallback) { this._parentAcceptCallback = oCallback; }; /** * Clears the currently existing cooldown period and starts new one if requested. * * @param {boolean} bStartNew whether to start new cooldown period after clearing previous one * @private */ TimePickerClocks.prototype._resetCooldown = function(bStartNew) { if (TYPE_COOLDOWN_DELAY === 0) { return; // if delay is 0, cooldown is disabled } if (this._typeCooldownId) { clearTimeout(this._typeCooldownId); } if (bStartNew) { this._startCooldown(); } }; /** * Starts new cooldown period. * * @private */ TimePickerClocks.prototype._startCooldown = function() { if (TYPE_COOLDOWN_DELAY === 0) { return; // if delay is 0, cooldown is disabled } this._typeCooldownId = setTimeout(function() { this._kbdBuffer = ""; this._typeCooldownId = null; if (this._exactMatch) { this._getActiveClock().setSelectedValue(this._exactMatch); this._exactMatch = null; } }.bind(this), TYPE_COOLDOWN_DELAY); }; /** * Switches to the next clock that can de focused. * * @param {boolean} bWrapAround whether to start with first clock after reaching the last one, or not * @param {boolean} bSkipAnimation whether to skip the animation or not * @private */ TimePickerClocks.prototype._switchNextClock = function(bWrapAround, bSkipAnimation) { let iActiveClock = this._getActiveClockIndex(); const aClocks = this.getAggregation("_clocks"), iClocksCount = aClocks.length, oActiveClock = this._getActiveClock(), iStartActiveClock = iActiveClock; if (!aClocks) { return; } if (this._clockIndexes.H === iActiveClock && this.getSupport2400()) { oActiveClock._save2400State(); if (oActiveClock.getSelectedValue() === 24) { return; // the rest of the clocks are disabled, so do nothing } } do { iActiveClock++; if (iActiveClock >= iClocksCount) { iActiveClock = bWrapAround ? 0 : iClocksCount - 1; } // false-positive finding of no-unmodified-loop-condition rule // eslint-disable-next-line no-unmodified-loop-condition } while (!oActiveClock.getEnabled() && iActiveClock !== iStartActiveClock && (bWrapAround || iActiveClock < iClocksCount - 1)); this._ctrlKeyDown = 0; // 0 = Ctrl is released, 1 = Ctrl is pressed, 2 = Ctrl key down flag must be reset due to value change if (iActiveClock !== iStartActiveClock && aClocks[iActiveClock].getEnabled()) { this._switchClock(iActiveClock, bSkipAnimation); } }; /** * Get some useful constraints of clocks. * * @returns {array} an array with constraints for each clock; each clock constraints object contain its min, max, step and prependZero properties * @private */ TimePickerClocks.prototype._getClocksConstraints = function() { var aClocks = this.getAggregation("_clocks"), bSupport2400 = this.getSupport2400(), aConstraints = [], iMin, iMax, iStep, iReplacement, iIndex; if (aClocks) { for (iIndex = 0; iIndex < aClocks.length; iIndex++) { iMin = aClocks[iIndex].getItemMin(); iMax = aClocks[iIndex].getItemMax(); iStep = aClocks[iIndex].getValueStep(); iReplacement = aClocks[iIndex].getLastItemReplacement(); if (iReplacement !== -1 && iReplacement < iMin) { iMin = iReplacement; if (iMax !== 24 || !bSupport2400) { iMax--; } } else if (iMax === 24 && bSupport2400) { iMin = 0; } aConstraints[iIndex] = {min: iMin, max: iMax, step: iStep, prependZero: aClocks[iIndex].getPrependZero()}; } } return aConstraints; }; /** * Returns the index of the active clock. * * @returns {int} Index of the active clock * @private */ TimePickerClocks.prototype._getActiveClockIndex = function() { return this._activeClock || 0; }; /** * Returns the active clock. * * @returns {sap.m.TimePickerClock} active clock object * @private */ TimePickerClocks.prototype._getActiveClock = function() { var iActiveClock = this._getActiveClockIndex(), aClocks = this.getAggregation("_clocks"); return aClocks && aClocks[iActiveClock] ? aClocks[iActiveClock] : null; }; /** * Set what clocks show. * * @param {object} oDate date instance * @param {boolean} bHoursValueIs24 whether the hours value is 24 or not * @private */ TimePickerClocks.prototype._setTimeValues = function(oDate, bHoursValueIs24) { var oHoursClock = this._getHoursClock(), oMinutesClock = this._getMinutesClock(), oSecondsClock = this._getSecondsClock(), oMinutesButton = this._getMinutesButton(), oSecondsButton = this._getSecondsButton(), oFormatButton = this._getFormatButton(), sValueFormat = this.getValueFormat(), iHours, sAmPm = null, bTwelveHourFormatDueToB = !this._isFormatSupport24() && sValueFormat.indexOf("B") !== -1, bTwelveHourFormatDueToA = sValueFormat.indexOf("a") !== -1 || sValueFormat === ""; oDate = oDate || UI5Date.getInstance(); // Cross frame check for a date should be performed here otherwise setDateValue would fail in OPA tests // because Date object in the test is different than the Date object in the application (due to the iframe). if (Object.prototype.toString.call(oDate) !== "[object Date]" || isNaN(oDate)) { throw new Error("Date must be a JavaScript or UI5Date date object; " + this); } if (!bHoursValueIs24) { // convert date object to value var sValue = this._formatValue(oDate, true); // set the property in any case but check validity on output this.setProperty("value", sValue, true); // no rerendering iHours = oDate.getHours(); } else { iHours = 24; } if ((bTwelveHourFormatDueToA || bTwelveHourFormatDueToB) && oFormatButton) { sAmPm = iHours >= 12 ? "pm" : "am"; iHours = (iHours > 12) ? iHours - 12 : iHours; iHours = (iHours === 0 ? 12 : iHours); oFormatButton && oFormatButton.setSelectedKey(sAmPm); } oHoursClock && oHoursClock.setSelectedValue(iHours); oMinutesClock && oMinutesClock.setSelectedValue(oDate.getMinutes()); oSecondsClock && oSecondsClock.setSelectedValue(oDate.getSeconds()); oHoursClock && this.getSupport2400() && oHoursClock._save2400State(); if (bHoursValueIs24) { oMinutesClock && oMinutesClock.setSelectedValue(0).setEnabled(false); oSecondsClock && oSecondsClock.setSelectedValue(0).setEnabled(false); oMinutesButton && oMinutesButton.setEnabled(false); oSecondsButton && oSecondsButton.setEnabled(false); } else { oMinutesClock && oMinutesClock.setEnabled(true); oSecondsClock && oSecondsClock.setEnabled(true); oMinutesButton && oMinutesButton.setEnabled(true); oSecondsButton && oSecondsButton.setEnabled(true); } }; /** * Returns the clock for the hours. * * @returns {sap.m.TimePickerClock|null} Hours clock * @private */ TimePickerClocks.prototype._getHoursClock = function() { var oClocks = this.getAggregation("_clocks"); return oClocks && this._clockIndexes && oClocks[this._clockIndexes.H] ? oClocks[this._clockIndexes.H] : null; }; /** * Returns the clock for the minutes. * * @returns {sap.m.TimePickerClock|null} Minutes clock * @private */ TimePickerClocks.prototype._getMinutesClock = function() { var oClocks = this.getAggregation("_clocks"); return oClocks && this._clockIndexes && oClocks[this._clockIndexes.M] ? oClocks[this._clockIndexes.M] : null; }; /** * Returns the clock for the seconds. * * @returns {sap.m.TimePickerClock|null} Seconds clock * @private */ TimePickerClocks.prototype._getSecondsClock = function() { var oClocks = this.getAggregation("_clocks"); return oClocks && this._clockIndexes && oClocks[this._clockIndexes.S] ? oClocks[this._clockIndexes.S] : null; }; /** * Returns the button that displays hours. * * @returns {sap.m.Button|null} button that displays hours * @private */ TimePickerClocks.prototype._getHoursButton = function() { var oButtons = this.getAggregation("_buttons"); return oButtons && this._clockIndexes && oButtons[this._clockIndexes.H] ? oButtons[this._clockIndexes.H] : null; }; /** * Returns the button that displays minutes. * * @returns {sap.m.Button|null} button that displays minutes * @private */ TimePickerClocks.prototype._getMinutesButton = function() { var oButtons = this.getAggregation("_buttons"); return oButtons && this._clockIndexes && oButtons[this._clockIndexes.M] ? oButtons[this._clockIndexes.M] : null; }; /** * Returns the button that displays seconds. * * @returns {sap.m.Button|null} button that displays seconds * @private */ TimePickerClocks.prototype._getSecondsButton = function() { var oButtons = this.getAggregation("_buttons"); return oButtons && this._clockIndexes && oButtons[this._clockIndexes.S] ? oButtons[this._clockIndexes.S] : null; }; /** * Destroys the controls stored in internal aggregations. * * @private */ TimePickerClocks.prototype._destroyControls = function() { this.destroyAggregation("_buttons"); this.destroyAggregation("_clocks"); this.destroyAggregation("_buttonAmPm"); }; /** * Creates the controls according to <code>displayFormat</code>. * * @private */ TimePickerClocks.prototype._createControls = function() { var sFormat = this._getDisplayFormatPattern(), sId = this.getId(), bFormatSupport24 = this._isFormatSupport24(), bSupport2400 = this.getSupport2400(), iSelectedHours = 0, iSelectedMinutes = 0, iSelectedSeconds = 0, sSelectedAmPm = "", iLastReplacement = -1, iIndexOfHH, iIndexOfH, bHoursValueIs24, aButtons, aClocks, iIndex = 0, bHours, iHoursMin, iHoursMax, bPrependZero = false, sValue, oDate, oLabels = { "hours": this._oResourceBundle.getText("TIMEPICKER_LBL_HOURS"), "minutes": this._oResourceBundle.getText("TIMEPICKER_LBL_MINUTES"), "seconds": this._oResourceBundle.getText("TIMEPICKER_LBL_SECONDS"), "ampm": this._oResourceBundle.getText("TIMEPICKER_AMPM_BUTTON_TOOLTIP") }; this._clockIndexes = {}; if (sFormat === undefined) { return; } iIndexOfHH = sFormat.indexOf("HH"); iIndexOfH = sFormat.indexOf("H"); if (iIndexOfHH !== -1) { bHours = true; bPrependZero = true; iLastReplacement = (bSupport2400) ? 24 : 0; iHoursMin = 0; iHoursMax = 23; } else if (iIndexOfH !== -1) { bHours = true; iLastReplacement = (bSupport2400) ? 24 : 0; iHoursMin = 0; iHoursMax = 23; } else if (sFormat.indexOf("hh") !== -1) { bHours = true; bPrependZero = true; iHoursMin = 1; iHoursMax = 12; } else if (sFormat.indexOf("h") !== -1) { bHours = true; iHoursMin = 1; iHoursMax = 12; } if (bHours) { // add Hours clock this.addAggregation("_clocks", new TimePickerClock(sId + "-clockH", { label: oLabels["hours"], selectedValue: iSelectedHours, itemMin: 1, itemMax: bFormatSupport24 ? 24 : 12, valueStep: 1, displayStep: bFormatSupport24 ? 2 : 1, fractions: bFormatSupport24, lastItemReplacement: iLastReplacement, prependZero: bPrependZero, support2400: bSupport2400 })); // add Hours button if (bSupport2400) { iHoursMax++; } this.addAggregation("_buttons", new ToggleSpinButton(sId + "-btnH", { tooltip: oLabels["hours"], min: iHoursMin, max: iHoursMax })); this._clockIndexes.H = iIndex++; } if (sFormat.indexOf("m") !== -1) { if (sFormat.indexOf("mm") !== -1) { iLastReplacement = 0; bPrependZero = true; } else { iLastReplacement = 0; bPrependZero = false; } // add Minutes clock this.addAggregation("_clocks", new TimePickerClock(sId + "-clockM", { label: oLabels["minutes"], selectedValue: iSelectedMinutes, itemMin: 1, itemMax: 60, valueStep: this.getMinutesStep(), lastItemReplacement: iLastReplacement, prependZero: bPrependZero })); // add Minutes button this.addAggregation("_buttons", new ToggleSpinButton(sId + "-btnM", { tooltip: oLabels["minutes"], min: 0, max: 59 })); this._clockIndexes.M = iIndex++; } if (sFormat.indexOf("s") !== -1) { if (sFormat.indexOf("ss") !== -1) { iLastReplacement = 0; bPrependZero = true; } else { iLastReplacement = 0; bPrependZero = false; } // add Seconds clock this.addAggregation("_clocks", new TimePickerClock(sId + "-clockS", { label: oLabels["seconds"], selectedValue: iSelectedSeconds, itemMin: 1, itemMax: 60, valueStep: this.getSecondsStep(), lastItemReplacement: iLastReplacement, prependZero: bPrependZero })); // add Seconds button this.addAggregation("_buttons", new ToggleSpinButton(sId + "-btnS", { tooltip: oLabels["seconds"], min: 0, max: 59 })); this._clockIndexes.S = iIndex++; } if (sFormat.indexOf("a") !== -1 || (sFormat.indexOf("B") !== -1 && !this._isFormatSupport24())) { // add AM/PM segmented button this.setAggregation("_buttonAmPm", new SegmentedButton(sId + "-format", { items: [ new sap.m.SegmentedButtonItem({ text: this._sAM, key: "am" }), new sap.m.SegmentedButtonItem({ text: this._sPM, key: "pm" }) ], selectedKey: sSelectedAmPm, tooltip: oLabels["ampm"] })); } if (!this.getAggregation("_nowButton")) { this.setAggregation("_nowButton", this._getCurrentTimeButton()); } aButtons = this.getAggregation("_buttons"); aClocks = this.getAggregation("_clocks"); // skip animation for the first clock this._clockCount = aClocks ? aClocks.length : 0; if (this._clockCount) { aClocks[0].setSkipAnimation(this.getSkipAnimation()).setFadeIn(true); } // attach events to the controls for (iIndex = 0; iIndex < this._clockCount; iIndex++) { this._attachEvents(aClocks[iIndex], aButtons[iIndex]); } // restore control values when recreating controls sValue = this.getValue(); if (sValue) { bHoursValueIs24 = TimePickerInternals._isHoursValue24(sValue, iIndexOfHH, iIndexOfH); oDate = this._parseValue(bHoursValueIs24 ? TimePickerInternals._replace24HoursWithZero(sValue, iIndexOfHH, iIndexOfH) : sValue); if (oDate) { this._setTimeValues(oDate, bHoursValueIs24); } } }; /** * Attaches events of the clocks. * * @param {sap.m.TimePickerClock} oClock Clock to attach events to * @param {sap.m.internal.ToggleSpinButton} oButton button to attach events to * @private */ TimePickerClocks.prototype._attachEvents = function(oClock, oButton) { oClock.attachChange(function(oEvent) { var iSelected = oEvent.getParameter("value"), bIsFinal = oEvent.getParameter("finalChange"), sValue = oEvent.getParameter("stringValue"), aButtons = this.getAggregation("_buttons"), sClockSuffix = oEvent.getParameter("id").slice(-1); // update corresponding button aButtons[this._clockIndexes[sClockSuffix]] && aButtons[this._clockIndexes[sClockSuffix]].setText(sValue); // handle hours change if (sClockSuffix === "H") { this._handleHoursChange(oEvent); } if (!bIsFinal) { return; } // switch to the next clock (if possible) if (!this.getSupport2400() || iSelected !== 24) { setTimeout(function() { this._switchNextClock(false); }.bind(this), 0); } }.bind(this)); oButton.attachPress(function(oEvent) { var sButtonSuffix = oEvent.getParameter("id").slice(-1), aClocks = this.getAggregation("_clocks"), aButtons = this.getAggregation("_buttons"); if (aClocks[this._clockIndexes[sButtonSuffix]].getEnabled()) { aButtons[this._clockIndexes[sButtonSuffix]].setPressed(true); this._switchClock(this._clockIndexes[sButtonSuffix], this.getSkipAnimation()); } }.bind(this)); oButton.onfocusin = function(oEvent) { var sButtonSuffix = oEvent.target.id.slice(-1), aClocks = this.getAggregation("_clocks"); if (aClocks[this._clockIndexes[sButtonSuffix]].getEnabled()) { this._switchClock(this._clockIndexes[sButtonSuffix]); } }.bind(this); }; /** * Switches to the specific clock. * * @param {int} iNewClock the index (in _clocks aggregation) of the clock * @param {boolean} bSkipAnimation whether to skip the animation (when switch is by keyboard shortcut) * @private */ TimePickerClocks.prototype._switchClock = function(iNewClock, bSkipAnimation) { if (this._activeClock === iNewClock) { return; } // Prevent switching while animation is in progress if (this._isSwitching && !bSkipAnimation) { return; } const oCurrentClock = this._getActiveClock(), oNewClock = this.getAggregation("_clocks")[iNewClock], aButtons = this.getAggregation("_buttons"); if (iNewClock !== this._activeClock) { oCurrentClock._save2400State(); } if (this.getSkipAnimation() && iNewClock !== 0 && this._activeClock === 0 && oNewClock) { oCurrentClock.setSkipAnimation(false); this.setSkipAnimation(false); } if (oNewClock && bSkipAnimation) { oCurrentClock.setFadeIn(false); this._activeClock !== undefined && aButtons[this._activeClock].setPressed(false); oNewClock.setSkipAnimation(true).setFadeIn(true); this._activeClock = iNewClock; aButtons[iNewClock].setPressed(true); aButtons[iNewClock].focus(); } else { this._isSwitching = true; oCurrentClock.getDomRef().querySelector(".sapMTPCItems .sapMTPCNumber").addEventListener("animationend", jQuery.proxy(this._swapClocks, this, this._activeClock, iNewClock), {once: true}); oCurrentClock.setSkipAnimation(false).setFadeOut(true); } }; TimePickerClocks.prototype._swapClocks = function(iPrevClock, iNextClock) { const aClocks = this.getAggregation("_clocks"), aButtons = this.getAggregation("_buttons"), oPrevClock = aClocks[iPrevClock], oNextClock = aClocks[iNextClock], bIsTherePrevClock = iPrevClock !== undefined && aClocks[iPrevClock]; if (bIsTherePrevClock) { oPrevClock.setFadeIn(false).setFadeOut(false).setSkipAnimation(false); aButtons.forEach(function(button, index) { button.setPressed(false); }); } this._activeClock = iNextClock; oNextClock.setFadeIn(true); aButtons[iNextClock].setPressed(true); aButtons[iNextClock].focus(); // Clear the switching flag to allow next switch this._isSwitching = false; }; /** * Returns the index (in _clocks aggreagtion) of specific clock. * * @param {sap.m.TimePickerClock} oClock a clock to return index of * @returns {int} index of the clock * @private */ TimePickerClocks.prototype._getClockIndex = function(oClock) { return this._clockIndexes[oClock.getId().slice(-1)]; }; /** * Handles minutes and seconds when hours are changed. * When hours are 24, then the other buttons must be set to 0 and appear disabled. * * @param {object} oEvent change event * @private */ TimePickerClocks.prototype._handleHoursChange = function(oEvent) { var iValue = parseInt(oEvent.getParameter("value")), oMinutesClock = this._getMinutesClock(), oSecondsClock = this._getSecondsClock(), oMinutesButton = this._getMinutesButton(), oSecondsButton = this._getSecondsButton(); if (!this.getSupport2400()) { return; } this._ctrlKeyDown = this._ctrlKeyDown ? 2 : 0; // 0 = Ctrl is released, 1 = Ctrl is pressed, 2 = Ctrl key down flag must be reset due to value change if (iValue === 24) { // Store last values if (oMinutesClock && oMinutesClock.getEnabled()) { this._sMinutes = oMinutesClock.getSelectedValue(); this._setControlValueAndEnabled(oMinutesClock, oMinutesButton, 0, false); } if (oSecondsClock && oSecondsClock.getEnabled()) { this._sSeconds = oSecondsClock.getSelectedValue(); this._setControlValueAndEnabled(oSecondsClock, oSecondsButton, 0, false); } } else { // restore last values if (oMinutesClock && !oMinutesClock.getEnabled()) { this._setControlValueAndEnabled(oMinutesClock, oMinutesButton, this._sMinutes, true); //set again the last value before snapping the hours to 24 } if (oSecondsClock && !oSecondsClock.getEnabled()) { this._setControlValueAndEnabled(oSecondsClock, oSecondsButton, parseInt(this._sSeconds), true); // set again the last value before snapping the hours to 24 } } this._getHoursButton().focus(); }; /** * Sets <code>value</code> and <code>enabled</code> properties of a clock and corresponding button. * * @param {sap.m.TimePickerClock} oClock A clock which value and enabled properties are being set * @param {sap.m.ToggleButton} oButton A button which enabled property is being set * @param {int|string} vValue A value to be set * @param {boolean} bEnabled An enabled state * @private */ TimePickerClocks.prototype._setControlValueAndEnabled = function (oClock, oButton, vValue, bEnabled) { oClock.setSelectedValue(parseInt(vValue)); oClock.setEnabled(bEnabled); oButton.setEnabled(bEnabled); }; /** * Return a value as string, formatted and prepended with zero if necessary. * * @param {int} iNumber A number to format * @param {boolean} bPrependZero Whether to prepend with zero or not * @param {int} iMax Max value of the number for this clock * @param {string} sReplacement A string to replace the maximum value * @returns {string} Formatted value * @private */ TimePickerClocks.prototype._formatNumberToString = function(iNumber, bPrependZero, iMax, sReplacement) { var sNumber; if (bPrependZero && iNumber < 10) { sNumber = iNumber.toString().padStart(2, "0"); } else if (iNumber === iMax && sReplacement !== "") { sNumber = sReplacement; } else { sNumber = iNumber.toString(); } return sNumber; }; return TimePickerClocks; });