LP1831390: combobox and date-select implement ControlValueAccessor user/sandbergja/lp1831390_combobox_date_select_ControlValueAccessor
authorJane Sandberg <sandbej@linnbenton.edu>
Tue, 25 Jun 2019 18:17:07 +0000 (11:17 -0700)
committerJane Sandberg <sandbej@linnbenton.edu>
Tue, 25 Jun 2019 19:56:11 +0000 (12:56 -0700)
This makes both components compatible with [(ngModel)] and
reactive forms.

Also adds sandbox examples.

Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
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 63f964e..82041be 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';
@@ -22,9 +23,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>;
@@ -110,9 +116,15 @@ export class ComboboxComponent implements OnInit {
     ngOnInit() {
     }
 
+    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(''));
     }
@@ -187,6 +199,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
@@ -250,6 +263,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 6256290..b78cebe 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:
@@ -9,9 +10,14 @@ import {NgbDateStruct} from '@ng-bootstrap/ng-bootstrap';
 
 @Component({
   selector: 'eg-date-select',
-  templateUrl: './date-select.component.html'
+  templateUrl: './date-select.component.html',
+  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)
@@ -61,6 +67,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);
     }
 
@@ -71,6 +78,20 @@ export class DateSelectComponent implements OnInit {
         return new Date(
             Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
     }
+
+    writeValue(value: string) {
+        if (value !== undefined) {
+            this.initialYmd = value;
+        }
+    }
+
+    propagateChange = (_: any) => {};
+
+    registerOnChange(fn) {
+        this.propagateChange = fn;
+    }
+
+    registerOnTouched() { }
 }
 
 
index aa21644..cc802d1 100644 (file)
 <h4>PCRUD auto flesh and FormatService detection</h4>
 <div *ngIf="aMetarecord">Fingerprint: {{aMetarecord}}</div>
 
+<div class="row">
+  <div class="card col-md-6">
+    <div class="card-body">
+      <h3 class="card-title">Do you like template-driven forms?</h3>
+      <div class="card-text">
+        <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]="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>
+</div>
index de94b5e..51501bf 100644 (file)
@@ -15,6 +15,7 @@ import {PrintService} from '@eg/share/print/print.service';
 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 import {FormatService} from '@eg/core/format.service';
 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {FormGroup, FormControl} from '@angular/forms';
 
 @Component({
   templateUrl: 'sandbox.component.html'
@@ -60,6 +61,10 @@ export class SandboxComponent implements OnInit {
 
     dynamicTitleText: string;
 
+    ranganathan: FormGroup;
+
+    dateString = '2019-09-09';
+
     complimentEvergreen: (rows: IdlObject[]) => void;
     notOneSelectedRow: (rows: IdlObject[]) => boolean;
 
@@ -86,6 +91,20 @@ export class SandboxComponent implements OnInit {
     }
 
     ngOnInit() {
+        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.ranganathan.get('law').valueChanges.subscribe(l => {
+            this.toast.success('You chose: ' + l.label);
+        });
 
         this.gridDataSource.data = [
             {name: 'Jane', state: 'AZ'},
index 58910dd..ebb886a 100644 (file)
@@ -2,6 +2,7 @@ import {NgModule} from '@angular/core';
 import {StaffCommonModule} from '@eg/staff/common.module';
 import {SandboxRoutingModule} from './routing.module';
 import {SandboxComponent} from './sandbox.component';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 
 @NgModule({
   declarations: [
@@ -10,6 +11,8 @@ import {SandboxComponent} from './sandbox.component';
   imports: [
     StaffCommonModule,
     SandboxRoutingModule,
+    FormsModule,
+    ReactiveFormsModule,
   ],
   providers: [
   ]