Skip to content

Commit c2e108e

Browse files
julianobrasilmmalerba
authored andcommitted
feat(datepicker): @output for year and month selected in multiyear/year (#9678)
* feat(datepicker): @output of year and month selected in multiyear/year views * address @mmalerba's comments * address comments * add types to some outputs * fix wrong parameter type * restore accidentally removed object * example and docs text * change emitted values to objects instead of just numbers * change datepicker.md and switch back tslint configs * fix nit * Remove changes in demo app
1 parent eebfce4 commit c2e108e

20 files changed

+334
-27
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/demo-app/datepicker/datepicker-demo.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ <h2>Input disabled datepicker</h2>
9393
<input matInput [matDatepicker]="datePicker1" [(ngModel)]="date" [min]="minDate" [max]="maxDate"
9494
[matDatepickerFilter]="filterOdd ? dateFilter : null" disabled>
9595
<mat-datepicker #datePicker1 [touchUi]="touch" [startAt]="startAt"
96-
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
96+
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
9797
</mat-form-field>
9898
</p>
9999

@@ -121,7 +121,7 @@ <h2>Input disabled, datepicker popup enabled</h2>
121121
<input matInput disabled [matDatepicker]="datePicker3" [(ngModel)]="date" [min]="minDate"
122122
[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null">
123123
<mat-datepicker #datePicker3 [touchUi]="touch" [disabled]="false" [startAt]="startAt"
124-
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
124+
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
125125
</mat-form-field>
126126
</p>
127127

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
mat-calendar {
22
width: 300px;
33
}
4+

src/demo-app/datepicker/datepicker-demo.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
import {ChangeDetectionStrategy, Component} from '@angular/core';
1010
import {FormControl} from '@angular/forms';
11-
import {MatDatepickerInputEvent} from '@angular/material/datepicker';
12-
11+
import {MatDatepickerInputEvent} from '@angular/material';
1312

1413
@Component({
1514
moduleId: module.id,
@@ -31,11 +30,11 @@ export class DatepickerDemo {
3130
lastDateInput: Date | null;
3231
lastDateChange: Date | null;
3332

33+
dateCtrl = new FormControl();
34+
3435
dateFilter =
3536
(date: Date) => !(date.getFullYear() % 2) && (date.getMonth() % 2) && !(date.getDate() % 2)
3637

3738
onDateInput = (e: MatDatepickerInputEvent<Date>) => this.lastDateInput = e.value;
3839
onDateChange = (e: MatDatepickerInputEvent<Date>) => this.lastDateChange = e.value;
39-
40-
dateCtrl = new FormControl();
4140
}

src/demo-app/demo-app-module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {AccessibilityDemoModule} from './a11y/a11y-module';
3131
],
3232
entryComponents: [
3333
EntryApp,
34-
],
34+
]
3535
})
3636
export class DemoAppModule {
3737
constructor(private _appRef: ApplicationRef) { }

src/lib/datepicker/calendar.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
[activeDate]="_activeDate"
3737
[selected]="selected"
3838
[dateFilter]="_dateFilterForViews"
39+
(monthSelected)="_monthSelectedInYearView($event)"
3940
(selectedChange)="_goToDateInView($event, 'month')">
4041
</mat-year-view>
4142

@@ -44,6 +45,7 @@
4445
[activeDate]="_activeDate"
4546
[selected]="selected"
4647
[dateFilter]="_dateFilterForViews"
48+
(yearSelected)="_yearSelectedInMultiYearView($event)"
4749
(selectedChange)="_goToDateInView($event, 'year')">
4850
</mat-multi-year-view>
4951
</div>

src/lib/datepicker/calendar.spec.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,40 @@ describe('MatCalendar', () => {
196196
expect(testComponent.selected).toEqual(new Date(2017, JAN, 31));
197197
});
198198

199+
it('should emit the selected month on cell clicked in year view', () => {
200+
periodButton.click();
201+
fixture.detectChanges();
202+
203+
expect(calendarInstance._currentView).toBe('multi-year');
204+
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
205+
206+
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
207+
208+
fixture.detectChanges();
209+
210+
expect(calendarInstance._currentView).toBe('year');
211+
212+
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
213+
214+
const normalizedMonth: Date = fixture.componentInstance.selectedMonth;
215+
expect(normalizedMonth.getMonth()).toEqual(0);
216+
});
217+
218+
it('should emit the selected year on cell clicked in multiyear view', () => {
219+
periodButton.click();
220+
fixture.detectChanges();
221+
222+
expect(calendarInstance._currentView).toBe('multi-year');
223+
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
224+
225+
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
226+
227+
fixture.detectChanges();
228+
229+
const normalizedYear: Date = fixture.componentInstance.selectedYear;
230+
expect(normalizedYear.getFullYear()).toEqual(2017);
231+
});
232+
199233
it('should re-render when the i18n labels have changed',
200234
inject([MatDatepickerIntl], (intl: MatDatepickerIntl) => {
201235
const button = fixture.debugElement.nativeElement
@@ -916,10 +950,18 @@ describe('MatCalendar', () => {
916950

917951

918952
@Component({
919-
template: `<mat-calendar [startAt]="startDate" [(selected)]="selected"></mat-calendar>`
953+
template: `
954+
<mat-calendar
955+
[startAt]="startDate"
956+
[(selected)]="selected"
957+
(yearSelected)="selectedYear=$event"
958+
(monthSelected)="selectedMonth=$event">
959+
</mat-calendar>`
920960
})
921961
class StandardCalendar {
922962
selected: Date;
963+
selectedYear: Date;
964+
selectedMonth: Date;
923965
startDate = new Date(2017, JAN, 31);
924966
}
925967

src/lib/datepicker/calendar.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
107107
/** Emits when the currently selected date changes. */
108108
@Output() readonly selectedChange: EventEmitter<D> = new EventEmitter<D>();
109109

110+
/**
111+
* Emits the year chosen in multiyear view.
112+
* This doesn't imply a change on the selected date.
113+
*/
114+
@Output() readonly yearSelected: EventEmitter<D> = new EventEmitter<D>();
115+
116+
/**
117+
* Emits the month chosen in year view.
118+
* This doesn't imply a change on the selected date.
119+
*/
120+
@Output() readonly monthSelected: EventEmitter<D> = new EventEmitter<D>();
121+
110122
/** Emits when any date is selected. */
111123
@Output() readonly _userSelection: EventEmitter<void> = new EventEmitter<void>();
112124

@@ -228,6 +240,16 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
228240
}
229241
}
230242

243+
/** Handles year selection in the multiyear view. */
244+
_yearSelectedInMultiYearView(normalizedYear: D) {
245+
this.yearSelected.emit(normalizedYear);
246+
}
247+
248+
/** Handles month selection in the year view. */
249+
_monthSelectedInYearView(normalizedMonth: D) {
250+
this.monthSelected.emit(normalizedMonth);
251+
}
252+
231253
_userSelected(): void {
232254
this._userSelection.emit();
233255
}

src/lib/datepicker/datepicker-content.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88
[dateFilter]="datepicker._dateFilter"
99
[selected]="datepicker._selected"
1010
(selectedChange)="datepicker._select($event)"
11+
(yearSelected)="datepicker._selectYear($event)"
12+
(monthSelected)="datepicker._selectMonth($event)"
1113
(_userSelection)="datepicker.close()">
1214
</mat-calendar>

src/lib/datepicker/datepicker.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,31 @@ year containing the `startAt` date.
5151

5252
<!-- example(datepicker-start-view) -->
5353

54+
#### Watching the views for changes on selected years and months
55+
56+
When a year or a month is selected in `multi-year` and `year` views respecively, the `yearSelected`
57+
and `monthSelected` outputs emit a normalized date representing the chosen year or month. By
58+
"normalized" we mean that the dates representing a year will have their month set to January and
59+
their day set to the 1st. Dates representing months will have their day set to the 1st of the
60+
month. For example, if `<mat-datepicker>` is configured to work with javascript native Date
61+
objects, the `yearSelected` will emit `new Date(2017, 0, 1)` if the user selects 2017 in
62+
`multi-year` view. Similarly, `monthSelected` will emit `new Date(2017, 1, 0)` if the user
63+
selects **February** in `year` view and the current date value of the connected `<input>` was
64+
something like `new Date(2017, MM, dd)` (the month and day are irrelevant in this case).
65+
66+
Notice that the emitted value does not affect the current value in the connected `<input>`, which
67+
is only bound to the selection made in the `month` view. So if the end user closes the calendar
68+
after choosing a year in `multi-view` mode (by pressing the `ESC` key, for example), the selected
69+
year, emitted by `yearSelected` output, will not cause any change in the value of the date in the
70+
associated `<input>`.
71+
72+
The following example uses `yearSelected` and `monthSelected` outputs to emulate a month and year
73+
picker (if you're not familiar with the usage of `MomentDateAdapter` and `MAT_DATE_FORMATS`
74+
you can [read more about them](#choosing-a-date-implementation-and-date-format-settings) below in
75+
this document to fully understand the example).
76+
77+
<!-- example(datepicker-views-selection) -->
78+
5479
### Setting the selected date
5580

5681
The type of values that the datepicker expects depends on the type of `DateAdapter` provided in your

0 commit comments

Comments
 (0)