1 import {Component, OnInit, AfterViewInit, Input, Output, ViewChild,
2 EventEmitter, forwardRef} from '@angular/core';
3 import {ControlValueAccessor, FormGroup, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
4 import {Observable} from 'rxjs';
5 import {map} from 'rxjs/operators';
6 import {IdlObject} from '@eg/core/idl.service';
7 import {OrgService} from '@eg/core/org.service';
8 import {AuthService} from '@eg/core/auth.service';
9 import {PermService} from '@eg/core/perm.service';
10 import {PcrudService} from '@eg/core/pcrud.service';
11 import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.component';
12 import {StringComponent} from '@eg/share/string/string.component';
15 * Item (Copy) Location Selector.
17 * <eg-item-location-select [(ngModel)]="myAcplId"
18 [contextOrgId]="anOrgId" permFilter="ADMIN_STUFF">
19 * </eg-item-location-select>
23 selector: 'eg-item-location-select',
24 templateUrl: './item-location-select.component.html',
26 provide: NG_VALUE_ACCESSOR,
27 useExisting: forwardRef(() => ItemLocationSelectComponent),
31 export class ItemLocationSelectComponent
32 implements OnInit, AfterViewInit, ControlValueAccessor {
35 // Limit copy locations to those owned at or above org units where
36 // the user has work permissions for the provided permission code.
37 @Input() permFilter: string;
39 // Limit copy locations to those owned at or above this org unit.
40 private _contextOrgId: number;
41 @Input() set contextOrgId(value: number) {
42 this._contextOrgId = value;
46 get contextOrgId(): number {
47 return this._contextOrgId;
50 // Load locations for multiple context org units.
51 private _contextOrgIds = [];
52 @Input() set contextOrgIds(value: number[]) {
53 this._contextOrgIds = value;
56 get contextOrgIds(): number[] {
57 return this._contextOrgIds;
60 @Input() orgUnitLabelField = 'shortname';
62 // Emits an acpl object or null on combobox value change
63 @Output() valueChange: EventEmitter<IdlObject>;
65 @Input() required: boolean;
67 @Input() domId = 'eg-item-location-select-' +
68 ItemLocationSelectComponent.domIdAuto++;
70 @ViewChild('comboBox', {static: false}) comboBox: ComboboxComponent;
71 @ViewChild('unsetString', {static: false}) unsetString: StringComponent;
73 startId: number = null;
74 filterOrgs: number[] = [];
75 cache: {[id: number]: IdlObject} = {};
77 initDone = false; // true after first data load
78 propagateChange = (id: number) => {};
79 propagateTouch = () => {};
82 private org: OrgService,
83 private auth: AuthService,
84 private perm: PermService,
85 private pcrud: PcrudService
87 this.valueChange = new EventEmitter<IdlObject>();
92 .then(_ => this.getLocations())
93 .then(_ => this.initDone = true);
98 // Format the display of locations to include the org unit
99 this.comboBox.formatDisplayString = (result: ComboboxEntry) => {
100 let display = result.label || result.id;
101 display = (display + '').trim();
102 if (result.userdata) {
104 this.orgName(result.userdata.owning_lib()) + ')';
110 getLocations(): Promise<any> {
112 if (this.filterOrgs.length === 0) {
113 this.comboBox.entries = [];
114 return Promise.resolve();
117 const search: any = {deleted: 'f'};
120 // Guarantee we have the load-time copy location, which
121 // may not be included in the org-scoped set of locations
122 // we fetch by default.
125 {owning_lib: this.filterOrgs}
128 search.owning_lib = this.filterOrgs;
131 const entries: ComboboxEntry[] = [];
133 if (!this.required) {
134 entries.push({id: null, label: this.unsetString.text});
137 return this.pcrud.search('acpl', search, {order_by: {acpl: 'name'}}
139 this.cache[loc.id()] = loc;
140 entries.push({id: loc.id(), label: loc.name(), userdata: loc});
141 })).toPromise().then(_ => {
142 this.comboBox.entries = entries;
146 registerOnChange(fn) {
147 this.propagateChange = fn;
150 registerOnTouched(fn) {
151 this.propagateTouch = fn;
154 cboxChanged(entry: ComboboxEntry) {
155 const id = entry ? entry.id : null;
156 this.propagateChange(id);
157 this.valueChange.emit(id ? this.cache[id] : null);
160 writeValue(id: number) {
162 this.getOneLocation(id).then(_ => this.comboBox.selectedId = id);
168 getOneLocation(id: number) {
169 if (!id || this.cache[id]) { return Promise.resolve(); }
171 return this.pcrud.retrieve('acpl', id).toPromise()
173 this.cache[loc.id()] = loc;
174 this.comboBox.addAsyncEntry(
175 {id: loc.id(), label: loc.name(), userdata: loc});
179 setFilterOrgs(): Promise<number[]> {
180 let contextOrgIds: number[] = [];
182 if (this.contextOrgIds) {
183 contextOrgIds = this.contextOrgIds;
185 contextOrgIds = [this.contextOrgId || this.auth.user().ws_ou()];
189 contextOrgIds.forEach(id => orgIds = orgIds.concat(this.org.ancestors(id, true)));
191 if (!this.permFilter) {
192 return Promise.resolve(this.filterOrgs = orgIds);
195 return this.perm.hasWorkPermAt([this.permFilter], true)
197 // Always limit the org units to /at most/ those within
198 // scope of the context org ID.
200 const permOrgIds = values[this.permFilter];
201 const trimmedOrgIds = [];
202 permOrgIds.forEach(orgId => {
203 if (orgIds.includes(orgId)) {
204 trimmedOrgIds.push(orgId);
208 return this.filterOrgs = trimmedOrgIds;
212 orgName(orgId: number): string {
213 return this.org.get(orgId)[this.orgUnitLabelField]();