Skip to content

Commit

Permalink
fix(datepicker): date fns date format (#1172)
Browse files Browse the repository at this point in the history
  • Loading branch information
yggg authored and tibing-old-email committed Feb 15, 2019
1 parent 3343136 commit 257eb9a
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 36 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"bootstrap": "4.0.0",
"colors.js": "1.2.4",
"core-js": "2.5.7",
"date-fns": "^2.0.0-alpha.16",
"date-fns": ">=2.0.0-alpha.16 <=2.0.0-alpha.27",
"docsearch.js": "^2.5.2",
"gulp-bump": "2.7.0",
"highlight.js": "9.12.0",
Expand Down
1 change: 1 addition & 0 deletions scripts/gulp/tasks/bundle/rollup-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const ROLLUP_GLOBALS = {
// date libs for date service
'moment': 'moment',
'date-fns/parse': 'date-fns.parse',
'date-fns/format': 'date-fns.format',

// @nebular dependencies
'@nebular/theme': 'nb.theme',
Expand Down
28 changes: 24 additions & 4 deletions src/framework/date-fns/date-fns.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,34 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { NgModule } from '@angular/core';
import { ModuleWithProviders, NgModule } from '@angular/core';

import { NbDateService } from '@nebular/theme';
import { NbDateFnsDateService } from './services/date-fns-date.service';
import { NB_DATE_SERVICE_OPTIONS, NbDateService } from '@nebular/theme';
import { NbDateFnsOptions, NbDateFnsDateService } from './services/date-fns-date.service';

const dateFnsServiceProvider = { provide: NbDateService, useClass: NbDateFnsDateService };

@NgModule({
providers: [{ provide: NbDateService, useClass: NbDateFnsDateService }],
providers: [ dateFnsServiceProvider ],
})
export class NbDateFnsDateModule {
static forRoot(options: Partial<NbDateFnsOptions>): ModuleWithProviders {
return {
ngModule: NbDateFnsDateModule,
providers: [
dateFnsServiceProvider,
{ provide: NB_DATE_SERVICE_OPTIONS, useValue: options },
],
};
}

static forChild(options: Partial<NbDateFnsOptions>): ModuleWithProviders {
return {
ngModule: NbDateFnsDateModule,
providers: [
dateFnsServiceProvider,
{ provide: NB_DATE_SERVICE_OPTIONS, useValue: options },
],
};
}
}
2 changes: 1 addition & 1 deletion src/framework/date-fns/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
],
"peerDependencies": {
"@nebular/theme": "3.2.1",
"date-fns": "^2.0.0-alpha.16"
"date-fns": ">=2.0.0-alpha.16 <=2.0.0-alpha.27"
}
}
53 changes: 51 additions & 2 deletions src/framework/date-fns/services/date-fns-date.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,66 @@ import { NbDateService } from '@nebular/theme';

import { NbDateFnsDateService } from './date-fns-date.service';


describe('date-fns-date-service', () => {
let dateService: NbDateService<Date>;

beforeEach(() => {
TestBed.configureTestingModule({});
dateService = new NbDateFnsDateService(TestBed.get(LOCALE_ID));
dateService = new NbDateFnsDateService(TestBed.get(LOCALE_ID), null);
});

it('should parse date according to the MM.dd.yyyy format', () => {
const date = '06.15.2018';
expect(dateService.parse(date, 'MM.dd.yyyy')).toEqual(new Date(2018, 5, 15));
});

describe('service global config', () => {
const SEPARATOR = '_';
const FORMAT = `MM${SEPARATOR}dd${SEPARATOR}yyyy`;
const year = 2010;
const monthIndex = 10;
const month = monthIndex + 1;
const day = 20;
const date = new Date(year, monthIndex, day);
const formattedDate = `${month}${SEPARATOR}${day}${SEPARATOR}${year}`;

beforeEach(() => {
dateService = new NbDateFnsDateService(
TestBed.get(LOCALE_ID),
{
format: FORMAT,
parseOptions: { awareOfUnicodeTokens: true },
formatOptions: { awareOfUnicodeTokens: true },
},
);
});

it('should use format from global config if isn\'t passed as parameter', () => {
expect(dateService.format(date, undefined)).toEqual(formattedDate);

const parsedDate = dateService.parse(formattedDate, FORMAT);
expect(parsedDate.valueOf()).toEqual(date.valueOf());
});

it('should use parameter over global config format if presented', () => {
expect(dateService.format(date, undefined)).toEqual(formattedDate);

const parsedDate = dateService.parse(formattedDate, FORMAT);
expect(parsedDate.valueOf()).toEqual(date.valueOf());
});

it('should pass parseOptions to parse function', () => {
// date-fns require { awareOfUnicodeTokens: true } option to be passed to parse function
// when format contains 'DD' or 'YYYY' tokens, otherwise it throws. This option is
// passed as global config to service constructor so it shouldn't throw.
expect(() => dateService.parse(formattedDate, 'DD/MM/YYYY')).not.toThrow();
});

it('should pass formatOptions to format function', () => {
// date-fns require { awareOfUnicodeTokens: true } option to be passed to format function
// when format contains 'DD' or 'YYYY' tokens, otherwise it throws. This option is
// passed as global config to service constructor so it shouldn't throw.
expect(() => dateService.format(date, 'DD/MM/YYYY')).not.toThrow();
});
});
});
29 changes: 24 additions & 5 deletions src/framework/date-fns/services/date-fns-date.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,45 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { Inject, Injectable, LOCALE_ID } from '@angular/core';
import { Inject, Injectable, LOCALE_ID, Optional } from '@angular/core';

import { NbNativeDateService } from '@nebular/theme';
import { NB_DATE_SERVICE_OPTIONS, NbNativeDateService } from '@nebular/theme';

import * as dateFnsParse from 'date-fns/parse';
// @ts-ignore
import { default as rollupParse} from 'date-fns/parse';
import { default as rollupParse } from 'date-fns/parse';
import * as dateFnsFormat from 'date-fns/format';
// @ts-ignore
import { default as rollupFormat } from 'date-fns/format';

const parse = rollupParse || dateFnsParse;
const formatDate = rollupFormat || dateFnsFormat;

export interface NbDateFnsOptions {
format: string;
parseOptions: {},
formatOptions: {},
}

@Injectable()
export class NbDateFnsDateService extends NbNativeDateService {
constructor(@Inject(LOCALE_ID) locale: string) {
protected options: Partial<NbDateFnsOptions>;

constructor(
@Inject(LOCALE_ID) locale: string,
@Optional() @Inject(NB_DATE_SERVICE_OPTIONS) options,
) {
super(locale);
this.setLocale(locale);
this.options = options || {};
}

format(date: Date, format: string): string {
return formatDate(date, format || this.options.format, this.options.formatOptions);
}

parse(date: string, format: string): Date {
return parse(date, format, new Date());
return parse(date, format || this.options.format, new Date(), this.options.parseOptions);
}

getId(): string {
Expand Down
63 changes: 48 additions & 15 deletions src/framework/theme/components/datepicker/datepicker.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ import {
OnDestroy,
Output,
Type,
AfterViewInit,
OnInit,
SimpleChanges,
Optional,
} from '@angular/core';
import { takeWhile } from 'rxjs/operators';
import { Observable, Subject } from 'rxjs';
import { Observable, ReplaySubject, Subject } from 'rxjs';

import {
NbAdjustableConnectedPositionStrategy,
Expand All @@ -43,13 +47,15 @@ import {
NbCalendarViewMode,
NbDateService,
} from '../calendar-kit';
import { NbDatepicker, NbPickerValidatorConfig } from './datepicker.directive';
import { NB_DATE_SERVICE_OPTIONS, NbDatepicker, NbPickerValidatorConfig } from './datepicker.directive';


/**
* The `NbBasePicker` component concentrates overlay manipulation logic.
* */
export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements OnChanges, OnDestroy {
export abstract class NbBasePicker<D, T, P>
extends NbDatepicker<T>
implements OnInit, OnChanges, AfterViewInit, OnDestroy {
/**
* Datepicker date format. Can be used only with date adapters (moment, date-fns) since native date
* object doesn't support formatting.
Expand Down Expand Up @@ -139,6 +145,8 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements O
* */
protected hostRef: ElementRef;

protected init$: ReplaySubject<void> = new ReplaySubject<void>();

/**
* Stream of picker changes. Required to be the subject because picker hides and shows and picker
* change stream becomes recreated.
Expand All @@ -156,7 +164,7 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements O
* Queue contains the last value that was applied to the picker when it was hidden.
* This value will be passed to the picker as soon as it shown.
* */
protected queue: T;
protected queue: T | undefined;

protected blur$: Subject<void> = new Subject<void>();

Expand All @@ -166,6 +174,7 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements O
protected overlay: NbOverlayService,
protected cfr: ComponentFactoryResolver,
protected dateService: NbDateService<D>,
@Optional() @Inject(NB_DATE_SERVICE_OPTIONS) protected dateServiceOptions,
) {
super();
}
Expand All @@ -188,6 +197,10 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements O
return this.ref && this.ref.hasAttached();
}

get init(): Observable<void> {
return this.init$.asObservable();
}

/**
* Emits when datepicker looses focus.
*/
Expand All @@ -197,18 +210,24 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements O

protected abstract get pickerValueChange(): Observable<T>;

ngOnChanges() {
if (this.dateService.getId() === 'native' && this.format) {
throw new Error('Can\'t format native date. To use custom formatting you have to install @nebular/moment or ' +
'@nebular/date-fns package and import NbMomentDateModule or NbDateFnsDateModule accordingly.' +
'More information at "Formatting issue" ' +
'https://akveo.github.io/nebular/docs/components/datepicker/overview#nbdatepickercomponent');
ngOnInit() {
this.checkFormat();
}

ngOnChanges(changes: SimpleChanges) {
if (changes.format && !changes.format.isFirstChange()) {
this.checkFormat();
}
}

ngAfterViewInit() {
this.init$.next();
}

ngOnDestroy() {
this.alive = false;
this.hide();
this.init$.complete();

if (this.ref) {
this.ref.dispose();
Expand Down Expand Up @@ -328,6 +347,20 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T> implements O
this.picker.size = this.size;
this.picker.visibleDate = this.visibleDate;
}

protected checkFormat() {
if (this.dateService.getId() === 'native' && this.format) {
throw new Error('Can\'t format native date. To use custom formatting you have to install @nebular/moment or ' +
'@nebular/date-fns package and import NbMomentDateModule or NbDateFnsDateModule accordingly.' +
'More information at "Formatting issue" ' +
'https://akveo.github.io/nebular/docs/components/datepicker/overview#nbdatepickercomponent');
}

const isFormatSet = this.format || (this.dateServiceOptions && this.dateServiceOptions.format);
if (this.dateService.getId() === 'date-fns' && !isFormatSet) {
throw new Error('format is required when using NbDateFnsDateModule');
}
}
}

/**
Expand Down Expand Up @@ -355,8 +388,8 @@ export class NbDatepickerComponent<D> extends NbBasePicker<D, D, NbCalendarCompo
return this.valueChange as EventEmitter<D>;
}

get value(): D {
return this.picker.date;
get value(): D | undefined {
return this.picker ? this.picker.date : undefined;
}

set value(date: D) {
Expand Down Expand Up @@ -405,8 +438,8 @@ export class NbRangepickerComponent<D> extends NbBasePicker<D, NbCalendarRange<D
return this.valueChange as EventEmitter<NbCalendarRange<D>>;
}

get value(): NbCalendarRange<D> {
return this.picker.range;
get value(): NbCalendarRange<D> | undefined {
return this.picker ? this.picker.range : undefined;
}

set value(range: NbCalendarRange<D>) {
Expand All @@ -426,7 +459,7 @@ export class NbRangepickerComponent<D> extends NbBasePicker<D, NbCalendarRange<D
}

shouldHide(): boolean {
return super.shouldHide() && !!(this.value.start && this.value.end);
return super.shouldHide() && !!(this.value && this.value.start && this.value.end);
}

protected writeQueue() {
Expand Down
Loading

0 comments on commit 257eb9a

Please sign in to comment.