Skip to content

feat(datepicker): Add Custom Header to DatePicker #9639

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 64 commits into from
Mar 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
6251e1e
feature (add custom header to mat-calendar)
Jan 26, 2018
8d30140
refactor (define custom header component)
Jan 26, 2018
81c89fa
refactor (remove useless text)
Jan 26, 2018
737a563
refactor (pass custom header component type as input)
Jan 27, 2018
998be7c
refactor (add header as component portal)
Jan 27, 2018
a14dbd4
refactor (custom header as component portal)
Jan 27, 2018
585407f
refactor (switch for custom header component)
Jan 27, 2018
6668ae6
refactor (define entry components)
Jan 27, 2018
f9628a8
fix (correct entry point)
Jan 27, 2018
050904b
refactor (custom header component): add test function to custom heade…
Jan 27, 2018
a2db283
refactor (some code formatting)
Jan 27, 2018
7f071ec
refactor (set indentation to two spaces)
Jan 29, 2018
5c9f662
refactor (formatting)
Jan 30, 2018
3467c0a
refactor (custom header for mat-calendar): renaming and code improvem…
Jan 31, 2018
014777a
refactor (calendarHeaderComponent) rename input of MatDatepicker
Jan 31, 2018
179c9af
refactor (calendarHeaderComponent): make a demo for custom header
Jan 31, 2018
b6938f2
refactor (calendar header): make calendar header portal variable public
Feb 1, 2018
0faefc4
refactor (calendar header): formatting
Feb 1, 2018
aa8f837
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Feb 1, 2018
2b4d4d2
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Feb 9, 2018
b238107
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Feb 15, 2018
46b2c9d
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Feb 26, 2018
a45f3ee
tests (MatCalendar): fix tests
Feb 26, 2018
f8c4c5b
tests (calendar): fix import issue
Feb 26, 2018
b6b50cc
tests (MatCalendar): remove unused import
Feb 26, 2018
ca7a0db
tests (MatCalendar): import MatDatepickerModule without redeclaration
Feb 26, 2018
2058afb
tests (MatCalendar): fix imports
Feb 26, 2018
f6f19e8
refactor (MatCalendar): default header comes with empty template
Feb 26, 2018
702317d
refactor (MatCalendar): remove useless newline
Feb 26, 2018
528361e
feature (MatCalendarHeader): move header in own template
Mar 3, 2018
e36dc71
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Mar 7, 2018
a7975d2
refactor (MatCalendar): some code reformatting
Mar 8, 2018
02b5604
fix (MatCalendar): add missin change detection for MatCalendarHeader
Mar 8, 2018
57bd12c
refactor (MatCalendar): fix indentation
Mar 8, 2018
a88fecb
fix (MatCalendar): clean stream up after component destruction
Mar 8, 2018
4c66467
fix (MatCalendar): fix change detection for MatCalendarHeader
Mar 8, 2018
258929a
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Mar 8, 2018
81147f1
refactor (MatCalendarHeader): add dateadapter to constructor
Mar 12, 2018
586d5da
refactor (MatCalendarHeader): move methods to calendar header (ongoing)
Mar 12, 2018
a9d4c58
refactor (MatCalendarHeader): move label getter methods to MatCalenda…
Mar 12, 2018
9bba08f
refactor (MatCalendarHeader): move MatCalendarHeader component in a s…
Mar 12, 2018
1ff7e45
tests (MatCalendarHeader): move header related tests from MatCalendar…
Mar 12, 2018
5196ee7
refactor (MatCalendar): linting
Mar 12, 2018
4cee00c
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Mar 12, 2018
da49938
refactor (MatCalendarHeader): move MatCalendarHeader component back t…
Mar 12, 2018
70ff16f
refactor (MatCalendarHeader): move header related methods
Mar 12, 2018
533739e
feature (MatCalendarHeader): add sample custom header controls to dem…
Mar 12, 2018
c287495
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Mar 15, 2018
6d0a4d2
refactor (MatCalendarHeader): fix some styling issues
Mar 16, 2018
a33b70c
refactor (MatCalendarHeader): fix indentation
Mar 16, 2018
c7f21bf
refactor (MatCalendarHeader): make calendar private for custom header
Mar 16, 2018
715e413
refactor (MatCalendarHeader): use type parameter for header (generics)
Mar 16, 2018
5f4b0fc
refactor (MatCalendarHeader): make template file for custom header on…
Mar 16, 2018
3057f9d
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Mar 16, 2018
d81db60
refactor (MatCalendarHeader): separate section own demo page
Mar 16, 2018
94c825a
refactor (MatCalendarHeader): style custom header
Mar 16, 2018
b6fc003
refactor (MatCalendarHeader): fix styling issue
Mar 16, 2018
796cbf2
refactor (package-lock.json): revert changes to version on master branch
Mar 21, 2018
abf1c62
refactor (package-lock.json): revert to commit 6f63fd283eeb643523a9e3…
Mar 21, 2018
1fdb1c3
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Mar 21, 2018
822dd56
fix (MatCalendarHeader): correct forward reference to MatCalendar
Mar 21, 2018
ba4e839
Merge branch 'master' into tobiasschweizer/calendar-header-comp
Mar 21, 2018
1ee0f93
Add `stateChanges` to `MatCalendar`
mmalerba Mar 21, 2018
aff641d
fix nits
mmalerba Mar 21, 2018
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
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/demo-app/datepicker/custom-header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<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>
10 changes: 10 additions & 0 deletions src/demo-app/datepicker/custom-header.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.custom-header {
padding: 1em 1.5em;
display: flex;
align-items: center;
}

.custom-header-label {
flex: 1;
text-align: center;
}
11 changes: 11 additions & 0 deletions src/demo-app/datepicker/datepicker-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,14 @@ <h2>Datepicker with value property binding</h2>
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
</mat-form-field>
</p>

<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>
</mat-form-field>
</p>
39 changes: 37 additions & 2 deletions src/demo-app/datepicker/datepicker-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/

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


@Component({
moduleId: module.id,
selector: 'datepicker-demo',
Expand Down Expand Up @@ -40,4 +41,38 @@ export class DatepickerDemo {

onDateInput = (e: MatDatepickerInputEvent<Date>) => this.lastDateInput = e.value;
onDateChange = (e: MatDatepickerInputEvent<Date>) => this.lastDateChange = e.value;

// pass custom header component type as input
customHeader = CustomHeader;
}

// Custom header component for datepicker
@Component({
moduleId: module.id,
selector: 'custom-header',
templateUrl: 'custom-header.html',
styleUrls: ['custom-header.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomHeader<D> {
constructor(@Host() private _calendar: MatCalendar<D>,
private _dateAdapter: DateAdapter<D>) {}

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

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 src/demo-app/demo-app/demo-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {ButtonDemo} from '../button/button-demo';
import {CardDemo} from '../card/card-demo';
import {CheckboxDemo, MatCheckboxDemoNestedChecklist} from '../checkbox/checkbox-demo';
import {ChipsDemo} from '../chips/chips-demo';
import {DatepickerDemo} from '../datepicker/datepicker-demo';
import {CustomHeader, DatepickerDemo} from '../datepicker/datepicker-demo';
import {DemoMaterialModule} from '../demo-material-module';
import {ContentElementDialog, DialogDemo, IFrameDialog, JazzDialog} from '../dialog/dialog-demo';
import {DrawerDemo} from '../drawer/drawer-demo';
Expand Down Expand Up @@ -88,6 +88,7 @@ import {ConnectedOverlayDemo, DemoOverlay} from '../connected-overlay/connected-
ChipsDemo,
ContentElementDialog,
DatepickerDemo,
CustomHeader,
DemoApp,
DialogDemo,
DrawerDemo,
Expand Down Expand Up @@ -148,6 +149,7 @@ import {ConnectedOverlayDemo, DemoOverlay} from '../connected-overlay/connected-
ScienceJoke,
SpagettiPanel,
ExampleBottomSheet,
CustomHeader,
DemoOverlay,
],
})
Expand Down
21 changes: 21 additions & 0 deletions src/lib/datepicker/calendar-header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="mat-calendar-header">
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: normal indentation = 2 spaces, continuation lines can be indented 4 or align the attributes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in a7975d2

<div class="mat-calendar-controls">
<button mat-button class="mat-calendar-period-button"
(click)="currentPeriodClicked()" [attr.aria-label]="periodButtonLabel">
{{periodButtonText}}
<div class="mat-calendar-arrow" [class.mat-calendar-invert]="calendar.currentView != 'month'"></div>
</button>

<div class="mat-calendar-spacer"></div>

<button mat-icon-button class="mat-calendar-previous-button"
[disabled]="!previousEnabled()" (click)="previousClicked()"
[attr.aria-label]="prevButtonLabel">
</button>

<button mat-icon-button class="mat-calendar-next-button"
[disabled]="!nextEnabled()" (click)="nextClicked()"
[attr.aria-label]="nextButtonLabel">
</button>
</div>
</div>
170 changes: 170 additions & 0 deletions src/lib/datepicker/calendar-header.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import {Direction, Directionality} from '@angular/cdk/bidi';
import {MatDatepickerModule} from './datepicker-module';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MatDatepickerIntl} from './datepicker-intl';
import {DEC, FEB, JAN, MatNativeDateModule} from '@angular/material/core';
import {Component} from '@angular/core';
import {MatCalendar} from './calendar';
import {By} from '@angular/platform-browser';
import {yearsPerPage} from './multi-year-view';

describe('MatCalendarHeader', () => {
let dir: { value: Direction };

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MatNativeDateModule,
MatDatepickerModule,
],
declarations: [
// Test components.
StandardCalendar,
],
providers: [
MatDatepickerIntl,
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}
],
});

TestBed.compileComponents();
}));

describe('standard calendar', () => {
let fixture: ComponentFixture<StandardCalendar>;
let testComponent: StandardCalendar;
let calendarElement: HTMLElement;
let periodButton: HTMLElement;
let prevButton: HTMLElement;
let nextButton: HTMLElement;
let calendarInstance: MatCalendar<Date>;

beforeEach(() => {
fixture = TestBed.createComponent(StandardCalendar);
fixture.detectChanges();

let calendarDebugElement = fixture.debugElement.query(By.directive(MatCalendar));
calendarElement = calendarDebugElement.nativeElement;
periodButton = calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement;
prevButton = calendarElement.querySelector('.mat-calendar-previous-button') as HTMLElement;
nextButton = calendarElement.querySelector('.mat-calendar-next-button') as HTMLElement;

calendarInstance = calendarDebugElement.componentInstance;
testComponent = fixture.componentInstance;
});

it('should be in month view with specified month active', () => {
expect(calendarInstance.currentView).toBe('month');
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
});

it('should toggle view when period clicked', () => {
expect(calendarInstance.currentView).toBe('month');

periodButton.click();
fixture.detectChanges();

expect(calendarInstance.currentView).toBe('multi-year');

periodButton.click();
fixture.detectChanges();

expect(calendarInstance.currentView).toBe('month');
});

it('should go to next and previous month', () => {
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));

nextButton.click();
fixture.detectChanges();

expect(calendarInstance.activeDate).toEqual(new Date(2017, FEB, 28));

prevButton.click();
fixture.detectChanges();

expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 28));
});

it('should go to previous and next year', () => {
periodButton.click();
fixture.detectChanges();

expect(calendarInstance.currentView).toBe('multi-year');
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));

(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
fixture.detectChanges();

expect(calendarInstance.currentView).toBe('year');

nextButton.click();
fixture.detectChanges();

expect(calendarInstance.activeDate).toEqual(new Date(2018, JAN, 31));

prevButton.click();
fixture.detectChanges();

expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
});

it('should go to previous and next multi-year range', () => {
periodButton.click();
fixture.detectChanges();

expect(calendarInstance.currentView).toBe('multi-year');
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));

nextButton.click();
fixture.detectChanges();

expect(calendarInstance.activeDate).toEqual(new Date(2017 + yearsPerPage, JAN, 31));

prevButton.click();
fixture.detectChanges();

expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
});

it('should go back to month view after selecting year and month', () => {
periodButton.click();
fixture.detectChanges();

expect(calendarInstance.currentView).toBe('multi-year');
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));

let yearCells = calendarElement.querySelectorAll('.mat-calendar-body-cell');
(yearCells[0] as HTMLElement).click();
fixture.detectChanges();

expect(calendarInstance.currentView).toBe('year');
expect(calendarInstance.activeDate).toEqual(new Date(2016, JAN, 31));

let monthCells = calendarElement.querySelectorAll('.mat-calendar-body-cell');
(monthCells[monthCells.length - 1] as HTMLElement).click();
fixture.detectChanges();

expect(calendarInstance.currentView).toBe('month');
expect(calendarInstance.activeDate).toEqual(new Date(2016, DEC, 31));
expect(testComponent.selected).toBeFalsy('no date should be selected yet');
});

});
});

@Component({
template: `
<mat-calendar
[startAt]="startDate"
[(selected)]="selected"
(yearSelected)="selectedYear=$event"
(monthSelected)="selectedMonth=$event">
</mat-calendar>`
})
class StandardCalendar {
selected: Date;
selectedYear: Date;
selectedMonth: Date;
startDate = new Date(2017, JAN, 31);
}
29 changes: 5 additions & 24 deletions src/lib/datepicker/calendar.html
Original file line number Diff line number Diff line change
@@ -1,29 +1,10 @@
<div class="mat-calendar-header">
<div class="mat-calendar-controls">
<button mat-button class="mat-calendar-period-button"
(click)="_currentPeriodClicked()" [attr.aria-label]="_periodButtonLabel">
{{_periodButtonText}}
<div class="mat-calendar-arrow" [class.mat-calendar-invert]="_currentView != 'month'"></div>
</button>

<div class="mat-calendar-spacer"></div>
<ng-template [cdkPortalOutlet]="_calendarHeaderPortal"></ng-template>

<button mat-icon-button class="mat-calendar-previous-button"
[disabled]="!_previousEnabled()" (click)="_previousClicked()"
[attr.aria-label]="_prevButtonLabel">
</button>

<button mat-icon-button class="mat-calendar-next-button"
[disabled]="!_nextEnabled()" (click)="_nextClicked()"
[attr.aria-label]="_nextButtonLabel">
</button>
</div>
</div>

<div class="mat-calendar-content" [ngSwitch]="_currentView" cdkMonitorSubtreeFocus tabindex="-1">
<div class="mat-calendar-content" [ngSwitch]="currentView" cdkMonitorSubtreeFocus tabindex="-1">
<mat-month-view
*ngSwitchCase="'month'"
[(activeDate)]="_activeDate"
[(activeDate)]="activeDate"
[selected]="selected"
[dateFilter]="dateFilter"
[maxDate]="maxDate"
Expand All @@ -34,7 +15,7 @@

<mat-year-view
*ngSwitchCase="'year'"
[activeDate]="_activeDate"
[activeDate]="activeDate"
[selected]="selected"
[dateFilter]="dateFilter"
[maxDate]="maxDate"
Expand All @@ -45,7 +26,7 @@

<mat-multi-year-view
*ngSwitchCase="'multi-year'"
[activeDate]="_activeDate"
[activeDate]="activeDate"
[selected]="selected"
[dateFilter]="dateFilter"
[maxDate]="maxDate"
Expand Down
Loading