Skip to content

Commit

Permalink
feat(ui5-calendar): Declarative dates support added (#2648)
Browse files Browse the repository at this point in the history
  • Loading branch information
vladitasev committed Jan 12, 2021
1 parent b4f836a commit 6602fba
Show file tree
Hide file tree
Showing 13 changed files with 240 additions and 50 deletions.
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

0 comments on commit 6602fba

Please sign in to comment.