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';
13 /* User permission group select comoboxbox.
16 * [(ngModel)]="pgtObject" [useDisplayEntries]="true">
17 * </eg-profile-select>
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
25 selector: 'eg-profile-select',
26 templateUrl: './profile-select.component.html',
28 provide: NG_VALUE_ACCESSOR,
29 useExisting: forwardRef(() => ProfileSelectComponent),
33 export class ProfileSelectComponent implements ControlValueAccessor, OnInit {
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;
41 // Emits the selected 'pgt' object or null if the selector is cleared.
42 @Output() profileChange: EventEmitter<IdlObject>;
44 @ViewChild('combobox', {static: false}) cbox: ComboboxComponent;
47 cboxEntries: ComboboxEntry[] = [];
48 profiles: {[id: number]: IdlObject} = {};
50 // Stub functions required by ControlValueAccessor
51 propagateChange = (_: any) => {};
52 propagateTouch = () => {};
55 private org: OrgService,
56 private auth: AuthService,
57 private pcrud: PcrudService) {
58 this.profileChange = new EventEmitter<IdlObject>();
62 this.collectGroups().then(grps => this.sortGroups(grps));
65 collectGroups(): Promise<IdlObject[]> {
67 if (!this.useDisplayEntries) {
68 return this.fetchPgt();
71 return this.pcrud.search('pgtde',
72 {org: this.org.ancestors(this.auth.user().ws_ou(), true)},
73 {flesh: 1, flesh_fields: {'pgtde': ['grp']}},
76 ).toPromise().then(groups => {
78 if (groups.length === 0) { return this.fetchPgt(); }
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());
86 const org = this.org.get(g.org());
87 if (closestOrg.ou_type().depth() < org.ou_type().depth()) {
91 groups = groups.filter(g => g.org() === closestOrg.id());
93 // Link the display entry to its pgt.
95 groups.forEach(display => {
96 const pgt = display.grp();
97 pgt._display = display;
105 fetchPgt(): Promise<IdlObject[]> {
106 return this.pcrud.retrieveAll('pgt', {}, {atomic: true}).toPromise();
109 grpLabel(groups: IdlObject[], grp: IdlObject): string {
114 const pid = tmp._display ? tmp._display.parent() : tmp.parent();
115 if (!pid) { break; } // top of the tree
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];
125 return PAD_SPACE.repeat(depth) + grp.name();
128 sortGroups(groups: IdlObject[], grp?: IdlObject) {
130 grp = groups.filter(g => g.parent() === null)[0];
133 this.profiles[grp.id()] = grp;
134 this.cboxEntries.push(
135 {id: grp.id(), label: this.grpLabel(groups, grp)});
138 .filter(g => g.parent() === grp.id())
141 return a._display.position() < b._display.position() ? -1 : 1;
143 return a.name() < b.name() ? -1 : 1;
146 .forEach(child => this.sortGroups(groups, child));
149 writeValue(pgt: IdlObject) {
150 const id = pgt ? pgt.id() : null;
152 this.cbox.selectedId = id;
154 // Will propagate to cbox after its instantiated.
155 this.initialValue = id;
159 registerOnChange(fn) {
160 this.propagateChange = fn;
163 registerOnTouched(fn) {
164 this.propagateTouch = fn;
167 propagateCboxChange(entry: ComboboxEntry) {
169 const grp = this.profiles[entry.id];
170 this.propagateChange(grp);
171 this.profileChange.emit(grp);
173 this.profileChange.emit(null);
174 this.propagateChange(null);