@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
1,530 lines (1,265 loc) • 56.9 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.DatePicker.
sap.ui.define([
'sap/ui/thirdparty/jquery',
'sap/ui/Device',
"sap/ui/core/Element",
'./InputBase',
'./DateTimeField',
'./Button',
'./ResponsivePopover',
'sap/ui/core/date/UniversalDate',
'./library',
'sap/ui/core/Control',
'sap/ui/core/library',
"./DatePickerRenderer",
"sap/base/util/deepEqual",
"sap/base/assert",
"sap/base/Log",
"sap/ui/core/IconPool",
"./InstanceManager",
// jQuery Plugin "cursorPos"
"sap/ui/unified/Calendar",
"sap/ui/unified/DateRange",
'sap/ui/unified/DateTypeRange',
"sap/ui/unified/calendar/CustomMonthPicker",
"sap/ui/unified/calendar/CustomYearPicker",
"sap/ui/core/LabelEnablement",
"sap/ui/unified/library",
"sap/ui/core/Configuration",
"sap/ui/unified/calendar/CalendarUtils",
"sap/ui/core/date/UI5Date",
"sap/ui/core/date/CalendarWeekNumbering",
"sap/ui/core/Core",
"sap/ui/dom/jquery/cursorPos"
],
function(
jQuery,
Device,
Element,
InputBase,
DateTimeField,
Button,
ResponsivePopover,
UniversalDate,
library,
Control,
coreLibrary,
DatePickerRenderer,
deepEqual,
assert,
Log,
IconPool,
InstanceManager,
Calendar,
DateRange,
DateTypeRange,
CustomMonthPicker,
CustomYearPicker,
LabelEnablement,
unifiedLibrary,
Configuration,
CalendarUtils,
UI5Date,
CalendarWeekNumbering,
Core
) {
"use strict";
// shortcut for sap.ui.core.CalendarType
var CalendarType = coreLibrary.CalendarType;
var oResourceBundle = sap.ui.getCore().getLibraryResourceBundle("sap.m");
/**
* Constructor for a new <code>DatePicker</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
* Enables the users to select a localized date between 0001-01-01 and 9999-12-31.
*
* <h3>Overview</h3>
*
* The <code>DatePicker</code> lets the users select a localized date using touch,
* mouse, or keyboard input. It consists of two parts: the date input field and the
* date picker.
*
* <b>Note:</b> The application developer should add dependency to <code>sap.ui.unified</code>
* library on application level to ensure that the library is loaded before the module dependencies will be required.
* The {@link sap.ui.unified.Calendar} is used internally only if the
* <code>DatePicker</code> is opened (not used for the initial rendering). If the
* <code>sap.ui.unified</code> library is not loaded before the
* <code>DatePicker</code> is opened, it will be loaded upon opening. This could
* lead to CSP compliance issues and adds an additional waiting time when the <code>DatePicker</code> is opened for the
* first time. To prevent this, apps using the <code>DatePicker</code> should also
* load the <code>sap.ui.unified</code> library in advance.
*
* <h3>Usage</h3>
*
* The user can enter a date by:
* <ul><li>Using the calendar that opens in a popup</li>
* <li>Typing it directly in the input field</li></ul>
*
* On app level, there are two options to provide a date for the
* <code>DatePicker</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>DatePicker</code> to a model using the <code>sap.ui.model.type.Date</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,10,10)
* });
*
* new sap.m.DatePicker({
* value:{path:"/date",type:"sap.ui.model.type.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:'2022-11-10');
*
* new sap.m.DatePicker({
* value:{
* path:"/date",
* type:"sap.ui.model.type.Date",
* formatOptions:{
* source:{
* pattern:"yyyy-MM-dd"
* }
* }
* }
* });
*
* <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 day, month and year
* of the chosen date. Although it's possible to bind it, it's not recommended to do so.
* When binding is needed, use <code>value</code> property instead</li></ul>
*
* <h3>Formatting</h3>
*
* All formatting and parsing of dates from and to strings is done using the
* {@link sap.ui.core.format.DateFormat}. If a date is entered by typing it into
* the input field, it must fit to the used date 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}
*
* For example, if the <code>valueFormat</code> is "yyyy-MM-dd",
* the <code>displayFormat</code> is "MMM d, y", and the used locale is English, a
* valid value string is "2015-07-30", which leads to an output of "Jul 30, 2015".
*
* If no placeholder is set to the <code>DatePicker</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>DatePicker</code> is smaller in compact mode and provides a
* touch-friendly size in cozy mode.
*
* On mobile devices, one tap on the input field opens the <code>DatePicker</code>
* in full screen. To close the window, the user can select a date (which triggers
* the close event), or select Cancel.
*
* @extends sap.m.DateTimeField
* @version 1.117.4
*
* @constructor
* @public
* @since 1.22.0
* @alias sap.m.DatePicker
* @see {@link fiori:https://experience.sap.com/fiori-design-web/date-picker/ Date Picker}
*/
var DatePicker = DateTimeField.extend("sap.m.DatePicker", /** @lends sap.m.DatePicker.prototype */ {
metadata : {
library : "sap.m",
properties : {
/**
* Displays date in this given type in input field. Default value is taken from locale settings.
* Accepted are values of <code>sap.ui.core.CalendarType</code> or an empty string. If no type is set, the default type of the
* configuration is used.
* <b>Note:</b> If data binding on <code>value</code> property with type <code>sap.ui.model.type.Date</code> is used, this property will be ignored.
* @since 1.28.6
*/
displayFormatType : {type : "string", group : "Appearance", defaultValue : ""},
/**
* If set, the days in the calendar popup are also displayed in this calendar type
* If not set, the dates are only displayed in the primary calendar type
* @since 1.34.1
*/
secondaryCalendarType : {type : "sap.ui.core.CalendarType", group : "Appearance"},
/**
* Minimum date that can be shown and selected in the <code>DatePicker</code>. This must be a UI5Date or JavaScript Date object.
*
* <b>Note:</b> If the <code>minDate</code> is set to be after the <code>maxDate</code>,
* the <code>maxDate</code> and the <code>minDate</code> are switched before rendering.
* @since 1.38.0
*/
minDate : {type : "object", group : "Misc", defaultValue : null},
/**
* Maximum date that can be shown and selected in the <code>DatePicker</code>. This must be a UI5Date or JavaScript Date object.
*
* <b>Note:</b> If the <code>maxDate</code> is set to be before the <code>minDate</code>,
* the <code>maxDate</code> and the <code>minDate</code> are switched before rendering.
* @since 1.38.0
*/
maxDate : {type : "object", group : "Misc", defaultValue : null},
/**
* Hides or shows the popover's footer.
*
* @since 1.70
*/
showFooter : {type : "boolean", group : "Misc", defaultValue : false},
/**
* Determines whether there is a shortcut navigation to Today. When used in Month, Year or
* Year-range picker view, the calendar navigates to Day picker view.
*
* Note: The Current date button appears if the <code>displayFormat</code> property allows entering day.
*
* @since 1.95
*/
showCurrentDateButton : {type : "boolean", group : "Behavior", 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 Date 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 },
/**
* If set, the calendar week numbering is used for display.
* If not set, the calendar week numbering of the global configuration is used.
* @since 1.108.0
*/
calendarWeekNumbering : { type : "sap.ui.core.date.CalendarWeekNumbering", group : "Appearance", defaultValue: null}
},
aggregations : {
/**
* Date Range with type to visualize special days in the Calendar.
* If one day is assigned to more than one Type, only the first one will be used.
*
* To set a single date (instead of a range), set only the startDate property of the sap.ui.unified.DateRange class.
*
* <b>Note:</b> Since 1.48 you could set a non-working day via the sap.ui.unified.CalendarDayType.NonWorking
* enum type just as any other special date type using sap.ui.unified.DateRangeType.
*
* @since 1.38.5
*/
specialDates : {type : "sap.ui.core.Element", multiple : true, singularName : "specialDate"},
/**
* Internal aggregation that contains the inner picker pop-up.
*
* @since 1.70
*/
_popup: { type: "sap.m.ResponsivePopover", multiple : false, visibility: "hidden" }
},
associations: {
/**
* Association to the <code>CalendarLegend</code> explaining the colors of the <code>specialDates</code>.
*
* <b>Note</b> The legend does not have to be rendered but must exist, and all required types must be assigned.
* @since 1.38.5
*/
legend: { type: "sap.ui.core.Control", multiple: false}
},
events : {
/**
* Fired when navigating in <code>Calendar</code> popup.
* @since 1.46.0
*/
navigate : {
parameters : {
/**
* Date range containing the start and end date displayed in the <code>Calendar</code> popup.
*/
dateRange : {type : "sap.ui.unified.DateRange"},
/**
* Indicates if the event is fired, due to popup being opened.
*/
afterPopupOpened : {type : "boolean"}
}
},
/**
* 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 : {}
},
designtime: "sap/m/designtime/DatePicker.designtime",
dnd: { draggable: false, droppable: true }
},
renderer: DatePickerRenderer
});
/**
* The date is displayed in the input field using this format. By default, the medium format of the used locale is used.
*
* Supported format options are pattern-based on Unicode LDML Date Format notation. {@link http://unicode.org/reports/tr35/#Date_Field_Symbol_Table}
* <b>Note:</b> If you use data binding on the <code>value</code> property with type <code>sap.ui.model.type.Date</code> this property will be ignored.
* The format defined in the binding will be used.
*
* @returns {string} the value of property <code>displayFormat</code>
* @public
* @name sap.m.DatePicker#getDisplayFormat
* @function
*/
/**
* The date string expected and returned in the <code>value</code> property uses this format. By default the medium format of the used locale is used.
*
*
* Supported format options are pattern-based on Unicode LDML Date Format notation. {@link http://unicode.org/reports/tr35/#Date_Field_Symbol_Table}
*
* For example, if the date string represents an ABAP DATS type, the format should be "yyyyMMdd".
*
* <b>Note:</b> If data binding on <code>value</code> property with type <code>sap.ui.model.type.Date</code> is used, this property will be ignored.
* The format defined in the binding will be used.
*
* @returns {string} the value of property <code>valueFormat</code>
* @public
* @name sap.m.DatePicker#getValueFormat
* @function
*/
/**
* The date instance. This is independent from any formatter.
*
* <b>Note:</b> If this property is used, the <code>value</code> property should not be changed from the caller.
*
* @returns {Date|module:sap/ui/core/date/UI5Date} the value of property <code>dateValue</code>
* @public
* @name sap.m.DatePicker#getDateValue
* @function
*/
DatePicker.prototype.init = function() {
DateTimeField.prototype.init.apply(this, arguments);
this._bIntervalSelection = false;
this._bOnlyCalendar = true;
this._bValid = true;
this._oMinDate = UI5Date.getInstance(1, 0, 1); // set the date to minimum possible for that day
this._oMinDate.setFullYear(1); // otherwise year 1 will be converted to year 1901
this._oMaxDate = UI5Date.getInstance(9999, 11, 31, 23, 59, 59, 999); // set the date for the maximum possible for that day
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: oResourceBundle.getText("OPEN_PICKER_TEXT")
});
// idicates whether the picker is still open
this._bShouldClosePicker = 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);
};
/**
* Returns if the last entered value is valid.
*
* @returns {boolean}
* @public
* @since 1.64
*/
DatePicker.prototype.isValidValue = function() {
return this._bValid;
};
/**
* Checks if the picker is open
* @returns {boolean}
* @protected
*/
DatePicker.prototype.isOpen = function () {
return this._oPopup && this._oPopup.isOpen();
};
DatePicker.prototype.toggleOpen = function (bOpened) {
if (this.getEditable() && this.getEnabled()) {
if (bOpened) {
_cancel.call(this);
} else {
_open.call(this);
}
}
};
DatePicker.prototype.getIconSrc = function () {
return IconPool.getIconURI("appointment-2");
};
DatePicker.prototype.exit = function() {
InputBase.prototype.exit.apply(this, arguments);
if (this._oPopup) {
if (this._oPopup.isOpen()) {
this._oPopup.close();
}
delete this._oPopup;
}
if (this._getCalendar()) {
if (this._oCalendarAfterRenderDelegate) {
this._getCalendar().removeDelegate(this._oCalendarAfterRenderDelegate);
}
this._getCalendar().destroy();
delete this._getCalendar();
}
if (this._iInvalidateCalendar) {
clearTimeout(this._iInvalidateCalendar);
}
this._sUsedDisplayPattern = undefined;
this._sUsedDisplayCalendarType = undefined;
this._oDisplayFormat = undefined;
this._sUsedValuePattern = undefined;
this._sUsedValueCalendarType = undefined;
this._oValueFormat = undefined;
};
DatePicker.prototype.invalidate = function(oOrigin) {
if (!oOrigin || oOrigin != this._getCalendar()) {
// Calendar is only invalidated by DatePicker itself -> so don't invalidate DatePicker
Control.prototype.invalidate.apply(this, arguments);
// Invalidate calendar with a delayed call so it could have updated specialDates aggregation from DatePicker
this._iInvalidateCalendar = setTimeout(_invalidateCalendar.bind(this), 0);
}
};
DatePicker.prototype.onBeforeRendering = function() {
DateTimeField.prototype.onBeforeRendering.apply(this, arguments);
this._checkMinMaxDate();
var oValueHelpIcon = this._getValueHelpIcon();
if (oValueHelpIcon) {
oValueHelpIcon.setProperty("visible", this.getEditable());
}
};
/**
* Sets the displayFormat of the DatePicker.
*
* @param {string} sDisplayFormat new value for <code>displayFormat</code>
* @returns {this} Reference to <code>this</code> for method chaining
* @public
*/
DatePicker.prototype.setDisplayFormat = function(sDisplayFormat) {
this.setProperty("displayFormat", sDisplayFormat);
if (this._oCalendar) { // if the calendar already exists, destroy it and create new one according to the new format
this._oCalendar.removeDelegate(this._oCalendarAfterRenderDelegate);
this._oCalendar.destroy();
this._oCalendar = null;
this._createPopupContent();
}
return this;
};
/**
* Defines the width of the DatePicker. Default value is 100%
*
* @param {string} sWidth new value for <code>width</code>
* @returns {this} Reference to <code>this</code> for method chaining
* @public
*/
DatePicker.prototype.setWidth = function(sWidth) {
return InputBase.prototype.setWidth.call(this, sWidth || "100%");
};
DatePicker.prototype.getWidth = function(sWidth) {
return this.getProperty("width") || "100%";
};
DatePicker.prototype.applyFocusInfo = function(oFocusInfo) {
this._bFocusNoPopup = true;
if (!Device.support.touch || Device.system.desktop) {
InputBase.prototype.applyFocusInfo.apply(this, arguments);
}
};
DatePicker.prototype.onfocusin = function(oEvent) {
if (!jQuery(oEvent.target).hasClass("sapUiIcon")) {
DateTimeField.prototype.onfocusin.apply(this, arguments);
}
this._bFocusNoPopup = undefined;
};
DatePicker.prototype.onsapshow = function(oEvent) {
this.toggleOpen(this.isOpen());
oEvent.preventDefault(); // otherwise IE opens the address bar history
};
// ALT-UP and ALT-DOWN should behave the same
DatePicker.prototype.onsaphide = DatePicker.prototype.onsapshow;
/**
* 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
*/
DatePicker.prototype.onsapescape = function(oEvent) {
var sLastValue = this.getLastValue(),
oDate = this._parseValue( this._getInputValue(), true),
sValueFormatInputDate = this._formatValue(oDate, true);
if (sValueFormatInputDate !== sLastValue) {
oEvent.setMarked();
oEvent.preventDefault();
this.updateDomValue(sLastValue);
this.onValueRevertedByEscape(sLastValue, sValueFormatInputDate);
}
};
DatePicker.prototype.onsappageup = function(oEvent){
var sConstructorName = this._getCalendarConstructor().getMetadata().getName();
oEvent.preventDefault(); // prevent scrolling
if (sConstructorName != "sap.ui.unified.Calendar") {
return;
}
//increase by one day
this._increaseDate(1, "day");
};
DatePicker.prototype.onsappageupmodifiers = function(oEvent){
var sConstructorName = this._getCalendarConstructor().getMetadata().getName();
oEvent.preventDefault(); // prevent scrolling
if (!oEvent.ctrlKey && oEvent.shiftKey) {
if (sConstructorName == "sap.ui.unified.internal.CustomYearPicker") {
return;
}
// increase by one month
this._increaseDate(1, "month");
} else {
// increase by one year
this._increaseDate(1, "year");
}
};
DatePicker.prototype.onsappagedown = function(oEvent){
var sConstructorName = this._getCalendarConstructor().getMetadata().getName();
oEvent.preventDefault(); // prevent scrolling
if (sConstructorName != "sap.ui.unified.Calendar") {
return;
}
//decrease by one day
this._increaseDate(-1, "day");
};
DatePicker.prototype.onsappagedownmodifiers = function(oEvent){
var sConstructorName = this._getCalendarConstructor().getMetadata().getName();
oEvent.preventDefault(); // prevent scrolling
if (!oEvent.ctrlKey && oEvent.shiftKey) {
if (sConstructorName == "sap.ui.unified.internal.CustomYearPicker") {
return;
}
// decrease by one month
this._increaseDate(-1, "month");
} else {
// decrease by one year
this._increaseDate(-1, "year");
}
};
DatePicker.prototype.onkeypress = function(oEvent){
// the keypress event should be fired only when a character key is pressed,
// unfortunately some browsers fire the keypress event for control keys as well.
if (!oEvent.charCode || oEvent.metaKey || oEvent.ctrlKey) {
return;
}
var oFormatter = this._getFormatter(true);
var sChar = String.fromCharCode(oEvent.charCode);
if (sChar && oFormatter.sAllowedCharacters && oFormatter.sAllowedCharacters.indexOf(sChar) < 0) {
oEvent.preventDefault();
}
};
/**
* Getter for property <code>value</code>.
*
* Returns a date as a string in the format defined in property <code>valueFormat</code>.
*
* <b>Note:</b> If there is no data binding, the value is expected and updated in Gregorian calendar type. (Otherwise, the type of the binding is used.)
*
* If this property is used, the <code>dateValue</code> property should not be changed from the caller.
*
* @returns {string} the value of property <code>value</code>
* @public
* @name sap.m.DatePicker#getValue
* @function
*/
/**
* Setter for property <code>value</code>.
*
* Expects a date as a string in the format defined in property <code>valueFormat</code>.
*
* <b>Note:</b> If there is no data binding, the value is expected and updated in Gregorian calendar type. (Otherwise, the type of the binding is used.)
*
* If this property is used, the <code>dateValue</code> property should not be changed from the caller.
*
* If Data binding using a <code>sap.ui.model.type.Date</code> is used, please set the <code>formatOption</code> <code>stricktParsing</code> to <code>true</code>.
* This prevents unwanted automatic corrections of wrong input.
*
* @param {string} sValue The new value of the input.
* @returns {this} Reference to <code>this</code> for method chaining
* @public
* @name sap.m.DatePicker#setValue
* @function
*/
DatePicker.prototype._getValueHelpIcon = function () {
var oValueHelpIcon = this.getAggregation("_endIcon");
return oValueHelpIcon && oValueHelpIcon[0];
};
DatePicker.prototype._dateValidation = function (oDate) {
this._bValid = true;
if (oDate && (oDate.getTime() < this._oMinDate.getTime() || oDate.getTime() > this._oMaxDate.getTime())) {
this._bValid = false;
assert(this._bValid, "Date must be in valid range");
}
this.setProperty("dateValue", oDate);
return oDate;
};
/**
* Set minimum date that can be shown and selected in the <code>DatePicker</code>. This must be a date instance.
*
* @param {Date|module:sap/ui/core/date/UI5Date} oDate A date instance
* @returns {this} Reference to <code>this</code> for method chaining
* @public
*/
DatePicker.prototype.setMinDate = function(oDate) {
if (!this._isValidDate(oDate)) {
throw new Error("Date must be a JavaScript or UI5Date date object; " + this);
}
if (deepEqual(this.getMinDate(), oDate)) {
return this;
}
if (oDate) {
var iYear = oDate.getFullYear();
if (iYear < 1 || iYear > 9999) {
throw new Error("Date must be between 0001-01-01 and 9999-12-31; " + this);
}
this._oMinDate = UI5Date.getInstance(oDate.getTime());
var oDateValue = this.getDateValue();
if (oDateValue && oDateValue.getTime() < oDate.getTime()) {
this._bValid = false;
this._bOutOfAllowedRange = true;
Log.warning("DateValue not in valid date range", this);
}
} else {
this._oMinDate = UI5Date.getInstance(1, 0, 1);
this._oMinDate.setFullYear(1); // otherwise year 1 will be converted to year 1901
}
// re-render because order of parameter changes not clear -> check onBeforeRendering
this.setProperty("minDate", oDate);
if (this._getCalendar()) {
this._getCalendar().setMinDate(oDate);
}
this._oMinDate.setHours(0, 0, 0, 0);//clear the time part
return this;
};
/**
* Set maximum date that can be shown and selected in the <code>DatePicker</code>. This must be a date instance.
*
* @param {Date|module:sap/ui/core/date/UI5Date} oDate A date instance
* @returns {this} Reference to <code>this</code> for method chaining
* @public
*/
DatePicker.prototype.setMaxDate = function(oDate) {
if (!this._isValidDate(oDate)) {
throw new Error("Date must be a JavaScript or UI5Date date object; " + this);
}
if (deepEqual(this.getMaxDate(), oDate)) {
return this;
}
if (oDate) {
var iYear = oDate.getFullYear();
if (iYear < 1 || iYear > 9999) {
throw new Error("Date must be between 0001-01-01 and 9999-12-31; " + this);
}
this._oMaxDate = UI5Date.getInstance(oDate.getTime());
var oDateValue = this.getDateValue();
if (oDateValue && oDateValue.getTime() > oDate.getTime()) {
this._bValid = false;
this._bOutOfAllowedRange = true;
Log.warning("DateValue not in valid date", this);
}
} else {
this._oMaxDate = UI5Date.getInstance(9999, 11, 31, 23, 59, 59, 999);
}
// re-render because order of parameter changes not clear -> check onBeforeRendering
this.setProperty("maxDate", oDate);
if (this._getCalendar()) {
this._getCalendar().setMaxDate(oDate);
}
this._oMaxDate.setHours(23, 59, 59, 999);//set to max possible hours for this day
return this;
};
DatePicker.prototype.setCurrentDateButton = function(bShow) {
var oCalendar = this._getCalendar();
oCalendar && oCalendar.setCurrentDateButton(bShow);
return this.setProperty("showCurrentDateButton", bShow);
};
DatePicker.prototype._checkMinMaxDate = function () {
if (this._oMinDate.getTime() > this._oMaxDate.getTime()) {
Log.warning("minDate > MaxDate -> dates switched", this);
var oMaxDate = UI5Date.getInstance(this._oMinDate.getTime());
var oMinDate = UI5Date.getInstance(this._oMaxDate.getTime());
oMaxDate.setHours(23, 59, 59, 999);
oMinDate.setHours(0, 0, 0, 0);
this._oMinDate = UI5Date.getInstance(oMinDate.getTime());
this._oMaxDate = UI5Date.getInstance(oMaxDate.getTime());
this.setProperty("minDate", oMinDate, true);
this.setProperty("maxDate", oMaxDate, true);
if (this._getCalendar()) {
this._getCalendar().setMinDate(oMinDate);
this._getCalendar().setMaxDate(oMaxDate);
}
}
var oDateValue = this.getDateValue();
if (oDateValue &&
(oDateValue.getTime() < this._oMinDate.getTime() || oDateValue.getTime() > this._oMaxDate.getTime())) {
this._bValid = false;
Log.error("dateValue " + oDateValue.toString() + "(value=" + this.getValue() + ") does not match " +
"min/max date range(" + this._oMinDate.toString() + " - " + this._oMaxDate.toString() + "). App. " +
"developers should take care to maintain dateValue/value accordingly.", this);
}
};
DatePicker.prototype.getDisplayFormatType = function () {
return this.getProperty("displayFormatType");
};
DatePicker.prototype._handleDateValidation = function (oDate) {
this._bValid = true;
if (!oDate || 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);
}
// 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);
this.setProperty("dateValue", oDate);
};
DatePicker.prototype.setDisplayFormatType = function(sDisplayFormatType) {
if (sDisplayFormatType) {
var bFound = false;
for ( var sType in CalendarType) {
if (sType == sDisplayFormatType) {
bFound = true;
break;
}
}
if (!bFound) {
throw new Error(sDisplayFormatType + " is not a valid calendar type" + this);
}
}
this.setProperty("displayFormatType", sDisplayFormatType, true); // no rerendering
// reuse update from format function
this.setDisplayFormat(this.getDisplayFormat());
return this;
};
DatePicker.prototype.setSecondaryCalendarType = function(sCalendarType){
this._bSecondaryCalendarTypeSet = true; // as property can not be empty but we use it only if set
this.setProperty("secondaryCalendarType", sCalendarType, true);
if (this._getCalendar()) {
this._getCalendar().setSecondaryCalendarType(sCalendarType);
}
return this;
};
/**
* Sets <code>showFooter</code> property to the given boolean value
*
* @since 1.70
* @param {} bFlag when true footer is displayed
* @public
*/
DatePicker.prototype.setShowFooter = function(bFlag) {
var oPopup = this._oPopup,
oCalendar = this._getCalendar();
this.setProperty("showFooter", bFlag);
if (!oPopup || !oCalendar) {
return this;
}
oPopup._getButtonFooter().setVisible(bFlag);
return this;
};
/**
* Adds some <code>specialDate</code> to the aggregation <code>specialDates</code>.
*
* @since 1.38.5
* @param {sap.ui.unified.DateTypeRange} oSpecialDate the specialDate to add; if empty, nothing is added
* @returns {this} Reference to <code>this</code> for method chaining
* @public
*/
DatePicker.prototype.addSpecialDate = function(oSpecialDate){
_checkSpecialDate.call(this, oSpecialDate);
this.addAggregation("specialDates", oSpecialDate, true);
_invalidateCalendar.call(this);
return this;
};
/**
* Inserts a <code>specialDate</code> to the aggregation <code>specialDates</code>.
*
* @since 1.38.5
* @param {sap.ui.unified.DateTypeRange} oSpecialDate the specialDate to insert; if empty, nothing is inserted
* @param {int} iIndex the 0-based index the <code>specialDate</code> should be inserted at;
* for a negative value of <code>iIndex</code>, the <code>specialDate</code> is inserted at position 0;
* for a value greater than the current size of the aggregation, the <code>specialDate</code> is inserted at the last position
* @returns {this} Reference to <code>this</code> for method chaining
* @public
*/
DatePicker.prototype.insertSpecialDate = function(oSpecialDate, iIndex){
_checkSpecialDate.call(this, oSpecialDate);
this.insertAggregation("specialDates", oSpecialDate, iIndex, true);
_invalidateCalendar.call(this);
return this;
};
/**
* Removes a <code>specialDate</code> from the aggregation <code>specialDates</code>.
*
* @since 1.38.5
* @param {sap.ui.unified.DateTypeRange} oSpecialDate The <code>specialDate</code> to remove or its index or ID
* @returns {sap.ui.unified.DateTypeRange|null} The removed <code>specialDate</code> or <code>null</code>
* @public
*/
DatePicker.prototype.removeSpecialDate = function(oSpecialDate){
var oRemoved = this.removeAggregation("specialDates", oSpecialDate, true);
_invalidateCalendar.call(this);
return oRemoved;
};
DatePicker.prototype.removeAllSpecialDates = function(){
var aRemoved = this.removeAllAggregation("specialDates", true);
_invalidateCalendar.call(this);
return aRemoved;
};
DatePicker.prototype.destroySpecialDates = function(){
this.destroyAggregation("specialDates", true);
_invalidateCalendar.call(this);
return this;
};
/**
* Sets the associated legend.
*
* @since 1.38.5
* @param {sap.ui.core.ID | sap.ui.unified.CalendarLegend} oLegend ID of an element which becomes the new target of this <code>legend</code> association;
* alternatively, an element instance may be given
* @returns {this} Reference to <code>this</code> for method chaining
* @public
*/
DatePicker.prototype.setLegend = function(oLegend){
this.setAssociation("legend", oLegend, true);
var sId = this.getLegend();
if (sId) {
var CalendarLegend = sap.ui.require("sap/ui/unified/CalendarLegend");
oLegend = sap.ui.getCore().byId(sId);
if (oLegend && !(typeof CalendarLegend == "function" && oLegend instanceof CalendarLegend)) {
throw new Error(oLegend + " is not an sap.ui.unified.CalendarLegend. " + this);
}
}
if (this._getCalendar()) {
this._getCalendar().setLegend(sId);
}
return this;
};
DatePicker.prototype.onChange = function(oEvent) {
// don't call InputBase onChange because this calls setValue what would trigger a new formatting
// check the control is editable or not
if (!this.getEditable() || !this.getEnabled()) {
return;
}
// set date before fire change event
var sValue = this._$input.val(),
sOldValue = this._formatValue(this.getDateValue()),
oDate;
if (sValue == sOldValue && this._bValid) {
// only needed if value really changed
return;
}
if (this.getShowFooter() && this._oPopup && !sValue) {
this._oPopup.getBeginButton().setEnabled(false);
}
this._bValid = true;
if (sValue != "") {
oDate = this._parseValue(sValue, true);
if (!oDate || oDate.getTime() < this._oMinDate.getTime() || oDate.getTime() > this._oMaxDate.getTime()) {
this._bValid = false;
oDate = undefined;
} else {
// check if Formatter changed the value (it correct some wrong inputs or known patterns)
sValue = this._formatValue(oDate);
}
}
if (this.getDomRef() && (this._$input.val() !== sValue)) {
this._$input.val(sValue);
this._curpos = this._$input.cursorPos();
}
if (oDate) {//user input is parsed successfully and the date fits to the min/max range
// get the value in valueFormat
sValue = this._formatValue(oDate, true);
}
// compare with the old known value
if (this.getLastValue() !== sValue
|| (oDate && this.getDateValue() && oDate.getFullYear() !== this.getDateValue().getFullYear())) {
// remember the last value on change
this.setLastValue(sValue);
this.setProperty("value", sValue, true); // no rerendering
var sNewValue = this.getValue(); // in databinding a formatter could change the value (including dateValue) directly
if (this._bValid && sValue == sNewValue) {
this.setProperty("dateValue", oDate, true); // no rerendering
}
sValue = sNewValue;
if (this.isOpen()) {
if (this._bValid) {
oDate = this.getDateValue(); // as in databinding a formatter could change the date
}
this._getCalendar().focusDate(oDate);
var oStartDate = this._oDateRange.getStartDate();
if ((!oStartDate && oDate) || (oStartDate && oDate && oStartDate.getTime() != oDate.getTime())) {
this._oDateRange.setStartDate(UI5Date.getInstance(oDate.getTime()));
} else if (oStartDate && !oDate) {
this._oDateRange.setStartDate(undefined);
}
}
this.fireChangeEvent(sValue, {valid: this._bValid});
}
};
DatePicker.prototype.updateDomValue = function(sValue) {
if (this.isActive() && this._$input.val() !== sValue) {
// dom value updated other than value property
this._bCheckDomValue = true;
sValue = (typeof sValue == "undefined") ? this._$input.val() : sValue.toString();
this._curpos = this._$input.cursorPos();
var oDate = this._parseValue(sValue, true);
sValue = this._formatValue(oDate);
// if set to true, handle the user input and data
// model updates concurrency in order to not overwrite
// values coming from the user
if (this._bPreferUserInteraction) {
this.handleInputValueConcurrency(sValue);
} else {
// update the DOM value when necessary
// otherwise cursor can goto end of text unnecessarily
this._$input.val(sValue);
if (document.activeElement === this._$input[0]) {
this._$input.cursorPos(this._curpos);
}
}
}
return this;
};
function _open(oDomRef){
this._createPopup();
this._createPopupContent();
// set displayFormatType as PrimaryCalendarType
// not only one because it depends on DataBinding
var sCalendarType;
var oBinding = this.getBinding("value");
if (oBinding && oBinding.oType && oBinding.oType.oOutputFormat) {
sCalendarType = oBinding.oType.oOutputFormat.oFormatOptions.calendarType;
} else if (oBinding && oBinding.oType && oBinding.oType.oFormat) {
sCalendarType = oBinding.oType.oFormat.oFormatOptions.calendarType;
}
if (!sCalendarType) {
sCalendarType = this.getDisplayFormatType();
}
if (sCalendarType) {
this._getCalendar().setPrimaryCalendarType(sCalendarType);
}
var sValue = this._bValid ? this._formatValue(this.getDateValue()) : this.getValue();
if (sValue != this._$input.val()) {
this.onChange(); // to check manually typed in text
}
this._fillDateRange();
this._openPopup(oDomRef);
// Fire navigate event when the calendar popup opens
this.fireNavigate({
dateRange: this._getVisibleDatesRange(this._getCalendar()),
afterPopupOpened: true
});
}
// to be overwritten by DateTimePicker
DatePicker.prototype._createPopup = function(){
var sTitleText = "";
if (!this._oPopup) {
this._oPopup = new ResponsivePopover(this.getId() + "-RP", {
showCloseButton: false,
showArrow: false,
showHeader: false,
placement: library.PlacementType.VerticalPreferredBottom,
contentWidth: this.$().closest(".sapUiSizeCompact").length > 0 ? "18rem" : "21rem",
beginButton: new Button({
type: library.ButtonType.Emphasized,
text: oResourceBundle.getText("DATEPICKER_SELECTION_CONFIRM"),
press: this._handleOKButton.bind(this)
}),
afterOpen: _handleOpen.bind(this),
afterClose: _handleClose.bind(this)
}).addStyleClass("sapMRPCalendar");
if (this.getShowFooter()) {
this._oPopup.addStyleClass("sapMLandscapePadding");
}
this._oPopup._getPopup().setAutoClose(true);
if (Device.system.phone) {
sTitleText = LabelEnablement.getReferencingLabels(this)
.concat(this.getAriaLabelledBy())
.reduce(function(sAccumulator, sCurrent) {
var oCurrentControl = Element.registry.get(sCurrent);
return sAccumulator + " " + (oCurrentControl.getText ? oCurrentControl.getText() : "");
}, "")
.trim();
this._oPopup.setTitle(sTitleText);
this._oPopup.setShowHeader(true);
this._oPopup.setShowCloseButton(true);
} else {
// sap.m.Dialog used instead of the sap.m.ResponsivePopover doesen't display
// correctly without an animation on mobile devices so we remove the animation
// only for desktop when sap.m.Popover is used instead of sap.m.Dialog
this._oPopup._getPopup().setDurations(0, 0);
this._oPopup.setEndButton(new Button({
text: oResourceBundle.getText("DATEPICKER_SELECTION_CANCEL"),
press: this._handleCancelButton.bind(this)
})
);
}
// define a parent-child relationship between the control's and the _picker pop-up
this.setAggregation("_popup", this._oPopup, true);
}
};
// to be overwritten by DateTimePicker
DatePicker.prototype._openPopup = function(oDomRef){
if (!this._oPopup) {
return;
}
if (!oDomRef) {
oDomRef = this.getDomRef();
}
this._oPopup._getPopup().setExtraContent([oDomRef]);
this._oPopup.openBy(oDomRef || this);
};
/**
* 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 Date 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
*/
DatePicker.prototype.openBy = function(oDomRef) {
_open.call(this, oDomRef);
};
/**
* Creates a DateRange with the first and the last visible days in the calendar popup.
* @param {sap.ui.unified.Calendar} oCalendar the calendar whose DatesRange is wanted
* @returns {sap.ui.unified.DateRange} the DateRange of the visible dates
* @private
*/
DatePicker.prototype._getVisibleDatesRange = function (oCalendar) {
var aVisibleDays = oCalendar._getVisibleDays();
// Convert to local date instance
return new DateRange({
startDate: aVisibleDays[0].toLocalJSDate(), // First visible date
endDate: aVisibleDays[aVisibleDays.length - 1].toLocalJSDate() // Last visible date
});
};
/**
* Creates the sap.ui.unified.Calendar instance with defined properties and attached events
*/
DatePicker.prototype._createPopupContent = function(){
var CalendarConstructor = this._getCalendarConstructor();
if (!this._getCalendar()) {
this._oCalendar = new CalendarConstructor(this.getId() + "-cal", {
intervalSelection: this._bIntervalSelection,
minDate: this.getMinDate(),
maxDate: this.getMaxDate(),
legend: this.getLegend(),
calendarWeekNumbering: this.getCalendarWeekNumbering(),
startDateChange: function () {
this.fireNavigate({
dateRange: this._getVisibleDatesRange(this._getCalendar())
});
}.bind(this)
});
this._oCalendar.setShowCurrentDateButton(this.getShowCurrentDateButton());
this._oDateRange = new DateRange();
this._getCalendar().addSelectedDate(this._oDateRange);
this._getCalendar()._setSpecialDatesControlOrigin(this);
this._getCalendar().attachCancel(_cancel, this);
if (this.$().closest(".sapUiSizeCompact").length > 0) {
this._getCalendar().addStyleClass("sapUiSizeCompact");
}
if (this._bSecondaryCalendarTypeSet) {
this._getCalendar().setSecondaryCalendarType(this.getSecondaryCalendarType());
}
if (this._bOnlyCalendar) {
this._getCalendar().attachSelect(this._handleCalendarSelect, this);
this._getCalendar().attachEvent("_renderMonth", _resizeCalendar, this);
this._oPopup._getButtonFooter().setVisible(this.getShowFooter());
this._getCalendar()._bSkipCancelButtonRendering = true;
if (!this._oPopup.getContent().length) {
var oHeader = this._getValueStateHeader();
this._oPopup.addContent(this._getValueStateHeader());
oHeader.setPopup(this._oPopup._oControl);
}
this._oPopup.addContent(this._getCalendar());
if (!this.getDateValue()) {
this._oPopup.getBeginButton().setEnabled(false);
}
}
this._attachAfterRenderingDelegate();
}
};
DatePicker.prototype._attachAfterRenderingDelegate = function() {
this._oCalendarAfterRenderDelegate = {
onAfterRendering: function() {
var oPopup = this._oPopup && this._oPopup._getPopup();
oPopup && oPopup._oLastPosition && oPopup._applyPosition(oPopup._oLastPosition);
if (this._oPopup.isOpen()) {
this._oCalendar.focus();
}
}.bind(this)
};
this._oCalendar.addDelegate(this._oCalendarAfterRenderDelegate);
};
/**
* Gets the sap.ui.unified.Calendar constructor function depending on the displayFormat property
*
* @returns {Object} JS function Object
* @private
*/
DatePicker.prototype._getCalendarConstructor = function() {
var aPatternSymbolTypes = this._getFormatter(true)
.aFormatArray
.map(function(oPatternSymbolSettings) {
return oPatternSymbolSettings.type.toLowerCase();
}),
bDay = aPatternSymbolTypes.indexOf("day") >= 0,
bMonth = aPatternSymbolTypes.indexOf("month") >= 0 || aPatternSymbolTypes.indexOf("monthstandalone") >= 0,
bYear = aPatternSymbolTypes.indexOf("year") >= 0;
if (bDay && bMonth && bYear) {
return Calendar;
} else if (bMonth && bYear) {
return CustomMonthPicker;
} else if (bYear) {
return CustomYearPicker;
} else {
Log.warning("Not valid date pattern! Default Calendar constructor function is returned", this);
return Calendar;
}
};
DatePicker.prototype._fillDateRange = function(){
var oDate = this.getDateValue();
if (oDate &&
oDate.getTime() >= this._oMinDate.getTime() &&
oDate.getTime() <= this._oMaxDate.getTime()) {
this._getCalendar().focusDate(UI5Date.getInstance(oDate.getTime()));
if (!this._oDateRange.getStartDate() || this._oDateRange.getStartDate().getTime() != oDate.getTime()) {
this._oDateRange.setStartDate(UI5Date.getInstance(oDate.getTime()));
}
} else {
var oInitialFocusedDateValue = this.getInitialFocusedDateValue();
var oFocusDate = oInitialFocusedDateValue ? oInitialFocusedDateValue : UI5Date.getInstance();
if (oFocusDate.getTime() < this._oMinDate.getTime()) {
oFocusDate = this._oMinDate;
} else if (oFocusDate.getTime() > this._oMaxDate.getTime()) {
oFocusDate = this._oMaxDate;
}
this._getCalendar().focusDate(oFocusDate);
if (this._oDateRange.getStartDate()) {
this._oDateRange.setStartDate(undefined);
}
}
};
/**
* @see sap.ui.core.Control#getAccessibilityInfo
* @returns {sap.ui.core.AccessibilityInfo} Current accessibility state of the control.
* @protected
*/
DatePicker.prototype.getAccessibilityInfo = function() {
var oRenderer = this.getRenderer();
var oInfo = InputBase.prototype.getAccessibilityInfo.apply(this, arguments);
var sValue = this.getValue() || "";
var sRequired = this.getRequired() ? Core.getLibraryResourceBundle("sap.m").getText("ELEMENT_REQUIRED") : '';
if (this._bValid) {
var oDate = this.getDateValue();
if (oDate) {
sValue = this._formatValue(oDate);
}
}
oInfo.type = oResourceBundle.getText("ACC_CTR_TYPE_DATEINPUT");
oInfo.description = [sValue || this._getPlaceholder(), oRenderer.getLabelledByAnnouncement(this), oRenderer.getDescribedByAnnouncement(this), sRequired].join(" ").trim();
return oInfo;
};
DatePicker.prototype._selectDate = function() {
var oDateOld = this.getDateValue(),
oDate = this._getSelectedDate(),
sValue = "";
// do not use this.onChange() because output pattern will change date (e.g. only last 2 number of year -> 1966 -> 2066 )
if (!deepEqual(oDate, oDateOld)) {
this.setDateValue(UI5Date.getInstance(oDate.getTime()));
// compare Dates because value can be the same if only 2 digits for year
sValue = this.getValue();
this.fireChangeEvent(sValue, {valid: true});
this._focusInput();
} else if (!this._bValid){
// wrong input before open calendar
sValue = this._formatValue(oDate);
if (sValue != this._$input.val()) {
this._bValid = true;
if (this.getDomRef()) { // as control could be destroyed during update binding
this._$input.val(sValue);
this.setLastValue(sValue);
}
// we have to format the value with the existing format
// before setting it and firing the change event
sValue = this._formatValue(oDate, true);
this.setProperty("value", sValue, true); // no rerendering
this.fireChangeEvent(sValue, {valid: true});
this._focusInput();
}
} else if (Device.system.desktop || !Device.support.touch) {
this.focus();
}
// close popup and focus input after change event to allow application to reset value state or similar things
this._oPopup.close();
};
DatePicker.prototype._handleCalendarSelect = function(){
if (this.getShowFooter()) {
this._oPopup.getBeginButton().setEnabled(true);
return;
}
this._selectDate();
};
DatePicker.prototype._getTimezone = function(bUseDefaultAsFallback) {
return Configuration.getTimezone();
};
/* sets cursor inside the input in order to focus it */
DatePicker.prototype._focusInput = function(){
if (this.getDomRef() && (Device.system.desktop || !Device.support.touch)) { // as control could be destroyed during update binding
this._curpos = this._$input.val().length;
this._$input.cursorPos(this._curpos);
}
return this;
};
/**
* Getter for DatePicker's Calendar instance.
* @returns {sap.ui.unified.Calendar} The calendar object
* @private
*/
DatePicker.prototype._get