LP1850555 Angular Item (Copy) Location Select Component
authorBill Erickson <berickxx@gmail.com>
Wed, 6 Nov 2019 17:41:32 +0000 (12:41 -0500)
committerJane Sandberg <sandbej@linnbenton.edu>
Sun, 19 Jul 2020 16:21:59 +0000 (09:21 -0700)
<eg-item-location-select .../>

Adds a new item location select component which filters the list of
displayed locations based on a permission-check org or a specific
context org unit.

Values in the selector are decorated with the org unit short name in
parens after the location name to clarify the owning lib.

Sandbox example included.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
Open-ILS/src/eg2/src/app/share/combobox/combobox.component.ts
Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.module.ts [new file with mode: 0644]
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 41d2b30..3f5aab8 100644 (file)
@@ -18,6 +18,7 @@ export interface ComboboxEntry {
   // If no label is provided, the 'id' value is used.
   label?: string;
   freetext?: boolean;
+  userdata?: any; // opaque external value; ignored by this component.
 }
 
 @Component({
diff --git a/Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.html b/Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.html
new file mode 100644 (file)
index 0000000..b5ad4da
--- /dev/null
@@ -0,0 +1,13 @@
+
+<ng-template #displayTemplate let-r="result" i18n>
+  {{r.label}} ({{orgName(r.userdata.owning_lib())}})
+</ng-template>
+
+<eg-combobox #comboBox
+  [startId]="startId"
+  [displayTemplate]="displayTemplate"
+  (onChange)="cboxChanged($event)"
+  (blur)="propagateTouch()"
+  placeholder="Shelving Location..."
+  i18n-placeholder>
+</eg-combobox>
diff --git a/Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.ts b/Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.component.ts
new file mode 100644 (file)
index 0000000..082aa80
--- /dev/null
@@ -0,0 +1,118 @@
+import {Component, OnInit, 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';
+import {IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+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';
+
+/**
+ * Item (Copy) Location Selector.
+ *
+ * <eg-item-location-select [(ngModel)]="myAcplId"
+    [contextOrgId]="anOrgId" permFilter="ADMIN_STUFF">
+ * </eg-item-location-select>
+ */
+
+@Component({
+  selector: 'eg-item-location-select',
+  templateUrl: './item-location-select.component.html',
+  providers: [{
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => ItemLocationSelectComponent),
+      multi: true
+  }]
+})
+export class ItemLocationSelectComponent implements OnInit, ControlValueAccessor {
+
+    // Limit copy locations to those owned at or above org units where
+    // the user has work permissions for the provided permission code.
+    @Input() permFilter: string;
+
+    // Limit copy locations to those owned at or above this org unit.
+    @Input() contextOrgId: number;
+
+    @Input() orgUnitLabelField = 'shortname';
+
+    // Emits an acpl object or null on combobox value change
+    @Output() valueChange: EventEmitter<IdlObject>;
+
+    @ViewChild('comboBox', {static: false}) comboBox: ComboboxComponent;
+
+    startId: number = null;
+    filterOrgs: number[];
+    cache: {[id: number]: IdlObject} = {};
+
+    propagateChange = (id: number) => {};
+    propagateTouch = () => {};
+
+    constructor(
+        private org: OrgService,
+        private auth: AuthService,
+        private perm: PermService,
+        private pcrud: PcrudService
+    ) {
+        this.valueChange = new EventEmitter<IdlObject>();
+    }
+
+    ngOnInit() {
+        this.setFilterOrgs().then(_ => this.getLocations());
+    }
+
+    getLocations(): Promise<any> {
+        const entries: ComboboxEntry[] = [];
+        const search = {owning_lib: this.filterOrgs, deleted: 'f'};
+
+        return this.pcrud.search('acpl', search, {order_by: {acpl: 'name'}}
+        ).pipe(map(loc => {
+            this.cache[loc.id()] = loc;
+            entries.push({id: loc.id(), label: loc.name(), userdata: loc});
+        })).toPromise().then(_ => {
+            this.comboBox.entries = entries;
+        });
+    }
+
+    registerOnChange(fn) {
+        this.propagateChange = fn;
+    }
+
+    registerOnTouched(fn) {
+        this.propagateTouch = fn;
+    }
+
+    cboxChanged(entry: ComboboxEntry) {
+        const id = entry ? entry.id : null;
+        this.propagateChange(id);
+        this.valueChange.emit(id ? this.cache[id] : null);
+    }
+
+    writeValue(id: number) {
+        if (this.comboBox) { // May not yet be initialized
+            this.comboBox.selectedId = id;
+        } else if (id) {
+            this.startId = id;
+        }
+    }
+
+    setFilterOrgs(): Promise<number[]> {
+        if (this.permFilter) {
+            return this.perm.hasWorkPermAt([this.permFilter], true)
+                .then(values => this.filterOrgs = values[this.permFilter]);
+        }
+
+        const org = this.contextOrgId || this.auth.user().ws_ou();
+        this.filterOrgs = this.org.ancestors(this.contextOrgId, true);
+
+        return Promise.resolve(this.filterOrgs);
+    }
+
+    orgName(orgId: number): string {
+        return this.org.get(orgId)[this.orgUnitLabelField]();
+    }
+}
+
+
+
diff --git a/Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.module.ts b/Open-ILS/src/eg2/src/app/share/item-location-select/item-location-select.module.ts
new file mode 100644 (file)
index 0000000..f82989a
--- /dev/null
@@ -0,0 +1,26 @@
+import {NgModule} from '@angular/core';
+import {EgCommonModule} from '@eg/common.module';
+import {EgCoreModule} from '@eg/core/core.module';
+import {CommonWidgetsModule} from '@eg/share/common-widgets.module';
+import {ItemLocationSelectComponent} from './item-location-select.component';
+import {ReactiveFormsModule} from '@angular/forms';
+
+@NgModule({
+    declarations: [
+        ItemLocationSelectComponent
+    ],
+    imports: [
+        EgCommonModule,
+        EgCoreModule,
+        CommonWidgetsModule,
+        ReactiveFormsModule
+    ],
+    exports: [
+        ItemLocationSelectComponent
+    ],
+    providers: [
+    ]
+})
+
+export class ItemLocationSelectModule { }
+
index 3b9609b..7f98f45 100644 (file)
     <eg-grid-column name="hook"></eg-grid-column>
   </eg-grid>
 </div>
+
+<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" 
+        [(ngModel)]="locId" (valueChange)="aLocation = $event">
+      </eg-item-location-select>
+    </div>
+    <div class="col-lg-2">Selected ID: {{locId}}</div>
+    <div class="col-lg-4">
+      valueChange Handler Produced: {{aLocation ? aLocation.name() : '(none)'}}
+    </div>
+  </div>
+</div>
index c44a7f7..caf86fa 100644 (file)
@@ -109,6 +109,9 @@ export class SandboxComponent implements OnInit {
 
     myTimeForm: FormGroup;
 
+    locId = 1; // Stacks
+    aLocation: IdlObject; // acpl
+
     constructor(
         private idl: IdlService,
         private org: OrgService,
index a33deb8..15be7f3 100644 (file)
@@ -7,6 +7,7 @@ import {SandboxComponent} from './sandbox.component';
 import {ReactiveFormsModule} from '@angular/forms';
 import {SampleDataService} from '@eg/share/util/sample-data.service';
 import {OrgFamilySelectModule} from '@eg/share/org-family-select/org-family-select.module';
+import {ItemLocationSelectModule} from '@eg/share/item-location-select/item-location-select.module';
 
 @NgModule({
   declarations: [
@@ -17,6 +18,7 @@ import {OrgFamilySelectModule} from '@eg/share/org-family-select/org-family-sele
     TranslateModule,
     FmRecordEditorModule,
     OrgFamilySelectModule,
+    ItemLocationSelectModule,
     SandboxRoutingModule,
     ReactiveFormsModule
   ],