From fabd1a228c23f7d4b2e9112386954559fcc4476f Mon Sep 17 00:00:00 2001 From: Juliano Date: Thu, 1 Feb 2018 00:09:26 -0200 Subject: [PATCH 1/4] add max/min filter to multi year and year views --- src/lib/datepicker/calendar.html | 4 ++ src/lib/datepicker/multi-year-view.ts | 30 +++++++++++++++ src/lib/datepicker/year-view.ts | 54 ++++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/src/lib/datepicker/calendar.html b/src/lib/datepicker/calendar.html index 594fc00e1d5d..e6985e43095b 100644 --- a/src/lib/datepicker/calendar.html +++ b/src/lib/datepicker/calendar.html @@ -36,6 +36,8 @@ [activeDate]="_activeDate" [selected]="selected" [dateFilter]="_dateFilterForViews" + [maxDate]="maxDate" + [minDate]="minDate" (selectedChange)="_goToDateInView($event, 'month')"> @@ -44,6 +46,8 @@ [activeDate]="_activeDate" [selected]="selected" [dateFilter]="_dateFilterForViews" + [maxDate]="maxDate" + [minDate]="minDate" (selectedChange)="_goToDateInView($event, 'year')"> diff --git a/src/lib/datepicker/multi-year-view.ts b/src/lib/datepicker/multi-year-view.ts index 5c023839b894..b825019bcc7e 100644 --- a/src/lib/datepicker/multi-year-view.ts +++ b/src/lib/datepicker/multi-year-view.ts @@ -64,6 +64,22 @@ export class MatMultiYearView implements AfterContentInit { } private _selected: D | null; + /** The minimum selectable date. */ + @Input() + get minDate(): D | null { return this._minDate; } + set minDate(value: D | null) { + this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + } + private _minDate: D | null; + + /** The maximum selectable date. */ + @Input() + get maxDate(): D | null { return this._maxDate; } + set maxDate(value: D | null) { + this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + } + private _maxDate: D | null; + /** A function used to filter which dates are selectable. */ @Input() dateFilter: (date: D) => boolean; @@ -133,6 +149,20 @@ export class MatMultiYearView implements AfterContentInit { return true; } + // disable if the year is greater than maxDate + if (this.maxDate) { + if (year > this._dateAdapter.getYear(this.maxDate)) { + return false; + } + } + + // disable if the year is lower than maxDate + if (this.minDate) { + if (year < this._dateAdapter.getYear(this.minDate)) { + return false; + } + } + const firstOfYear = this._dateAdapter.createDate(year, 0, 1); // If any date in the year is enabled count the year as enabled. diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index f150ef153e1e..882508cc4098 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -59,6 +59,22 @@ export class MatYearView implements AfterContentInit { } private _selected: D | null; + /** The minimum selectable date. */ + @Input() + get minDate(): D | null { return this._minDate; } + set minDate(value: D | null) { + this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + } + private _minDate: D | null; + + /** The maximum selectable date. */ + @Input() + get maxDate(): D | null { return this._maxDate; } + set maxDate(value: D | null) { + this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + } + private _maxDate: D | null; + /** A function used to filter which dates are selectable. */ @Input() dateFilter: (date: D) => boolean; @@ -143,8 +159,17 @@ export class MatYearView implements AfterContentInit { return true; } - let firstOfMonth = this._dateAdapter.createDate( - this._dateAdapter.getYear(this.activeDate), month, 1); + const activeYear = this._dateAdapter.getYear(this.activeDate); + + let firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1); + + if (!this._yearAndMonthBeforeMax(activeYear, month)) { + return false; + } + + if (!this._yearAndMonthAfterMin(activeYear, month)) { + return false; + } // If any date in the month is enabled count the month as enabled. for (let date = firstOfMonth; this._dateAdapter.getMonth(date) == month; @@ -157,6 +182,31 @@ export class MatYearView implements AfterContentInit { return false; } + /** + * Tests whether the combination month/year is before this.maxDate, considering + * just the month and year of this.maxDate + */ + private _yearAndMonthBeforeMax(year: number, month: number) { + if (this.maxDate) { + const maxYear = this._dateAdapter.getYear(this.maxDate); + const maxMonth = this._dateAdapter.getMonth(this.maxDate); + + return year < maxYear || (year === maxYear && month < maxMonth); + } + + return true; + } + + _yearAndMonthAfterMin(year: number, month: number) { + if (this.minDate) { + const minYear = this._dateAdapter.getYear(this.minDate); + const minMonth = this._dateAdapter.getMonth(this.minDate); + return year > minYear || (year === minYear && month > minMonth); + } + + return true; + } + /** * @param obj The object to check. * @returns The given object if it is both a date instance and valid, otherwise null. From 421dc860f8972ea9eae432a03b992789408b1c27 Mon Sep 17 00:00:00 2001 From: Juliano Date: Thu, 1 Feb 2018 15:22:05 -0200 Subject: [PATCH 2/4] address @mmalerba's comments --- src/lib/datepicker/calendar.html | 6 +++--- src/lib/datepicker/calendar.ts | 10 +--------- src/lib/datepicker/year-view.ts | 4 ++-- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/lib/datepicker/calendar.html b/src/lib/datepicker/calendar.html index e6985e43095b..d3562636adda 100644 --- a/src/lib/datepicker/calendar.html +++ b/src/lib/datepicker/calendar.html @@ -26,7 +26,7 @@ *ngSwitchCase="'month'" [activeDate]="_activeDate" [selected]="selected" - [dateFilter]="_dateFilterForViews" + [dateFilter]="dateFilter" (selectedChange)="_dateSelected($event)" (_userSelection)="_userSelected()"> @@ -35,7 +35,7 @@ *ngSwitchCase="'year'" [activeDate]="_activeDate" [selected]="selected" - [dateFilter]="_dateFilterForViews" + [dateFilter]="dateFilter" [maxDate]="maxDate" [minDate]="minDate" (selectedChange)="_goToDateInView($event, 'month')"> @@ -45,7 +45,7 @@ *ngSwitchCase="'multi-year'" [activeDate]="_activeDate" [selected]="selected" - [dateFilter]="_dateFilterForViews" + [dateFilter]="dateFilter" [maxDate]="maxDate" [minDate]="minDate" (selectedChange)="_goToDateInView($event, 'year')"> diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index 2243ee53abad..ff692895c77f 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -119,14 +119,6 @@ export class MatCalendar implements AfterContentInit, OnDestroy, OnChanges { /** Reference to the current multi-year view component. */ @ViewChild(MatMultiYearView) multiYearView: MatMultiYearView; - /** Date filter for the month, year, and multi-year views. */ - _dateFilterForViews = (date: D) => { - return !!date && - (!this.dateFilter || this.dateFilter(date)) && - (!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) && - (!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0); - } - /** * The current active date. This determines which time period is shown and which date is * highlighted when using keyboard navigation. @@ -346,7 +338,7 @@ export class MatCalendar implements AfterContentInit, OnDestroy, OnChanges { this._dateAdapter.addCalendarMonths(this._activeDate, 1); break; case ENTER: - if (this._dateFilterForViews(this._activeDate)) { + if (this.dateFilter(this._activeDate)) { this._dateSelected(this._activeDate); this._userSelected(); // Prevent unexpected default actions such as form submission. diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index 882508cc4098..99015a4fee16 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -161,7 +161,7 @@ export class MatYearView implements AfterContentInit { const activeYear = this._dateAdapter.getYear(this.activeDate); - let firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1); + const firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1); if (!this._yearAndMonthBeforeMax(activeYear, month)) { return false; @@ -197,7 +197,7 @@ export class MatYearView implements AfterContentInit { return true; } - _yearAndMonthAfterMin(year: number, month: number) { + private _yearAndMonthAfterMin(year: number, month: number) { if (this.minDate) { const minYear = this._dateAdapter.getYear(this.minDate); const minMonth = this._dateAdapter.getMonth(this.minDate); From 64a48a2c5d8ff59f67a58e47e4af8bc824f8491c Mon Sep 17 00:00:00 2001 From: Juliano Date: Fri, 2 Feb 2018 01:57:06 -0200 Subject: [PATCH 3/4] fix and tighten up logic --- src/lib/datepicker/calendar.html | 2 ++ src/lib/datepicker/month-view.ts | 39 +++++++++++++++++++++------ src/lib/datepicker/multi-year-view.ts | 26 +++++++----------- src/lib/datepicker/year-view.ts | 38 ++++++++++++++------------ 4 files changed, 64 insertions(+), 41 deletions(-) diff --git a/src/lib/datepicker/calendar.html b/src/lib/datepicker/calendar.html index d3562636adda..eeb7ffb038c8 100644 --- a/src/lib/datepicker/calendar.html +++ b/src/lib/datepicker/calendar.html @@ -27,6 +27,8 @@ [activeDate]="_activeDate" [selected]="selected" [dateFilter]="dateFilter" + [maxDate]="maxDate" + [minDate]="minDate" (selectedChange)="_dateSelected($event)" (_userSelection)="_userSelected()"> diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index eb5ef809b9df..933608feef60 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -64,6 +64,22 @@ export class MatMonthView implements AfterContentInit { } private _selected: D | null; + /** The minimum selectable date. */ + @Input() + get minDate(): D | null { return this._minDate; } + set minDate(value: D | null) { + this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + } + private _minDate: D | null; + + /** The maximum selectable date. */ + @Input() + get maxDate(): D | null { return this._maxDate; } + set maxDate(value: D | null) { + this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); + } + private _maxDate: D | null; + /** A function used to filter which dates are selectable. */ @Input() dateFilter: (date: D) => boolean; @@ -154,25 +170,32 @@ export class MatMonthView implements AfterContentInit { /** Creates MatCalendarCells for the dates in this month. */ private _createWeekCells() { - let daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate); - let dateNames = this._dateAdapter.getDateNames(); + const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate); + const dateNames = this._dateAdapter.getDateNames(); this._weeks = [[]]; for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) { if (cell == DAYS_PER_WEEK) { this._weeks.push([]); cell = 0; } - let date = this._dateAdapter.createDate( - this._dateAdapter.getYear(this.activeDate), - this._dateAdapter.getMonth(this.activeDate), i + 1); - let enabled = !this.dateFilter || - this.dateFilter(date); - let ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel); + const date = this._dateAdapter.createDate( + this._dateAdapter.getYear(this.activeDate), + this._dateAdapter.getMonth(this.activeDate), i + 1); + const enabled = this._shouldEnableDate(date); + const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel); this._weeks[this._weeks.length - 1] .push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled)); } } + /** Date filter for the month */ + private _shouldEnableDate(date: D): boolean { + return !!date && + (!this.dateFilter || this.dateFilter(date)) && + (!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) && + (!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0); + } + /** * Gets the date in this month that the given Date falls on. * Returns null if the given Date is in another month. diff --git a/src/lib/datepicker/multi-year-view.ts b/src/lib/datepicker/multi-year-view.ts index b825019bcc7e..faca59091293 100644 --- a/src/lib/datepicker/multi-year-view.ts +++ b/src/lib/datepicker/multi-year-view.ts @@ -140,27 +140,21 @@ export class MatMultiYearView implements AfterContentInit { /** Creates an MatCalendarCell for the given year. */ private _createCellForYear(year: number) { let yearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(year, 0, 1)); - return new MatCalendarCell(year, yearName, yearName, this._isYearEnabled(year)); + return new MatCalendarCell(year, yearName, yearName, this._shouldEnableYear(year)); } /** Whether the given year is enabled. */ - private _isYearEnabled(year: number) { - if (!this.dateFilter) { - return true; - } - - // disable if the year is greater than maxDate - if (this.maxDate) { - if (year > this._dateAdapter.getYear(this.maxDate)) { - return false; - } + private _shouldEnableYear(year: number) { + // disable if the year is greater than maxDate lower than minDate + if (year === undefined || year === null || + (this.maxDate && year > this._dateAdapter.getYear(this.maxDate)) || + (this.minDate && year < this._dateAdapter.getYear(this.minDate))) { + return false; } - // disable if the year is lower than maxDate - if (this.minDate) { - if (year < this._dateAdapter.getYear(this.minDate)) { - return false; - } + // enable if it reaches here and there's no filter defined + if (!this.dateFilter) { + return true; } const firstOfYear = this._dateAdapter.createDate(year, 0, 1); diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index 99015a4fee16..44a91872160d 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -150,27 +150,26 @@ export class MatYearView implements AfterContentInit { this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1), this._dateFormats.display.monthYearA11yLabel); return new MatCalendarCell( - month, monthName.toLocaleUpperCase(), ariaLabel, this._isMonthEnabled(month)); + month, monthName.toLocaleUpperCase(), ariaLabel, this._shouldEnableMonth(month)); } /** Whether the given month is enabled. */ - private _isMonthEnabled(month: number) { - if (!this.dateFilter) { - return true; - } + private _shouldEnableMonth(month: number) { const activeYear = this._dateAdapter.getYear(this.activeDate); - const firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1); - - if (!this._yearAndMonthBeforeMax(activeYear, month)) { + if (month === undefined || month === null || + this._isYearAndMonthAfterMaxDate(activeYear, month) || + this._isYearAndMonthBeforeMinDate(activeYear, month)) { return false; } - if (!this._yearAndMonthAfterMin(activeYear, month)) { - return false; + if (!this.dateFilter) { + return true; } + const firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1); + // If any date in the month is enabled count the month as enabled. for (let date = firstOfMonth; this._dateAdapter.getMonth(date) == month; date = this._dateAdapter.addCalendarDays(date, 1)) { @@ -183,28 +182,33 @@ export class MatYearView implements AfterContentInit { } /** - * Tests whether the combination month/year is before this.maxDate, considering + * Tests whether the combination month/year is after this.maxDate, considering * just the month and year of this.maxDate */ - private _yearAndMonthBeforeMax(year: number, month: number) { + private _isYearAndMonthAfterMaxDate(year: number, month: number) { if (this.maxDate) { const maxYear = this._dateAdapter.getYear(this.maxDate); const maxMonth = this._dateAdapter.getMonth(this.maxDate); - return year < maxYear || (year === maxYear && month < maxMonth); + return year > maxYear || (year === maxYear && month > maxMonth); } - return true; + return false; } - private _yearAndMonthAfterMin(year: number, month: number) { + /** + * Tests whether the combination month/year is before this.minDate, considering + * just the month and year of this.minDate + */ + private _isYearAndMonthBeforeMinDate(year: number, month: number) { if (this.minDate) { const minYear = this._dateAdapter.getYear(this.minDate); const minMonth = this._dateAdapter.getMonth(this.minDate); - return year > minYear || (year === minYear && month > minMonth); + + return year < minYear || (year === minYear && month < minMonth); } - return true; + return false; } /** From 2ae66592462d6aa656ca0a4b63efddd501a8a6fd Mon Sep 17 00:00:00 2001 From: Juliano Date: Sun, 4 Feb 2018 08:10:00 -0200 Subject: [PATCH 4/4] possibility to have a null filter in month view --- src/lib/datepicker/calendar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index ff692895c77f..3df6ec01b456 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -338,7 +338,7 @@ export class MatCalendar implements AfterContentInit, OnDestroy, OnChanges { this._dateAdapter.addCalendarMonths(this._activeDate, 1); break; case ENTER: - if (this.dateFilter(this._activeDate)) { + if (!this.dateFilter || this.dateFilter(this._activeDate)) { this._dateSelected(this._activeDate); this._userSelected(); // Prevent unexpected default actions such as form submission.