Skip to content

docs(datepicker): add docs for custom calendar header #10543

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

Merged
merged 3 commits into from
Apr 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions src/demo-app/datepicker/custom-header.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
<div class="custom-header">
<button mat-icon-button (click)="previousClicked('year')">&lt;&lt;</button>
<button mat-icon-button (click)="previousClicked('month')">&lt;</button>
<span class="custom-header-label">{{periodLabel}}</span>
<button mat-icon-button (click)="nextClicked('month')">&gt;</button>
<button mat-icon-button (click)="nextClicked('year')">&gt;&gt;</button>
<div class="demo-calendar-header">
<button mat-icon-button class="demo-double-arrow" (click)="previousClicked('year')">
<mat-icon>keyboard_arrow_left</mat-icon>
<mat-icon>keyboard_arrow_left</mat-icon>
</button>
<button mat-icon-button (click)="previousClicked('month')">
<mat-icon>keyboard_arrow_left</mat-icon>
</button>
<span class="demo-calendar-header-label">{{periodLabel}}</span>
<button mat-icon-button (click)="nextClicked('month')">
<mat-icon>keyboard_arrow_right</mat-icon>
</button>
<button mat-icon-button class="demo-double-arrow" (click)="nextClicked('year')">
<mat-icon>keyboard_arrow_right</mat-icon>
<mat-icon>keyboard_arrow_right</mat-icon>
</button>
</div>
12 changes: 9 additions & 3 deletions src/demo-app/datepicker/custom-header.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
.custom-header {
padding: 1em 1.5em;
.demo-calendar-header {
display: flex;
align-items: center;
padding: 0.5em;
}

.custom-header-label {
.demo-calendar-header-label {
flex: 1;
height: 1em;
font-weight: bold;
text-align: center;
}

.demo-double-arrow .mat-icon {
margin: -22%;
}
7 changes: 3 additions & 4 deletions src/demo-app/datepicker/datepicker-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,8 @@ <h2>Datepicker with custom header</h2>
<p>
<mat-form-field>
<mat-label>Custom calendar header</mat-label>
<input matInput [matDatepicker]="customCalendarHeaderPicker"
[disabled]="inputDisabled">
<mat-datepicker-toggle matSuffix [for]="customCalendarHeaderPicker"></mat-datepicker-toggle>
<mat-datepicker #customCalendarHeaderPicker [touchUi]="touch" [disabled]="datepickerDisabled" [calendarHeaderComponent]="customHeader"></mat-datepicker>
<input matInput [matDatepicker]="customHeaderPicker">
<mat-datepicker-toggle matSuffix [for]="customHeaderPicker"></mat-datepicker-toggle>
<mat-datepicker #customHeaderPicker [calendarHeaderComponent]="customHeader"></mat-datepicker>
</mat-form-field>
</p>
44 changes: 33 additions & 11 deletions src/demo-app/datepicker/datepicker-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ChangeDetectionStrategy, Component, Host} from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Host,
Inject,
OnDestroy
} from '@angular/core';
import {FormControl} from '@angular/forms';
import {DateAdapter, ThemePalette} from '@angular/material/core';
import {MatCalendar, MatDatepickerInputEvent} from '@angular/material/datepicker';

import {MatCalendar} from '@angular/material';
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats, ThemePalette} from '@angular/material/core';
import {MatDatepickerInputEvent} from '@angular/material/datepicker';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';

@Component({
moduleId: module.id,
Expand Down Expand Up @@ -53,25 +62,38 @@ export class DatepickerDemo {
styleUrls: ['custom-header.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomHeader<D> {
export class CustomHeader<D> implements OnDestroy {
private _destroyed = new Subject<void>();

constructor(@Host() private _calendar: MatCalendar<D>,
private _dateAdapter: DateAdapter<D>) {}
private _dateAdapter: DateAdapter<D>,
@Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
cdr: ChangeDetectorRef) {
_calendar.stateChanges
.pipe(takeUntil(this._destroyed))
.subscribe(() => cdr.markForCheck());
}

ngOnDestroy() {
this._destroyed.next();
this._destroyed.complete();
}

get periodLabel() {
const year = this._dateAdapter.getYearName(this._calendar.activeDate);
const month = (this._dateAdapter.getMonth(this._calendar.activeDate) + 1);
return `${month}/${year}`;
return this._dateAdapter
.format(this._calendar.activeDate, this._dateFormats.display.monthYearLabel)
.toLocaleUpperCase();
}

previousClicked(mode: 'month' | 'year') {
this._calendar.activeDate = mode == 'month' ?
this._dateAdapter.addCalendarMonths(this._calendar.activeDate, -1) :
this._dateAdapter.addCalendarYears(this._calendar.activeDate, -1);
this._dateAdapter.addCalendarYears(this._calendar.activeDate, -1);
}

nextClicked(mode: 'month' | 'year') {
this._calendar.activeDate = mode == 'month' ?
this._dateAdapter.addCalendarMonths(this._calendar.activeDate, 1) :
this._dateAdapter.addCalendarYears(this._calendar.activeDate, 1);
this._dateAdapter.addCalendarYears(this._calendar.activeDate, 1);
}
}
14 changes: 14 additions & 0 deletions src/lib/datepicker/datepicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,20 @@ export class MyApp {}

<!-- example(datepicker-formats) -->

#### Customizing the calendar header

The header section of the calendar (the part containing the view switcher and previous and next
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not the right time to ask this question, but why an API that takes a component as an input? We don't have any APIs like that elsewhere in the library, instead using templates and/or content projection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Content projection wasn't feasible because it would have had to be passed through several layers <mat-datepicker> --> <mat-datepicker-content> --> <mat-calendar>. A template may have been workable, but there's a fair chunk of logic that goes with the header so a component seemed cleaner. At some point I want to break the datepicker into a bunch of composable pieces, we may be able to change how this works at that point

buttons) can be replaced with a custom component if desired. This is accomplished using the
`calendarHeaderComponent` property of `<mat-datepicker>`. It takes a component class and constructs
an instance of the component to use as the header.

In order to interact with the calendar in your custom header component, you can inject the parent
`MatCalendar` in the constructor. To make sure your header stays in sync with the calendar,
subscribe to the `stateChanges` observable of the calendar and mark your header component for change
detection.

<!-- example(datepicker-custom-header) -->

#### Localizing labels and messages

The various text strings used by the datepicker are provided through `MatDatepickerIntl`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.example-header {
display: flex;
align-items: center;
padding: 0.5em;
}

.example-header-label {
flex: 1;
height: 1em;
font-weight: bold;
text-align: center;
}

.example-double-arrow .mat-icon {
margin: -22%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<mat-form-field>
<mat-label>Custom calendar header</mat-label>
<input matInput [matDatepicker]="picker">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker [calendarHeaderComponent]="exampleHeader"></mat-datepicker>
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Host,
Inject,
OnDestroy,
ViewEncapsulation
} from '@angular/core';
import {MatCalendar} from '@angular/material';
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';

/** @title Datepicker with custom calendar header */
@Component({
selector: 'datepicker-custom-header-example',
templateUrl: 'datepicker-custom-header-example.html',
styleUrls: ['datepicker-custom-header-example.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DatepickerCustomHeaderExample {
exampleHeader = ExampleHeader;
}

/** Custom header component for datepicker. */
@Component({
selector: 'example-header',
template: `
<div class="example-header">
<button mat-icon-button class="example-double-arrow" (click)="previousClicked('year')">
<mat-icon>keyboard_arrow_left</mat-icon>
<mat-icon>keyboard_arrow_left</mat-icon>
</button>
<button mat-icon-button (click)="previousClicked('month')">
<mat-icon>keyboard_arrow_left</mat-icon>
</button>
<span class="example-header-label">{{periodLabel}}</span>
<button mat-icon-button (click)="nextClicked('month')">
<mat-icon>keyboard_arrow_right</mat-icon>
</button>
<button mat-icon-button class="example-double-arrow" (click)="nextClicked('year')">
<mat-icon>keyboard_arrow_right</mat-icon>
<mat-icon>keyboard_arrow_right</mat-icon>
</button>
</div>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExampleHeader<D> implements OnDestroy {
private destroyed = new Subject<void>();

constructor(@Host() private calendar: MatCalendar<D>,
private dateAdapter: DateAdapter<D>,
@Inject(MAT_DATE_FORMATS) private dateFormats: MatDateFormats,
cdr: ChangeDetectorRef) {
calendar.stateChanges
.pipe(takeUntil(this.destroyed))
.subscribe(() => cdr.markForCheck());
}

ngOnDestroy() {
this.destroyed.next();
this.destroyed.complete();
}

get periodLabel() {
return this.dateAdapter
.format(this.calendar.activeDate, this.dateFormats.display.monthYearLabel)
.toLocaleUpperCase();
}

previousClicked(mode: 'month' | 'year') {
this.calendar.activeDate = mode == 'month' ?
this.dateAdapter.addCalendarMonths(this.calendar.activeDate, -1) :
this.dateAdapter.addCalendarYears(this.calendar.activeDate, -1);
}

nextClicked(mode: 'month' | 'year') {
this.calendar.activeDate = mode == 'month' ?
this.dateAdapter.addCalendarMonths(this.calendar.activeDate, 1) :
this.dateAdapter.addCalendarYears(this.calendar.activeDate, 1);
}
}
4 changes: 3 additions & 1 deletion tools/gulp/tasks/example-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,9 @@ task('build-examples-module', () => {

for (const meta of secondaryComponents) {
example.additionalComponents.push(meta.component);
example.additionalFiles.push(meta.templateUrl);
if (meta.templateUrl) {
example.additionalFiles.push(meta.templateUrl);
}
example.selectorName.push(meta.component);
}
}
Expand Down