From 035b0a449b240a2615cb0599ff242604a7864e83 Mon Sep 17 00:00:00 2001 From: jonaswinkler <17569239+jonaswinkler@users.noreply.github.com> Date: Wed, 24 Feb 2021 18:00:26 +0100 Subject: [PATCH] use ng-bootstrap date selector, with proper formatting/parsing according to the current locale #177 --- src-ui/src/app/app.module.ts | 14 +++-- .../input/date-time/date-time.component.html | 13 ---- .../input/date-time/date-time.component.ts | 61 ------------------- .../common/input/date/date.component.html | 15 +++++ .../date.component.scss} | 0 .../date.component.spec.ts} | 12 ++-- .../common/input/date/date.component.ts | 32 ++++++++++ .../document-detail.component.html | 2 +- .../manage/settings/settings.component.ts | 9 +-- src-ui/src/app/services/settings.service.ts | 36 ++++++++--- src-ui/src/app/utils/ngb-date-adapter.ts | 23 +++++++ .../app/utils/ngb-date-parser-formatter.ts | 59 ++++++++++++++++++ 12 files changed, 176 insertions(+), 100 deletions(-) delete mode 100644 src-ui/src/app/components/common/input/date-time/date-time.component.html delete mode 100644 src-ui/src/app/components/common/input/date-time/date-time.component.ts create mode 100644 src-ui/src/app/components/common/input/date/date.component.html rename src-ui/src/app/components/common/input/{date-time/date-time.component.scss => date/date.component.scss} (100%) rename src-ui/src/app/components/common/input/{date-time/date-time.component.spec.ts => date/date.component.spec.ts} (55%) create mode 100644 src-ui/src/app/components/common/input/date/date.component.ts create mode 100644 src-ui/src/app/utils/ngb-date-adapter.ts create mode 100644 src-ui/src/app/utils/ngb-date-parser-formatter.ts diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index 0773a9ace..9e0dc56de 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -3,7 +3,7 @@ import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbDateAdapter, NgbDateParserFormatter, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { DocumentListComponent } from './components/document-list/document-list.component'; import { DocumentDetailComponent } from './components/document-detail/document-detail.component'; @@ -39,7 +39,6 @@ import { SelectComponent } from './components/common/input/select/select.compone import { CheckComponent } from './components/common/input/check/check.component'; import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; -import { DateTimeComponent } from './components/common/input/date-time/date-time.component'; import { TagsComponent } from './components/common/input/tags/tags.component'; import { SortableDirective } from './directives/sortable.directive'; import { CookieService } from 'ngx-cookie-service'; @@ -60,6 +59,9 @@ import { NgSelectModule } from '@ng-select/ng-select'; import { NumberComponent } from './components/common/input/number/number.component'; import { SafePipe } from './pipes/safe.pipe'; import { CustomDatePipe } from './pipes/custom-date.pipe'; +import { DateComponent } from './components/common/input/date/date.component'; +import { ISODateAdapter } from './utils/ngb-date-adapter'; +import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter'; import localeFr from '@angular/common/locales/fr'; import localeNl from '@angular/common/locales/nl'; @@ -106,7 +108,6 @@ registerLocaleData(localeEnGb) SelectComponent, CheckComponent, SaveViewConfigDialogComponent, - DateTimeComponent, TagsComponent, SortableDirective, SavedViewWidgetComponent, @@ -122,7 +123,8 @@ registerLocaleData(localeEnGb) SelectDialogComponent, NumberComponent, SafePipe, - CustomDatePipe + CustomDatePipe, + DateComponent ], imports: [ BrowserModule, @@ -144,7 +146,9 @@ registerLocaleData(localeEnGb) multi: true }, FilterPipe, - DocumentTitlePipe + DocumentTitlePipe, + {provide: NgbDateAdapter, useClass: ISODateAdapter}, + {provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter} ], bootstrap: [AppComponent] }) diff --git a/src-ui/src/app/components/common/input/date-time/date-time.component.html b/src-ui/src/app/components/common/input/date-time/date-time.component.html deleted file mode 100644 index 7c002db1b..000000000 --- a/src-ui/src/app/components/common/input/date-time/date-time.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
- - -
-
- - -
-
- - - \ No newline at end of file diff --git a/src-ui/src/app/components/common/input/date-time/date-time.component.ts b/src-ui/src/app/components/common/input/date-time/date-time.component.ts deleted file mode 100644 index bce208ec8..000000000 --- a/src-ui/src/app/components/common/input/date-time/date-time.component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { formatDate } from '@angular/common'; -import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; - -@Component({ - providers: [{ - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => DateTimeComponent), - multi: true - }], - selector: 'app-input-date-time', - templateUrl: './date-time.component.html', - styleUrls: ['./date-time.component.scss'] -}) -export class DateTimeComponent implements OnInit,ControlValueAccessor { - - constructor() { - } - - onChange = (newValue: any) => {}; - - onTouched = () => {}; - - writeValue(newValue: any): void { - this.dateValue = formatDate(newValue, 'yyyy-MM-dd', "en-US") - this.timeValue = formatDate(newValue, 'HH:mm:ss', 'en-US') - } - registerOnChange(fn: any): void { - this.onChange = fn; - } - registerOnTouched(fn: any): void { - this.onTouched = fn; - } - setDisabledState?(isDisabled: boolean): void { - this.disabled = isDisabled; - } - - @Input() - titleDate: string = "Date" - - @Input() - titleTime: string - - @Input() - disabled: boolean = false - - @Input() - hint: string - - timeValue - - dateValue - - ngOnInit(): void { - } - - dateOrTimeChanged() { - this.onChange(formatDate(this.dateValue + "T" + this.timeValue,"yyyy-MM-ddTHH:mm:ssZZZZZ", "en-us", "UTC")) - } - -} diff --git a/src-ui/src/app/components/common/input/date/date.component.html b/src-ui/src/app/components/common/input/date/date.component.html new file mode 100644 index 000000000..ca9ccc040 --- /dev/null +++ b/src-ui/src/app/components/common/input/date/date.component.html @@ -0,0 +1,15 @@ +
+ +
+ +
+ +
+
Invalid date.
+
+
diff --git a/src-ui/src/app/components/common/input/date-time/date-time.component.scss b/src-ui/src/app/components/common/input/date/date.component.scss similarity index 100% rename from src-ui/src/app/components/common/input/date-time/date-time.component.scss rename to src-ui/src/app/components/common/input/date/date.component.scss diff --git a/src-ui/src/app/components/common/input/date-time/date-time.component.spec.ts b/src-ui/src/app/components/common/input/date/date.component.spec.ts similarity index 55% rename from src-ui/src/app/components/common/input/date-time/date-time.component.spec.ts rename to src-ui/src/app/components/common/input/date/date.component.spec.ts index 0657768bd..ea92c7b30 100644 --- a/src-ui/src/app/components/common/input/date-time/date-time.component.spec.ts +++ b/src-ui/src/app/components/common/input/date/date.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { DateTimeComponent } from './date-time.component'; +import { DateComponent } from './date.component'; -describe('DateTimeComponent', () => { - let component: DateTimeComponent; - let fixture: ComponentFixture; +describe('DateComponent', () => { + let component: DateComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ DateTimeComponent ] + declarations: [ DateComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(DateTimeComponent); + fixture = TestBed.createComponent(DateComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src-ui/src/app/components/common/input/date/date.component.ts b/src-ui/src/app/components/common/input/date/date.component.ts new file mode 100644 index 000000000..b3b863581 --- /dev/null +++ b/src-ui/src/app/components/common/input/date/date.component.ts @@ -0,0 +1,32 @@ +import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NgbDateAdapter, NgbDateParserFormatter, NgbDatepickerContent } from '@ng-bootstrap/ng-bootstrap'; +import { SettingsService } from 'src/app/services/settings.service'; +import { v4 as uuidv4 } from 'uuid'; +import { AbstractInputComponent } from '../abstract-input'; + + +@Component({ + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DateComponent), + multi: true + }], + selector: 'app-input-date', + templateUrl: './date.component.html', + styleUrls: ['./date.component.scss'] +}) +export class DateComponent extends AbstractInputComponent implements OnInit { + + constructor(private settings: SettingsService) { + super() + } + + ngOnInit(): void { + super.ngOnInit() + this.placeholder = this.settings.getLocalizedDateInputFormat() + } + + placeholder: string + +} diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index 2814a1242..f9b87aee3 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -58,7 +58,7 @@ - + o.code == dateLocale)?.dateInputFormat || "yyyy-mm-dd" + } + get(key: string): any { let setting = SETTINGS.find(s => s.key == key) diff --git a/src-ui/src/app/utils/ngb-date-adapter.ts b/src-ui/src/app/utils/ngb-date-adapter.ts new file mode 100644 index 000000000..19711319d --- /dev/null +++ b/src-ui/src/app/utils/ngb-date-adapter.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@angular/core"; +import { NgbDateAdapter, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap"; + +@Injectable() +export class ISODateAdapter extends NgbDateAdapter { + + fromModel(value: string | null): NgbDateStruct | null { + if (value) { + let date = new Date(value) + return { + day : date.getDate(), + month : date.getMonth() + 1, + year : date.getFullYear() + } + } else { + return null + } + } + + toModel(date: NgbDateStruct | null): string | null { + return date ? new Date(date.year, date.month - 1, date.day).toISOString() : null + } +} diff --git a/src-ui/src/app/utils/ngb-date-parser-formatter.ts b/src-ui/src/app/utils/ngb-date-parser-formatter.ts new file mode 100644 index 000000000..7d819a6bf --- /dev/null +++ b/src-ui/src/app/utils/ngb-date-parser-formatter.ts @@ -0,0 +1,59 @@ +import { Injectable } from "@angular/core" +import { NgbDateParserFormatter, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap" +import { SettingsService } from "../services/settings.service" + +@Injectable() +export class LocalizedDateParserFormatter extends NgbDateParserFormatter { + + constructor(private settings: SettingsService) { + super() + } + + private getDateInputFormat() { + return this.settings.getLocalizedDateInputFormat() + } + + /** + * This constructs a regular expression from a date input format which is then + * used to parse dates. + */ + private getDateParseRegex() { + return new RegExp( + "^" + this.getDateInputFormat() + .replace('dd', '(?[0-9]+)') + .replace('mm', '(?[0-9]+)') + .replace('yyyy', '(?[0-9]+)') + .split('.').join('\\.\\s*') + "$" // allow whitespace(s) after dot (specific for German) + ) + } + + parse(value: string): NgbDateStruct | null { + let match = this.getDateParseRegex().exec(value) + if (match) { + let dateStruct = { + day: +match.groups.day, + month: +match.groups.month, + year: +match.groups.year + } + if (dateStruct.year <= (new Date().getFullYear() - 2000)) { + dateStruct.year += 2000 + } else if (dateStruct.year < 100) { + dateStruct.year += 1900 + } + return dateStruct + } else { + return null + } + } + + format(date: NgbDateStruct | null): string { + if (date) { + return this.getDateInputFormat() + .replace('dd', date.day.toString().padStart(2, '0')) + .replace('mm', date.month.toString().padStart(2, '0')) + .replace('yyyy', date.year.toString().padStart(4, '0')) + } else { + return null + } + } +} \ No newline at end of file