@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
1,579 lines (1,382 loc) • 78.6 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 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/ui/core/EnabledPropagator',
'sap/ui/core/IconPool',
'./TimePickerInternals',
'./TimePickerClocks',
'./TimePickerInputs',
'./MaskEnabler',
'sap/ui/Device',
'sap/ui/core/format/DateFormat',
'sap/ui/core/Locale',
'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/Configuration",
"sap/ui/core/date/UI5Date",
"sap/ui/core/Core"
],
function(
InputBase,
DateTimeField,
MaskInputRule,
Toolbar,
ToolbarSpacer,
Popover,
ResponsivePopover,
EnabledPropagator,
IconPool,
TimePickerInternals,
TimePickerClocks,
TimePickerInputs,
MaskEnabler,
Device,
DateFormat,
Locale,
library,
LocaleData,
TimePickerRenderer,
KeyCodes,
Log,
InvisibleText,
Button,
jQuery,
Configuration,
UI5Date,
Core
) {
"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>
* @example <caption> binding the <code>value</code> property by using types </caption>
* new sap.ui.model.json.JSONModel({date: sap.ui.core.date.UI5Date.getInstance(2022,10,10,10,15,10)});
*
* new sap.m.TimePicker({
* value: {
* type: "sap.ui.model.type.Time",
* path:"/date"
* }
* });
*
* <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>
* @example <caption> binding the <code>value</code> property by using types </caption>
* 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"
* }
* }
* }
* });
*
* <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.
* 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.117.4
*
* @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 whether the mask is enabled. When disabled, there are no restrictions and
* validation for the user and no placeholders are displayed.
*
* <b>Note:</b> A disabled mask does not reset any validation rules that are already
* set. You can update the <code>mask</code> property and add new <code>rules</code>
* while it is disabled. When <code>maskMode</code> is set to <code>On</code> again,
* the <code>rules</code> and the updated <code>mask</code> will be applied.
*
* @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 = sap.ui.getCore().getLibraryResourceBundle("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,
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);
var oValueHelpIcon = this._getValueHelpIcon();
if (oValueHelpIcon) {
oValueHelpIcon.setProperty("visible", this.getEditable());
}
};
/**
* Called from parent if the control is destroyed.
*
* @private
*/
TimePicker.prototype.exit = function () {
if (this._oTimeSemanticMaskHelper) {
this._oTimeSemanticMaskHelper.destroy();
}
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 (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;
};
/**
* 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;
};
/**
* 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));
/* Mark input as active */
this.$().addClass(InputBase.ICON_PRESSED_CSS_CLASS);
};
/**
* Called after the clock picker appears.
*
* @override
* @public
*/
TimePicker.prototype.onAfterOpen = function() {
var oClocks = this._getClocks();
if (oClocks) {
oClocks.showFirstClock();
oClocks._focusActiveButton();
}
this.fireAfterValueHelpOpen();
};
/**
* Called after the clock picker closes.
*
* @override
* @public
*/
TimePicker.prototype.onAfterClose = function() {
this.$().removeClass(InputBase.ICON_PRESSED_CSS_CLASS);
this._getClocks().showFirstClock(); // prepare for the next opening
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];
};
/**
* 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.getValueFormat() || (this._sValueFormat && this._sValueFormat.oFormatOptions.pattern),
iIndexOfHH,
iIndexOfH;
sFormat = sFormat ? sFormat : "";
iIndexOfHH = sFormat.indexOf("HH");
iIndexOfH = sFormat.indexOf("H");
sValue = sValue || this._$input.val();
sThatValue = sValue;
bThatValue2400 = TimePickerInternals._isHoursValue24(sThatValue, iIndexOfHH, iIndexOfH);
bEnabled2400 = this.getSupport2400() && bThatValue2400;
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);
// 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 ? "24:" + sValue.replace(/[0-9]/g, "0").slice(0, -3) : 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);
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);
};
/**
* Sets the title label inside the picker.
*
* @param {string} title A title
* @returns {this} Reference to <code>this</code> for method chaining
*/
TimePicker.prototype.setTitle = function(title) {
var oClocks = this._getClocks();
if (oClocks) {
oClocks.setLabelText(title);
}
this.setProperty("title", title, true);
return this;
};
/**
* 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;
};
/**
* 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) : Configuration.getFormatSettings().getFormatLocale();
};
/**
* @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} control for a picker.
*
* @param {string} sFormat Time format used for creating the clocks inside the picker
* @returns {sap.m.TimePicker} the sap.m.TimePicker
* @private
*/
TimePicker.prototype._createPicker = function(sFormat) {
var that = this,
oPopover,
oPicker,
oClocks,
oResourceBundle,
sOKButtonText,
sCancelButtonText,
sTitle,
oIcon = this.getAggregation("_endIcon")[0],
sLocaleId = this._getLocale().getLanguage(),
sArialabelledby,
sLabelId,
sLabel;
oResourceBundle = sap.ui.getCore().getLibraryResourceBundle("sap.m");
sOKButtonText = oResourceBundle.getText("TIMEPICKER_SET");
sCancelButtonText = oResourceBundle.getText("TIMEPICKER_CANCEL");
sTitle = this._oResourceBundle.getText("TIMEPICKER_SET_TIME");
oClocks = new TimePickerClocks(this.getId() + "-clocks", {
support2400: this.getSupport2400(),
displayFormat: sFormat,
valueFormat: sFormat,
localeId: sLocaleId,
minutesStep: this.getMinutesStep(),
secondsStep: this.getSecondsStep(),
showCurrentTimeButton: this.getShowCurrentTimeButton()
});
oClocks._setAcceptCallback(this._handleOkPress.bind(this));
var oHeader = this._getValueStateHeader();
oPicker = new ResponsivePopover(that.getId() + "-RP", {
showCloseButton: false,
showHeader: false,
horizontalScrolling: false,
verticalScrolling: true,
title: sTitle,
placement: PlacementType.VerticalPreferredBottom,
contentWidth: "20rem",
beginButton: new Button(this.getId() + "-OK", {
text: sOKButtonText,
type: ButtonType.Emphasized,
press: this._handleOkPress.bind(this)
}),
endButton: new Button(this.getId() + "-Cancel", {
text: sCancelButtonText,
press: this._handleCancelPress.bind(this)
}),
content: [
oHeader,
oClocks
],
ariaLabelledBy: InvisibleText.getStaticId("sap.m", "TIMEPICKER_SET_TIME"),
beforeOpen: this.onBeforeOpen.bind(this),
afterOpen: this.onAfterOpen.bind(this),
afterClose: this.onAfterClose.bind(this)
});
oHeader.setPopup(oPicker._oControl);
oPopover = oPicker.getAggregation("_popup");
// hide arrow in case of popover as dialog does not have an arrow
if (oPopover.setShowArrow) {
oPopover.setShowArrow(false);
}
oPopover.oPopup.setExtraContent([oIcon]);
if (Device.system.phone) {
sArialabelledby = this.$("inner").attr("aria-labelledby");
sLabelId = sArialabelledby && sArialabelledby.split(" ")[0];
sLabel = sLabelId ? document.getElementById(sLabelId).textContent : "";
if (sLabel) {
oPicker.setTitle(sLabel);
}
oPicker.setShowHeader(true);
} else {
this._oPopoverKeydownEventDelegate = {
onkeydown: function(oEvent) {
var oKC = KeyCodes,
iKC