UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,577 lines (1,377 loc) 83 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides control sap.m.TimePicker. sap.ui.define([ './InputBase', './DateTimeField', './MaskInputRule', './Toolbar', './ToolbarSpacer', './Popover', './ResponsivePopover', "sap/base/i18n/Formatting", 'sap/ui/core/EnabledPropagator', 'sap/ui/core/IconPool', './TimePickerInternals', './TimePickerClocks', './TimePickerInputs', './MaskEnabler', 'sap/ui/Device', "sap/ui/core/Element", "sap/ui/core/Lib", 'sap/ui/core/format/DateFormat', 'sap/ui/core/Locale', "sap/ui/core/LabelEnablement", 'sap/m/library', 'sap/ui/core/LocaleData', './TimePickerRenderer', "sap/ui/events/KeyCodes", "sap/base/Log", "sap/ui/core/InvisibleText", './Button', "sap/ui/thirdparty/jquery", "sap/ui/core/date/UI5Date" ], function( InputBase, DateTimeField, MaskInputRule, Toolbar, ToolbarSpacer, Popover, ResponsivePopover, Formatting, EnabledPropagator, IconPool, TimePickerInternals, TimePickerClocks, TimePickerInputs, MaskEnabler, Device, Element, Library, DateFormat, Locale, LabelEnablement, library, LocaleData, TimePickerRenderer, KeyCodes, Log, InvisibleText, Button, jQuery, UI5Date ) { "use strict"; // shortcut for sap.m.PlacementType, sap.m.TimePickerMaskMode, sap.m.ButtonType and default step for minutes // and seconds var PlacementType = library.PlacementType, TimePickerMaskMode = library.TimePickerMaskMode, ButtonType = library.ButtonType, DEFAULT_STEP = 1; /** * Constructor for a new <code>TimePicker</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 single-field input control that enables the users to fill time related input fields. * * <h3>Overview</h3> * * The <code>TimePicker</code> control enables the users to fill time related input * fields using touch, mouse, or keyboard input. * * <h3>Usage</h3> * * Use this control if you want the user to select a time. If you want the user to * select a duration (1 hour), use the {@link sap.m.Select} control instead. * * The user can fill time by: * * <ul><li>Using the time picker button that opens a popover with а time picker clock dial</li> * <li>Using the time input field. On desktop - by changing the time directly via keyboard input. * On mobile/touch device - in another input field that opens in a popup after tap.</li></ul> * * On app level, there are two options to provide value for the * <code>TimePicker</code> - as a string to the <code>value</code> property or as a * UI5Date or JavaScript Date object to the <code>dateValue</code> property (only one of these * properties should be used at a time): * * <ul><li>Use the <code>value</code> property if you want to bind the * <code>TimePicker</code> to a model using the * <code>sap.ui.model.type.Time</code></li> * <caption> binding the <code>value</code> property by using types </caption> * <pre> * // UI5Date imported from sap/ui/core/date/UI5Date * new sap.ui.model.json.JSONModel({date: UI5Date.getInstance(2022,10,10,10,15,10)}); * * new sap.m.TimePicker({ * value: { * type: "sap.ui.model.type.Time", * path:"/date" * } * }); * </pre> * <li>Use the <code>value</code> property if the date is provided as a string from * the backend or inside the app (for example, as ABAP type DATS field)</li> * <caption> binding the <code>value</code> property by using types </caption> * <pre> * new sap.ui.model.json.JSONModel({date:"10:15:10"}); * new sap.m.TimePicker({ * value: { * type: "sap.ui.model.type.Time", * path: "/date", * formatOptions: { * source: { * pattern: "HH:mm:ss" * } * } * } * }); * </pre> * <b>Note:</b> There are multiple binding type choices, such as: * sap.ui.model.type.Date * sap.ui.model.odata.type.DateTime * sap.ui.model.odata.type.DateTimeOffset * See {@link sap.ui.model.type.Date}, {@link sap.ui.model.odata.type.DateTime} or {@link sap.ui.model.odata.type.DateTimeOffset} * * <li>Use the <code>dateValue</code> property if the date is already provided as a * UI5Date or JavaScript Date object or you want to work with a UI5Date or JavaScript Date object. * Use <code>dateValue</code> as a helper property to easily obtain the hours, minutes and seconds * of the chosen time. Although possible to bind it, the recommendation is to not to do it. * When binding is needed, use <code>value</code> property instead</li></ul> * * <h3>Formatting</h3> * * All formatting and parsing of values from and to strings is done using the * {@link sap.ui.core.format.DateFormat}. If a value is entered by typing it into * the input field, it must fit to the used time format and locale. * * Supported format options are pattern-based on Unicode LDML Date Format notation. * The format pattern symbols supported in TimePicker are as follows: * "h"/"H" (Hour), "m" (Minute), "s" (Second), and "a" (AM/PM). * * See {@link http://unicode.org/reports/tr35/#Date_Field_Symbol_Table} * * A time format must be specified, otherwise the default "HH:mm:ss a" will be * used. For example, if the <code>valueFormat</code> is "HH-mm-ss a", the * <code>displayFormat</code> is "HH:mm:ss a", and the used locale is English, a * valid value string is "10-30-15 AM", which leads to an output of "10:30:15 AM". * * If no placeholder is set to the <code>TimePicker</code>, the used * <code>displayFormat</code> is displayed as a placeholder. If another placeholder * is needed, it must be set. * * <b>Note:</b> If the string does NOT match the <code>displayFormat</code> * (from user input) or the <code>valueFormat</code> (on app level), the * {@link sap.ui.core.format.DateFormat} makes an attempt to parse it based on the * locale settings. For more information, see the respective documentation in the * API Reference. * * <h3>Responsive behavior</h3> * * The <code>TimePicker</code> is responsive and fully adapts to all device types. * For larger screens, such as tablet or desktop, it opens as a popover. For * mobile devices, it opens in full screen. * * @extends sap.m.DateTimeField * * @author SAP SE * @version 1.146.0 * * @constructor * @public * @since 1.32 * @alias sap.m.TimePicker * @see {@link fiori:https://experience.sap.com/fiori-design-web/time-picker/ Time Picker} */ var TimePicker = DateTimeField.extend("sap.m.TimePicker", /** @lends sap.m.TimePicker.prototype */ { metadata : { library : "sap.m", designtime: "sap/m/designtime/TimePicker.designtime", properties : { /** * Defines the locale used to parse string values representing time. * * Determines the locale, used to interpret the string, supplied by the * <code>value</code> property. * * Example: AM in the string "09:04 AM" is locale (language) dependent. * The format comes from the browser language settings if not set explicitly. * Used in combination with 12 hour <code>displayFormat</code> containing 'a', which * stands for day period string. */ localeId: {type : "string", group: "Data"}, /** * Displays the text of the general picker label and is read by screen readers. * It is visible only on phone. */ title: {type: "string", group: "Misc", defaultValue: null}, /** * Sets the minutes step. If step is less than 1, it will be automatically converted back to 1. * The minutes clock is populated only by multiples of the step. * @since 1.40 */ minutesStep: {type: "int", group: "Misc", defaultValue: DEFAULT_STEP}, /** * Sets the seconds step. If step is less than 1, it will be automatically converted back to 1. * The seconds clock is populated only by multiples of the step. * @since 1.40 */ secondsStep: {type: "int", group: "Misc", defaultValue: DEFAULT_STEP}, /** * Defines a placeholder symbol. Shown at the position where there is no user input yet. */ placeholderSymbol: {type: "string", group: "Misc", defaultValue: "_"}, /** * Mask defined by its characters type (respectively, by its length). * You should consider the following important facts: * 1. The mask characters normally correspond to an existing rule (one rule per unique char). * Characters which don't, are considered immutable characters (for example, the mask '2099', where '9' corresponds to a rule * for digits, has the characters '2' and '0' as immutable). * 2. Adding a rule corresponding to the <code>placeholderSymbol</code> is not recommended and would lead to an unpredictable behavior. * 3. You can use the special escape character '^' called "Caret" prepending a rule character to make it immutable. * Use the double escape '^^' if you want to make use of the escape character as an immutable one. */ mask: {type: "string", group: "Misc", defaultValue: null}, /** * Defines the state of the mask. The available mask modes are: * <code>On</code> - The mask is automatically enabled for fixed-length time formats, and disabled when the time format does not have a fixed length. * <code>Off</code> - The mask is disabled. In this mode, there are no restrictions or validations for the user input. * <code>Enforce</code> - The mask will always be enforced, regardless of the length of the time format. * * <b>Note:</b> The mask functions correctly only with fixed-length time formats. * The mask is always disabled when using a mobile device * Using the <code>Enforce</code> value with time formats that do not have a fixed length may lead to unpredictable behavior. * Changing the mask mode does not reset any pre-set validation rules. These rules will be applied according to the selected mask mode. * @since 1.54 */ maskMode: {type: "sap.m.TimePickerMaskMode", group: "Misc", defaultValue: TimePickerMaskMode.On}, /** * Allows to set a value of 24:00, used to indicate the end of the day. * Works only with HH or H formats. Don't use it together with am/pm. * @since 1.54 * * When this property is set to <code>true</code>, the clock can display either 24 or 00 as last hour. * If you use the time picker clock dial, the change between 24 and 00 (and vice versa) can be done as follows: * * - on a desktop device: hold down the <code>Ctrl</code> key (this changes 24 to 00 and vice versa), and either * click with mouse on the 00/24 number, or navigate to this value using Arrow keys/PageUp/PageDown and press * <code>Space</code> key (Space key selects the highlighted value and switch to the next available clock). * * - on mobile/touch device: make a long touch on 24/00 value - this action toggles the value to the opposite one. * * - on both device types, if there is a keyboard attached: 24 or 00 can be typed directly. */ support2400: {type: "boolean", group: "Misc", defaultValue: false}, /** * Determines whether the input field of the picker is hidden or visible. * When set to <code>true</code>, the input field becomes invisible and there is no way to open the picker popover. * In that case it can be opened by another control through calling of picker's <code>openBy</code> method, and * the opening control's DOM reference must be provided as parameter. * * Note: Since the picker is not responsible for accessibility attributes of the control which opens its popover, * those attributes should be added by the application developer. The following is recommended to be added to the * opening control: a text or tooltip that describes the action (example: "Open Time Picker"), and also aria-haspopup * attribute with value of <code>sap.ui.core.aria.HasPopup.Dialog</code>. * * @since 1.97 */ hideInput: { type: "boolean", group: "Misc", defaultValue: false }, /** * Determines whether there is a shortcut navigation to current time. * * @since 1.98 */ showCurrentTimeButton : {type : "boolean", group : "Behavior", defaultValue : false} }, aggregations: { /** A list of validation rules (one rule per mask character). */ rules: {type: "sap.m.MaskInputRule", multiple: true, singularName: "rule"}, /** * Internal aggregation that contains the inner clock _picker pop-up. */ _picker: { type: "sap.m.ResponsivePopover", multiple: false, visibility: "hidden" }, /** * Internal aggregation that contains the inner numeric _picker pop-up. */ _numPicker: { type: "sap.m.Popover", multiple: false, visibility: "hidden" } }, events: { /** * Fired when <code>value help</code> dialog opens. * @since 1.102.0 */ afterValueHelpOpen: {}, /** * Fired when <code>value help</code> dialog closes. * @since 1.102.0 */ afterValueHelpClose: {}, /** * Fired when the value of the <code>TimePicker</code> is changed by user interaction - each keystroke, delete, paste, etc. * * <b>Note:</b> Browsing autocomplete suggestions doesn't fire the event. * @since 1.104.0 */ liveChange: { parameters : { /** * The current value of the input, after a live change event. */ value: {type : "string"}, /** * The previous value of the input, before the last user interaction. */ previousValue: {type : "string"} } } }, dnd: { draggable: false, droppable: true } }, renderer: TimePickerRenderer }); /** * Determines the format, displayed in the input field and the picker clocks/numeric inputs. * * The default value is the browser's medium time format locale setting * {@link sap.ui.core.LocaleData#getTimePattern}. * If data binding with type {@link sap.ui.model.type.Time} or {@link sap.ui.model.odata.type.Time} * is used for the <code>value</code> property, the <code>displayFormat</code> property * is ignored as the information is provided from the binding itself. * * @returns {string} the value of property <code>displayFormat</code> * @public * @name sap.m.TimePicker#getDisplayFormat * @function */ /** * Determines the format of the value property. * * The default value is the browser's medium time format locale setting * {@link sap.ui.core.LocaleData#getTimePattern}. * If data binding with type {@link sap.ui.model.type.Time} or {@link sap.ui.model.odata.type.Time} * is used for the <code>value</code> property, the <code>valueFormat</code> property * is ignored as the information is provided from the binding itself. * * @returns {string} the value of property <code>valueFormat</code> * @public * @name sap.m.TimePicker#getValueFormat * @function */ /** * Holds a reference to a UI5Date or JavaScript Date object. The <code>value</code> (string) * property will be set according to it. Alternatively, if the <code>value</code> * and <code>valueFormat</code> pair properties are supplied instead, * the <code>dateValue</code> will be instantiated according to the parsed * <code>value</code>. * * @returns {Date|module:sap/ui/core/date/UI5Date} the value of property <code>dateValue</code> * @public * @name sap.m.TimePicker#getDateValue * @function */ IconPool.insertFontFaceStyle(); EnabledPropagator.call(TimePicker.prototype, true); MaskEnabler.call(TimePicker.prototype); var TimeFormatStyles = { Short: "short", Medium: "medium", Long: "long" }, TimeParts = { Hour: "hour", Minute: "minute", Second: "second" }, PLACEHOLDER_SYMBOL = '-'; /** * Initializes the control. * * @public */ TimePicker.prototype.init = function() { DateTimeField.prototype.init.apply(this, arguments); MaskEnabler.init.apply(this, arguments); this.setDisplayFormat(getDefaultDisplayFormat()); this._oResourceBundle = Library.getResourceBundleFor("sap.m"); // marks if the value is valid or not this._bValid = false; /* stores the type of the used locale (e.g. 'medium', 'long') for the display see https://sdk.openui5.org/api/sap.ui.core.LocaleData/methods/getTimePattern */ this._sUsedDisplayPattern = null; /* stores the type of the used locale (e.g. 'medium', 'long') for inputting see https://sdk.openui5.org/api/sap.ui.core.LocaleData/methods/getTimePattern */ this._sUsedValuePattern = null; this._oDisplayFormat = null; this._sValueFormat = null; this._oPopoverKeydownEventDelegate = null; this._rPlaceholderRegEx = new RegExp(PLACEHOLDER_SYMBOL, 'g'); this._sLastChangeValue = null; var oIcon = this.addEndIcon({ id: this.getId() + "-icon", src: this.getIconSrc(), noTabStop: true, decorative: !Device.support.touch || Device.system.desktop ? true : false, useIconTooltip: false, tooltip: this._oResourceBundle.getText("OPEN_PICKER_TEXT"), alt: this._oResourceBundle.getText("OPEN_PICKER_TEXT") }); // indicates whether the clock picker is still open this._bShouldClosePicker = false; // indicates whether the numeric picker is still open this._bShouldCloseNumericPicker = false; oIcon.addEventDelegate({ onmousedown: function (oEvent) { // as the popup closes automatically on blur - we need to remember its state this._bShouldClosePicker = this.isOpen(); } }, this); oIcon.attachPress(function () { this.toggleOpen(this._bShouldClosePicker); }, this); this._sMinutes = "00"; //needed for the support2400 scenario to store the minutes when changing hour to 24 and back this._sSeconds = "00"; //needed for the support2400 scenario to store the seconds when changing hour to 24 and back }; /** * Before rendering. * * @private */ TimePicker.prototype.onBeforeRendering = function() { DateTimeField.prototype.onBeforeRendering.apply(this, arguments); const oValueHelpIcon = this._getValueHelpIcon(), sAccessibleName = this._getPickerAccessibleName(); if (oValueHelpIcon) { oValueHelpIcon.setProperty("visible", this.getEditable()); } // update invisible label if (this._invisibleLabelText) { this._invisibleLabelText.setText(sAccessibleName); } // update title of the dialog on mobile devices if (Device.system.phone) { const oPicker = this._getPicker(); oPicker?.setTitle(sAccessibleName); } }; /** * Called from parent if the control is destroyed. * * @private */ TimePicker.prototype.exit = function () { if (this._oTimeSemanticMaskHelper) { this._oTimeSemanticMaskHelper.destroy(); } if (this._invisibleLabelText) { this._invisibleLabelText.destroy(); this._invisibleLabelText = null; } MaskEnabler.exit.apply(this, arguments); this._removePickerEvents(); this._oResourceBundle = null; this._bValid = false; this._sUsedDisplayPattern = null; this._oDisplayFormat = null; this._oPopoverKeydownEventDelegate = null; this._sUsedValuePattern = null; this._sValueFormat = null; this._sLastChangeValue = null; }; /** * Returns the icon source. * * @private * @returns {string} the URI of the icon */ TimePicker.prototype.getIconSrc = function () { return IconPool.getIconURI("time-entry-request"); }; /** * Returns whether the clock picker is open or not. * * @private * @returns {boolean} true if the clock picker is open. */ TimePicker.prototype.isOpen = function () { return this._getPicker() && this._getPicker().isOpen(); }; /** * Toggle (open/close) the clock picker. * * @param {boolean} bOpen Whether the clock popover is open or not * @private */ TimePicker.prototype.toggleOpen = function (bOpen) { if (this.getEditable() && this.getEnabled()) { this[bOpen ? "_closePicker" : "_openPicker"](); } }; /** * Returns whether the numeric picker is open or not. * * @private * @returns {boolean} true if the numeric popover is open. */ TimePicker.prototype.isNumericOpen = function () { return this._getNumericPicker() && this._getNumericPicker().isOpen(); }; /** * Toggle (open/close) the numeric picker. * * @param {boolean} bOpen Whether the clock popover is open or not * @private */ TimePicker.prototype.toggleNumericOpen = function (bOpen) { if (this.getEditable() && this.getEnabled()) { this[bOpen ? "_closeNumericPicker" : "_openNumericPicker"](); this._openByFocusIn = false; this._openByClick = false; } }; /** * Handles the focusin event. * * @param {jQuery.Event} oEvent Event object */ TimePicker.prototype.onfocusin = function (oEvent) { var oPicker = this._getPicker(), bIconClicked = this._isIconClicked(oEvent), oNumericPicker = this._getNumericPicker(), bOpen = oNumericPicker && oNumericPicker.isOpen(); if (!this._isMobileDevice()) { DateTimeField.prototype.onfocusin.apply(this, arguments); MaskEnabler.onfocusin.apply(this, arguments); if (this.getMaskMode() !== TimePickerMaskMode.Off) { var sPlaceholder = this._getPlaceholder(); this._$input.attr("aria-description", sPlaceholder); } } if (oPicker && oPicker.isOpen() && !bIconClicked) { this._closePicker(); return; } if (this._openByClick) { this._openByClick = false; return; } if (!this._isMobileDevice()) { return; } if (!bIconClicked) { this.toggleNumericOpen(bOpen); } this._openByFocusIn = true; }; var _maskEnablerOnFocusOut = TimePicker.prototype.onfocusout; /** * Handles the focusout event. * * @param {jQuery.Event} oEvent Event object */ TimePicker.prototype.onfocusout = function(oEvent) { if (this._bClickOnValueStateLink(oEvent)) { return; } if (!this._isMobileDevice()) { _maskEnablerOnFocusOut.apply(this, arguments); this._$input.removeAttr("aria-description"); } }; /** * Onclick handler assures opening/closing of the numeric picker. * * @private */ TimePicker.prototype.onclick = function (oEvent) { var bIconClicked = this._isIconClicked(oEvent), oPicker = this._getNumericPicker(), bOpen = oPicker && oPicker.isOpen(); if (this._openByFocusIn) { this._openByFocusIn = false; return; } if (!this._isMobileDevice()) { return; } if (!bIconClicked) { this.toggleNumericOpen(bOpen); } this._openByClick = true; }; /** * Onmouseup handler assures moving of the cursor at the beginning of the input field * if there is mask set and there is no entry in the input field. * * @private */ TimePicker.prototype.onmouseup = function() { if (this._isMaskEnabled() && this._isValueEmpty()) { this._positionCaret(); } }; /** * Returns whether the icon for opening the clock picker is clicked or not. * * @private * @returns {boolean} true if the icon is clicked. */ TimePicker.prototype._isIconClicked = function (oEvent) { return jQuery(oEvent.target).hasClass("sapUiIcon") || jQuery(oEvent.target).hasClass("sapMInputBaseIconContainer") || jQuery(oEvent.target).hasClass("sapUiIconTitle"); }; /** * Called before the clock picker appears. * * @override * @public */ TimePicker.prototype.onBeforeOpen = function() { /* Set the timevalues of the picker here to prevent user from seeing it */ var oClocks = this._getClocks(), oDateValue = this.getDateValue(), sFormat = this._getFormatter(true).oFormatOptions.pattern, iIndexOfHH = sFormat.indexOf("HH"), iIndexOfH = sFormat.indexOf("H"), sInputValue = TimePickerInternals._isHoursValue24(this._$input.val(), iIndexOfHH, iIndexOfH) ? TimePickerInternals._replace24HoursWithZero(this._$input.val(), iIndexOfHH, iIndexOfH) : this._$input.val(); var oCurrentDateValue = this._getFormatter(true).parse(sInputValue) || oDateValue; if (oCurrentDateValue) { var sDisplayFormattedValue = this._getFormatter(true).format(oCurrentDateValue); oClocks.setValue(sDisplayFormattedValue); } if (this._shouldSetInitialFocusedDateValue()) { oDateValue = this.getInitialFocusedDateValue() || oDateValue; } oClocks._setTimeValues(oDateValue, TimePickerInternals._isHoursValue24(this._$input.val(), iIndexOfHH, iIndexOfH)); oClocks.prepareForOpen(); /* Mark input as active */ this.$().addClass(InputBase.ICON_PRESSED_CSS_CLASS); }; /** * Called after the clock picker appears. * * @override * @public */ TimePicker.prototype.onAfterOpen = function() { this.fireAfterValueHelpOpen(); }; /** * Called after the clock picker closes. * * @override * @public */ TimePicker.prototype.onAfterClose = function() { this.$().removeClass(InputBase.ICON_PRESSED_CSS_CLASS); this.fireAfterValueHelpClose(); }; /** * Returns whether the device is mobile or not. * * @private * @returns {boolean} true if the device is mobile. */ TimePicker.prototype._isMobileDevice = function() { return !Device.system.desktop && (Device.system.phone || Device.system.tablet); }; /** * Called before the numeric picker appears. * * @private */ TimePicker.prototype.onBeforeNumericOpen = function() { /* Set the timevalues of the picker here to prevent user from seeing it */ var oInputs = this._getInputs(), oDateValue = this.getDateValue(), sInputValue = this._$input.val(), sFormat = this._getFormatter(true).oFormatOptions.pattern, iIndexOfHH = sFormat.indexOf("HH"), iIndexOfH = sFormat.indexOf("H"); var oCurrentDateValue = this._getFormatter(true).parse(sInputValue) || oDateValue; var sDisplayFormattedValue = this._getFormatter(true).format(oCurrentDateValue); oInputs.setValue(sDisplayFormattedValue); if (this._shouldSetInitialFocusedDateValue()) { oDateValue = this.getInitialFocusedDateValue(); } oInputs._setTimeValues(oDateValue, TimePickerInternals._isHoursValue24(sDisplayFormattedValue, iIndexOfHH, iIndexOfH)); }; /** * Returns Value help icon. * * @private * @returns {sap.ui.core.Icon} the icon on the right side of the Input. */ TimePicker.prototype._getValueHelpIcon = function () { var oValueHelpIcon = this.getAggregation("_endIcon"); return oValueHelpIcon && oValueHelpIcon[0]; }; TimePicker.prototype._format2400Value = function (sValue, iIndexOfH, iIndexOfHH) { if (sValue.substring(iIndexOfH, 2) === "24") { return; } var iHoursDigits = 2, sTrailingSpaces = ' ', iSubStringIndex = iIndexOfHH, bTrailingSpaces = sValue.charAt(iIndexOfH) === sTrailingSpaces, iExtraIndex = bTrailingSpaces ? 1 : 0, oSignificantNumbers = /[1-9]/g; if (iIndexOfH === -1) { return sValue; } if (iIndexOfHH === -1) { iHoursDigits = 1; iSubStringIndex = iIndexOfH; } sValue = sValue.replace(oSignificantNumbers, "0"); return sValue.substring(0, iSubStringIndex) + "24" + sValue.substring(iSubStringIndex + iExtraIndex + iHoursDigits); }; /** * Handles input's change event by synchronizing <code>value</code>, * and <code>dateValue</code> properties with the input field. * * @param {string} sValue The string value to be synchronized with, if the input value is used * @private * @returns {boolean} true if <code>change</code> event was called, false otherwise. */ TimePicker.prototype._handleInputChange = function (sValue) { var oDate, sThatValue, bThatValue2400, bEnabled2400, sFormat = this.getDisplayFormat() || this.getValueFormat() || (this._sValueFormat && this._sValueFormat.oFormatOptions.pattern), iIndexOfHH, iIndexOfH, bContains24; sFormat = sFormat ? sFormat : ""; iIndexOfHH = sFormat.indexOf("HH"); iIndexOfH = sFormat.indexOf("H"); sValue = sValue?.trim() || this._$input.val()?.trim(); sThatValue = sValue; bThatValue2400 = TimePickerInternals._isHoursValue24(sThatValue, iIndexOfHH, iIndexOfH); bContains24 = sValue.substr(iIndexOfH, 2) === "24"; bEnabled2400 = this._getSupport2400() && bThatValue2400 && bContains24; this._bValid = true; if (sValue !== "") { //keep the oDate not changed by the 24 hrs oDate = this._parseValue(bThatValue2400 ? TimePickerInternals._replace24HoursWithZero(sValue, iIndexOfHH, iIndexOfH) : sValue, true); if (bEnabled2400) { // ih the hour is 24, the control "zeroes" the minutes and seconds, but not in this date object oDate.setMinutes(0, 0); } if (!oDate) { this._bValid = false; } else { // check if Formatter changed the value (it corrects some wrong inputs or known patterns) sValue = this._formatValue(oDate, false, true); // reset the mask as the value might be changed without firing focus out event, // which is unexpected behavior in regards to the MaskEnabler temporary value storage if (this.getMaskMode() && this.getMask()) { this._setupMaskVariables(); } } } sThatValue = bEnabled2400 ? this._format2400Value(sValue, iIndexOfH,iIndexOfHH) : sValue; //instead on key stroke zeroes could be added after entering '24' this.updateDomValue(sThatValue); if (oDate) { // get the value in valueFormat sThatValue = sValue = this._formatValue(oDate, true, true); if (bEnabled2400 && oDate && oDate.getHours() === 0) { // put back 24 as hour if needed sThatValue = sValue = TimePickerInternals._replaceZeroHoursWith24(sValue, iIndexOfHH, iIndexOfH); } } this.setProperty("value", sThatValue, true); // no rerendering this.setLastValue(sValue); if (this._bValid) { this.setProperty("dateValue", oDate, true); // no rerendering } this.fireChangeEvent(sThatValue, {valid: this._bValid}); return true; }; /** * Handles the input change event. * * @override * @param {jQuery.Event} oEvent Event object * @returns {boolean} true if <code>change</code> event was called, false otherwise. */ TimePicker.prototype.onChange = function(oEvent) { // don't call InputBase onChange because this calls setValue what would trigger a new formatting var sValueParam = oEvent ? oEvent.value : null; // check the control is editable or not if (this.getEditable() && this.getEnabled()) { return this._handleInputChange(sValueParam); } return false; }; /** * Sets the minutes step of clocks and inputs. * * @param {int} step The step used to generate values for the minutes clock/input * @returns {this} Reference to <code>this</code> for method chaining * @public */ TimePicker.prototype.setMinutesStep = function(step) { var oClocks = this._getClocks(), oInputs = this._getInputs(); step = Math.max(DEFAULT_STEP, step || DEFAULT_STEP); if (oClocks) { oClocks.setMinutesStep(step); } if (oInputs) { oInputs.setMinutesStep(step); } return this.setProperty("minutesStep", step, true); }; /** * Sets the seconds step of clocks and inputs. * * @param {int} step The step used to generate values for the seconds clock/input * @returns {this} Reference to <code>this</code> for method chaining * @public */ TimePicker.prototype.setSecondsStep = function(step) { var oClocks = this._getClocks(), oInputs = this._getInputs(); step = Math.max(DEFAULT_STEP, step || DEFAULT_STEP); if (oClocks) { oClocks.setSecondsStep(step); } if (oInputs) { oInputs.setSecondsStep(step); } return this.setProperty("secondsStep", step, true); }; /** * Gets current value of property width. * * @returns {string} The value of property width or "100%" * @public * @override */ TimePicker.prototype.getWidth = function() { return this.getProperty("width") || "100%"; }; /** * Handles data validation. * * @param {Date|module:sap/ui/core/date/UI5Date} oDate date instance * @private */ TimePicker.prototype._handleDateValidation = function (oDate) { if (!oDate) { this._bValid = false; Log.warning("Value can not be converted to a valid date", this); } else { this._bValid = true; this.setProperty("dateValue", oDate, true); // no rerendering var sValue = this._formatValue(oDate); if (this.isActive()) { this.updateDomValue(sValue); } else { this.setProperty("value", sValue, true); // no rerendering this.setLastValue(sValue); this._sLastChangeValue = sValue; } } }; /** * Sets <code>support2400</code> of the control. * * Allows the control to use 24-hour format. * Recommended usage is to not use it with am/pm format. * * @param {boolean} bSupport2400 * @returns {this} Reference to <code>this</code> for method chaining * @public */ TimePicker.prototype.setSupport2400 = function (bSupport2400) { var oClocks = this._getClocks(), oInputs = this._getInputs(); this.setProperty("support2400", bSupport2400, true); // no rerendering if (oClocks) { oClocks.setSupport2400(bSupport2400); } if (oInputs) { oInputs.setSupport2400(bSupport2400); } this._initMask(); return this; }; TimePicker.prototype._getSupport2400 = function () { if (this.getDisplayFormat().indexOf("H") === -1) { return false; } return this.getSupport2400(); }; /** * Sets the display format. * * @param {string} sDisplayFormat display format to set * @public * @returns {this} Reference to <code>this</code> for method chaining */ TimePicker.prototype.setDisplayFormat = function (sDisplayFormat) { var oClocks = this._getClocks(), oInputs = this._getInputs(); this.setProperty("displayFormat", sDisplayFormat, true); // no rerendering this._initMask(); if (oClocks) { oClocks.setValueFormat(sDisplayFormat); oClocks.setDisplayFormat(sDisplayFormat); } if (oInputs) { oInputs.setValueFormat(sDisplayFormat); oInputs.setDisplayFormat(sDisplayFormat); } var oDateValue = this.getDateValue(); if (!oDateValue) { return this; } var sOutputValue = this._formatValue(oDateValue); this.updateDomValue(sOutputValue); this.setLastValue(sOutputValue); return this; }; /** * Sets the current <code>value</code> of the control. * * Sets to whatever string was given if it cannot be parsed based on the * current <code>valueFormat</code>. Recommended usage is when <code>dateValue</code> * is not set as they are mutually exclusive. * * @override * @param {string} sValue New value * @returns {this} Reference to <code>this</code> for method chaining * @public */ TimePicker.prototype.setValue = function(sValue) { if (sValue) { this._getFormatter(); // initialise DateFormatter } var oDate, sOutputValue, sFormat = this.getValueFormat() || (this._sValueFormat && this._sValueFormat.oFormatOptions.pattern), oClocks = this._getClocks(), oInputs = this._getInputs(), iIndexOfHH, iIndexOfH, bEmpty = false; sFormat = sFormat ? sFormat : ""; iIndexOfHH = sFormat.indexOf("HH"); iIndexOfH = sFormat.indexOf("H"); sValue = this.validateProperty("value", sValue); this._initMask(); // set last change value only if the new value is different than current one if (this.getValue() !== sValue) { this._sLastChangeValue = sValue; } if (this.getDomRef() && !this._getInputValue()) { bEmpty = true; } MaskEnabler.setValue.call(this, sValue); // Make sure that the input element is empty in case it was empty before calling the setter, // in order to enable the input field value selection, which is part of the prefered user interaction restricted API. // Later on the updateDomValue method will fill the input field element properly if (this.getDomRef() && this._bPreferUserInteraction && bEmpty) { this.getFocusDomRef().value = ""; } // We need to reset the mask temporary value when using a setter // as the given value might not formatted according to mask value format if (this.getMask()) { this._setupMaskVariables(); } this._bValid = true; // convert to date object if (sValue) { //date object have to be consistent, so if value is 2400, set oDate to 00 oDate = this._parseValue(TimePickerInternals._isHoursValue24(sValue, iIndexOfHH, iIndexOfH) ? TimePickerInternals._replace24HoursWithZero(sValue, iIndexOfHH, iIndexOfH) : sValue); if (!oDate) { this._bValid = false; Log.warning("Value can not be converted to a valid date", this); } } if (this._bValid) { this.setProperty("dateValue", oDate, true); // no rerendering } // convert to output if (oDate && !this._getSupport2400()) { sOutputValue = this._formatValue(oDate); } else { sOutputValue = sValue; } if (oClocks) { oClocks.setValue(this._formatValue(oDate)); } if (oInputs) { oInputs.setValue(this._formatValue(oDate)); } // do not call InputBase.setValue because the displayed value and the output value might have different pattern this.updateDomValue(sOutputValue); this.setLastValue(sOutputValue); return this; }; /** * Sets the value of the date. * * @public * @param {Date|module:sap/ui/core/date/UI5Date} oDate A date instance * @returns {this} Reference to <code>this</code> for method chaining */ TimePicker.prototype.setDateValue = function(oDate) { this._initMask(); return DateTimeField.prototype.setDateValue.apply(this, arguments); }; /** * Sets the locale of the control. * * Used for parsing and formatting the time values in languages different than English. * Necessary for translation and auto-complete of the day periods, such as AM and PM. * * @param {string} sLocaleId A locale identifier like 'en_US' * @returns {this} Reference to <code>this</code> for method chaining * @public */ TimePicker.prototype.setLocaleId = function(sLocaleId) { var sCurrentValue = this.getValue(), oClocks = this._getClocks(), oInputs = this._getInputs(); this.setProperty("localeId", sLocaleId, true); this._initMask(); this._oDisplayFormat = null; this._sValueFormat = null; if (sCurrentValue) { this.setValue(sCurrentValue); } if (oClocks) { oClocks.setLocaleId(sLocaleId); } if (oInputs) { oInputs.setLocaleId(sLocaleId); } return this; }; TimePicker.prototype.setShowCurrentTimeButton = function(bShow) { var oClocks = this._getClocks(), oNumericPicker = this._getNumericPicker(); oClocks && oClocks.setShowCurrentTimeButton(bShow); oNumericPicker && oNumericPicker.getContent()[0].setShowCurrentTimeButton(bShow); return this.setProperty("showCurrentTimeButton", bShow); }; /** * @private * @returns {string} default display format style */ TimePicker.prototype._getDefaultDisplayStyle = function () { return TimeFormatStyles.Medium; }; /** * @private * @returns {string} default value format style */ TimePicker.prototype._getDefaultValueStyle = function () { return TimeFormatStyles.Medium; }; /** * if the user has set localeId, create Locale from it, if not get the locate from the FormatSettings. * * @private * @returns {sap.ui.core.Locale} the locale instance */ TimePicker.prototype._getLocale = function () { var sLocaleId = this.getLocaleId(); return sLocaleId ? new Locale(sLocaleId) : new Locale(Formatting.getLanguageTag()); }; /** * @private * @returns {object} the instance of the formatter */ TimePicker.prototype._getFormatterInstance = function(oFormat, sPattern, bRelative, sCalendarType, bDisplayFormat) { var oLocale = this._getLocale(); if (sPattern === TimeFormatStyles.Short || sPattern === TimeFormatStyles.Medium || sPattern === TimeFormatStyles.Long) { oFormat = DateFormat.getTimeInstance({style: sPattern, strictParsing: true, relative: bRelative}, oLocale); } else { oFormat = DateFormat.getTimeInstance({pattern: sPattern, strictParsing: true, relative: bRelative}, oLocale); } if (bDisplayFormat) { this._sUsedDisplayPattern = sPattern; this._oDisplayFormat = oFormat; } else { this._sUsedValuePattern = sPattern; this._sValueFormat = oFormat; } return oFormat; }; /** * Obtains time pattern. * * @returns {*} the time pattern * @private */ TimePicker.prototype._getFormat = function () { var sFormat = this._getDisplayFormatPattern(); if (!sFormat) { sFormat = TimeFormatStyles.Medium; } if (Object.keys(TimeFormatStyles).indexOf(sFormat) !== -1) { sFormat = getDefaultDisplayFormat(); } return sFormat; }; /** * Handles the pageup event. * * Increases time by one hour. * * @param {jQuery.Event} oEvent Event object */ TimePicker.prototype.onsappageup = function(oEvent) { //increase by one hour this._increaseTime(1, TimeParts.Hour); oEvent.preventDefault(); //do not move cursor }; /** * Handles the shift + pageup and ctrl + shift + pageup events. * * Increases time by one minute or second. * * @param {jQuery.Event} oEvent Event object */ TimePicker.prototype.onsappageupmodifiers = function(oEvent) { if (!(oEvent.ctrlKey || oEvent.metaKey || oEvent.altKey) && oEvent.shiftKey) { //shift // increase by one minute this._increaseTime(1, TimeParts.Minute); } if (!oEvent.altKey && oEvent.shiftKey && (oEvent.ctrlKey || oEvent.metaKey)) { //ctrl+shift // increase by one second this._increaseTime(1, TimeParts.Second); } oEvent.preventDefault(); // do not move cursor }; /** * Handles the pagedown event. * * Decreases time by one hour. * * @param {jQuery.Event} oEvent Event object */ TimePicker.prototype.onsappagedown = function(oEvent) { //decrease by one hour this._increaseTime(-1, TimeParts.Hour); oEvent.preventDefault(); // do not move cursor }; /** * Handle when escape is pressed. Escaping unsaved input will restore * the last valid value. If the value cannot be parsed into a date, * the input will be cleared. * * @param {jQuery.Event} oEvent The event object. * @private */ TimePicker.prototype.onsapescape = function(oEvent) { var oLastDate = this._parseValue(this.getLastValue(), true), oInputDate = this._parseValue(this._getInputValue(), true), sDisplayFormatLastDate = this._formatValue(oLastDate, false), sDisplayFormatInputDate = this._formatValue(oInputDate, false), sInputValue = this.getMaskMode() === "Off" ? this._getInputValue() : sDisplayFormatInputDate; if (sInputValue !== sDisplayFormatLastDate) { oEvent.setMarked(); oEvent.preventDefault(); this.updateDomValue(sDisplayFormatLastDate); this.onValueRevertedByEscape(sDisplayFormatLastDate, sDisplayFormatInputDate); } this._bCheckForLiveChange = true; }; /** * Handles the shift + pagedown and ctrl + shift + pagedown events. * * Decreases time by one minute or second. * * @param {jQuery.Event} oEvent Event object * @private */ TimePicker.prototype.onsappagedownmodifiers = function(oEvent) { if (!(oEvent.ctrlKey || oEvent.metaKey || oEvent.altKey) && oEvent.shiftKey) { //shift // decrease by one minute this._increaseTime(-1, TimeParts.Minute); } if (!oEvent.altKey && oEvent.shiftKey && (oEvent.ctrlKey || oEvent.metaKey)) { //ctrl+shift // decrease by one second this._increaseTime(-1, TimeParts.Second); } oEvent.preventDefault(); // do not move cursor }; /** * Handles the keydown event. * * Opens or closes the picker if specific key combinations are pressed. * * @param {jQuery.Event} oEvent Event object * @private */ TimePicker.prototype.onkeydown = function(oEvent) { var oKC = KeyCodes, iKC = oEvent.which || oEvent.keyCode, bAlt = oEvent.altKey, bPickerOpened; // Popover should be opened when F4, ALT+UP or ALT+DOWN is pressed if (iKC === oKC.F4 || (bAlt && (iKC === oKC.ARROW_UP || iKC === oKC.ARROW_DOWN))) { bPickerOpened = this._getPicker() && this._getPicker().isOpen(); if (!bPickerOpened) { this._openPicker(); } else { this._closePicker(); } oEvent.preventDefault(); //ie expands the address bar on F4 } else if (!this._isMobileDevice()) { if (iKC !== oKC.ESCAPE) { MaskEnabler.onkeydown.call(this, oEvent); } } else { if (iKC === KeyCodes.ENTER || iKC === KeyCodes.SPACE) { this._openNumericPicker(); } } }; /** * Gets the picker aggregation. * * @returns {sap.m.ResponsivePopover|undefined} The picker aggregation * @private */ TimePicker.prototype._getPicker = function() { return this.getAggregation("_picker"); }; /** * Gets the numeric picker aggregation. * * @returns {sap.m.Popover|undefined} The picker aggregation * @private */ TimePicker.prototype._getNumericPicker = function() { return this.getAggregation("_numPicker"); }; /** * Detaches the picker from the keyboard events. * * @private */ TimePicker.prototype._removePickerEvents = function() { var oPopover, oPicker = this._getPicker(); if (oPicker) { oPopover = oPicker.getAggregation("_popup"); if (typeof this._oPopoverKeydownEventDelegate === 'function') { oPopover.removeEventDelegate(this._oPopoverKeydownEventDelegate); } } }; /** * Opens the picker popover. The popover is positioned relatively to the control given as <code>oDomRef</code> parameter on tablet or desktop * and is full screen on phone. Therefore the control parameter is only used on tablet or desktop and is ignored on phone. * * Note: use this method to open the picker popover only when the <code>hideInput</code> property is set to <code>true</code>. Please consider * opening of the picker popover by another control only in scenarios that comply with Fiori guidelines. For example, opening the picker popover * by another popover is not recommended. * The application developer should implement the following accessibility attributes to the opening control: a text or tooltip that describes * the action (example: "Open Time Picker"), and aria-haspopup attribute with value of <code>sap.ui.core.aria.HasPopup.Dialog</code>. * * @since 1.97 * @param {HTMLElement} oDomRef DOM reference of the opening control. On tablet or desktop, the popover is positioned relatively to this control. * @public */ TimePicker.prototype.openBy = function(oDomRef) { this._openPicker(oDomRef); }; /** * Opens the picker. * * Creates the picker if necessary. * * @param {HTMLElement} oDomRef DOM reference of the opening control. On tablet or desktop, the TimePicker popover is positioned relative to this control. * @returns {sap.m.ResponsivePopover} The picker part as a control, used for chaining * @private */ TimePicker.prototype._openPicker = function (oDomRef) { var oPicker = this._getPicker(); if (!oPicker) { oPicker = this._createPicker(this._getDisplayFormatPattern()); } if (!oDomRef) { oDomRef = this.getDomRef(); } oPicker.openBy(oDomRef); oPicker.getContent()[0]._sMinutes = this._sMinutes; oPicker.getContent()[0]._sSeconds = this._sSeconds; return oPicker; }; /** * Closes the TimePicker popover. * * @returns {sap.m.ResponsivePopover|undefined} The picker part as a control, used for chaining * @private */ TimePicker.prototype._closePicker = function () { var oPicker = this._getPicker(); if (oPicker) { this._sMinutes = oPicker.getContent()[0]._sMinutes; this._sSeconds = oPicker.getContent()[0]._sSeconds; oPicker.close(); } else { Log.warning("There is no picker to close."); } return oPicker; }; /** * Opens the numeric picker. * * Creates the picker if necessary. * * @returns {sap.m.Popover} The picker part as a control, used for chaining * @private */ TimePicker.prototype._openNumericPicker = function () { var oPicker = this._getNumericPicker(); if (!oPicker) { oPicker = this._createNumericPicker(this._getDisplayFormatPattern()); } oPicker.open(); oPicker.getContent()[0]._sMinutes = this._sMinutes; oPicker.getContent()[0]._sSeconds = this._sSeconds; return oPicker; }; /** Closes the numeric popover. * * @returns {sap.m.Popover|undefined} The picker part as a control, used for chaining * @private */ TimePicker.prototype._closeNumericPicker = function () { var oPicker = this._getNumericPicker(); if (oPicker) { this._sMinutes = oPicker.getContent()[0]._sMinutes; this._sSeconds = oPicker.getContent()[0]._sSeconds; oPicker.close(); this.getDomRef("inner").select(); } else { Log.warning("There is no picker to close."); } return oPicker; }; /** * Creates the picker. * * Uses {@link sap.m.ResponsivePopover} co