LP#1831780: various improvements to the Angular date-select widget
authorGalen Charlton <gmc@equinoxinitiative.org>
Mon, 25 Mar 2019 19:21:46 +0000 (15:21 -0400)
committerBill Erickson <berickxx@gmail.com>
Tue, 30 Jul 2019 21:12:14 +0000 (17:12 -0400)
Styling
-------
- the widget is now narrower
- the widget now enables form validation styles; in particular,
  entry of an incorrectly-formatted date is now highlighted
- the calendar drop-down is now allowed to overflow the containing
  element when expanded, making it easier to embed the date
  selector in other controls
- the calendar button (and any material icons button that's part of
  an input group) now has the same default font size as main text,
  making the overall date-select look cleaner

API
---
- add a reset() method
- hitting enter in the text box can now triggers emitting date
  change events
- a new onCleared event is emitted if the suer hits enter on an
  empty input
- onChangeAsYmd() now pads month and day to two digits apiece,
  making the result conform to ISO 8601 and thus more easily
  plugged into queries.
- adds the following methods to retrieve the current date; these
  are meant to be used via local template references in parent
  templates:

  currentAsYmd()
  currentAsIso()
  currentAsDate()

Sponsored-by: MassLNC
Sponsored-by: Georgia Public Library Service
Sponsored-by: Indiana State Library
Sponsored-by: CW MARS
Sponsored-by: King County Library System
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/date-select/date-select.component.css [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/date-select/date-select.component.html
Open-ILS/src/eg2/src/app/share/date-select/date-select.component.ts
Open-ILS/src/eg2/src/styles.css

diff --git a/Open-ILS/src/eg2/src/app/share/date-select/date-select.component.css b/Open-ILS/src/eg2/src/app/share/date-select/date-select.component.css
new file mode 100644 (file)
index 0000000..277f131
--- /dev/null
@@ -0,0 +1,3 @@
+.eg-date-select {
+    max-width: 11em;
+}
index 7e65f76..4142533 100644 (file)
@@ -1,16 +1,18 @@
 
 
-<div class="input-group">
-  <input
+<div class="input-group eg-date-select form-validated">
+  <input 
     class="form-control"
     ngbDatepicker
     #datePicker="ngbDatepicker"
     [attr.id]="domId.length ? domId : null"
     placeholder="yyyy-mm-dd"
     class="form-control"
     class="form-control"
     ngbDatepicker
     #datePicker="ngbDatepicker"
     [attr.id]="domId.length ? domId : null"
     placeholder="yyyy-mm-dd"
     class="form-control"
+    container="body"
     name="{{fieldName}}"
     [disabled]="_disabled"
     [required]="required"
     [(ngModel)]="current"
     name="{{fieldName}}"
     [disabled]="_disabled"
     [required]="required"
     [(ngModel)]="current"
+    (keyup.enter)="onDateEnter()"
     (dateSelect)="onDateSelect($event)"/>
   <div class="input-group-append">
     <button class="btn btn-outline-secondary" [disabled]="_disabled"
     (dateSelect)="onDateSelect($event)"/>
   <div class="input-group-append">
     <button class="btn btn-outline-secondary" [disabled]="_disabled"
index 6256290..077058a 100644 (file)
@@ -9,7 +9,8 @@ import {NgbDateStruct} from '@ng-bootstrap/ng-bootstrap';
 
 @Component({
   selector: 'eg-date-select',
 
 @Component({
   selector: 'eg-date-select',
-  templateUrl: './date-select.component.html'
+  templateUrl: './date-select.component.html',
+  styleUrls: ['date-select.component.css']
 })
 export class DateSelectComponent implements OnInit {
 
 })
 export class DateSelectComponent implements OnInit {
 
@@ -30,11 +31,35 @@ export class DateSelectComponent implements OnInit {
     @Output() onChangeAsDate: EventEmitter<Date>;
     @Output() onChangeAsIso: EventEmitter<string>;
     @Output() onChangeAsYmd: EventEmitter<string>;
     @Output() onChangeAsDate: EventEmitter<Date>;
     @Output() onChangeAsIso: EventEmitter<string>;
     @Output() onChangeAsYmd: EventEmitter<string>;
+    @Output() onCleared: EventEmitter<string>;
+
+    // convenience methods to access current selected date
+    currentAsYmd(): string {
+        if (this.current == null) { return null; }
+        if (!this.isValidDate(this.current)) { return null; }
+        return `${this.current.year}-${String(this.current.month).padStart(2, '0')}-${String(this.current.day).padStart(2, '0')}`;
+    }
+    currentAsIso(): string {
+        if (this.current == null) { return null; }
+        if (!this.isValidDate(this.current)) { return null; }
+        const ymd = `${this.current.year}-${String(this.current.month).padStart(2, '0')}-${String(this.current.day).padStart(2, '0')}`;
+        const date = this.localDateFromYmd(ymd);
+        const iso = date.toISOString();
+        return iso;
+    }
+    currentAsDate(): Date {
+        if (this.current == null) { return null; }
+        if (!this.isValidDate(this.current)) { return null; }
+        const ymd = `${this.current.year}-${String(this.current.month).padStart(2, '0')}-${String(this.current.day).padStart(2, '0')}`;
+        const date = this.localDateFromYmd(ymd);
+        return date;
+    }
 
     constructor() {
         this.onChangeAsDate = new EventEmitter<Date>();
         this.onChangeAsIso = new EventEmitter<string>();
         this.onChangeAsYmd = new EventEmitter<string>();
 
     constructor() {
         this.onChangeAsDate = new EventEmitter<Date>();
         this.onChangeAsIso = new EventEmitter<string>();
         this.onChangeAsYmd = new EventEmitter<string>();
+        this.onCleared = new EventEmitter<string>();
     }
 
     ngOnInit() {
     }
 
     ngOnInit() {
@@ -55,8 +80,21 @@ export class DateSelectComponent implements OnInit {
         }
     }
 
         }
     }
 
+    isValidDate(dt: NgbDateStruct): dt is NgbDateStruct {
+        return (<NgbDateStruct>dt).year !== undefined;
+    }
+
+    onDateEnter() {
+        if (this.current === null) {
+            this.onCleared.emit('cleared');
+        } else if (this.isValidDate(this.current)) {
+            this.onDateSelect(this.current);
+        }
+        // ignoring invalid input for now
+    }
+
     onDateSelect(evt) {
     onDateSelect(evt) {
-        const ymd = `${evt.year}-${evt.month}-${evt.day}`;
+        const ymd = `${evt.year}-${String(evt.month).padStart(2, '0')}-${String(evt.day).padStart(2, '0')}`;
         const date = this.localDateFromYmd(ymd);
         const iso = date.toISOString();
         this.onChangeAsDate.emit(date);
         const date = this.localDateFromYmd(ymd);
         const iso = date.toISOString();
         this.onChangeAsDate.emit(date);
@@ -71,6 +109,15 @@ export class DateSelectComponent implements OnInit {
         return new Date(
             Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
     }
         return new Date(
             Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
     }
+
+    reset() {
+        this.current = {
+            year: null,
+            month: null,
+            day: null
+        };
+    }
+
 }
 
 
 }
 
 
index 10424f2..0ffd6b5 100644 (file)
@@ -89,6 +89,10 @@ h5 {font-size: .95rem}
     line-height: inherit;
 }
 
     line-height: inherit;
 }
 
+.input-group .mat-icon-in-button {
+    font-size: .88rem !important; /* useful for buttons that cuddle up with inputs */
+}
+
 .material-icons {
   /** default is 24px which is pretty chunky */
   font-size: 22px;
 .material-icons {
   /** default is 24px which is pretty chunky */
   font-size: 22px;