LP#1850547: eg-combobox: add per-IDL-class formatting
authorGalen Charlton <gmc@equinoxinitiative.org>
Wed, 26 Feb 2020 15:39:10 +0000 (10:39 -0500)
committerBill Erickson <berickxx@gmail.com>
Thu, 3 Sep 2020 15:51:07 +0000 (11:51 -0400)
This patch teaches eg-combobox to apply display templates
and label formatters for when idlClass is set. Currently templates
are defined for acqf (to display the fund as "FUND_CODE (YEAR)" and
acpl (to display the shelving location as "LABEL (OWNING_LIBRARY)".

Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Tiffany Little <tlittle@georgialibraries.org>
Signed-off-by: Bill Erickson <berickxx@gmail.com>

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/common-widgets.module.ts

index 83df22e..4f5ae6e 100644 (file)
@@ -4,6 +4,13 @@
 {{r.label || r.id}}
 </ng-template>
 
+<ng-template #acqfTemplate egIdlClass="acqf" let-r="result">
+  {{r.fm.code()}} ({{r.fm.year()}})
+</ng-template>
+<ng-template #acplTemplate egIdlClass="acpl" let-r="result">
+  {{r.fm.name()}} ({{getOrgShortname(r.fm.owning_lib())}})
+</ng-template>
+
 <div class="d-flex">
   <input type="text" 
     class="form-control"
@@ -14,7 +21,7 @@
     [required]="isRequired"
     [(ngModel)]="selected" 
     [ngbTypeahead]="filter"
-    [resultTemplate]=" displayTemplate || defaultDisplayTemplate"
+    [resultTemplate]=" displayTemplate || idlDisplayTemplateMap[idlClass] || defaultDisplayTemplate"
     [inputFormatter]="formatDisplayString"
     (click)="onClick($event)"
     (blur)="onBlur()"
index 2153239..91a5154 100644 (file)
@@ -4,14 +4,16 @@
  * </eg-combobox>
  */
 import {Component, OnInit, Input, Output, ViewChild,
+    Directive, ViewChildren, QueryList, AfterViewInit,
     TemplateRef, 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';
 import {StoreService} from '@eg/core/store.service';
-import {IdlService} from '@eg/core/idl.service';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
 import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
 
 export interface ComboboxEntry {
   id: any;
@@ -19,6 +21,15 @@ export interface ComboboxEntry {
   label?: string;
   freetext?: boolean;
   userdata?: any; // opaque external value; ignored by this component.
+  fm?: IdlObject;
+}
+
+@Directive({
+    selector: 'ng-template[egIdlClass]'
+})
+export class IdlClassTemplateDirective {
+  @Input() egIdlClass: string;
+  constructor(public template: TemplateRef<any>) {}
 }
 
 @Component({
@@ -34,13 +45,15 @@ export interface ComboboxEntry {
     multi: true
   }]
 })
-export class ComboboxComponent implements ControlValueAccessor, OnInit {
+export class ComboboxComponent implements ControlValueAccessor, OnInit, AfterViewInit {
 
     selected: ComboboxEntry;
     click$: Subject<string>;
     entrylist: ComboboxEntry[];
 
     @ViewChild('instance', { static: true }) instance: NgbTypeahead;
+    @ViewChild('defaultDisplayTemplate', { static: true}) t: TemplateRef<any>;
+    @ViewChildren(IdlClassTemplateDirective) idlClassTemplates: QueryList<IdlClassTemplateDirective>;
 
     // Applies a name attribute to the input.
     // Useful in forms.
@@ -95,7 +108,8 @@ export class ComboboxComponent implements ControlValueAccessor, OnInit {
                 .subscribe(rec => {
                     this.entrylist = [{
                         id: id,
-                        label: rec[this.idlField]()
+                        label: this.getFmRecordLabel(rec),
+                        fm: rec
                     }];
                     this.selected = this.entrylist.filter(e => e.id === id)[0];
                 });
@@ -157,6 +171,9 @@ export class ComboboxComponent implements ControlValueAccessor, OnInit {
     // and display.  Default version trims leading/trailing spaces.
     formatDisplayString: (e: ComboboxEntry) => string;
 
+    idlDisplayTemplateMap: { [key: string]: TemplateRef<any> } = {};
+    getFmRecordLabel: (fm: IdlObject) => string;
+
     // Stub functions required by ControlValueAccessor
     propagateChange = (_: any) => {};
     propagateTouch = () => {};
@@ -166,6 +183,7 @@ export class ComboboxComponent implements ControlValueAccessor, OnInit {
       private store: StoreService,
       private idl: IdlService,
       private pcrud: PcrudService,
+      private org: OrgService,
     ) {
         this.entrylist = [];
         this.asyncIds = {};
@@ -177,6 +195,26 @@ export class ComboboxComponent implements ControlValueAccessor, OnInit {
             const display = result.label || result.id;
             return (display + '').trim();
         };
+
+        this.getFmRecordLabel = (fm: IdlObject) => {
+            // FIXME: it would be cleaner if we could somehow use
+            // the per-IDL-class ng-templates directly
+            switch (this.idlClass) {
+                case 'acqf':
+                    return fm.code() + ' (' + fm.year() + ')';
+                    break;
+                case 'acpl':
+                    return fm.name() + ' (' + this.getOrgShortname(fm.owning_lib()) + ')';
+                    break;
+                default:
+                    const field = this.idlField;
+                    if (this.idlIncludeLibraryInLabel) {
+                        return fm[field]() + ' (' + fm[this.idlIncludeLibraryInLabel]().shortname() + ')';
+                    } else {
+                        return fm[field]();
+                    }
+            }
+        };
     }
 
     ngOnInit() {
@@ -207,22 +245,38 @@ export class ComboboxComponent implements ControlValueAccessor, OnInit {
                     return this.pcrud.search(this.idlClass, args, extra_args).pipe(map(data => {
                         return {
                             id: data[pkeyField](),
-                            label: data[field]() + ' (' + data[this.idlIncludeLibraryInLabel]().shortname() + ')'
+                            label: this.getFmRecordLabel(data),
+                            fm: data
                         };
                     }));
                 } else {
                     return this.pcrud.search(this.idlClass, args, extra_args).pipe(map(data => {
-                        return {id: data[pkeyField](), label: data[field]()};
+                        return {id: data[pkeyField](), label: this.getFmRecordLabel(data), fm: data};
                     }));
                 }
             };
         }
     }
 
+    ngAfterViewInit() {
+        this.idlDisplayTemplateMap = this.idlClassTemplates.reduce((acc, cur) => {
+            acc[cur.egIdlClass] = cur.template;
+            return acc;
+        }, {});
+    }
+
     onClick($event) {
         this.click$.next($event.target.value);
     }
 
+    getOrgShortname(ou: any) {
+        if (typeof ou === 'object') {
+            return ou.shortname();
+        } else {
+            return this.org.get(ou).shortname();
+        }
+    }
+
     openMe($event) {
         // Give the input a chance to focus then fire the click
         // handler to force open the typeahead
index 5064f88..c0b0f4e 100644 (file)
@@ -8,7 +8,7 @@ import {CommonModule} from '@angular/common';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
 import {EgCoreModule} from '@eg/core/core.module';
-import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {ComboboxComponent, IdlClassTemplateDirective} from '@eg/share/combobox/combobox.component';
 import {ComboboxEntryComponent} from '@eg/share/combobox/combobox-entry.component';
 import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
 import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
@@ -27,6 +27,7 @@ import {FileReaderComponent} from '@eg/share/file-reader/file-reader.component';
     DateRangeSelectComponent,
     DateTimeSelectComponent,
     FileReaderComponent,
+    IdlClassTemplateDirective
   ],
   imports: [
     CommonModule,