LP1834662: Various fixes to the datetime select
authorJane Sandberg <sandbej@linnbenton.edu>
Sun, 7 Jul 2019 20:40:27 +0000 (13:40 -0700)
committerBill Erickson <berickxx@gmail.com>
Mon, 26 Aug 2019 15:47:45 +0000 (11:47 -0400)
* 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 <sandbej@linnbenton.edu>
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/package.json
Open-ILS/src/eg2/src/app/core/format.service.ts
Open-ILS/src/eg2/src/app/core/format.spec.ts
Open-ILS/src/eg2/src/app/share/daterange-select/daterange-select.component.spec.ts
Open-ILS/src/eg2/src/app/share/datetime-select/datetime-select.component.html
Open-ILS/src/eg2/src/app/share/datetime-select/datetime-select.component.ts
Open-ILS/src/eg2/src/app/share/validators/datetime_validator.directive.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts

index 1bafd4d..4b6b8bc 100644 (file)
@@ -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"
index 20f0fdf..b6fba2d 100644 (file)
@@ -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});
     }
 }
-
index 81b3201..272ab41 100644 (file)
@@ -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);
index 52a0c47..5a23c01 100644 (file)
@@ -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: ''
 })
index 7f13c12..3b931e9 100644 (file)
@@ -3,7 +3,7 @@
   [formGroup]="dateTimeForm"
   class="input-group"
   ngbDropdown
-  [autoClose]="false"
+  [autoClose]="'outside'"
   #dt="ngbDropdown">
   <input type="datetime"
     [attr.id]="domId.length ? domId : null" 
index e187413..6f7cf8b 100644 (file)
@@ -34,7 +34,7 @@ export class DateTimeSelectComponent implements OnInit, ControlValueAccessor {
         @Self()
         public controlDir: NgControl, // so that the template can access validation state
     ) {
-        controlDir.valueAccessor = this;
+        if (controlDir) { controlDir.valueAccessor = this; }
         this.onChangeAsIso = new EventEmitter<string>();
         const startValue = Moment.tz([], this.timezone);
         this.dateTimeForm = new FormGroup({
index bed582e..15a9ae9 100644 (file)
@@ -25,7 +25,7 @@ export class DatetimeValidator implements Validator {
     selector: '[egValidDatetime]',
     providers: [{
         provide: NG_VALIDATORS,
-        useExisting: forwardRef(() => DatetimeValidator),
+        useExisting: DatetimeValidatorDirective,
         multi: true
     }]
 })
index b46f85d..b2d14c1 100644 (file)
     </form>
   </div>
 </div>
+<label for="date-time-input">
+  Set the datetime and timezone library settings, and enter a valid datetime string for an exciting animation surprise:
+</label>
+<input id="date-time-input" type="text" class="date-time-input" ngModel egValidDatetime required>
 <br/><br/>
 
 <h4>Grid with filtering</h4>
index 8ab704a..c6ea7c3 100644 (file)
@@ -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 {