]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/conify/global/vandelay/match_set.js
move parts to conify namespace
[working/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
13 var localeStrings, node_editor, _crads, CGI, tree, match_set;
14
15 function _find_crad_by_name(name) {
16     for (var i = 0; i < _crads.length; i++) {
17         if (_crads[i].name() == name)
18             return _crads[i];
19     }
20     return null;
21 }
22
23 function NodeEditor() {
24     var self = this;
25
26     var _svf_select_template = null;
27     var _factories_by_type = {
28         "svf": function() {
29             if (!_svf_select_template) {
30                 _svf_select_template = dojo.create(
31                     "select", {"fmfield": "svf"}
32                 );
33                 for (var i=0; i<_crads.length; i++) {
34                     dojo.create(
35                         "option", {
36                             "value": _crads[i].name(),
37                             "innerHTML": _crads[i].label()
38                         }, _svf_select_template
39                     );
40                 }
41             }
42
43             var select = dojo.clone(_svf_select_template);
44             dojo.attr(select, "id", "svf-select");
45             var label = dojo.create(
46                 "label", {
47                     "for": "svf-select", "innerHTML": "Single-Value-Field:"
48                 }
49             );
50
51             var tr = dojo.create("tr");
52             dojo.place(label, dojo.create("td", null, tr));
53             dojo.place(select, dojo.create("td", null, tr));
54
55             return [tr];
56         },
57         "tag": function() {
58             var rows = [dojo.create("tr"), dojo.create("tr")];
59             dojo.create(
60                 "label", {
61                     "for": "tag-input", "innerHTML": "Tag:"
62                 }, dojo.create("td", null, rows[0])
63             );
64             dojo.create(
65                 "input", {
66                     "id": "tag-input",
67                     "type": "text",
68                     "size": 4,
69                     "maxlength": 3,
70                     "fmfield": "tag"
71                 }, dojo.create("td", null, rows[0])
72             );
73             dojo.create(
74                 "label", {
75                     "for": "subfield-input", "innerHTML": "Subfield: \u2021"
76                 }, dojo.create("td", null, rows[1])
77             );
78             dojo.create(
79                 "input", {
80                     "id": "subfield-input",
81                     "type": "text",
82                     "size": 2,
83                     "maxlength": 1,
84                     "fmfield": "subfield"
85                 }, dojo.create("td", null, rows[1])
86             );
87             return rows;
88         },
89         "bool_op": function() {
90             var tr = dojo.create("tr");
91             dojo.create(
92                 "label",
93                 {"for": "operator-select", "innerHTML": "Operator:"},
94                 dojo.create("td", null, tr)
95             );
96             var select = dojo.create(
97                 "select", {"fmfield": "bool_op", "id": "operator-select"},
98                 dojo.create("td", null, tr)
99             );
100             dojo.create("option", {"value": "AND", "innerHTML": "AND"}, select);
101             dojo.create("option", {"value": "OR", "innerHTML": "OR"}, select);
102
103             return [tr];
104         }
105     };
106
107     function _simple_value_getter(control) {
108         if (typeof control.selectedIndex != "undefined")
109             return control.options[control.selectedIndex].value;
110         else if (dojo.attr(control, "type") == "checkbox")
111             return control.checked;
112         else
113             return control.value;
114     };
115
116     this._init = function(dnd_source, node_editor_container) {
117         this.dnd_source = dnd_source;
118         this.node_editor_container = dojo.byId(node_editor_container);
119     };
120
121     this.clear = function() {
122         this.dnd_source.selectAll().deleteSelectedNodes();
123         dojo.create(
124             "em", {"innerHTML": localeStrings.WORKING_MP_HERE},
125             this.node_editor_container, "only"
126         );
127         this.dnd_source._ready = false;
128     };
129
130     this.is_sensible = function(mp) {
131         var need_one = 0;
132         ["tag", "svf", "bool_op"].forEach(
133             function(field) { if (mp[field]()) need_one++; }
134         );
135
136         if (need_one != 1) {
137             alert(localeStrings.POINT_NEEDS_ONE);
138             return false;
139         }
140
141         if (mp.tag()) {
142             if (
143                 !mp.tag().match(/^\d{3}$/) ||
144                 mp.subfield().length != 1 ||
145                 !mp.subfield().match(/\S/) ||
146                 mp.subfield().charCodeAt(0) < 32
147             ) {
148                 alert(localeStrings.FAULTY_MARC);
149                 return false;
150             }
151         }
152
153         return true;
154     };
155
156     this.build_vmsp = function() {
157         var match_point = new vmsp();
158         var controls = dojo.query("[fmfield]", this.node_editor_container);
159         for (var i = 0; i < controls.length; i++) {
160             var field = dojo.attr(controls[i], "fmfield");
161             var value = _simple_value_getter(controls[i]);
162             match_point[field](value);
163         }
164
165         if (!this.is_sensible(match_point)) return null;    /* will alert() */
166         else return match_point;
167     };
168
169     this.update_draggable = function(draggable) {
170         var mp;
171
172         if (!(mp = this.build_vmsp())) return;  /* will alert() */
173
174         draggable.match_point = mp;
175         dojo.attr(draggable, "innerHTML", render_vmsp_label(mp));
176         this.dnd_source._ready = true;
177     };
178
179     this._add_consistent_controls = function(tgt) {
180         if (!this._consistent_controls) {
181             var trs = dojo.query("[consistent-controls]");
182             this._consistent_controls = [];
183             for (var i = 0; i < trs.length; i++)
184                 this._consistent_controls[i] = dojo.clone(trs[i]);
185             dojo.empty(trs[0].parentNode);
186         }
187
188         this._consistent_controls.forEach(
189             function(node) { dojo.place(dojo.clone(node), tgt); }
190         );
191     };
192
193     this.add = function(type) {
194         this.clear();
195
196         /* a representation, not the editing widgets, but will also carry
197          * the fieldmapper object when dragged to the tree */
198         var draggable = dojo.create(
199             "li", {"innerHTML": localeStrings.DEFINE_MP}
200         );
201
202         /* these are the editing widgets */
203         var table = dojo.create("table", {"className": "node-editor"});
204
205         var nodes = _factories_by_type[type]();
206         for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
207
208         this._add_consistent_controls(table);
209
210         dojo.create(
211             "input", {
212                 "type": "submit", "value": localeStrings.OK,
213                 "onclick": function() { self.update_draggable(draggable); }
214             }, dojo.create(
215                 "td", {"colspan": 2, "align": "center"},
216                 dojo.create("tr", null, table)
217             )
218         );
219
220         dojo.place(table, this.node_editor_container, "only");
221
222         this.dnd_source.insertNodes(false, [draggable]);
223
224         /* nice */
225         try { dojo.query("select, input", table)[0].focus(); }
226         catch(E) { console.log(String(E)); }
227
228     };
229
230     this._init.apply(this, arguments);
231 }
232
233 function render_vmsp_label(point, minimal) {
234     /* quick and dirty */
235     if (point.bool_op()) {
236         return (openils.Util.isTrue(point.negate()) ? "N" : "") +
237             point.bool_op();
238     } else if (point.svf()) {
239         return (openils.Util.isTrue(point.negate()) ? "NOT " : "") + (
240             minimal ?  point.svf() :
241                 (point.svf() + " / " + _find_crad_by_name(point.svf()).label())
242         );
243     } else {
244         return (openils.Util.isTrue(point.negate()) ? "NOT " : "") +
245             point.tag() + " \u2021" + point.subfield();
246     }
247 }
248
249 function replace_mode(explicit) {
250     if (typeof explicit == "undefined")
251         tree.model.replace_mode ^= 1;
252     else
253         tree.model.replace_mode = explicit;
254
255     dojo.attr(
256         "replacer", "innerHTML",
257         localeStrings[
258             (tree.model.replace_mode ? "EXIT" : "ENTER") + "_REPLACE_MODE"
259         ]
260     );
261     dojo[tree.model.replace_mode ? "addClass" : "removeClass"](
262         "replacer", "replace-mode"
263     );
264 }
265
266 function delete_selected_in_tree() {
267     /* relies on the fact that we only have one tree that would have
268      * registered a dnd controller. */
269     _tree_dnd_controllers[0].getSelectedItems().forEach(
270         function(item) {
271             if (item === tree.model.root)
272                 alert(localeStrings.LEAVE_ROOT_ALONE);
273             else
274                 tree.model.store.deleteItem(item);
275         }
276     );
277 }
278
279 function new_match_set_tree() {
280     var point = new vmsp();
281     point.bool_op("AND");
282     return [
283         {
284             "id": "root",
285             "children": [],
286             "name": render_vmsp_label(point),
287             "match_point": point
288         }
289     ];
290 }
291
292 /* dojoize_match_set_tree() takes an argument, "point", that is actually a
293  * vmsp fieldmapper object with descendants fleshed hierarchically. It turns
294  * that into a syntactically flat array but preserving the hierarchy
295  * semantically in the language used by dojo data stores, i.e.,
296  *
297  * [
298  *  {'id': 'root', children:[{'_reference': '0'}, {'_reference': '1'}]},
299  *  {'id': '0', children:[]},
300  *  {'id': '1', children:[]}
301  * ],
302  *
303  */
304 function dojoize_match_set_tree(point, refgen) {
305     var root = false;
306     if (!refgen) {
307         if (!point) {
308             return new_match_set_tree();
309         }
310         refgen = 0;
311         root = true;
312     }
313
314     var bathwater = point.children();
315     point.children([]);
316     var item = {
317         "id": (root ? "root" : refgen),
318         "name": render_vmsp_label(point),
319         "match_point": point.clone(),
320         "children": []
321     };
322     point.children(bathwater);
323
324     var results = [item];
325
326     if (point.children()) {
327         for (var i = 0; i < point.children().length; i++) {
328             var child = point.children()[i];
329             item.children.push({"_reference": ++refgen});
330             results = results.concat(
331                 dojoize_match_set_tree(child, refgen)
332             );
333         }
334     }
335
336     return results;
337 }
338
339 function render_vms_metadata(match_set) {
340     dojo.byId("vms-name").innerHTML = match_set.name();
341     dojo.byId("vms-owner").innerHTML =
342         aou.findOrgUnit(match_set.owner()).name();
343     dojo.byId("vms-mtype").innerHTML = match_set.mtype();
344 }
345
346 function redraw_expression_preview() {
347     tree.model.getRoot(
348         function(root) {
349             tree.model.get_simple_tree(
350                 root, function(r) {
351                     dojo.attr(
352                         "expr-preview",
353                         "innerHTML",
354                         render_expression_preview(r)
355                     );
356                 }
357             );
358         }
359     );
360 }
361
362 function render_expression_preview(r) {
363     if (r.children().length) {
364         return "(" + r.children().map(render_expression_preview).join(
365             " " + render_vmsp_label(r) + " "
366         ) + ")";
367     } else if (!r.bool_op()) {
368         return render_vmsp_label(r, true /* minimal */);
369     } else {
370         return "()";
371     }
372 }
373
374 function save_tree() {
375     progress_dialog.show(true);
376
377     tree.model.getRoot(
378         function(root) {
379             tree.model.get_simple_tree(
380                 root, function(r) {
381                     fieldmapper.standardRequest(
382                         ["open-ils.vandelay",
383                             "open-ils.vandelay.match_set.update"],/* XXX TODO */{
384                             "params": [
385                                 openils.User.authtoken, match_set.id(), r
386                             ],
387                             "async": true,
388                             "oncomplete": function(r) {
389                                 progress_dialog.hide();
390                                 /* catch exceptions */
391                                 r = openils.Util.readResponse(r);
392
393                                 location.href = location.href;
394                             }
395                         }
396                     );
397                 }
398             );
399         }
400     );
401 }
402 function my_init() {
403     progress_dialog.show(true);
404
405     dojo.requireLocalization("openils.vandelay", "match_set");
406     localeStrings = dojo.i18n.getLocalization("openils.vandelay", "match_set");
407
408     pcrud = new openils.PermaCrud();
409     CGI = new openils.CGI();
410
411     if (!CGI.param("match_set")) {
412         alert(localeStrings.NO_CAN_DO);
413         progress_dialog.hide();
414         return;
415     }
416
417     render_vms_metadata(
418         match_set = pcrud.retrieve("vms", CGI.param("match_set"))
419     );
420
421     /* No-one should have hundreds of these or anything, but theoretically
422      * this could be problematic with a big enough list of crad objects. */
423     _crads = pcrud.retrieveAll("crad", {"order_by": {"crad": "label"}});
424
425     var match_set_tree = fieldmapper.standardRequest(
426         ["open-ils.vandelay", "open-ils.vandelay.match_set.get_tree"],
427         [openils.User.authtoken, CGI.param("match_set")]
428     );
429
430     var store = new dojo.data.ItemFileWriteStore({
431         "data": {
432             "identifier": "id",
433             "label": "name",
434             "items": dojoize_match_set_tree(match_set_tree)
435         }
436     });
437
438     var tree_model = new openils.vandelay.TreeStoreModel({
439         "store": store, "query": {"id": "root"}
440     });
441
442     var src = new dojo.dnd.Source("src-here");
443     tree = new dijit.Tree(
444         {
445             "model": tree_model,
446             "dndController": openils.vandelay.TreeDndSource,
447             "dragThreshold": 8,
448             "betweenThreshold": 5,
449             "persist": false
450         }, "tree-here"
451     );
452
453     node_editor = new NodeEditor(src, "node-editor-container");
454
455     replace_mode(0);
456
457     dojo.connect(
458         src, "onDndDrop", null,
459         function(source, nodes, copy, target) {
460             /* Because of the... interesting... characteristics of DnD
461              * design in dojo/dijit (at least as of 1.3), this callback will
462              * fire both for our working node dndSource and for the tree!
463              */
464             if (source == this)
465                 node_editor.clear();  /* ... because otherwise this acts like a
466                                          copy operation no matter what the user
467                                          does, even though we really want a
468                                          "move." */
469         }
470     );
471
472     redraw_expression_preview();
473     node_editor.clear();
474     progress_dialog.hide();
475 }
476
477 openils.Util.addOnLoad(my_init);