LP1850555 Item location selector improvements
authorBill Erickson <berickxx@gmail.com>
Wed, 20 May 2020 20:36:23 +0000 (16:36 -0400)
committerJane Sandberg <sandbej@linnbenton.edu>
Sun, 19 Jul 2020 16:23:00 +0000 (09:23 -0700)
* Ensure that the desired location is always available in the selector
  regardless of whether the staff have permission to use the location.

* Display org unit short name for selected locations simimilar to how
  they are displayed in the selector dropdown.

* Adds an <Unset> option in cases where the new 'required' flag is set
  to false.  This explicitly makes it possible for staff to clear the
  value.

* Gracefully handle cases where locations from no org units are eligible
  for display.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.html
Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html

index b5ad4da..99bd390 100644 (file)
@@ -1,12 +1,16 @@
 
 <ng-template #displayTemplate let-r="result" i18n>
-  {{r.label}} ({{orgName(r.userdata.owning_lib())}})
+  {{r.label}} <ng-container *ngIf="r.userdata">
+    ({{orgName(r.userdata.owning_lib())}})</ng-container>
 </ng-template>
 
+<eg-string #unsetString text="<Unset>" i18n-text></eg-string>
+
 <eg-combobox #comboBox
   [startId]="startId"
   [displayTemplate]="displayTemplate"
   (onChange)="cboxChanged($event)"
+  [required]="required"
   (blur)="propagateTouch()"
   placeholder="Shelving Location..."
   i18n-placeholder>
index 082aa80..2f41a10 100644 (file)
@@ -1,4 +1,5 @@
-import {Component, OnInit, Input, Output, ViewChild, EventEmitter, forwardRef} from '@angular/core';
+import {Component, OnInit, AfterViewInit, Input, Output, ViewChild,
+    EventEmitter, forwardRef} from '@angular/core';
 import {ControlValueAccessor, FormGroup, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
 import {Observable} from 'rxjs';
 import {map} from 'rxjs/operators';
@@ -8,6 +9,7 @@ import {AuthService} from '@eg/core/auth.service';
 import {PermService} from '@eg/core/perm.service';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {StringComponent} from '@eg/share/string/string.component';
 
 /**
  * Item (Copy) Location Selector.
@@ -26,7 +28,8 @@ import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.comp
       multi: true
   }]
 })
-export class ItemLocationSelectComponent implements OnInit, ControlValueAccessor {
+export class ItemLocationSelectComponent
+    implements OnInit, AfterViewInit, ControlValueAccessor {
 
     // Limit copy locations to those owned at or above org units where
     // the user has work permissions for the provided permission code.
@@ -40,12 +43,16 @@ export class ItemLocationSelectComponent implements OnInit, ControlValueAccessor
     // Emits an acpl object or null on combobox value change
     @Output() valueChange: EventEmitter<IdlObject>;
 
+    @Input() required: boolean;
+
     @ViewChild('comboBox', {static: false}) comboBox: ComboboxComponent;
+    @ViewChild('unsetString', {static: false}) unsetString: StringComponent;
 
     startId: number = null;
-    filterOrgs: number[];
+    filterOrgs: number[] = [];
     cache: {[id: number]: IdlObject} = {};
 
+    initDone = false; // true after first data load
     propagateChange = (id: number) => {};
     propagateTouch = () => {};
 
@@ -59,12 +66,51 @@ export class ItemLocationSelectComponent implements OnInit, ControlValueAccessor
     }
 
     ngOnInit() {
-        this.setFilterOrgs().then(_ => this.getLocations());
+        this.setFilterOrgs()
+        .then(_ => this.getLocations())
+        .then(_ => this.initDone = true);
+    }
+
+    ngAfterViewInit() {
+
+        // Format the display of locations to include the org unit
+        this.comboBox.formatDisplayString = (result: ComboboxEntry) => {
+            let display = result.label || result.id;
+            display = (display + '').trim();
+            if (result.userdata) {
+                display += ' (' +
+                    this.orgName(result.userdata.owning_lib()) + ')';
+            }
+            return display;
+        };
     }
 
     getLocations(): Promise<any> {
+
+        if (this.filterOrgs.length === 0) {
+            this.comboBox.entries = [];
+            return Promise.resolve();
+        }
+
+        const search: any = {deleted: 'f'};
+
+        if (this.startId) {
+            // Guarantee we have the load-time copy location, which
+            // may not be included in the org-scoped set of locations
+            // we fetch by default.
+            search['-or'] = [
+                {id: this.startId},
+                {owning_lib: this.filterOrgs}
+            ];
+        } else {
+            search.owning_lib = this.filterOrgs;
+        }
+
         const entries: ComboboxEntry[] = [];
-        const search = {owning_lib: this.filterOrgs, deleted: 'f'};
+
+        if (!this.required) {
+            entries.push({id: null, label: this.unsetString.text});
+        }
 
         return this.pcrud.search('acpl', search, {order_by: {acpl: 'name'}}
         ).pipe(map(loc => {
@@ -90,13 +136,24 @@ export class ItemLocationSelectComponent implements OnInit, ControlValueAccessor
     }
 
     writeValue(id: number) {
-        if (this.comboBox) { // May not yet be initialized
-            this.comboBox.selectedId = id;
-        } else if (id) {
+        if (this.initDone) {
+            this.getOneLocation(id).then(_ => this.comboBox.selectedId = id);
+        } else {
             this.startId = id;
         }
     }
 
+    getOneLocation(id: number) {
+        if (!id || this.cache[id]) { return Promise.resolve(); }
+
+        return this.pcrud.retrieve('acpl', id).toPromise()
+        .then(loc => {
+            this.cache[loc.id()] = loc;
+            this.comboBox.entries.push(
+                {id: loc.id(), label: loc.name(), userdata: loc});
+        });
+    }
+
     setFilterOrgs(): Promise<number[]> {
         if (this.permFilter) {
             return this.perm.hasWorkPermAt([this.permFilter], true)
index 7f98f45..da1155e 100644 (file)
 <div class="mt-4 mb-4">
   <h4>Item (Copy) Location Selector</h4>
   <div class="row">
-    <div class="col-lg-3">
-      <eg-item-location-select permFilter="CREATE_WORKSTATION" 
+    <div class="col-lg-3 form-validated">
+      <eg-item-location-select permFilter="UPDATE_COPY"
         [(ngModel)]="locId" (valueChange)="aLocation = $event">
       </eg-item-location-select>
     </div>