Skip to content

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

Merged
mmalerba merged 64 commits into
angular:masterfrom
dhlab-basel:tobiasschweizer/calendar-header-comp
Mar 22, 2018
Merged

feat(datepicker): Add Custom Header to DatePicker#9639
mmalerba merged 64 commits into
angular:masterfrom
dhlab-basel:tobiasschweizer/calendar-header-comp

Conversation

@tobiasschweizer

@tobiasschweizertobiasschweizer commented Jan 27, 2018

Copy link
Copy Markdown
Contributor

This PR provides a new input for DatePicker so that a custom header component can be added to mat-calendar.

I intend to add a custom header from which date formats can be converted (see #2519).

@googlebot

Copy link
Copy Markdown

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed, please reply here (e.g. I signed it!) and we'll verify. Thanks.


  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address on your commit. Check your existing CLA data and verify that your email is set on your git commits.
  • If your company signed a CLA, they designated a Point of Contact who decides which employees are authorized to participate. You may need to contact the Point of Contact for your company and ask to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the project maintainer to go/cla#troubleshoot. The email used to register you as an authorized contributor must be the email used for the Git commit.
  • In order to pass this check, please resolve this problem and have the pull request author add another comment and the bot will run again. If the bot doesn't comment, it means it doesn't think anything has changed.

@googlebotgooglebot added the cla: no PR author must sign Google's Contributor License Agreement: https://opensource.google.com/docs/cla label Jan 27, 2018
templateUrl: 'datepicker-demo.html',
styleUrls: ['datepicker-demo.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
moduleId: module.id,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your formatting should be 2 spaces instead of a tab or four characters.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@Chan4077 Thanks for pointing that out. I changed the indentation to two spaces, undoing the changes introduced previously thereby.

@EdricChan03

Copy link
Copy Markdown
Contributor

@tobiasschweizer Could you sign the CLA?

@tobiasschweizer

Copy link
Copy Markdown
ContributorAuthor

I did on behalf of my organization (https://github.com/dhlab-basel). Probably my boss needs to confirm it.

@mmalerba

Copy link
Copy Markdown
Contributor

@tobiasschweizer you need to sign it with the same account you used to make the git commits

@mmalerbammalerba self-assigned this Jan 29, 2018

@mmalerbammalerba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@tobiasschweizer try running the following git command:

git config user.email

and make sure you've signed with that email

Comment threadsrc/lib/datepicker/calendar.ts Outdated
* Default header of a [MatCalendar].
*/
@Component({
selector: 'default-header',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

mat-calendar-header

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

done in 3467c0a

Comment threadsrc/lib/datepicker/calendar.ts Outdated
export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {

/** An input indicating the type of the custom header component, if set. */
@Input() customCalendarHeaderComponent: ComponentType<any>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lets just call the input headerComponent (we know its for the calendar since we're in the calendar component and the "custom" doesn't add much useful info for the user)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

done in 3467c0a

Comment threadsrc/lib/datepicker/calendar.ts Outdated
@Input() customCalendarHeaderComponent: ComponentType<any>;

/** A portal containing the header for this calendar. */
calendarHeaderPortal: Portal<any>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

name this with a leading _ to indicate internal

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

done in 3467c0a

I renamed it as you suggested, made it private and added a getter method since the template needs to access it. To my understanding, private members are not accessible from the component's template (although this would make sense, conceptually speaking): https://stackoverflow.com/questions/34574167/angular2-should-private-variables-be-accessible-in-the-template?rq=1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rather than a getter method just name it with an _ and leave it public. We use this convention throughout Angular Material to mean "we had to leave this public due to technical constraints, but you shouldn't mess with it". We also automatically strip _ properties when generating our docs.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Got it!

done in b6938f2

Comment threadsrc/lib/datepicker/calendar.ts Outdated

ngAfterContentInit() {

if (this.customCalendarHeaderComponent !== undefined) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think you can just simplify this logic to:

this.clalendarHeaderPortal=newComponentPortal(this.calendarHeaderComponent||MatCalendarHeader);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

done in 3467c0a

In general I try to be as specific as possible and not rely on implicit type conversions. JavaScript may behave very unexpectedly in some cases.

This is my understanding of the construct:
If this.calendarHeaderComponent is set, we expect it to be evaluated to true and thus returned. If not (undefined), to false and thus MatCalendarHeader to be returned.

Comment threadsrc/lib/datepicker/datepicker.ts Outdated
export class MatDatepicker<D> implements OnDestroy {

/** An input indicating the type of the custom header component for the calendar, if set. */
@Input() customCalendarHeaderComponent: ComponentType<any>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

calendarHeaderComponent

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

done in 014777a

Comment threadsrc/lib/datepicker/calendar.html Outdated
@@ -1,4 +1,7 @@
<div class="mat-calendar-header">

<ng-template [cdkPortalOutlet]="calendarHeaderPortal"></ng-template>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are you planning to move the .mat-calendar-controls below into the mat-calendar-header? That way we can allow people to swap out the default controls for a custom set

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That's a good idea.

In that case it would make sense to add them to mat-calendar-header (component MatCalendarHeader) which is the default.

So the template of MatCalendarHeader should contain:

<divclass="mat-calendar-controls"><buttonmat-buttonclass="mat-calendar-period-button"
(click)="_currentPeriodClicked()" [attr.aria-label]="_periodButtonLabel">
{{_periodButtonText}}
<divclass="mat-calendar-arrow" [class.mat-calendar-invert]="_currentView != 'month'"></div></button><divclass="mat-calendar-spacer"></div><buttonmat-icon-buttonclass="mat-calendar-previous-button"
[disabled]="!_previousEnabled()" (click)="_previousClicked()"
[attr.aria-label]="_prevButtonLabel"></button><buttonmat-icon-buttonclass="mat-calendar-next-button"
[disabled]="!_nextEnabled()" (click)="_nextClicked()"
[attr.aria-label]="_nextButtonLabel"></button></div>

And then we could remove that code from calendar.html since it will be inserted by <ng-template [cdkPortalOutlet]="_calendarHeaderPortal"></ng-template>.

However, if someone provides a custom header, she would have to take care of these controls.

Could we make more sub-components for the buttons so they could be combined in a more modular way?

Could we make components for the buttons (previous, next)? But how would that work with the callback methods that have to be called on MatCalendar?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh sorry, I missed this comment somehow. Yeah, that's pretty much what I was thinking. We could package the buttons up into their own components, but it seems a little bit overkill to me since they're not very complex. We will need to make some of the internal properties and methods on calendar public (remove the _) to indicate that its ok for people to inject the calendar and call those methods in their custom header

@tobiasschweizertobiasschweizerMar 4, 2018

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@mmalerba done in 528361e

One test is failing in src/lib/datepicker/calendar.spec.ts:

it('should re-render when the i18n labels have changed',
inject([MatDatepickerIntl], (intl: MatDatepickerIntl) => {
const button = fixture.debugElement.nativeElement
.querySelector('.mat-calendar-period-button');
intl.switchToMultiYearViewLabel = 'Go to multi-year view?';
intl.changes.next();
fixture.detectChanges();
expect(button.getAttribute('aria-label')).toBe('Go to multi-year view?');
})
);

And another test does not pass in src/lib/select/select.spec.ts:

it('should emit to `optionSelectionChanges` when an option is selected', fakeAsync(() => {
trigger.click();
fixture.detectChanges();
flush();
const spy = jasmine.createSpy('option selection spy');
const subscription = fixture.componentInstance.select.optionSelectionChanges.subscribe(spy);
const option = overlayContainerElement.querySelector('mat-option') as HTMLElement;
option.click();
fixture.detectChanges();
flush();
expect(spy).toHaveBeenCalledWith(jasmine.any(MatOptionSelectionChange));
subscription.unsubscribe();
}));

I assume that this related to the use of querySelector that tries to get an element that was moved into another template (at least in the first case). I do not know why the second test fails. I cannot find the element in the MatCalendarHeader template. But maybe it is the menu that pops up.

Comment threadsrc/lib/datepicker/calendar.ts Outdated
import {ComponentPortal, ComponentType, Portal} from '@angular/cdk/portal';

/**
* Default header of a [MatCalendar].

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

/** Default header for MatCalendar */

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

done in 0faefc4


dateFilter =
(date: Date) => !(date.getFullYear() % 2) && (date.getMonth() % 2) && !(date.getDate() % 2)
(date: Date) => !(date.getFullYear() % 2) && (date.getMonth() % 2) && !(date.getDate() % 2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: continuation lines indented 2

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

done in 0faefc4

@tobiasschweizer

Copy link
Copy Markdown
ContributorAuthor

@mmalerba Thanks, that looks better now ;-)

@tobiasschweizer

Copy link
Copy Markdown
ContributorAuthor

@mmalerba Ok, everything is green now. Thx.!

Comment threadsrc/lib/datepicker/calendar.ts Outdated
private _destroyed = new Subject<void>();

constructor(private _intl: MatDatepickerIntl,
@Host() public calendar: MatCalendar<D>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@tobiasschweizer I'm seeing a number of failures when trying to presubmit this related to injecting the calendar here. I think you may need to do this:

@Host() @Inject(forwardRef(()=>MatCalendar))public calendar: MatCalendar<D>

Since MatCalendarHeader is declared before MatCalendar

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@mmalerba Good point, which test gave you the error?

fixed in 822dd56

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tests in Google's codebase for other project teams. I can't link to them since they're internal, I'll rerun the presubmit with the fix though.

In the mean time, it looks like you need to resolve some conflicts again.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

ok, merge done in ba4e839

Tobias Schweizer added 2 commits March 21, 2018 21:19
<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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn’t be only 2 spaces?


get periodLabel() {
let year = this._dateAdapter.getYearName(this._calendar.activeDate);
let month = (this._dateAdapter.getMonth(this._calendar.activeDate) + 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both variables could be const.

flex: 1;
text-align: center;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Extra new line.

})
export class MatCalendarHeader<D> implements OnDestroy {
/** Subject that emits when the component has been destroyed. */
private _destroyed = new Subject<void>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn’t it be readonly, @jelbourn?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have seen some of the _destroyed subjects in our code marked readonly, but to me it feels unnecessary for private properties.

@mmalerba
mmalerba merged commit 4e1bb26 into angular:masterMar 22, 2018
@tobiasschweizer
tobiasschweizer deleted the tobiasschweizer/calendar-header-comp branch March 27, 2018 13:59
crisbeto added a commit to crisbeto/material2 that referenced this pull request Jul 30, 2018
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since angular#9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
jelbourn pushed a commit that referenced this pull request Aug 23, 2018
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since #9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
jelbourn pushed a commit that referenced this pull request Aug 25, 2018
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since #9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
crisbeto added a commit to crisbeto/material2 that referenced this pull request Sep 27, 2018
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since angular#9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
vivian-hu-zz pushed a commit that referenced this pull request Sep 27, 2018
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since #9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
crisbeto added a commit to crisbeto/material2 that referenced this pull request Sep 28, 2018
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since angular#9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
crisbeto added a commit to crisbeto/material2 that referenced this pull request Oct 2, 2018
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since angular#9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
vivian-hu-zz pushed a commit that referenced this pull request Nov 6, 2018
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since #9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
crisbeto added a commit to crisbeto/material2 that referenced this pull request Dec 13, 2018
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since angular#9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
crisbeto added a commit to crisbeto/material2 that referenced this pull request Jan 2, 2019
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since angular#9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
crisbeto added a commit to crisbeto/material2 that referenced this pull request May 30, 2019
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since angular#9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
crisbeto added a commit to crisbeto/material2 that referenced this pull request May 30, 2019
…ontent
This is something I ran into while working on aligning the datepicker with the most-recent Material design spec. Since angular#9639 we use a portal outlet to render the calendar header. The portal outlet directive will detach in `ngOnDestroy` and it won't wait for the parent animation to finish, which ends up shifting the entire calendar up while it's animating away. The only reason that this isn't visible at the moment is because the current animation isn't configured correctly, which causes it to go to `opacity: 0` immediately.
@angular-automatic-lock-bot

Copy link
Copy Markdown

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-botangular-automatic-lock-botBot locked and limited conversation to collaborators Sep 8, 2019
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: mergeThe PR is ready for merge by the caretakercla: yesPR author has agreed to Google's Contributor License Agreementtarget: minorThis PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tobiasschweizer@googlebot@EdricChan03@mmalerba@rafaelss95