]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/share/patron/profile-select.component.ts
LP2042879 Shelving Location Groups Admin accessibility
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / share / patron / profile-select.component.ts
1 import {Component, Input, Output, OnInit,
2     EventEmitter, ViewChild, forwardRef} from '@angular/core';
3 import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
4 import {Observable, of} from 'rxjs';
5 import {map} from 'rxjs/operators';
6 import {IdlService, IdlObject} from '@eg/core/idl.service';
7 import {OrgService} from '@eg/core/org.service';
8 import {AuthService} from '@eg/core/auth.service';
9 import {PcrudService} from '@eg/core/pcrud.service';
10 import {ComboboxEntry, ComboboxComponent
11     } from '@eg/share/combobox/combobox.component';
12
13 /* User permission group select comoboxbox.
14  *
15  * <eg-profile-select
16  *  [(ngModel)]="pgtObject" [useDisplayEntries]="true">
17  * </eg-profile-select>
18  */
19
20 // Use a unicode char for spacing instead of ASCII=32 so the browser
21 // won't collapse the nested display entries down to a single space.
22 const PAD_SPACE = ' '; // U+2007
23
24 @Component({
25   selector: 'eg-profile-select',
26   templateUrl: './profile-select.component.html',
27   providers: [{
28     provide: NG_VALUE_ACCESSOR,
29     useExisting: forwardRef(() => ProfileSelectComponent),
30     multi: true
31   }]
32 })
33 export class ProfileSelectComponent implements ControlValueAccessor, OnInit {
34
35     // If true, attempt to build the selector from
36     // permission.grp_tree_display_entry's for the current org unit.
37     // If false OR if no permission.grp_tree_display_entry's exist
38     // build the selector from the full permission.grp_tree
39     @Input() useDisplayEntries: boolean;
40
41     // Emits the selected 'pgt' object or null if the selector is cleared.
42     @Output() profileChange: EventEmitter<IdlObject>;
43
44     @ViewChild('combobox', {static: false}) cbox: ComboboxComponent;
45
46     initialValue: number;
47     cboxEntries: ComboboxEntry[] = [];
48     profiles: {[id: number]: IdlObject} = {};
49
50     // Stub functions required by ControlValueAccessor
51     propagateChange = (_: any) => {};
52     propagateTouch = () => {};
53
54     constructor(
55         private org: OrgService,
56         private auth: AuthService,
57         private pcrud: PcrudService) {
58         this.profileChange = new EventEmitter<IdlObject>();
59     }
60
61     ngOnInit() {
62         this.collectGroups().then(grps => this.sortGroups(grps));
63     }
64
65     collectGroups(): Promise<IdlObject[]> {
66
67         if (!this.useDisplayEntries) {
68             return this.fetchPgt();
69         }
70
71         return this.pcrud.search('pgtde',
72             {org: this.org.ancestors(this.auth.user().ws_ou(), true)},
73             {flesh: 1, flesh_fields: {'pgtde': ['grp']}},
74             {atomic: true}
75
76         ).toPromise().then(groups => {
77
78             if (groups.length === 0) { return this.fetchPgt(); }
79
80             // In the query above, we fetch display entries for our org
81             // unit plus ancestors.  However, we only want to use one
82             // collection of display entries, those owned at our org
83             // unit or our closest ancestor.
84             let closestOrg = this.org.get(groups[0].org());
85             groups.forEach(g => {
86                 const org = this.org.get(g.org());
87                 if (closestOrg.ou_type().depth() < org.ou_type().depth()) {
88                     closestOrg = org;
89                 }
90             });
91             groups = groups.filter(g => g.org() === closestOrg.id());
92
93             // Link the display entry to its pgt.
94             const pgtList = [];
95             groups.forEach(display => {
96                 const pgt = display.grp();
97                 pgt._display = display;
98                 pgtList.push(pgt);
99             });
100
101             return pgtList;
102         });
103     }
104
105     fetchPgt(): Promise<IdlObject[]> {
106         return this.pcrud.retrieveAll('pgt', {}, {atomic: true}).toPromise();
107     }
108
109     grpLabel(groups: IdlObject[], grp: IdlObject): string {
110         let tmp = grp;
111         let depth = 0;
112
113         do {
114             const pid = tmp._display ? tmp._display.parent() : tmp.parent();
115             if (!pid) { break; } // top of the tree
116
117             // Should always produce a value unless a perm group
118             // display tree is poorly structured.
119             tmp = groups.filter(g => g.id() === pid)[0];
120
121             depth++;
122
123         } while (tmp);
124
125         return PAD_SPACE.repeat(depth) + grp.name();
126     }
127
128     sortGroups(groups: IdlObject[], grp?: IdlObject) {
129         if (!grp) {
130             grp = groups.filter(g => g.parent() === null)[0];
131         }
132
133         this.profiles[grp.id()] = grp;
134         this.cboxEntries.push(
135             {id: grp.id(), label: this.grpLabel(groups, grp)});
136
137         groups
138             .filter(g => g.parent() === grp.id())
139             .sort((a, b) => {
140                 if (a._display) {
141                     return a._display.position() < b._display.position() ? -1 : 1;
142                 } else {
143                     return a.name() < b.name() ? -1 : 1;
144                 }
145             })
146             .forEach(child => this.sortGroups(groups, child));
147     }
148
149     writeValue(pgt: IdlObject) {
150         const id = pgt ? pgt.id() : null;
151         if (this.cbox) {
152             this.cbox.selectedId = id;
153         } else {
154             // Will propagate to cbox after its instantiated.
155             this.initialValue = id;
156         }
157     }
158
159     registerOnChange(fn) {
160         this.propagateChange = fn;
161     }
162
163     registerOnTouched(fn) {
164         this.propagateTouch = fn;
165     }
166
167     propagateCboxChange(entry: ComboboxEntry) {
168         if (entry) {
169             const grp = this.profiles[entry.id];
170             this.propagateChange(grp);
171             this.profileChange.emit(grp);
172         } else {
173             this.profileChange.emit(null);
174             this.propagateChange(null);
175         }
176     }
177 }
178