]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/conify/global/vandelay/match_set.js
Merge remote branch 'working/user/shadowspar/ttopac-altcleanup' into template-toolkit...
[Evergreen.git] / Open-ILS / web / js / ui / default / conify / global / vandelay / match_set.js
1 dojo.require("dijit.Tree");
2 dojo.require("dijit.form.Button");
3 dojo.require("dojo.data.ItemFileWriteStore");
4 dojo.require("dojo.dnd.Source");
5 dojo.require("openils.vandelay.TreeDndSource");
6 dojo.require("openils.vandelay.TreeStoreModel");
7 dojo.require("openils.CGI");
8 dojo.require("openils.User");
9 dojo.require("openils.Util");
10 dojo.require("openils.PermaCrud");
11 dojo.require("openils.widget.ProgressDialog");
12 dojo.require("openils.widget.AutoGrid");
13
14 var localeStrings, node_editor, qnode_editor, _crads, CGI, tree, match_set;
15
16 var NodeEditorAbstract = {
17     "_svf_select_template": null,
18     "_simple_value_getter": function(control) {
19         if (typeof control.selectedIndex != "undefined")
20             return control.options[control.selectedIndex].value;
21         else if (dojo.attr(control, "type") == "checkbox")
22             return control.checked;
23         else
24             return control.value;
25     },
26     "is_sensible": function(thing) {
27         var need_one = 0;
28         this.foi.forEach(function(field) { if (thing[field]()) need_one++; });
29
30         if (need_one != 1) {
31             alert(localeStrings.POINT_NEEDS_ONE);
32             return false;
33         }
34
35         if (thing.tag()) {
36             if (
37                 !thing.tag().match(/^\d{3}$/) ||
38                 thing.subfield().length != 1 ||
39                 !thing.subfield().match(/\S/) ||
40                 thing.subfield().charCodeAt(0) < 32
41             ) {
42                 alert(localeStrings.FAULTY_MARC);
43                 return false;
44             }
45         }
46
47         return true;
48     },
49     "_add_consistent_controls": function(tgt) {
50         if (!this._consistent_controls) {
51             var trs = dojo.query(this._consistent_controls_query);
52             this._consistent_controls = [];
53             for (var i = 0; i < trs.length; i++)
54                 this._consistent_controls[i] = dojo.clone(trs[i]);
55         }
56
57         this._consistent_controls.forEach(
58             function(node) { dojo.place(dojo.clone(node), tgt); }
59         );
60     },
61     "_factories_by_type": {
62         "svf": function() {
63             if (!self._svf_select_template) {
64                 self._svf_select_template = dojo.create(
65                     "select", {"fmfield": "svf"}
66                 );
67                 for (var i=0; i<_crads.length; i++) {
68                     dojo.create(
69                         "option", {
70                             "value": _crads[i].name(),
71                             "innerHTML": _crads[i].label()
72                         }, self._svf_select_template
73                     );
74                 }
75             }
76
77             var select = dojo.clone(self._svf_select_template);
78             dojo.attr(select, "id", "svf-select");
79             var label = dojo.create(
80                 "label", {
81                     "for": "svf-select", "innerHTML": localeStrings.SVF + ":"
82                 }
83             );
84
85             var tr = dojo.create("tr");
86             dojo.place(label, dojo.create("td", null, tr));
87             dojo.place(select, dojo.create("td", null, tr));
88
89             return [tr];
90         },
91         "tag": function() {
92             var rows = [dojo.create("tr"), dojo.create("tr")];
93             dojo.create(
94                 "label", {
95                     "for": "tag-input", "innerHTML": "Tag:"
96                 }, dojo.create("td", null, rows[0])
97             );
98             dojo.create(
99                 "input", {
100                     "id": "tag-input",
101                     "type": "text",
102                     "size": 4,
103                     "maxlength": 3,
104                     "fmfield": "tag"
105                 }, dojo.create("td", null, rows[0])
106             );
107             dojo.create(
108                 "label", {
109                     "for": "subfield-input", "innerHTML": "Subfield: \u2021"
110                 }, dojo.create("td", null, rows[1])
111             );
112             dojo.create(
113                 "input", {
114                     "id": "subfield-input",
115                     "type": "text",
116                     "size": 2,
117                     "maxlength": 1,
118                     "fmfield": "subfield"
119                 }, dojo.create("td", null, rows[1])
120             );
121             return rows;
122         },
123         "bool_op": function() {
124             var tr = dojo.create("tr");
125             dojo.create(
126                 "label",
127                 {"for": "operator-select", "innerHTML": "Operator:"},
128                 dojo.create("td", null, tr)
129             );
130             var select = dojo.create(
131                 "select", {"fmfield": "bool_op", "id": "operator-select"},
132                 dojo.create("td", null, tr)
133             );
134             dojo.create("option", {"value": "AND", "innerHTML": "AND"}, select);
135             dojo.create("option", {"value": "OR", "innerHTML": "OR"}, select);
136
137             return [tr];
138         }
139     }
140 };
141
142 function apply_base_class(cls, basecls) {
143     openils.Util.objectProperties(basecls).forEach(
144         function(m) { cls[m] = basecls[m]; }
145     );
146 }
147
148 function QualityNodeEditor() {
149     var self = this;
150     this.foi = ["tag", "svf"]; /* Fields of Interest - starting points for UI */
151
152     this._init = function(qnode_editor_container) {
153         this._consistent_controls_query =
154             "[consistent-controls], [quality-controls]";
155         this.qnode_editor_container = dojo.byId(qnode_editor_container);
156         this.clear();
157     };
158
159     this.clear = function() {
160         dojo.create(
161             "em", {"innerHTML": localeStrings.WORKING_QM_HERE},
162             this.qnode_editor_container, "only"
163         );
164     };
165
166     this.build_vmsq = function() {
167         var metric = new vmsq();
168         metric.match_set(match_set.id());   /* using global */
169         var controls = dojo.query("[fmfield]", this.qnode_editor_container);
170         for (var i = 0; i < controls.length; i++) {
171             var field = dojo.attr(controls[i], "fmfield");
172             var value = this._simple_value_getter(controls[i]);
173             metric[field](value);
174         }
175
176         if (!this.is_sensible(metric)) return null;    /* will alert() */
177         else return metric;
178     };
179
180     this.add = function(type) {
181         this.clear();
182
183         /* these are the editing widgets */
184         var table = dojo.create("table", {"className": "node-editor"});
185
186         var nodes = this._factories_by_type[type]();
187         for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
188
189         this._add_consistent_controls(table);
190
191         var ok_cxl_td = dojo.create(
192             "td", {"colspan": 2, "align": "center", "className": "space-me"},
193             dojo.create("tr", null, table)
194         );
195
196         dojo.create(
197             "input", {
198                 "type": "submit", "value": localeStrings.OK,
199                 "onclick": function() {
200                     var metric = self.build_vmsq();
201                     if (metric) {
202                         self.clear();
203                         pcrud.create(
204                             metric, {
205                                 /* borrowed from openils.widget.AutoGrid */
206                                 "oncomplete": function(req, cudResults) {
207                                     var fmObject = cudResults[0];
208                                     if (vmsq_grid.onPostCreate)
209                                         vmsq_grid.onPostCreate(fmObject);
210                                     if (fmObject) {
211                                         vmsq_grid.store.newItem(
212                                             fmObject.toStoreItem()
213                                         );
214                                     }
215                                     setTimeout(function() {
216                                         try {
217                                             vmsq_grid.selection.select(vmsq_grid.rowCount-1);
218                                             vmsq_grid.views.views[0].getCellNode(vmsq_grid.rowCount-1, 1).focus();
219                                         } catch (E) {}
220                                     },200);
221                                 }
222                             }
223                         );
224                     }
225                 }
226             }, ok_cxl_td
227         );
228         dojo.create(
229             "input", {
230                 "type": "reset", "value": localeStrings.CANCEL,
231                 "onclick": function() { self.clear(); }
232             }, ok_cxl_td
233         );
234
235         dojo.place(table, this.qnode_editor_container, "only");
236
237         /* nice */
238         try { dojo.query("select, input", table)[0].focus(); }
239         catch(E) { console.log(String(E)); }
240
241     };
242
243     apply_base_class(self, NodeEditorAbstract);
244     this._init.apply(this, arguments);
245 }
246
247 function NodeEditor() {
248     var self = this;
249     this.foi = ["tag", "svf", "bool_op"]; /* Fields of Interest - starting points for UI */
250
251     this._init = function(dnd_source, node_editor_container) {
252         this._consistent_controls_query =
253             "[consistent-controls], [point-controls]";
254         this.dnd_source = dnd_source;
255         this.node_editor_container = dojo.byId(node_editor_container);
256     };
257
258     this.clear = function() {
259         this.dnd_source.selectAll().deleteSelectedNodes();
260         dojo.create(
261             "em", {"innerHTML": localeStrings.WORKING_MP_HERE},
262             this.node_editor_container, "only"
263         );
264         this.dnd_source._ready = false;
265     };
266
267     this.build_vmsp = function() {
268         var match_point = new vmsp();
269         var controls = dojo.query("[fmfield]", this.node_editor_container);
270         for (var i = 0; i < controls.length; i++) {
271             var field = dojo.attr(controls[i], "fmfield");
272             var value = this._simple_value_getter(controls[i]);
273             match_point[field](value);
274         }
275
276         if (!this.is_sensible(match_point)) return null;    /* will alert() */
277         else return match_point;
278     };
279
280     this.update_draggable = function(draggable) {
281         var mp;
282
283         if (!(mp = this.build_vmsp())) return;  /* will alert() */
284
285         draggable.match_point = mp;
286         dojo.attr(draggable, "innerHTML", render_vmsp_label(mp));
287         this.dnd_source._ready = true;
288     };
289
290     this.add = function(type) {
291         this.clear();
292
293         /* a representation, not the editing widgets, but will also carry
294          * the fieldmapper object when dragged to the tree */
295         var draggable = dojo.create(
296             "li", {"innerHTML": localeStrings.DEFINE_MP}
297         );
298
299         /* these are the editing widgets */
300         var table = dojo.create("table", {"className": "node-editor"});
301
302         var nodes = this._factories_by_type[type]();
303         for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
304
305         if (type != "bool_op")
306             this._add_consistent_controls(table);
307
308         dojo.create(
309             "input", {
310                 "type": "submit", "value": localeStrings.OK,
311                 "onclick": function() { self.update_draggable(draggable); }
312             }, dojo.create(
313                 "td", {"colspan": 2, "align": "center"},
314                 dojo.create("tr", null, table)
315             )
316         );
317
318         dojo.place(table, this.node_editor_container, "only");
319
320         this.dnd_source.insertNodes(false, [draggable]);
321
322         /* nice */
323         try { dojo.query("select, input", table)[0].focus(); }
324         catch(E) { console.log(String(E)); }
325
326     };
327
328     apply_base_class(self, NodeEditorAbstract);
329
330     this._init.apply(this, arguments);
331 }
332
333 function find_crad_by_name(name) {
334     for (var i = 0; i < _crads.length; i++) {
335         if (_crads[i].name() == name)
336             return _crads[i];
337     }
338     return null;
339 }
340
341 function render_vmsp_label(point, minimal) {
342     /* "minimal" has these implications:
343      * for svf, only show the code, not the longer label.
344      * no quality display
345      */
346     if (point.bool_op()) {
347         return point.bool_op();
348     } else if (point.svf()) {
349         return (openils.Util.isTrue(point.negate()) ? "NOT " : "") + (
350             minimal ?  point.svf() :
351                 (point.svf() + " / " + find_crad_by_name(point.svf()).label()) +
352                 " | " + dojo.string.substitute(
353                     localeStrings.MATCH_SCORE, [point.quality()]
354                 )
355         );
356     } else {
357         return (openils.Util.isTrue(point.negate()) ? "NOT " : "") +
358             point.tag() + " \u2021" + point.subfield() + (minimal ? "" : " | " +
359                 dojo.string.substitute(
360                     localeStrings.MATCH_SCORE, [point.quality()]
361                 )
362             );
363     }
364 }
365
366 function replace_mode(explicit) {
367     if (typeof explicit == "undefined")
368         tree.model.replace_mode ^= 1;
369     else
370         tree.model.replace_mode = explicit;
371
372     dojo.attr(
373         "replacer", "innerHTML",
374         localeStrings[
375             (tree.model.replace_mode ? "EXIT" : "ENTER") + "_REPLACE_MODE"
376         ]
377     );
378     dojo[tree.model.replace_mode ? "addClass" : "removeClass"](
379         "replacer", "replace-mode"
380     );
381 }
382
383 function delete_selected_in_tree() {
384     /* relies on the fact that we only have one tree that would have
385      * registered a dnd controller. */
386     _tree_dnd_controllers[0].getSelectedItems().forEach(
387         function(item) {
388             if (item === tree.model.root)
389                 alert(localeStrings.LEAVE_ROOT_ALONE);
390             else
391                 tree.model.store.deleteItem(item);
392         }
393     );
394 }
395
396 function new_match_set_tree() {
397     var point = new vmsp();
398     point.bool_op("AND");
399     return [
400         {
401             "id": "root",
402             "children": [],
403             "name": render_vmsp_label(point),
404             "match_point": point
405         }
406     ];
407 }
408
409 /* dojoize_match_set_tree() takes an argument, "point", that is actually a
410  * vmsp fieldmapper object with descendants fleshed hierarchically. It turns
411  * that into a syntactically flat array but preserving the hierarchy
412  * semantically in the language used by dojo data stores, i.e.,
413  *
414  * [
415  *  {'id': 'root', children:[{'_reference': '0'}, {'_reference': '1'}]},
416  *  {'id': '0', children:[]},
417  *  {'id': '1', children:[]}
418  * ],
419  *
420  */
421 function dojoize_match_set_tree(point, refgen) {
422     var root = false;
423     if (!refgen) {
424         if (!point) {
425             return new_match_set_tree();
426         }
427         refgen = 0;
428         root = true;
429     }
430
431     var bathwater = point.children();
432     point.children([]);
433     var item = {
434         "id": (root ? "root" : refgen),
435         "name": render_vmsp_label(point),
436         "match_point": point.clone(),
437         "children": []
438     };
439     point.children(bathwater);
440
441     var results = [item];
442
443     if (point.children()) {
444         for (var i = 0; i < point.children().length; i++) {
445             var child = point.children()[i];
446             item.children.push({"_reference": ++refgen});
447             results = results.concat(
448                 dojoize_match_set_tree(child, refgen)
449             );
450         }
451     }
452
453     return results;
454 }
455
456 function render_vms_metadata(match_set) {
457     dojo.byId("vms-name").innerHTML = match_set.name();
458     dojo.byId("vms-owner").innerHTML =
459         aou.findOrgUnit(match_set.owner()).name();
460     dojo.byId("vms-mtype").innerHTML = match_set.mtype();
461 }
462
463 function redraw_expression_preview() {
464     tree.model.getRoot(
465         function(root) {
466             tree.model.get_simple_tree(
467                 root, function(r) {
468                     dojo.attr(
469                         "expr-preview",
470                         "innerHTML",
471                         render_expression_preview(r)
472                     );
473                 }
474             );
475         }
476     );
477 }
478
479 function render_expression_preview(r) {
480     if (r.children().length) {
481         return "(" + r.children().map(render_expression_preview).join(
482             " " + render_vmsp_label(r) + " "
483         ) + ")";
484     } else if (!r.bool_op()) {
485         return render_vmsp_label(r, true /* minimal */);
486     } else {
487         return "()";
488     }
489 }
490
491 function save_tree() {
492     progress_dialog.show(true);
493
494     tree.model.getRoot(
495         function(root) {
496             tree.model.get_simple_tree(
497                 root, function(r) {
498                     fieldmapper.standardRequest(
499                         ["open-ils.vandelay",
500                             "open-ils.vandelay.match_set.update"], {
501                             "params": [
502                                 openils.User.authtoken, match_set.id(), r
503                             ],
504                             "async": true,
505                             "oncomplete": function(r) {
506                                 progress_dialog.hide();
507                                 /* catch exceptions */
508                                 r = openils.Util.readResponse(r);
509
510                                 location.href = location.href;
511                             }
512                         }
513                     );
514                 }
515             );
516         }
517     );
518 }
519
520 function init_vmsq_grid() {
521     vmsq_grid.loadAll(
522         {"order_by": {"vmsq": "quality"}},
523         {"match_set": match_set.id()}
524     );
525 }
526
527 function my_init() {
528     progress_dialog.show(true);
529
530     dojo.requireLocalization("openils.vandelay", "match_set");
531     localeStrings = dojo.i18n.getLocalization("openils.vandelay", "match_set");
532
533     pcrud = new openils.PermaCrud();
534     CGI = new openils.CGI();
535
536     if (!CGI.param("match_set")) {
537         alert(localeStrings.NO_CAN_DO);
538         progress_dialog.hide();
539         return;
540     }
541
542     render_vms_metadata(
543         match_set = pcrud.retrieve("vms", CGI.param("match_set"))
544     );
545
546     /* No-one should have hundreds of these or anything, but theoretically
547      * this could be problematic with a big enough list of crad objects. */
548     _crads = pcrud.retrieveAll("crad", {"order_by": {"crad": "label"}});
549
550     var match_set_tree = fieldmapper.standardRequest(
551         ["open-ils.vandelay", "open-ils.vandelay.match_set.get_tree"],
552         [openils.User.authtoken, CGI.param("match_set")]
553     );
554
555     var store = new dojo.data.ItemFileWriteStore({
556         "data": {
557             "identifier": "id",
558             "label": "name",
559             "items": dojoize_match_set_tree(match_set_tree)
560         }
561     });
562
563     var tree_model = new openils.vandelay.TreeStoreModel({
564         "store": store, "query": {"id": "root"}
565     });
566
567     var src = new dojo.dnd.Source("src-here");
568     tree = new dijit.Tree(
569         {
570             "model": tree_model,
571             "dndController": openils.vandelay.TreeDndSource,
572             "dragThreshold": 8,
573             "betweenThreshold": 5,
574             "persist": false
575         }, "tree-here"
576     );
577
578     node_editor = new NodeEditor(src, "node-editor-container");
579     qnode_editor = new QualityNodeEditor("qnode-editor-container");
580
581     replace_mode(0);
582
583     dojo.connect(
584         src, "onDndDrop", null,
585         function(source, nodes, copy, target) {
586             /* Because of the... interesting... characteristics of DnD
587              * design in dojo/dijit (at least as of 1.3), this callback will
588              * fire both for our working node dndSource and for the tree!
589              */
590             if (source == this)
591                 node_editor.clear();  /* ... because otherwise this acts like a
592                                          copy operation no matter what the user
593                                          does, even though we really want a
594                                          "move." */
595         }
596     );
597
598     redraw_expression_preview();
599     node_editor.clear();
600
601     init_vmsq_grid();
602
603     progress_dialog.hide();
604 }
605
606 openils.Util.addOnLoad(my_init);