Skip to content

feat(datepicker): align multi-year-view based on minDate and maxDate #16018

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

Conversation

jysoo
Copy link
Contributor

@jysoo jysoo commented May 13, 2019

If minDate is set (and maxDate not set), let first page begin with minYear.
If maxDate is set, let last page end with maxYear,
and disable year(s) < minYear (if any) in first page.

Fixes #10646

@jysoo jysoo requested a review from mmalerba as a code owner May 13, 2019 22:58
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label May 13, 2019
Copy link
Contributor

@mmalerba mmalerba left a comment

Choose a reason for hiding this comment

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

This is awesome, thanks!! Only request: could you add a test that verifies the behavior for min set, max set, and both set?

@mmalerba mmalerba added pr: lgtm action: merge The PR is ready for merge by the caretaker labels May 17, 2019
@jysoo
Copy link
Contributor Author

jysoo commented May 17, 2019

@mmalerba Cheers!

Copy link
Contributor

@Splaktar Splaktar left a comment

Choose a reason for hiding this comment

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

Thank you for the contribution! I only had a few minor nits.

}

/** mod that handles case where first number is negative */
private _mod(a: number, b: number) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: this can be an un-exported file-level function

@@ -69,11 +69,24 @@ export class MatCalendarHeader<D> {
if (this.calendar.currentView == 'year') {
return this._dateAdapter.getYearName(this.calendar.activeDate);
}

This comment was marked as resolved.

if (this.calendar.maxDate) {
let maxYear = this._dateAdapter.getYear(this.calendar.maxDate);
minYear = maxYear - yearsPerPage + 1;
}
Copy link
Member

Choose a reason for hiding this comment

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

This block is repeated a few times; could you make it a shared function?

minYear = maxYear - yearsPerPage + 1;
}
let activeOffset = this._mod((activeYear - minYear), yearsPerPage);
let minYearOfPage = activeYear - activeOffset;
Copy link
Member

Choose a reason for hiding this comment

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

It would also be good to deduplicate this logic a bit

@jelbourn jelbourn added target: patch This PR is targeted for the next patch release and removed action: merge The PR is ready for merge by the caretaker labels May 18, 2019
Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

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

For some reason GitHub lost one of my comments from the last pass

@@ -149,8 +162,21 @@ export class MatCalendarHeader<D> {
return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2);
}
// Otherwise we are in 'multi-year' view.
return Math.floor(this._dateAdapter.getYear(date1) / yearsPerPage) ==
Math.floor(this._dateAdapter.getYear(date2) / yearsPerPage);
let minYear = 0;
Copy link
Member

Choose a reason for hiding this comment

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

I would tweak this slightly:

// We want a range years such that we maximize the number of
// enabled dates visible at once. This prevents issues where the minimum year
// is the last year of a page or the maximum year is the first item of a page.


// We pick a "starting" year such that either the maximum year would be at the end
// or the minimum year would be at the begining of a page.
let startingYear = 0;
if (this.calendar.maxDate) {
  let maxYear = this._dateAdapter.getYear(this.calendar.maxDate);
  startingYear = maxYear - yearsPerPage + 1;
} else if (this.calendar.minDate) {
  startingYear = this._dateAdapter.getYear(this.calendar.minDate);
}

// When the multi-year view is first opened, the active year will be in view.
// So we compute how many years are between the active year and the *slot* where our
// "starting" year year will render when paged into view.
const activeYear = this._dateAdapter.getYear(this.calendar.activeDate);
const activeOffset = this._mod((activeYear - startingYear), yearsPerPage);

// The offset from the active year to the "slot" for the starting year is the
// *actual* first rendered year in the multi-year view, and the lasy year is
// just one yearsPerPage away.
const minYearOfPage = activeYear - activeOffset;
const maxYearOfPage = minYearOfPage + yearsPerPage - 1;

const firstYearInView = this._dateAdapter.getYearName(
    this._dateAdapter.createDate(minYearOfPage, 0, 1));
const lastYearInView = this._dateAdapter.getYearName(
    this._dateAdapter.createDate(maxYearOfPage, 0, 1));
return `${firstYearInView} \u2013 ${lastYearInView}`;
  • Adds some explanatory comments
  • Changes let to const where appropriate
  • Combines two separate if blocks into an if-elseif
  • Changes minDate to startingDate since it's not necessarily the minDate

@jysoo
Copy link
Contributor Author

jysoo commented May 19, 2019

Thank you for the comments! :)

  1. Modifications to originally existing code: the === sign that Michael mentioned, as well as plugging in the minYearOfPage and maxYearOfPage directly into the periodButtonText string (instead of getYearName after creating Date from Year).

  2. Shared logic (I simply exported the getActiveOffset and isSameMultiYearView in multi-year-view.ts so that calendar-ts can use them as well):
    getStartingYear is used for:

  • getActiveOffset (which is used for _getActiveCell as well as minYearOfPage)
  • isSameMultiYearView

Please let me know if you have any suggestions for improving the implementation.

Copy link
Contributor

@Splaktar Splaktar left a comment

Choose a reason for hiding this comment

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

LGTM other than a couple minor nits.

@@ -39,6 +39,43 @@ export const yearsPerPage = 24;

export const yearsPerRow = 4;

/** Gets remainder that is non-negative, even if first number is negative */
function euclideanModulo (a: number, b: number): number {
Copy link
Member

Choose a reason for hiding this comment

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

Could you move these helper functions to the bottom of the file? We usually organize them that way

function getStartingYear(minDate: any, maxDate: any): number {
let startingYear = 0;
if (maxDate) {
const maxYear = maxDate.getFullYear();
Copy link
Member

Choose a reason for hiding this comment

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

It looks like these helper functions aren't going through the adapter any more, so they're assuming native JS Date (here and in getActiveOffset). You could make these private class methods so you still have access to the adapter.

You also would want to avoid using any in the types because it masks issues like this

* So we compute how many years are between the active year and the *slot* where our
* "startingYear" will render when paged into view.
*/
export function getActiveOffset(activeDate: any, minDate: any, maxDate: any): number {
Copy link
Member

Choose a reason for hiding this comment

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

If there are new exported helper functions like this, then they need to be omitted from datepicker/public-api.ts in order to avoid introducing them as public APIs.

const year1 = date1.getFullYear();
const year2 = date2.getFullYear();
const startingYear = getStartingYear(minDate, maxDate);
return Math.floor( (year1 - startingYear) / yearsPerPage) ===
Copy link
Member

Choose a reason for hiding this comment

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

Extra spaces inside parens

this._years = [];
for (let i = 0, row: number[] = []; i < yearsPerPage; i++) {
row.push(activeYear - activeOffset + i);
row.push( minYearOfPage + i);
Copy link
Member

Choose a reason for hiding this comment

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

Extra space

@andrewseguin andrewseguin added the P4 A relatively minor issue that is not relevant to core functions label May 30, 2019
@jysoo jysoo force-pushed the feature/datepicker-multi-year-view-min-max branch 3 times, most recently from 1c44b75 to 20c6256 Compare June 5, 2019 06:38
return this._isSameMultiYearView(date1, date2);
}

private _isSameMultiYearView(date1: D, date2: D): boolean {
Copy link
Member

Choose a reason for hiding this comment

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

These functions are still duplicated; it would be better to extract them to one place where they accept a dateAdapter as an argument rather than defining them in both classes.

@jysoo
Copy link
Contributor Author

jysoo commented Jun 14, 2019

@mmalerba @jelbourn @Splaktar
I'm not familiar with this; may I have some help/advice with the CircleCI 'Too long with no output (exceeded 10m0s)' issue during the browserstack test? Thanks.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

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

LGTM aside from one small formatting nit

import {MatMultiYearView,
yearsPerPage,
getActiveOffset,
isSameMultiYearView} from './multi-year-view';
Copy link
Member

Choose a reason for hiding this comment

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

This formatting seems a bit off

@Splaktar
Copy link
Contributor

Splaktar commented Jun 14, 2019

I'm not familiar with this; may I have some help/advice with the CircleCI 'Too long with no output (exceeded 10m0s)' issue during the browserstack test? Thanks.

@jysoo that's likely just a flakey test or a issue with CircleCI and not likely a problem with your changes.

It looks like Safari on macOS isn't launching properly and timing out. It looks like your branch is still based on 8.0.0-rc.4. Can you please rebase on master?

jysoo and others added 7 commits June 14, 2019 10:23
If minDate is set (and maxDate not set), let first page begin with minYear.
If maxDate is set, let last page end with maxYear,
and disable year(s) < minYear (if any) in first page.

Fixes angular#10646
refactor with private methods feat(datepicker): align multi-year-view based on minDate and maxDate
@mmalerba mmalerba force-pushed the feature/datepicker-multi-year-view-min-max branch from 9150ac0 to f708475 Compare June 14, 2019 17:29
@mmalerba
Copy link
Contributor

@jysoo I rebased and fixed the formatting nit for you, hopefully CI should pass. Thanks for all your work on this!

@mmalerba mmalerba added the action: merge The PR is ready for merge by the caretaker label Jun 14, 2019
@jysoo
Copy link
Contributor Author

jysoo commented Jun 14, 2019

@jysoo I rebased and fixed the formatting nit for you, hopefully CI should pass. Thanks for all your work on this!

Thank you!

@mmalerba mmalerba merged commit f28a294 into angular:master Jun 17, 2019
@jysoo jysoo deleted the feature/datepicker-multi-year-view-min-max branch June 20, 2019 19:30
RudolfFrederiksen pushed a commit to RudolfFrederiksen/material2 that referenced this pull request Jun 21, 2019
…ngular#16018)

* feat(datepicker): align multi-year-view based on minDate and maxDate

If minDate is set (and maxDate not set), let first page begin with minYear.
If maxDate is set, let last page end with maxYear,
and disable year(s) < minYear (if any) in first page.

Fixes angular#10646

* add tests for feat(datepicker): align multi-year-view based on minDate and maxDate

* refactor feat(datepicker): align multi-year-view based on minDate and maxDate

* omit helper func from public api

* fixup, resolve conflict

refactor with private methods feat(datepicker): align multi-year-view based on minDate and maxDate

* share func feat(datepicker): align multi-year-view based on minDate and maxDate

* fix formatting nit
andrewseguin pushed a commit that referenced this pull request Jul 2, 2019
…16018)

* feat(datepicker): align multi-year-view based on minDate and maxDate

If minDate is set (and maxDate not set), let first page begin with minYear.
If maxDate is set, let last page end with maxYear,
and disable year(s) < minYear (if any) in first page.

Fixes #10646

* add tests for feat(datepicker): align multi-year-view based on minDate and maxDate

* refactor feat(datepicker): align multi-year-view based on minDate and maxDate

* omit helper func from public api

* fixup, resolve conflict

refactor with private methods feat(datepicker): align multi-year-view based on minDate and maxDate

* share func feat(datepicker): align multi-year-view based on minDate and maxDate

* fix formatting nit
@angular-automatic-lock-bot
Copy link

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-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 11, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement P4 A relatively minor issue that is not relevant to core functions target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Hide years superior to max date in datepicker
6 participants