]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/vandelay/match_set.js
Match Set Tree editor improvements/cleanup
[working/Evergreen.git] / Open-ILS / web / js / ui / default / 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;
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.empty(this.node_editor_container);
124         this.dnd_source._ready = false;
125     };
126
127     this.is_sensible = function(mp) {
128         var need_one = 0;
129         ["tag", "svf", "bool_op"].forEach(
130             function(field) { if (mp[field]()) need_one++; }
131         );
132
133         if (need_one != 1) {
134             alert(localeStrings.POINT_NEEDS_ONE);
135             return false;
136         }
137
138         if (mp.tag()) {
139             if (
140                 !mp.tag().match(/^\d{3}$/) ||
141                 mp.subfield().length != 1 ||
142                 !mp.subfield().match(/\S/) ||
143                 mp.subfield().charCodeAt(0) < 32
144             ) {
145                 alert(localeStrings.FAULTY_MARC);
146                 return false;
147             }
148         }
149
150         return true;
151     };
152
153     this.build_vmsp = function() {
154         var match_point = new vmsp();
155         var controls = dojo.query("[fmfield]", this.node_editor_container);
156         for (var i = 0; i < controls.length; i++) {
157             var field = dojo.attr(controls[i], "fmfield");
158             var value = _simple_value_getter(controls[i]);
159             match_point[field](value);
160         }
161
162         if (!this.is_sensible(match_point)) return null;    /* will alert() */
163         else return match_point;
164     };
165
166     this.update_draggable = function(draggable) {
167         var mp;
168
169         if (!(mp = this.build_vmsp())) return;  /* will alert() */
170
171         draggable.match_point = mp;
172         dojo.attr(draggable, "innerHTML", render_vmsp_label(mp));
173         this.dnd_source._ready = true;
174     };
175
176     this._add_consistent_controls = function(tgt) {
177         if (!this._consistent_controls) {
178             var trs = dojo.query("[consistent-controls]");
179             this._consistent_controls = [];
180             for (var i = 0; i < trs.length; i++)
181                 this._consistent_controls[i] = dojo.clone(trs[i]);
182             dojo.empty(trs[0].parentNode);
183         }
184
185         this._consistent_controls.forEach(
186             function(node) { dojo.place(dojo.clone(node), tgt); }
187         );
188     };
189
190     this.add = function(type) {
191         this.clear();
192
193         /* a representation, not the editing widgets, but will also carry
194          * the fieldmapper object when dragged to the tree */
195         var draggable = dojo.create(
196             "li", {"innerHTML": localeStrings.DEFINE_MP}
197         );
198
199         /* these are the editing widgets */
200         var table = dojo.create("table", {"className": "node-editor"});
201
202         var nodes = _factories_by_type[type]();
203         for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
204
205         this._add_consistent_controls(table);
206
207         dojo.create(
208             "input", {
209                 "type": "submit", "value": localeStrings.OK,
210                 "onclick": function() { self.update_draggable(draggable); }
211             }, dojo.create(
212                 "td", {"colspan": 2, "align": "center"},
213                 dojo.create("tr", null, table)
214             )
215         );
216
217         dojo.place(table, this.node_editor_container, "only");
218         /* XXX around here attach other data structures to the node */
219         this.dnd_source.insertNodes(false, [draggable]);
220     };
221
222     this._init.apply(this, arguments);
223 }
224
225 function render_vmsp_label(point) {
226     /* quick and dirty */
227     if (point.bool_op()) {
228         return (openils.Util.isTrue(point.negate()) ? "N" : "") +
229             point.bool_op();
230     } else if (point.svf()) {
231         return (openils.Util.isTrue(point.negate()) ? "NOT " : "") +
232             point.svf() + " / " + _find_crad_by_name(point.svf()).label();
233     } else {
234         return (openils.Util.isTrue(point.negate()) ? "NOT " : "") +
235             point.tag() + " \u2021" + point.subfield();
236     }
237 }
238
239 function replace_mode(explicit) {
240     if (typeof explicit == "undefined")
241         tree.model.replace_mode ^= 1;
242     else
243         tree.model.replace_mode = explicit;
244
245     dojo.attr(
246         "replacer", "innerHTML",
247         localeStrings[
248             (tree.model.replace_mode ? "EXIT" : "ENTER") + "_REPLACE_MODE"
249         ]
250     );
251     dojo[tree.model.replace_mode ? "addClass" : "removeClass"](
252         "replacer", "replace-mode"
253     );
254 }
255
256 function delete_selected_in_tree() {
257     /* relies on the fact that we only have one tree that would have
258      * registered a dnd controller. */
259     _tree_dnd_controllers[0].getSelectedItems().forEach(
260         function(item) {
261             if (item === tree.model.root)
262                 alert(localeStrings.LEAVE_ROOT_ALONE);
263             else
264                 tree.model.store.deleteItem(item);
265         }
266     );
267 }
268
269 function new_match_set_tree() {
270     var point = new vmsp();
271     point.bool_op("AND");
272     return [
273         {
274             "id": "root",
275             "children": [],
276             "name": render_vmsp_label(point),
277             "match_point": point
278         }
279     ];
280 }
281
282 /* dojoize_match_set_tree() takes an argument, "point", that is actually a
283  * vmsp fieldmapper object with descendants fleshed hierarchically. It turns
284  * that into a syntactically flat array but preserving the hierarchy
285  * semantically in the language used by dojo data stores, i.e.,
286  *
287  * [
288  *  {'id': 'root', children:[{'_reference': '0'}, {'_reference': '1'}]},
289  *  {'id': '0', children:[]},
290  *  {'id': '1', children:[]}
291  * ],
292  *
293  */
294 function dojoize_match_set_tree(point, refgen) {
295     var root = false;
296     if (!refgen) {
297         if (!point) {
298             return new_match_set_tree();
299         }
300         refgen = 0;
301         root = true;
302     }
303
304     var bathwater = point.children();
305     point.children([]);
306     var item = {
307         "id": (root ? "root" : refgen),
308         "name": render_vmsp_label(point),
309         "match_point": point.clone(),
310         "children": []
311     };
312     point.children(bathwater);
313
314     var results = [item];
315
316     if (point.children()) {
317         for (var i = 0; i < point.children().length; i++) {
318             var child = point.children()[i];
319             item.children.push({"_reference": ++refgen});
320             results = results.concat(
321                 dojoize_match_set_tree(child, refgen)
322             );
323         }
324     }
325
326     return results;
327 }
328
329 function render_vms_metadata(match_set) {
330     dojo.byId("vms-name").innerHTML = match_set.name();
331     dojo.byId("vms-owner").innerHTML =
332         aou.findOrgUnit(match_set.owner()).name();
333     dojo.byId("vms-mtype").innerHTML = match_set.mtype();
334 }
335
336 function my_init() {
337     progress_dialog.show(true);
338
339     dojo.requireLocalization("openils.vandelay", "match_set");
340     localeStrings = dojo.i18n.getLocalization("openils.vandelay", "match_set");
341
342     pcrud = new openils.PermaCrud();
343     CGI = new openils.CGI();
344
345     if (!CGI.param("match_set")) {
346         alert(localeStrings.NO_CAN_DO);
347         progress_dialog.hide();
348         return;
349     }
350
351     render_vms_metadata(pcrud.retrieve("vms", CGI.param("match_set")));
352
353     /* No-one should have hundreds of these or anything, but theoretically
354      * this could be problematic with a big enough list of crad objects. */
355     _crads = pcrud.retrieveAll("crad", {"order_by": {"crad": "label"}});
356
357     var match_set_tree = fieldmapper.standardRequest(
358         ["open-ils.vandelay", "open-ils.vandelay.match_set.get_tree"],
359         [openils.User.authtoken, CGI.param("match_set")]
360     );
361
362     var store = new dojo.data.ItemFileWriteStore({
363         "data": {
364             "identifier": "id",
365             "label": "name",
366             "items": dojoize_match_set_tree(match_set_tree)
367         }
368     });
369
370     var tree_model = new openils.vandelay.TreeStoreModel({
371         "store": store, "query": {"id": "root"}
372     });
373
374     var src = new dojo.dnd.Source("src-here");
375     tree = new dijit.Tree(
376         {
377             "model": tree_model,
378             "dndController": openils.vandelay.TreeDndSource,
379             "dragThreshold": 8,
380             "betweenThreshold": 5,
381             "persist": false
382         }, "tree-here"
383     );
384
385     node_editor = new NodeEditor(src, "node-editor-container");
386
387     replace_mode(0);
388
389     dojo.connect(
390         src, "onDndDrop", null,
391         function(source, nodes, copy, target) {
392             /* Because of the... interesting... characteristics of DnD
393              * design in dojo/dijit (at least as of 1.3), this callback will
394              * fire both for our working node dndSource and for the tree!
395              */
396             if (source == this)
397                 node_editor.clear();  /* ... because otherwise this acts like a
398                                          copy operation no matter what the user
399                                          does, even though we really want a
400                                          "move." */
401         }
402     );
403     progress_dialog.hide();
404 }
405
406 openils.Util.addOnLoad(my_init);