LP1831390: combobox and date-select implement ControlValueAccessor
authorJane Sandberg <sandbej@linnbenton.edu>
Tue, 25 Jun 2019 18:17:07 +0000 (11:17 -0700)
committerGalen Charlton <gmc@equinoxinitiative.org>
Thu, 1 Aug 2019 13:59:40 +0000 (09:59 -0400)
This makes both components compatible with [(ngModel)] and
reactive forms.

Also adds sandbox examples.

Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Open-ILS/src/eg2/src/app/share/combobox/combobox.component.html
Open-ILS/src/eg2/src/app/share/combobox/combobox.component.ts
Open-ILS/src/eg2/src/app/share/date-select/date-select.component.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.module.ts

index 0a5deee..b6099fc 100644 (file)
@@ -16,7 +16,7 @@
     [ngbTypeahead]="filter"
     [resultTemplate]="displayTemplate"
     [inputFormatter]="formatDisplayString"
-    (click)="click$.next($event.target.value)"
+    (click)="onClick($event)"
     (blur)="onBlur()"
     (selectItem)="selectorChanged($event)"
     #instance="ngbTypeahead"/>
index 9920632..b4c85bf 100644 (file)
@@ -3,7 +3,8 @@
  *  <!-- see also <eg-combobox-entry> -->
  * </eg-combobox>
  */
-import {Component, OnInit, Input, Output, ViewChild, EventEmitter, ElementRef} from '@angular/core';
+import {Component, OnInit, Input, Output, ViewChild, EventEmitter, ElementRef, forwardRef} from '@angular/core';
+import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
 import {Observable, of, Subject} from 'rxjs';
 import {map, tap, reduce, mergeMap, mapTo, debounceTime, distinctUntilChanged, merge, filter} from 'rxjs/operators';
 import {NgbTypeahead, NgbTypeaheadSelectItemEvent} from '@ng-bootstrap/ng-bootstrap';
@@ -24,9 +25,14 @@ export interface ComboboxEntry {
   styles: [`
     .icons {margin-left:-18px}
     .material-icons {font-size: 16px;font-weight:bold}
-  `]
+  `],
+  providers: [{
+    provide: NG_VALUE_ACCESSOR,
+    useExisting: forwardRef(() => ComboboxComponent),
+    multi: true
+  }]
 })
-export class ComboboxComponent implements OnInit {
+export class ComboboxComponent implements ControlValueAccessor, OnInit {
 
     selected: ComboboxEntry;
     click$: Subject<string>;
@@ -145,9 +151,15 @@ export class ComboboxComponent implements OnInit {
         }
     }
 
+    onClick($event) {
+        this.registerOnTouched();
+        this.click$.next($event.target.value)
+    }
+
     openMe($event) {
         // Give the input a chance to focus then fire the click
         // handler to force open the typeahead
+        this.registerOnTouched();
         this.elm.nativeElement.getElementsByTagName('input')[0].focus();
         setTimeout(() => this.click$.next(''));
     }
@@ -222,6 +234,7 @@ export class ComboboxComponent implements OnInit {
     // Fired by the typeahead to inform us of a change.
     selectorChanged(selEvent: NgbTypeaheadSelectItemEvent) {
         this.onChange.emit(selEvent.item);
+        this.propagateChange(selEvent.item);
     }
 
     // Adds matching async entries to the entry list
@@ -286,6 +299,22 @@ export class ComboboxComponent implements OnInit {
             })
         );
     }
+
+    writeValue(value: any) {
+        if (value !== undefined) {
+            this.startId = value;
+            this.startIdFiresOnChange = true;
+        }
+    }
+
+    propagateChange = (_: any) => {};
+
+    registerOnChange(fn) {
+        this.propagateChange = fn;
+    }
+
+    registerOnTouched() { }
+
 }
 
 
index d877300..079c4fe 100644 (file)
@@ -1,5 +1,6 @@
-import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core';
+import {Component, OnInit, Input, Output, ViewChild, EventEmitter, forwardRef} from '@angular/core';
 import {NgbDateStruct} from '@ng-bootstrap/ng-bootstrap';
+import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
 
 /**
  * RE: displaying locale dates in the input field:
@@ -10,9 +11,14 @@ import {NgbDateStruct} from '@ng-bootstrap/ng-bootstrap';
 @Component({
   selector: 'eg-date-select',
   templateUrl: './date-select.component.html',
-  styleUrls: ['date-select.component.css']
+  styleUrls: ['date-select.component.css'],
+  providers: [ {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => DateSelectComponent),
+      multi: true
+  } ]
 })
-export class DateSelectComponent implements OnInit {
+export class DateSelectComponent implements OnInit, ControlValueAccessor {
 
     @Input() initialIso: string; // ISO string
     @Input() initialYmd: string; // YYYY-MM-DD (uses local time zone)
@@ -96,6 +102,7 @@ export class DateSelectComponent implements OnInit {
         const iso = date.toISOString();
         this.onChangeAsDate.emit(date);
         this.onChangeAsYmd.emit(ymd);
+        this.propagateChange(ymd);
         this.onChangeAsIso.emit(iso);
     }
 
@@ -115,6 +122,19 @@ export class DateSelectComponent implements OnInit {
         };
     }
 
+    writeValue(value: string) {
+        if (value !== undefined) {
+            this.initialYmd = value;
+        }
+    }
+
+    propagateChange = (_: any) => {};
+
+    registerOnChange(fn) {
+        this.propagateChange = fn;
+    }
+
+    registerOnTouched() { }
 }
 
 
index 37e46d9..fd8a755 100644 (file)
           ngModel #bestOnes="ngModel">
         </eg-org-family-select>
         The best libraries are: {{bestOnes.value | json}}
+        <hr>
+        <eg-combobox ngModel #templateEntry="ngModel">
+          <eg-combobox-entry entryId="Bacteria"></eg-combobox-entry>
+          <eg-combobox-entry entryId="Archaea"></eg-combobox-entry>
+          <eg-combobox-entry entryId="Protozoa"></eg-combobox-entry>
+          <eg-combobox-entry entryId="Chromista"></eg-combobox-entry>
+          <eg-combobox-entry entryId="Plantae"></eg-combobox-entry>
+          <eg-combobox-entry entryId="Fungi"></eg-combobox-entry>
+          <eg-combobox-entry entryId="Animalia"></eg-combobox-entry>
+        </eg-combobox>
+      Result: {{templateEntry.value | json}}
+        <hr>
+        <eg-date-select [(ngModel)]="dateString">
+        </eg-date-select>
+      ngModel: {{dateString}}
       </div>
     </div>
   </div>
-  <form class="card col-md-6" [formGroup]="badOrgForm">
+  <form class="card col-md-4" [formGroup]="ranganathan">
     <div class="card-body">
       <h3 class="card-title">Or perhaps reactive forms interest you?</h3>
       <div class="card-text">
+        Choose your favorite law of library science: 
+        <eg-combobox formControlName="law" value="second">
+          <eg-combobox-entry entryId="first" entryLabel="Books are for use" i18n-entryLabel></eg-combobox-entry>
+          <eg-combobox-entry entryId="second" entryLabel="Every person his or her book" i18n-entryLabel></eg-combobox-entry>
+          <eg-combobox-entry entryId="third" entryLabel="Every book its reader" i18n-entryLabel></eg-combobox-entry>
+          <eg-combobox-entry entryId="fourth" entryLabel="Save the time of the reader" i18n-entryLabel></eg-combobox-entry>
+          <eg-combobox-entry entryId="fifth" entryLabel="Library is a growing organism" i18n-entryLabel></eg-combobox-entry>
+          <eg-combobox-entry entryId="wrong" entryLabel="42" i18n-entryLabel></eg-combobox-entry>
+        </eg-combobox>
+        <div *ngIf="!ranganathan.valid" class="alert alert-danger">
+          <span class="material-icons">error</span>
+          <span i18n>That isn't a real law of library science!</span>
+        </div>
+      </div>
+    </div>
+  </form>
+  <form class="card col-md-4" [formGroup]="badOrgForm">
+    <div class="card-body">
+      <h3 class="card-title">Another reactive form!</h3>
+      <div class="card-text">
         <eg-org-family-select
           formControlName="badOrgSelector"
           labelText="Choose the fanciest libraries">
index a816caf..373e86f 100644 (file)
@@ -81,6 +81,10 @@ export class SandboxComponent implements OnInit {
 
     badOrgForm: FormGroup;
 
+    ranganathan: FormGroup;
+
+    dateString = '2019-09-09';
+
     complimentEvergreen: (rows: IdlObject[]) => void;
     notOneSelectedRow: (rows: IdlObject[]) => boolean;
 
@@ -119,10 +123,25 @@ export class SandboxComponent implements OnInit {
             } )
         });
 
+        this.ranganathan = new FormGroup({
+            'law': new FormControl('second', (c: FormControl) => {
+                // An Angular custom validator
+                if ("wrong" === c.value.id) {
+                    return { notALaw: 'That\'s not a real law of library science!' };
+                    } else {
+                        return null;
+                    }
+            } )
+        });
+
         this.badOrgForm.get('badOrgSelector').valueChanges.subscribe(bad => {
             this.toast.danger('The fanciest libraries are: ' + JSON.stringify(bad.orgIds));
         });
 
+        this.ranganathan.get('law').valueChanges.subscribe(l => {
+            this.toast.success('You chose: ' + l.label);
+        });
+
         this.gridDataSource.data = [
             {name: 'Jane', state: 'AZ'},
             {name: 'Al', state: 'CA'},
index ec817d0..ebb886a 100644 (file)
@@ -12,7 +12,7 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms';
     StaffCommonModule,
     SandboxRoutingModule,
     FormsModule,
-    ReactiveFormsModule
+    ReactiveFormsModule,
   ],
   providers: [
   ]