UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

653 lines (538 loc) 20.2 kB
/*! * 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.DateTimeField. sap.ui.define([ 'sap/ui/model/type/Date', 'sap/ui/model/odata/type/ODataType', 'sap/ui/model/odata/type/DateTimeBase', './InputBase', './ValueStateHeader', 'sap/ui/core/Core', 'sap/ui/core/LocaleData', 'sap/ui/core/library', 'sap/ui/core/format/DateFormat', './DateTimeFieldRenderer', "sap/base/util/deepEqual", "sap/base/Log", "sap/ui/thirdparty/jquery", "sap/ui/core/Configuration", 'sap/ui/core/date/UI5Date', 'sap/ui/unified/calendar/CalendarUtils', // jQuery Plugin "cursorPos" "sap/ui/dom/jquery/cursorPos" ], function( SimpleDateType, ODataType, DateTimeBase, InputBase, ValueStateHeader, Core, LocaleData, coreLibrary, DateFormat, DateTimeFieldRenderer, deepEqual, Log, jQuery, Configuration, UI5Date, CalendarUtils ) { "use strict"; // shortcut for sap.ui.core.CalendarType var CalendarType = coreLibrary.CalendarType; // shortcut for sap.ui.core.ValueState var ValueState = coreLibrary.ValueState; /** * Constructor for a new <code>sap.m.DateTimeField</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 * The <code>sap.m.DateTimeField</code> control provides a basic functionality for date/time input controls. * * To be extended by date and time picker controls. For internal use only. * @abstract * * @extends sap.m.InputBase * * @author SAP SE * @version 1.117.4 * * @constructor * @public * @since 1.50.0 * @alias sap.m.DateTimeField */ var DateTimeField = InputBase.extend("sap.m.DateTimeField", /** @lends sap.m.DateTimeField.prototype */ { metadata: { "abstract" : true, library: "sap.m", properties: { /** * Determines the format, displayed in the input field. */ displayFormat: {type: "string", group: "Appearance", defaultValue: null}, /** * Determines the format of the value property. */ valueFormat: {type: "string", group: "Data", defaultValue: null}, /** * 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>. * Use <code>dateValue</code> as a helper property to easily obtain the day, month, year, hours, minutes * and seconds of the chosen date and time. Although possible to bind it, the recommendation is not to do it. * When binding is needed, use <code>value</code> property instead. */ dateValue: {type: "object", group: "Data", defaultValue: null}, /** * Holds a reference to a UI5Date or JavaScript Date object to define the initially focused * date/time when the picker popup is opened. * * <b>Notes:</b> * <ul> * <li>Setting this property does not change the <code>value</code> property.</li> * <li>Depending on the context this property is used in ({@link sap.m.TimePicker}, * {@link sap.m.DatePicker} or {@link sap.m.DateTimePicker}), it takes into account only the time part, only * the date part or both parts of the UI5Date or JavaScript Date object.</li> * </ul> * @since 1.54 */ initialFocusedDateValue: {type: "object", group: "Data", defaultValue: null} }, events : { /** * Fired when the value of the <code>DateTimeField</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"} } } } }, renderer: DateTimeFieldRenderer }); DateTimeField.prototype.setValue = function (sValue) { sValue = this.validateProperty("value", sValue); // to convert null and undefined to "" var sOldValue = this.getValue(); if (sValue === sOldValue) { return this; } else { this.setLastValue(sValue); } // convert to date object and check validity on output var oDate = this._parseAndValidateValue(sValue); this.setProperty("dateValue", oDate, this._bPreferUserInteraction); // do not call InputBase.setValue because the displayed value and the output value might have different pattern this._formatValueAndUpdateOutput(oDate, sValue); this.setProperty("value", sValue, this._bPreferUserInteraction); return this; }; DateTimeField.prototype._parseAndValidateValue = function(sValue) { this._bValid = true; // convert to date object var oDate; if (sValue) { try { oDate = this._parseValue(sValue); } catch (e) { //ignore parsing error } if (Array.isArray(oDate)) { oDate = oDate[0]; } if (!oDate || !oDate.getTime || oDate.getTime() < this._oMinDate.getTime() || oDate.getTime() > this._oMaxDate.getTime()) { this._bValid = false; Log.warning("Value can not be converted to a valid date", this); } } return oDate; }; DateTimeField.prototype._formatValueAndUpdateOutput = function(oDate, sValue) { if (!this.getDomRef()) { return; } // convert to output var sOutputValue = oDate ? this._formatValue(oDate) : sValue; if (this._bPreferUserInteraction) { // Handle the value concurrency before setting the value property of the control, // in order to distinguish whether the user only focused the input field or typed in it this.handleInputValueConcurrency(sOutputValue); } else if (this._$input.val() !== sOutputValue) { // update the DOM value when necessary // otherwise cursor can go to the end of text unnecessarily this._$input.val(sOutputValue); this._curpos = this._$input.cursorPos(); } }; /** * Determines if a user is currently typing into the input field and this interaction should be taken with priority. * @returns {boolean} True if a user interaction is currently getting handled with priority. */ DateTimeField.prototype._inPreferredUserInteraction = function() { if (this._bPreferUserInteraction && this.getDomRef()) { var oInnerDomRef = this.getFocusDomRef(), sInputDOMValue = oInnerDomRef && this._getInputValue(), sInputPropertyValue = this.getProperty("value"), bInputFocused = document.activeElement === oInnerDomRef; // if the user is currently in the field and he has typed a value, // the changes from the model should not overwrite the user input return bInputFocused && sInputDOMValue && (sInputPropertyValue !== sInputDOMValue); } return false; }; /** * Setter for property <code>dateValue</code>. * * The date and time in DateTimeField as UI5Date or JavaScript Date object. * * @param {Date|module:sap/ui/core/date/UI5Date} oDate A date instance * @returns {this} Reference to <code>this</code> for method chaining * @public */ DateTimeField.prototype.setDateValue = function (oDate) { if (!this._isValidDate(oDate)) { throw new Error("Date must be a JavaScript or UI5Date date object; " + this); } if (deepEqual(this.getDateValue(), oDate)) { return this; } oDate = this._dateValidation(oDate); // convert date object to value var sValue = this._formatValue(oDate, true); if (sValue !== this.getValue()) { this.setLastValue(sValue); } // set the property in any case but check validity on output this.setProperty("value", sValue); if (this.getDomRef()) { // convert to output var sOutputValue = this._formatValue(oDate); if (this._$input.val() !== sOutputValue) { this._$input.val(sOutputValue); this._curpos = this._$input.cursorPos(); } } return this; }; DateTimeField.prototype.setValueFormat = function (sValueFormat) { // if valueFormat changes the value must be parsed again this.setProperty("valueFormat", sValueFormat, true); // no rerendering var sValue = this.getValue(); if (sValue) { this._handleDateValidation(this._parseValue(sValue)); } return this; }; DateTimeField.prototype.setDisplayFormat = function (sDisplayFormat) { this.setProperty("displayFormat", sDisplayFormat, true); // no rerendering this.updateDomValue(this._formatValue(this.getDateValue())); this.setPlaceholder(this._getPlaceholder()); return this; }; DateTimeField.prototype.getDisplayFormatType = function () { return null; }; DateTimeField.prototype.onfocusin = function(oEvent) { if (!jQuery(oEvent.target).hasClass("sapUiIcon")) { this.addStyleClass("sapMFocus"); } if (!jQuery(oEvent.target).hasClass("sapMInputBaseIconContainer") && !(this._oPopup && this._oPopup.isOpen())) { // open value state message popup when focus is in the input this.openValueStateMessage(); } else if (this._oValueStateHeader) { this._oValueStateHeader .setValueState(this.getValueState()) .setText(this._getTextForPickerValueStateContent()) .setVisible(this.getValueState() !== ValueState.None); } this._sPreviousValue = this.getDOMValue(); }; /** * Event handler for user input. * * @public * @param {jQuery.Event} oEvent User input. */ DateTimeField.prototype.oninput = function(oEvent) { InputBase.prototype.oninput.call(this, oEvent); if (oEvent.isMarked("invalid")) { return; } var sValue = this.getDOMValue(); if (sValue !== this._sPreviousValue) { this.fireLiveChange({ value: sValue, previousValue : this._sPreviousValue }); this._sPreviousValue = sValue; } }; /** * Gets the inner input DOM value. * * @protected * @returns {any} The value of the input. */ DateTimeField.prototype.getDOMValue = function() { return this._$input.val(); }; DateTimeField.prototype._getValueStateHeader = function () { var sValueState; if (!this._oValueStateHeader) { sValueState = this.getValueState(); this._oValueStateHeader = new ValueStateHeader({ text: this._getTextForPickerValueStateContent(), valueState: sValueState, visible: sValueState !== ValueState.None }); } return this._oValueStateHeader; }; DateTimeField.prototype._dateValidation = function (oDate) { this._bValid = true; this.setProperty("dateValue", oDate); return oDate; }; DateTimeField.prototype._handleDateValidation = function (oDate) { this._bValid = true; this.setProperty("dateValue", oDate); }; DateTimeField.prototype._getPlaceholder = function() { var sPlaceholder = this.getPlaceholder(), oBinding = this.getBinding("value"), oBindingType = oBinding && oBinding.getType && oBinding.getType(), bDisplayFormat; if (!sPlaceholder) { if (oBindingType instanceof SimpleDateType) { return oBindingType.getPlaceholderText(); } if (oBindingType instanceof ODataType && oBindingType.oFormat) { return oBindingType.oFormat.getPlaceholderText(); } bDisplayFormat = !!this._getDisplayFormatPattern(); sPlaceholder = this._getFormatter(bDisplayFormat).getPlaceholderText(); } return sPlaceholder; }; DateTimeField.prototype._getLocaleBasedPattern = function (sPlaceholder) { return LocaleData.getInstance( Configuration.getFormatSettings().getFormatLocale() ).getDatePattern(sPlaceholder); }; DateTimeField.prototype._parseValue = function (sValue, bDisplayFormat) { var oBinding = this.getBinding("value"), oBindingType = oBinding && oBinding.getType && oBinding.getType(), // The internal "_getFormatter" method gets called now if there is a binding to the "value" property with // a supported binding type. As a result all needed internal control variables are created. oFormatter = this._getFormatter(bDisplayFormat), oFormatOptions, oDateLocal, oDate; if (this._isSupportedBindingType(oBindingType)) { try { oDate = oBindingType.parseValue(sValue, "string"); if (typeof (oDate) === "string" && oBindingType instanceof DateTimeBase) { oDate = DateTimeBase.prototype.parseValue.call(oBindingType, sValue, "string"); } oFormatOptions = oBindingType.oFormatOptions; if (oFormatOptions && oFormatOptions.source && oFormatOptions.source.pattern == "timestamp") { // convert timestamp back to Date oDate = UI5Date.getInstance(oDate); } else if (oFormatOptions && oFormatOptions.source && typeof oFormatOptions.source.pattern === "string") { oDate = oBindingType.oInputFormat.parse(sValue); } } catch (e) { // ignore, ParseException to be handled in ManagedObject.updateModelProperty() } if (oDate && ((oBindingType.oFormatOptions && this._isFormatOptionsUTC(oBindingType.oFormatOptions)) || (oBindingType.oConstraints && oBindingType.oConstraints.isDateOnly))) { // convert to local date because it was parsed as UTC date oDateLocal = UI5Date.getInstance(oDate.getUTCFullYear(), oDate.getUTCMonth(), oDate.getUTCDate(), oDate.getUTCHours(), oDate.getUTCMinutes(), oDate.getUTCSeconds(), oDate.getUTCMilliseconds()); oDateLocal.setFullYear(oDate.getUTCFullYear()); oDate = oDateLocal; } return oDate; } return oFormatter.parse(sValue); }; /* The bValueFormat variable defines whether the result is in valueFormat(true) or displayFormat(false) */ DateTimeField.prototype._formatValue = function (oDate, bValueFormat) { if (!oDate) { return ""; } var oBinding = this.getBinding("value"), oBindingType = oBinding && oBinding.getType && oBinding.getType(), oFormatOptions, oDateUTC; if (this._isSupportedBindingType(oBindingType)) { if ((oBindingType.oFormatOptions && oBindingType.oFormatOptions.UTC) || (oBindingType.oConstraints && oBindingType.oConstraints.isDateOnly)) { // convert to UTC date because it will be formatted as UTC date oDateUTC = CalendarUtils._createUTCDate(oDate, true); oDateUTC.setUTCFullYear(oDate.getFullYear()); oDate = oDateUTC; } oFormatOptions = oBindingType.oFormatOptions; if (oFormatOptions && oFormatOptions.source && oFormatOptions.source.pattern == "timestamp") { // convert Date to timestamp oDate = oDate.getTime(); } else if (oBindingType.oOutputFormat) { return oBindingType.oOutputFormat.format(oDate); } return oBindingType.formatValue(oDate, "string"); } /* The logic of _getFormatter function expects the opposite boolean variable of bValueFormat */ return this._getFormatter(!bValueFormat).format(oDate); }; DateTimeField.prototype._isSupportedBindingType = function (oBindingType) { return !!oBindingType && oBindingType.isA([ "sap.ui.model.type.Date", "sap.ui.model.odata.type.DateTime", "sap.ui.model.odata.type.DateTimeOffset" ]); }; DateTimeField.prototype._isFormatOptionsUTC = function (oBindingTypeFormatOptions) { // UTC can be set directly in oFormatOptions or inside the source along with the pattern return (oBindingTypeFormatOptions.UTC || (oBindingTypeFormatOptions.source && oBindingTypeFormatOptions.source.UTC)); }; DateTimeField.prototype._getDefaultDisplayStyle = function () { return "medium"; }; DateTimeField.prototype._getDefaultValueStyle = function () { return "short"; }; /* The bDisplayFormat variable defines whether the result is in displayFormat(true) or valueFormat(false) */ DateTimeField.prototype._getFormatter = function (bDisplayFormat) { var sPattern = this._getBoundValueTypePattern(), bRelative = false, // if true strings like "Tomorrow" are parsed fine oFormat, oBinding = this.getBinding("value"), sCalendarType; if (oBinding && oBinding.oType && oBinding.oType.oOutputFormat) { bRelative = !!oBinding.oType.oOutputFormat.oFormatOptions.relative; sCalendarType = oBinding.oType.oOutputFormat.oFormatOptions.calendarType; } /* eslint-disable no-lonely-if */ if (!sPattern) { // not databinding is used -> use given format if (bDisplayFormat) { sPattern = ( this.getDisplayFormat() || this._getDefaultDisplayStyle() ); sCalendarType = this.getDisplayFormatType(); } else { sPattern = ( this.getValueFormat() || this._getDefaultValueStyle() ); sCalendarType = CalendarType.Gregorian; } } if (!sCalendarType) { sCalendarType = Configuration.getCalendarType(); } if (bDisplayFormat) { if (sPattern === this._sUsedDisplayPattern && sCalendarType === this._sUsedDisplayCalendarType) { oFormat = this._oDisplayFormat; } } else { if (sPattern === this._sUsedValuePattern && sCalendarType === this._sUsedValueCalendarType) { oFormat = this._oValueFormat; } } if (oFormat) { return oFormat; } return this._getFormatterInstance(oFormat, sPattern, bRelative, sCalendarType, bDisplayFormat); }; DateTimeField.prototype._getFormatterInstance = function (oFormat, sPattern, bRelative, sCalendarType, bDisplayFormat) { if (this._checkStyle(sPattern)) { oFormat = this._getFormatInstance({style: sPattern, strictParsing: true, relative: bRelative, calendarType: sCalendarType}, bDisplayFormat); } else { oFormat = this._getFormatInstance({pattern: sPattern, strictParsing: true, relative: bRelative, calendarType: sCalendarType}, bDisplayFormat); } if (bDisplayFormat) { this._sUsedDisplayPattern = sPattern; this._sUsedDisplayCalendarType = sCalendarType; this._oDisplayFormat = oFormat; } else { this._sUsedValuePattern = sPattern; this._sUsedValueCalendarType = sCalendarType; this._oValueFormat = oFormat; } return oFormat; }; DateTimeField.prototype._getFormatInstance = function (oArguments, bDisplayFormat) { return DateFormat.getInstance(oArguments); }; DateTimeField.prototype._checkStyle = function (sPattern) { return (sPattern === "short" || sPattern === "medium" || sPattern === "long" || sPattern === "full"); }; DateTimeField.prototype._getDisplayFormatPattern = function () { var sPattern = this._getBoundValueTypePattern(); if (sPattern) { return sPattern; } sPattern = this.getDisplayFormat(); if (this._checkStyle(sPattern)) { sPattern = this._getLocaleBasedPattern(sPattern); } return sPattern; }; DateTimeField.prototype._getBoundValueTypePattern = function () { var oBinding = this.getBinding("value"), oBindingType = oBinding && oBinding.getType && oBinding.getType(); if (oBindingType instanceof SimpleDateType) { return oBindingType.getOutputPattern(); } if (oBindingType instanceof ODataType) { return oBindingType.getFormat().oFormatOptions.pattern; } return undefined; }; // 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). DateTimeField.prototype._isValidDate = function (oDate) { return !oDate || Object.prototype.toString.call(oDate) === "[object Date]"; }; /** * Gets the text for the picker's subheader title. * In case <code>valueStateText</code> is not set, a default value is returned. * @returns {string} * @private */ DateTimeField.prototype._getTextForPickerValueStateContent = function() { return this.getValueStateText() || this._getDefaultTextForPickerValueStateContent(); }; /** * Gets the default text for the picker's subheader title. * @returns {string} * @private */ DateTimeField.prototype._getDefaultTextForPickerValueStateContent = function() { var sValueState = this.getValueState(), oResourceBundle, sText; if (sValueState === ValueState.None) { sText = ""; } else { oResourceBundle = Core.getLibraryResourceBundle("sap.ui.core"); sText = oResourceBundle.getText("VALUE_STATE_" + sValueState.toUpperCase()); } return sText; }; return DateTimeField; });