Skip to content
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

datepicker: create injectable for date formats and bundle it along with date adapter into MdNativeDateModule #4296

Merged
merged 5 commits into from
Apr 27, 2017
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
16 changes: 9 additions & 7 deletions src/demo-app/demo-app-module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import {NgModule, ApplicationRef} from '@angular/core';
import {ApplicationRef, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {HttpModule} from '@angular/http';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {DemoApp, Home} from './demo-app/demo-app';
import {
MaterialModule,
OverlayContainer,
FullscreenOverlayContainer,
MaterialModule,
MdNativeDateModule,
MdSelectionModule,
OverlayContainer
} from '@angular/material';
import {DEMO_APP_ROUTES} from './demo-app/routes';
import {ProgressBarDemo} from './progress-bar/progress-bar-demo';
import {JazzDialog, ContentElementDialog, DialogDemo, IFrameDialog} from './dialog/dialog-demo';
import {ContentElementDialog, DialogDemo, IFrameDialog, JazzDialog} from './dialog/dialog-demo';
import {RippleDemo} from './ripple/ripple-demo';
import {IconDemo} from './icon/icon-demo';
import {GesturesDemo} from './gestures/gestures-demo';
Expand All @@ -27,18 +28,18 @@ import {ListDemo} from './list/list-demo';
import {BaselineDemo} from './baseline/baseline-demo';
import {GridListDemo} from './grid-list/grid-list-demo';
import {LiveAnnouncerDemo} from './live-announcer/live-announcer-demo';
import {OverlayDemo, SpagettiPanel, RotiniPanel} from './overlay/overlay-demo';
import {OverlayDemo, RotiniPanel, SpagettiPanel} from './overlay/overlay-demo';
import {SlideToggleDemo} from './slide-toggle/slide-toggle-demo';
import {ToolbarDemo} from './toolbar/toolbar-demo';
import {ButtonDemo} from './button/button-demo';
import {MdCheckboxDemoNestedChecklist, CheckboxDemo} from './checkbox/checkbox-demo';
import {CheckboxDemo, MdCheckboxDemoNestedChecklist} from './checkbox/checkbox-demo';
import {SelectDemo} from './select/select-demo';
import {SliderDemo} from './slider/slider-demo';
import {SidenavDemo} from './sidenav/sidenav-demo';
import {SnackBarDemo} from './snack-bar/snack-bar-demo';
import {PortalDemo, ScienceJoke} from './portal/portal-demo';
import {MenuDemo} from './menu/menu-demo';
import {TabsDemo, SunnyTabContent, RainyTabContent, FoggyTabContent} from './tabs/tabs-demo';
import {FoggyTabContent, RainyTabContent, SunnyTabContent, TabsDemo} from './tabs/tabs-demo';
import {PlatformDemo} from './platform/platform-demo';
import {AutocompleteDemo} from './autocomplete/autocomplete-demo';
import {InputDemo} from './input/input-demo';
Expand All @@ -55,6 +56,7 @@ import {DatepickerDemo} from './datepicker/datepicker-demo';
ReactiveFormsModule,
RouterModule.forRoot(DEMO_APP_ROUTES),
MaterialModule.forRoot(),
MdNativeDateModule,
MdSelectionModule,
],
declarations: [
Expand Down
3 changes: 0 additions & 3 deletions src/lib/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {OverlayModule} from './overlay/overlay-directives';
import {A11yModule} from './a11y/index';
import {MdSelectionModule} from './selection/index';
import {MdRippleModule} from './ripple/index';
import {DatetimeModule} from './datetime/index';


// RTL
Expand Down Expand Up @@ -128,7 +127,6 @@ export * from './datetime/index';
A11yModule,
MdOptionModule,
MdSelectionModule,
DatetimeModule,
],
exports: [
MdLineModule,
Expand All @@ -140,7 +138,6 @@ export * from './datetime/index';
A11yModule,
MdOptionModule,
MdSelectionModule,
DatetimeModule,
],
})
export class MdCoreModule {
Expand Down
25 changes: 5 additions & 20 deletions src/lib/core/datetime/date-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,6 @@ export abstract class DateAdapter<D> {
*/
abstract getYearName(date: D): string;

/**
* Gets the name for the month and year of the given date.
* @param date The date to get the month and year name for.
* @param monthStyle The naming style for the month
* (e.g. long = 'January', short = 'Jan', narrow = 'J').
* @returns The name of the month and year of the given date (e.g. 'Jan 2017').
*/
abstract getMonthYearName(date: D, monthStyle: 'long' | 'short' | 'narrow'): string;

/**
* Gets the first day of the week.
* @returns The first day of the week (0-indexed, 0 = Sunday).
Expand All @@ -80,13 +71,6 @@ export abstract class DateAdapter<D> {
*/
abstract getNumDaysInMonth(date: D): number;

/**
* Gets a set of default formats to use for displaying the date in different contexts.
* @returns An object with the following default formats:
* - date: The default format for showing just the date without any time information.
*/
abstract getDefaultFormats(): {date: any};

/**
* Clones the given date.
* @param date The date to clone
Expand All @@ -113,18 +97,19 @@ export abstract class DateAdapter<D> {
/**
* Parses a date from a value.
* @param value The value to parse.
* @param fmt The expected format of the value being parsed (type is implementation-dependent).
* @param parseFormat The expected format of the value being parsed
* (type is implementation-dependent).
* @returns The parsed date, or null if date could not be parsed.
*/
abstract parse(value: any, fmt?: any): D | null;
abstract parse(value: any, parseFormat: any): D | null;

/**
* Formats a date as a string.
* @param date The value to parse.
* @param fmt The format to use for the result string.
* @param displayFormat The format to use to display the date as a string.
* @returns The parsed date, or null if date could not be parsed.
*/
abstract format(date: D, fmt?: any): string;
abstract format(date: D, displayFormat: any): string;

/**
* Adds the given number of years to the date. Years are counted as if flipping 12 pages on the
Expand Down
15 changes: 15 additions & 0 deletions src/lib/core/datetime/date-formats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {InjectionToken} from '@angular/core';


export type MdDateFormats = {
parse: {
dateInput: any
},
display: {
dateInput: any,
monthYearLabel: any,
}
};


export const MD_DATE_FORMATS = new InjectionToken<MdDateFormats>('md-date-formats');
11 changes: 10 additions & 1 deletion src/lib/core/datetime/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {NgModule} from '@angular/core';
import {DateAdapter} from './date-adapter';
import {NativeDateAdapter} from './native-date-adapter';
import {MD_DATE_FORMATS} from './date-formats';
import {MD_NATIVE_DATE_FORMATS} from './native-date-formats';


export * from './date-adapter';
Expand All @@ -10,4 +12,11 @@ export * from './native-date-adapter';
@NgModule({
providers: [{provide: DateAdapter, useClass: NativeDateAdapter}],
})
export class DatetimeModule {}
export class NativeDateModule {}


@NgModule({
imports: [NativeDateModule],
providers: [{provide: MD_DATE_FORMATS, useValue: MD_NATIVE_DATE_FORMATS}],
})
export class MdNativeDateModule {}
22 changes: 0 additions & 22 deletions src/lib/core/datetime/native-date-adapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,32 +104,10 @@ describe('NativeDateAdapter', () => {
expect(adapter.getYearName(new Date(2017, JAN, 1))).toBe('2017年');
});

it('should get long month and year name', () => {
expect(adapter.getMonthYearName(new Date(2017, JAN, 1), 'long')).toBe('January 2017');
});

it('should get short month and year name', () => {
expect(adapter.getMonthYearName(new Date(2017, JAN, 1), 'short')).toBe('Jan 2017');
});

it('should get narrow month and year name', () => {
expect(adapter.getMonthYearName(new Date(2017, JAN, 1), 'narrow')).toBe('J 2017');
});

it('should get month and year name in a different locale', () => {
adapter.setLocale('ja-JP');
expect(adapter.getMonthYearName(new Date(2017, JAN, 1), 'long')).toBe('2017年1月');
});

it('should get first day of week', () => {
expect(adapter.getFirstDayOfWeek()).toBe(0);
});

it('should get default formats', () => {
let dtf = new Intl.DateTimeFormat('en-US', adapter.getDefaultFormats().date);
expect(dtf.format(new Date(2017, 1, 1))).toEqual('2/1/2017');
});

it('should create Date', () => {
expect(adapter.createDate(2017, JAN, 1)).toEqual(new Date(2017, JAN, 1));
});
Expand Down
25 changes: 3 additions & 22 deletions src/lib/core/datetime/native-date-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,6 @@ export class NativeDateAdapter extends DateAdapter<Date> {
return String(this.getYear(date));
}

getMonthYearName(date: Date, monthStyle: 'long' | 'short' | 'narrow'): string {
if (SUPPORTS_INTL_API) {
let dtf = new Intl.DateTimeFormat(this.locale, {month: monthStyle, year: 'numeric'});
return dtf.format(date);
}
let monthName = this.getMonthNames(monthStyle)[this.getMonth(date)];
return `${monthName} ${this.getYear(date)}`;
}

getFirstDayOfWeek(): number {
// We can't tell using native JS Date what the first day of the week is, we default to Sunday.
return 0;
Expand All @@ -104,16 +95,6 @@ export class NativeDateAdapter extends DateAdapter<Date> {
this.getYear(date), this.getMonth(date) + 1, 0));
}

getDefaultFormats(): {date: Object} {
return {
date: {
year: 'numeric',
month: 'numeric',
day: 'numeric'
}
};
}

clone(date: Date): Date {
return this.createDate(this.getYear(date), this.getMonth(date), this.getDate(date));
}
Expand All @@ -140,16 +121,16 @@ export class NativeDateAdapter extends DateAdapter<Date> {
return new Date();
}

parse(value: any, fmt?: Object): Date | null {
parse(value: any, parseFormat: Object): Date | null {
// We have no way using the native JS Date to set the parse format or locale, so we ignore these
// parameters.
let timestamp = typeof value == 'number' ? value : Date.parse(value);
return isNaN(timestamp) ? null : new Date(timestamp);
}

format(date: Date, fmt?: Object): string {
format(date: Date, displayFormat: Object): string {
if (SUPPORTS_INTL_API) {
let dtf = new Intl.DateTimeFormat(this.locale, fmt);
let dtf = new Intl.DateTimeFormat(this.locale, displayFormat);
return dtf.format(date);
}
return date.toDateString();
Expand Down
12 changes: 12 additions & 0 deletions src/lib/core/datetime/native-date-formats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {MdDateFormats} from './date-formats';


export const MD_NATIVE_DATE_FORMATS: MdDateFormats = {
parse: {
dateInput: null,
},
display: {
dateInput: {year: 'numeric', month: 'numeric', day: 'numeric'},
monthYearLabel: {year: 'numeric', month: 'short'},
}
};
4 changes: 2 additions & 2 deletions src/lib/datepicker/calendar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {By} from '@angular/platform-browser';
import {MdMonthView} from './month-view';
import {MdYearView} from './year-view';
import {MdCalendarBody} from './calendar-body';
import {DatetimeModule} from '../core/datetime/index';
import {
dispatchFakeEvent,
dispatchKeyboardEvent,
Expand All @@ -23,6 +22,7 @@ import {
UP_ARROW
} from '../core/keyboard/keycodes';
import {MdDatepickerIntl} from './datepicker-intl';
import {MdNativeDateModule} from '../core/datetime/index';


// When constructing a Date, the month is zero-based. This can be confusing, since people are
Expand All @@ -35,7 +35,7 @@ describe('MdCalendar', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DatetimeModule,
MdNativeDateModule,
],
declarations: [
MdCalendar,
Expand Down
34 changes: 28 additions & 6 deletions src/lib/datepicker/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Inject,
Input,
Optional,
Output,
ViewEncapsulation
} from '@angular/core';
Expand All @@ -20,6 +22,8 @@ import {
} from '../core/keyboard/keycodes';
import {DateAdapter} from '../core/datetime/index';
import {MdDatepickerIntl} from './datepicker-intl';
import {createMissingDateImplError} from './datepicker-errors';
import {MD_DATE_FORMATS, MdDateFormats} from '../core/datetime/date-formats';


/**
Expand All @@ -41,7 +45,9 @@ export class MdCalendar<D> implements AfterContentInit {
/** A date representing the period (month or year) to start the calendar in. */
@Input()
get startAt(): D { return this._startAt; }
set startAt(value: D) { this._startAt = this._dateAdapter.parse(value); }
set startAt(value: D) {
this._startAt = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);
}
private _startAt: D;

/** Whether the calendar should be started in month or year view. */
Expand All @@ -50,19 +56,25 @@ export class MdCalendar<D> implements AfterContentInit {
/** The currently selected date. */
@Input()
get selected(): D { return this._selected; }
set selected(value: D) { this._selected = this._dateAdapter.parse(value); }
set selected(value: D) {
this._selected = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);
}
private _selected: D;

/** The minimum selectable date. */
@Input()
get minDate(): D { return this._minDate; }
set minDate(date: D) { this._minDate = this._dateAdapter.parse(date); }
set minDate(date: D) {
this._minDate = this._dateAdapter.parse(date, this._dateFormats.parse.dateInput);
}
private _minDate: D;

/** The maximum selectable date. */
@Input()
get maxDate(): D { return this._maxDate; }
set maxDate(date: D) { this._maxDate = this._dateAdapter.parse(date); }
set maxDate(date: D) {
this._maxDate = this._dateAdapter.parse(date, this._dateFormats.parse.dateInput);
}
private _maxDate: D;

/** A function used to filter which dates are selectable. */
Expand Down Expand Up @@ -95,7 +107,8 @@ export class MdCalendar<D> implements AfterContentInit {
/** The label for the current calendar view. */
get _periodButtonText(): string {
return this._monthView ?
this._dateAdapter.getMonthYearName(this._activeDate, 'short').toLocaleUpperCase() :
this._dateAdapter.format(this._activeDate, this._dateFormats.display.monthYearLabel)
.toLocaleUpperCase() :
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't the upper-casing be part of what the formatter does?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think either the format language used by Intl.DatetimeFormat or Moment.js gives control over the case, so we're stuck doing this

this._dateAdapter.getYearName(this._activeDate);
}

Expand All @@ -113,7 +126,16 @@ export class MdCalendar<D> implements AfterContentInit {
return this._monthView ? this._intl.nextMonthLabel : this._intl.nextYearLabel;
}

constructor(private _dateAdapter: DateAdapter<D>, private _intl: MdDatepickerIntl) {}
constructor(private _intl: MdDatepickerIntl,
@Optional() private _dateAdapter: DateAdapter<D>,
@Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats) {
if (!this._dateAdapter) {
throw createMissingDateImplError('DateAdapter');
}
if (!this._dateFormats) {
throw createMissingDateImplError('MD_DATE_FORMATS');
}
}

ngAfterContentInit() {
this._activeDate = this.startAt || this._dateAdapter.today();
Expand Down
6 changes: 6 additions & 0 deletions src/lib/datepicker/datepicker-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @docs-private */
export function createMissingDateImplError(provider: string) {
return new Error(
`MdDatepicker: No provider found for ${provider}. You must import one of the following` +
`modules at your application root: MdNativeDateModule, or provide a custom implementation.`);
}
Loading