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

fix(datepicker): date fns date format #1172

Merged
merged 14 commits into from
Feb 15, 2019
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
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