From 0dc564508af0d7faf4329faf04301aa52ce8f2cf Mon Sep 17 00:00:00 2001 From: Jane Sandberg Date: Sun, 7 Jul 2019 13:40:27 -0700 Subject: [PATCH] LP1834662: Various fixes to the datetime select * Adds the caret specifier to package.json and bumps up the version of moment-timezone * Adds an example of the egValidDatetime directive * Makes the makeFormatParseable function localizable * Gets rid of an error when this component is used without ngModel or reactive forms * Fixes various linting errors * Closes the datetimepicker if the user opens another datetimepicker Signed-off-by: Jane Sandberg Signed-off-by: Bill Erickson --- Open-ILS/src/eg2/package.json | 2 +- .../src/eg2/src/app/core/format.service.ts | 70 ++++++++++++++----- Open-ILS/src/eg2/src/app/core/format.spec.ts | 37 ++++++++-- .../daterange-select.component.spec.ts | 1 + .../datetime-select.component.html | 2 +- .../datetime-select.component.ts | 2 +- .../datetime_validator.directive.ts | 2 +- .../app/staff/sandbox/sandbox.component.html | 4 ++ .../app/staff/sandbox/sandbox.component.ts | 5 +- 9 files changed, 98 insertions(+), 27 deletions(-) diff --git a/Open-ILS/src/eg2/package.json b/Open-ILS/src/eg2/package.json index 1bafd4d4c2..4b6b8bcf5d 100644 --- a/Open-ILS/src/eg2/package.json +++ b/Open-ILS/src/eg2/package.json @@ -30,7 +30,7 @@ "file-saver": "^2.0.2", "material-design-icons": "^3.0.1", "moment": "2.24.0", - "moment-timezone": "0.5.23", + "moment-timezone": "^0.5.26", "ngx-cookie": "^4.1.2", "rxjs": "^6.5.2", "zone.js": "^0.8.29" diff --git a/Open-ILS/src/eg2/src/app/core/format.service.ts b/Open-ILS/src/eg2/src/app/core/format.service.ts index 20f0fdff18..b6fba2dea4 100644 --- a/Open-ILS/src/eg2/src/app/core/format.service.ts +++ b/Open-ILS/src/eg2/src/app/core/format.service.ts @@ -1,7 +1,8 @@ import {Injectable, Pipe, PipeTransform} from '@angular/core'; -import {DatePipe, CurrencyPipe} from '@angular/common'; +import {DatePipe, CurrencyPipe, getLocaleDateFormat, getLocaleTimeFormat, getLocaleDateTimeFormat, FormatWidth} from '@angular/common'; import {IdlService, IdlObject} from '@eg/core/idl.service'; import {OrgService} from '@eg/core/org.service'; +import {LocaleService} from '@eg/core/locale.service'; import * as Moment from 'moment-timezone'; /** @@ -31,7 +32,8 @@ export class FormatService { private datePipe: DatePipe, private currencyPipe: CurrencyPipe, private idl: IdlService, - private org: OrgService + private org: OrgService, + private locale: LocaleService ) { // Create an inilne polyfill for Number.isNaN, which is @@ -195,44 +197,81 @@ export class FormatService { * * Returns a blank string if it can't do this transformation. */ - private makeFormatParseable(original: string): string { + private makeFormatParseable(original: string, locale?: string): string { if (!original) { return ''; } + if (!locale) { locale = locale; } switch (original) { case 'short': { - return 'M/D/YY, h:mm a'; + const template = getLocaleDateTimeFormat(locale, FormatWidth.Short); + const date = getLocaleDateFormat(locale, FormatWidth.Short); + const time = getLocaleTimeFormat(locale, FormatWidth.Short); + original = template + .replace('{1}', date) + .replace('{0}', time) + .replace(/\'(\w+)\'/, '[$1]'); + break; } case 'medium': { - return 'MMM D, Y, h:mm:ss a'; + const template = getLocaleDateTimeFormat(locale, FormatWidth.Medium); + const date = getLocaleDateFormat(locale, FormatWidth.Medium); + const time = getLocaleTimeFormat(locale, FormatWidth.Medium); + original = template + .replace('{1}', date) + .replace('{0}', time) + .replace(/\'(\w+)\'/, '[$1]'); + break; } case 'long': { - return 'MMMM D, Y, h:mm:ss a [GMT]Z'; + const template = getLocaleDateTimeFormat(locale, FormatWidth.Long); + const date = getLocaleDateFormat(locale, FormatWidth.Long); + const time = getLocaleTimeFormat(locale, FormatWidth.Long); + original = template + .replace('{1}', date) + .replace('{0}', time) + .replace(/\'(\w+)\'/, '[$1]'); + break; } case 'full': { - return 'dddd, MMMM D, Y, h:mm:ss a [GMT]Z'; + const template = getLocaleDateTimeFormat(locale, FormatWidth.Full); + const date = getLocaleDateFormat(locale, FormatWidth.Full); + const time = getLocaleTimeFormat(locale, FormatWidth.Full); + original = template + .replace('{1}', date) + .replace('{0}', time) + .replace(/\'(\w+)\'/, '[$1]'); + break; } case 'shortDate': { - return 'M/D/YY'; + original = getLocaleDateFormat(locale, FormatWidth.Short); + break; } case 'mediumDate': { - return 'MMM D, Y'; + original = getLocaleDateFormat(locale, FormatWidth.Medium); + break; } case 'longDate': { - return 'MMMM D, Y'; + original = getLocaleDateFormat(locale, FormatWidth.Long); + break; } case 'fullDate': { - return 'dddd, MMMM D, Y'; + original = getLocaleDateFormat(locale, FormatWidth.Full); + break; } case 'shortTime': { - return 'h:mm a'; + original = getLocaleTimeFormat(locale, FormatWidth.Short); + break; } case 'mediumTime': { - return 'h:mm:ss a'; + original = getLocaleTimeFormat(locale, FormatWidth.Medium); + break; } case 'longTime': { - return 'h:mm:ss a [GMT]Z'; + original = getLocaleTimeFormat(locale, FormatWidth.Long); + break; } case 'fullTime': { - return 'h:mm:ss a [GMT]Z'; + original = getLocaleTimeFormat(locale, FormatWidth.Full); + break; } } return original @@ -260,4 +299,3 @@ export class FormatValuePipe implements PipeTransform { return this.formatter.transform({value: value, datatype: datatype}); } } - diff --git a/Open-ILS/src/eg2/src/app/core/format.spec.ts b/Open-ILS/src/eg2/src/app/core/format.spec.ts index 81b3201897..272ab41b3f 100644 --- a/Open-ILS/src/eg2/src/app/core/format.spec.ts +++ b/Open-ILS/src/eg2/src/app/core/format.spec.ts @@ -1,4 +1,4 @@ -import {DatePipe, CurrencyPipe} from '@angular/common'; +import {DatePipe, CurrencyPipe, registerLocaleData} from '@angular/common'; import {IdlService} from './idl.service'; import {EventService} from './event.service'; import {NetService} from './net.service'; @@ -6,8 +6,13 @@ import {AuthService} from './auth.service'; import {PcrudService} from './pcrud.service'; import {StoreService} from './store.service'; import {OrgService} from './org.service'; +import {LocaleService} from './locale.service'; +import {Location} from '@angular/common'; import {FormatService} from './format.service'; - +import {SpyLocation} from '@angular/common/testing'; +import localeArJO from '@angular/common/locales/ar-JO'; +import localeCs from '@angular/common/locales/cs'; +import localeFrCA from '@angular/common/locales/fr-CA'; describe('FormatService', () => { @@ -20,6 +25,9 @@ describe('FormatService', () => { let orgService: OrgService; let evtService: EventService; let storeService: StoreService; + let localeService: LocaleService; + // tslint:disable-next-line:prefer-const + let location: SpyLocation; let service: FormatService; beforeEach(() => { @@ -32,11 +40,13 @@ describe('FormatService', () => { authService = new AuthService(evtService, netService, storeService); pcrudService = new PcrudService(idlService, netService, authService); orgService = new OrgService(netService, authService, pcrudService); + localeService = new LocaleService(location, null, pcrudService); service = new FormatService( datePipe, currencyPipe, idlService, - orgService + orgService, + localeService ); }); @@ -98,9 +108,24 @@ describe('FormatService', () => { const momentVersion = service['makeFormatParseable']('MMMM d, y, h:mm:ss a z'); expect(momentVersion).toBe('MMMM D, Y, h:mm:ss a [GMT]Z'); }); - it('should transform full Angular format strings to a valid MomentJS one', () => { - const momentVersion = service['makeFormatParseable']('full'); - expect(momentVersion).toBe('dddd, MMMM D, Y, h:mm:ss a [GMT]Z'); + it('should transform full Angular format strings to a valid MomentJS one using Angular locale en-US', () => { + const momentVersion = service['makeFormatParseable']('full', 'en-US'); + expect(momentVersion).toBe('dddd, MMMM D, Y [at] h:mm:ss a [GMT]Z'); + }); + it('should transform shortDate Angular format strings to a valid MomentJS one using Angular locale cs-CZ', () => { + registerLocaleData(localeCs); + const momentVersion = service['makeFormatParseable']('shortDate', 'cs-CZ'); + expect(momentVersion).toBe('DD.MM.YY'); + }); + it('should transform mediumDate Angular format strings to a valid MomentJS one using Angular locale fr-CA', () => { + registerLocaleData(localeFrCA); + const momentVersion = service['makeFormatParseable']('mediumDate', 'fr-CA'); + expect(momentVersion).toBe('D MMM Y'); + }); + it('should transform long Angular format strings to a valid MomentJS one using Angular locale ar-JO', () => { + registerLocaleData(localeArJO); + const momentVersion = service['makeFormatParseable']('long', 'ar-JO'); + expect(momentVersion).toBe('D MMMM Y h:mm:ss a [GMT]Z'); }); it('can create a valid Momentjs object given a valid datetime string and correct format', () => { const moment = service['momentize']('7/3/12, 6:06 PM', 'M/D/YY, h:mm a', 'Africa/Addis_Ababa', false); diff --git a/Open-ILS/src/eg2/src/app/share/daterange-select/daterange-select.component.spec.ts b/Open-ILS/src/eg2/src/app/share/daterange-select/daterange-select.component.spec.ts index 52a0c47e1d..5a23c01da6 100644 --- a/Open-ILS/src/eg2/src/app/share/daterange-select/daterange-select.component.spec.ts +++ b/Open-ILS/src/eg2/src/app/share/daterange-select/daterange-select.component.spec.ts @@ -6,6 +6,7 @@ import {ReactiveFormsModule} from '@angular/forms'; import {NgbDate} from '@ng-bootstrap/ng-bootstrap'; @Component({ + // tslint:disable-next-line:component-selector selector: 'ngb-datepicker', template: '' }) diff --git a/Open-ILS/src/eg2/src/app/share/datetime-select/datetime-select.component.html b/Open-ILS/src/eg2/src/app/share/datetime-select/datetime-select.component.html index 7f13c12444..3b931e904c 100644 --- a/Open-ILS/src/eg2/src/app/share/datetime-select/datetime-select.component.html +++ b/Open-ILS/src/eg2/src/app/share/datetime-select/datetime-select.component.html @@ -3,7 +3,7 @@ [formGroup]="dateTimeForm" class="input-group" ngbDropdown - [autoClose]="false" + [autoClose]="'outside'" #dt="ngbDropdown"> (); const startValue = Moment.tz([], this.timezone); this.dateTimeForm = new FormGroup({ diff --git a/Open-ILS/src/eg2/src/app/share/validators/datetime_validator.directive.ts b/Open-ILS/src/eg2/src/app/share/validators/datetime_validator.directive.ts index bed582e426..15a9ae9acc 100644 --- a/Open-ILS/src/eg2/src/app/share/validators/datetime_validator.directive.ts +++ b/Open-ILS/src/eg2/src/app/share/validators/datetime_validator.directive.ts @@ -25,7 +25,7 @@ export class DatetimeValidator implements Validator { selector: '[egValidDatetime]', providers: [{ provide: NG_VALIDATORS, - useExisting: forwardRef(() => DatetimeValidator), + useExisting: DatetimeValidatorDirective, multi: true }] }) diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html index b46f85d502..b2d14c1b95 100644 --- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html +++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html @@ -238,6 +238,10 @@ + +

Grid with filtering

diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts index 8ab704a352..c6ea7c3f17 100644 --- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts @@ -23,7 +23,10 @@ import {GridComponent} from '@eg/share/grid/grid.component'; import * as Moment from 'moment-timezone'; @Component({ - templateUrl: 'sandbox.component.html' + templateUrl: 'sandbox.component.html', + styles: ['.date-time-input.ng-invalid {border: 5px purple solid;}', + '.date-time-input.ng-valid {border: 5px green solid; animation: slide 5s linear 1s infinite alternate;}', + '@keyframes slide {0% {margin-left:0px;} 50% {margin-left:200px;}'] }) export class SandboxComponent implements OnInit { -- 2.43.2