Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui5-calendar): Declarative dates support added #2648

Merged
merged 12 commits into from
Jan 12, 2021
6 changes: 3 additions & 3 deletions packages/main/src/Calendar.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
id="{{_id}}-daypicker"
?hidden="{{_isDayPickerHidden}}"
format-pattern="{{_formatPattern}}"
.selectedDates="{{selectedDates}}"
.selectedDates="{{_selectedDatesTimestamps}}"
._hidden="{{_isDayPickerHidden}}"
.primaryCalendarType="{{_primaryCalendarType}}"
.selectionMode="{{selectionMode}}"
Expand All @@ -23,7 +23,7 @@
id="{{_id}}-MP"
?hidden="{{_isMonthPickerHidden}}"
format-pattern="{{_formatPattern}}"
.selectedDates="{{selectedDates}}"
.selectedDates="{{_selectedDatesTimestamps}}"
._hidden="{{_isMonthPickerHidden}}"
.primaryCalendarType="{{_primaryCalendarType}}"
.minDate="{{minDate}}"
Expand All @@ -37,7 +37,7 @@
id="{{_id}}-YP"
?hidden="{{_isYearPickerHidden}}"
format-pattern="{{_formatPattern}}"
.selectedDates="{{selectedDates}}"
.selectedDates="{{_selectedDatesTimestamps}}"
._hidden="{{_isYearPickerHidden}}"
.primaryCalendarType="{{_primaryCalendarType}}"
.minDate="{{minDate}}"
Expand Down
98 changes: 92 additions & 6 deletions packages/main/src/Calendar.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDate.js";
import RenderScheduler from "@ui5/webcomponents-base/dist/RenderScheduler.js";
import {
isF4,
isF4Shift,
} from "@ui5/webcomponents-base/dist/Keys.js";
import * as CalendarDateComponent from "./CalendarDate.js";
import CalendarPart from "./CalendarPart.js";
import CalendarHeader from "./CalendarHeader.js";
import DayPicker from "./DayPicker.js";
Expand Down Expand Up @@ -73,16 +75,36 @@ const metadata = {
type: Boolean,
},
},
managedSlots: true,
slots: /** @lends sap.ui.webcomponents.main.Calendar.prototype */ {
/**
* Defines the selected date or dates (depending on the <code>selectionMode</code> property) for this calendar as instances of <code>ui5-date</code>
*
* @type {HTMLElement[]}
* @slot
* @public
*/
"default": {
propertyName: "dates",
type: HTMLElement,
invalidateOnChildChange: true,
},
},
events: /** @lends sap.ui.webcomponents.main.Calendar.prototype */ {
/**
* Fired when the selected dates changed.
* Fired when the selected dates change.
* <b>Note:</b> If you call <code>preventDefault()</code> for this event, <code>ui5-calendar</code> will not
* create instances of <code>ui5-date</code> for the newly selected dates. In that case you should do this manually.
*
* @event sap.ui.webcomponents.main.Calendar#selected-dates-change
* @param {Array} dates The selected dates timestamps
* @param {Array} values The selected dates
* @param {Array} dates The selected dates as UTC timestamps
* @public
*/
"selected-dates-change": {
detail: {
dates: { type: Array },
values: { type: Array },
},
},
},
Expand All @@ -93,7 +115,14 @@ const metadata = {
*
* <h3 class="comment-api-title">Overview</h3>
*
* The <code>ui5-calendar</code> can be used stand alone to display the years, months, weeks and days
* The <code>ui5-calendar</code> component allows users to select one or more dates.
* <br><br>
* Currently selected dates are represented with instances of <code>ui5-date</code> as
* children of the <code>ui5-calendar</code>. The value property of each <code>ui5-date</code> must be a
* date string, correctly formatted according to the <code>ui5-calendar</code>'s <code>formatPattern</code> property.
* Whenever the user changes the date selection, <code>ui5-calendar</code> will automatically create/remove instances
* of <code>ui5-date</code> in itself, unless you prevent this behavior by calling <code>preventDefault()</code> for the
* <code>selected-dates-change</code> event. This is useful if you want to control the selected dates externally.
* <br><br>
*
* <h3>Usage</h3>
Expand All @@ -105,7 +134,7 @@ const metadata = {
* <li>Pressing over an year inside the years view</li>
* </ul>
* <br>
* The user can comfirm a date selection by pressing over a date inside the days view.
* The user can confirm a date selection by pressing over a date inside the days view.
* <br><br>
*
* <h3>Keyboard Handling</h3>
Expand Down Expand Up @@ -159,6 +188,7 @@ const metadata = {
* @alias sap.ui.webcomponents.main.Calendar
* @extends CalendarPart
* @tagname ui5-calendar
* @appenddocs CalendarDate
* @public
* @since 1.0.0-rc.11
*/
Expand All @@ -175,6 +205,36 @@ class Calendar extends CalendarPart {
return calendarCSS;
}

/**
* @private
*/
get _selectedDatesTimestamps() {
return this.dates.map(date => {
const value = date.value;
return value && !!this.getFormat().parse(value) ? this._getTimeStampFromString(value) / 1000 : undefined;
}).filter(date => !!date);
}

/**
* @private
*/
_setSelectedDates(selectedDates) {
const selectedValues = selectedDates.map(timestamp => this.getFormat().format(new Date(timestamp * 1000), true)); // Format as UTC
const valuesInDOM = [...this.dates].map(dateElement => dateElement.value);

// Remove all elements for dates that are no longer selected
this.dates.filter(dateElement => !selectedValues.includes(dateElement.value)).forEach(dateElement => {
this.removeChild(dateElement);
});

// Create tags for the selected dates that don't already exist in DOM
selectedValues.filter(value => !valuesInDOM.includes(value)).forEach(value => {
const dateElement = document.createElement("ui5-date");
dateElement.value = value;
this.appendChild(dateElement);
});
}

async onAfterRendering() {
await RenderScheduler.whenFinished(); // Await for the current picker to render and then ask if it has previous/next pages
this._previousButtonDisabled = !this._currentPickerDOM._hasPreviousPage();
Expand Down Expand Up @@ -237,10 +297,16 @@ class Calendar extends CalendarPart {
onSelectedDatesChange(event) {
const timestamp = event.detail.timestamp;
const selectedDates = event.detail.dates;
const datesValues = selectedDates.map(ts => {
const calendarDate = CalendarDate.fromTimestamp(ts * 1000, this._primaryCalendarType);
return this.getFormat().format(calendarDate.toUTCJSDate(), true);
});

this.timestamp = timestamp;
this.selectedDates = selectedDates;
this.fireEvent("selected-dates-change", { timestamp, dates: [...selectedDates] });
const defaultPrevented = !this.fireEvent("selected-dates-change", { timestamp, dates: [...selectedDates], values: datesValues }, true);
if (!defaultPrevented) {
this._setSelectedDates(selectedDates);
}
}

onSelectedMonthChange(event) {
Expand All @@ -267,8 +333,28 @@ class Calendar extends CalendarPart {
}
}

/**
* Returns an array of UTC timestamps, representing the selected dates.
* @protected
* @deprecated
*/
get selectedDates() {
return this._selectedDatesTimestamps;
}

/**
* Creates instances of <code>ui5-date</code> inside this <code>ui5-calendar</code> with values, equal to the provided UTC timestamps
* @protected
* @deprecated
* @param selectedDates Array of UTC timestamps
*/
set selectedDates(selectedDates) {
this._setSelectedDates(selectedDates);
}

static get dependencies() {
return [
CalendarDateComponent.default,
CalendarHeader,
DayPicker,
MonthPicker,
Expand Down
44 changes: 44 additions & 0 deletions packages/main/src/CalendarDate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";

/**
* @public
*/
const metadata = {
tag: "ui5-date",
properties: /** @lends sap.ui.webcomponents.main.CalendarDate.prototype */ {

/**
* The date formatted according to the <code>formatPattern</code> property of the <code>ui5-calendar</code> that hosts the <code>ui5-date</code>
*
* @type {string}
* @public
*/
value: {
type: String,
},
},
};

/**
* @class
*
* <h3 class="comment-api-title">Overview</h3>
*
* The <code>ui5-date</code> component defines a calendar date to be used inside <code>ui5-calendar</code>
*
* @constructor
* @author SAP SE
* @alias sap.ui.webcomponents.main.CalendarDate
* @extends sap.ui.webcomponents.base.UI5Element
* @tagname ui5-date
* @public
*/
class CalendarDate extends UI5Element {
static get metadata() {
return metadata;
}
}

CalendarDate.define();

export default CalendarDate;
13 changes: 1 addition & 12 deletions packages/main/src/CalendarPart.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,11 @@ const metadata = {
* The timestamp of the currently focused date. Set this property to move the component's focus to a certain date.
* <b>Node:</b> Timestamp is 10-digit Integer representing the seconds (not milliseconds) since the Unix Epoch.
* @type {Integer}
* @public
* @protected
*/
timestamp: {
type: Integer,
},

/**
* An array of UTC timestamps representing the selected date or dates depending on the capabilities of the picker component.
* @type {Array}
* @public
*/
selectedDates: {
type: Integer,
multiple: true,
compareValues: true,
},
},
};

Expand Down
15 changes: 6 additions & 9 deletions packages/main/src/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Icon from "./Icon.js";
import Button from "./Button.js";
import ResponsivePopover from "./ResponsivePopover.js";
import Calendar from "./Calendar.js";
import * as CalendarDateComponent from "./CalendarDate.js";
import Input from "./Input.js";
import InputType from "./types/InputType.js";
import DatePickerTemplate from "./generated/templates/DatePickerTemplate.lit.js";
Expand Down Expand Up @@ -388,12 +389,8 @@ class DatePicker extends DateComponentBase {
* @protected
*/
get _calendarSelectedDates() {
if (!this.value) {
return [];
}

if (this._checkValueValidity(this.value)) {
return [getRoundedTimestamp(this.dateValueUTC.getTime())];
if (this.value && this._checkValueValidity(this.value)) {
return [this.value];
}

return [];
Expand Down Expand Up @@ -640,9 +637,8 @@ class DatePicker extends DateComponentBase {
* @protected
*/
onSelectedDatesChange(event) {
const timestamp = event.detail.dates && event.detail.dates[0];
const calendarDate = CalendarDate.fromTimestamp(timestamp * 1000, this._primaryCalendarType);
const newValue = this.getFormat().format(calendarDate.toUTCJSDate(), true);
event.preventDefault();
const newValue = event.detail.values && event.detail.values[0];
this._updateValueAndFireEvents(newValue, true, ["change", "value-changed"]);

this._focusInputAfterClose = true;
Expand Down Expand Up @@ -728,6 +724,7 @@ class DatePicker extends DateComponentBase {
Icon,
ResponsivePopover,
Calendar,
CalendarDateComponent.default,
Input,
Button,
];
Expand Down
7 changes: 5 additions & 2 deletions packages/main/src/DatePickerPopover.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,17 @@
primary-calendar-type="{{_primaryCalendarType}}"
format-pattern="{{_formatPattern}}"
timestamp="{{_calendarTimestamp}}"
.selectedDates={{_calendarSelectedDates}}
.selectionMode="{{_calendarSelectionMode}}"
.minDate="{{minDate}}"
.maxDate="{{maxDate}}"
@ui5-selected-dates-change="{{onSelectedDatesChange}}"
?hide-week-numbers="{{hideWeekNumbers}}"
._currentPicker="{{_calendarCurrentPicker}}"
></ui5-calendar>
>
{{#each _calendarSelectedDates}}
<ui5-date value="{{this}}"></ui5-date>
{{/each}}
</ui5-calendar>
{{/inline}}

{{#*inline "footer"}}{{/inline}}
30 changes: 26 additions & 4 deletions packages/main/src/DateRangePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ const metadata = {
type: String,
defaultValue: "-",
},

/**
* The first date in the range during selection (this is a temporary value, not the first date in the value range)
* @private
*/
_tempValue: {
type: String,
},
},
};

Expand Down Expand Up @@ -104,7 +112,13 @@ class DateRangePicker extends DatePicker {
* @override
*/
get _calendarSelectedDates() {
return [this._firstDateTimestamp, this._lastDateTimestamp].filter(date => !!date);
if (this._tempValue) {
return [this._tempValue];
}
if (this.value && this._checkValueValidity(this.value)) {
return this._splitValueByDelimiter(this.value);
}
return [];
}

/**
Expand Down Expand Up @@ -178,13 +192,21 @@ class DateRangePicker extends DatePicker {
* @override
*/
onSelectedDatesChange(event) {
const selectedDates = event.detail.dates;
if (selectedDates.length !== 2) { // Do nothing until the user selects 2 dates, we don't change any state at all for one date
event.preventDefault(); // never let the calendar update its own dates, the parent component controls them
const values = event.detail.values;

if (values.length === 0) {
return;
}

if (values.length === 1) { // Do nothing until the user selects 2 dates, we don't change any state at all for one date
this._tempValue = values[0];
return;
}

const newValue = this._buildValue(...selectedDates); // the value will be normalized so we don't need to order them here
const newValue = this._buildValue(...event.detail.dates); // the value will be normalized so we don't need to order them here
this._updateValueAndFireEvents(newValue, true, ["change", "value-changed"]);
this._tempValue = "";
this._focusInputAfterClose = true;
this.closePicker();
}
Expand Down
Loading