991206853d176807c6b39a961410603c4ba62e3d
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / cat / vandelay / match-set-expression.component.ts
1 import {Component, OnInit, ViewChild, AfterViewInit, Input} from '@angular/core';
2 import {IdlObject, IdlService} from '@eg/core/idl.service';
3 import {PcrudService} from '@eg/core/pcrud.service';
4 import {NetService} from '@eg/core/net.service';
5 import {AuthService} from '@eg/core/auth.service';
6 import {OrgService} from '@eg/core/org.service';
7 import {Tree, TreeNode} from '@eg/share/tree/tree';
8 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
9 import {StringService} from '@eg/share/string/string.service';
10 import {MatchSetNewPointComponent} from './match-set-new-point.component';
11
12 @Component({
13   selector: 'eg-match-set-expression',
14   templateUrl: 'match-set-expression.component.html'
15 })
16 export class MatchSetExpressionComponent implements OnInit {
17
18     // Match set arrives from parent async.
19     matchSet_: IdlObject;
20     @Input() set matchSet(ms: IdlObject) {
21         this.matchSet_ = ms;
22         if (ms && !this.initDone) {
23             this.matchSetType = ms.mtype();
24             this.initDone = true;
25             this.refreshTree();
26         }
27     }
28
29     tree: Tree;
30     initDone: boolean;
31     matchSetType: string;
32     changesMade: boolean;
33
34     // Current type of new match point
35     newPointType: string;
36     newId: number;
37
38     @ViewChild('newPoint') newPoint: MatchSetNewPointComponent;
39
40     constructor(
41         private idl: IdlService,
42         private pcrud: PcrudService,
43         private net: NetService,
44         private auth: AuthService,
45         private org: OrgService,
46         private strings: StringService
47     ) {
48         this.newId = -1;
49     }
50
51     ngOnInit() {}
52
53     refreshTree(): Promise<any> {
54         if (!this.matchSet_) { return Promise.resolve(); }
55
56         return this.pcrud.search('vmsp',
57             {match_set: this.matchSet_.id()}, {}, 
58             {atomic: true, authoritative: true}
59         ).toPromise().then(points => this.ingestMatchPoints(points));
60     }
61
62     ingestMatchPoints(points: IdlObject[]) {
63         const nodes = [];
64         const idmap: any = {};
65
66         // massage data, create tree nodes
67         points.forEach(point => {
68
69             point.negate(point.negate() === 't' ? true : false);
70             point.heading(point.heading() === 't' ? true : false);
71             point.children([]);
72
73             const node = new TreeNode({
74                 id: point.id(),
75                 expanded: true,
76                 callerData: {point: point}
77             });
78             idmap[node.id + ''] = node;
79             this.setNodeLabel(node, point).then(() => nodes.push(node));
80         });
81
82         // apply the tree parent/child relationships
83         points.forEach(point => {
84             const node = idmap[point.id() + ''];
85             if (point.parent()) {
86                 idmap[point.parent() + ''].children.push(node);
87             } else {
88                 this.tree = new Tree(node);
89             }
90         });
91     }
92
93     setNodeLabel(node: TreeNode, point: IdlObject): Promise<any> {
94         if (node.label) { return Promise.resolve(null); }
95         return Promise.all([
96             this.getPointLabel(point, true).then(txt => node.label = txt),
97             this.getPointLabel(point, false).then(
98                 txt => node.callerData.slimLabel = txt)
99         ]);
100     }
101
102     getPointLabel(point: IdlObject, showmatch?: boolean): Promise<string> {
103         return this.strings.interpolate(
104             'staff.cat.vandelay.matchpoint.label', 
105             {point: point, showmatch: showmatch}
106         );
107     }
108
109     nodeClicked(node: TreeNode) {}
110
111     deleteNode() {
112         this.changesMade = true;
113         const node = this.tree.selectedNode()
114         this.tree.removeNode(node);
115     }
116
117     hasSelectedNode(): boolean {
118         return Boolean(this.tree.selectedNode());
119     }
120
121     selectedIsBool(): boolean {
122         if (this.tree) {
123             const node = this.tree.selectedNode();
124             return node && node.callerData.point.bool_op();
125         }
126         return false;
127     }
128
129     addChildNode() {
130         this.changesMade = true;
131
132         const pnode = this.tree.selectedNode();
133         const point = this.idl.create('vmsp');
134         point.id(this.newId--);
135         point.isnew(true);
136         point.parent(pnode.id);
137         point.match_set(this.matchSet_.id());
138         point.children([]);
139
140         const ptype = this.newPoint.values.pointType;
141
142         if (ptype === 'bool') {
143             point.bool_op(this.newPoint.values.boolOp);
144
145         } else {
146
147             if (ptype == 'attr') {
148                 point.svf(this.newPoint.values.recordAttr);
149
150             } else if (ptype == 'marc') {
151                 point.tag(this.newPoint.values.marcTag);
152                 point.subfield(this.newPoint.values.marcSf);
153             } else if (ptype == 'heading') {
154                 point.heading(true);
155             }
156
157             point.negate(this.newPoint.values.negate);
158             point.quality(this.newPoint.values.matchScore);
159         }
160
161         const node: TreeNode = new TreeNode({
162             id: point.id(), 
163             callerData: {point: point}
164         });
165
166         // Match points are added to the DB only when the tree is saved.
167         this.setNodeLabel(node, point).then(() => pnode.children.push(node));
168     }
169
170     expressionAsString(): string {
171         if (!this.tree) { return ''; }
172
173         const renderNode = (node: TreeNode): string => {
174             if (!node) { return ''; }
175
176             if (node.children.length) {
177                 return '(' + node.children.map(renderNode).join(
178                     ' ' + node.callerData.slimLabel + ' ') + ')'
179             } else if (!node.callerData.point.bool_op()) {
180                 return node.callerData.slimLabel;
181             } else {
182                 return '()';
183             }
184         }
185
186         return renderNode(this.tree.rootNode);
187     }
188
189     // Server API deletes and recreates the tree on update.
190     // It manages parent/child relationships via the children array.
191     // We only need send the current tree in a form the API recognizes.
192     saveTree(): Promise<any> {
193
194
195         const compileTree = (node?: TreeNode) => {
196
197             if (!node) { node = this.tree.rootNode; }
198
199             const point = node.callerData.point;
200
201             node.children.forEach(child =>
202                 point.children().push(compileTree(child)));
203
204             return point;
205         };
206
207         const rootPoint: IdlObject = compileTree();
208
209         return this.net.request(
210             'open-ils.vandelay',
211             'open-ils.vandelay.match_set.update',
212             this.auth.token(), this.matchSet_.id(), rootPoint
213         ).toPromise().then(
214             ok =>this.refreshTree(),
215             err => console.error(err)
216         );
217     }
218 }
219