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");
14 var localeStrings, node_editor, qnode_editor, _crads, CGI, tree, match_set;
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;
26 "is_sensible": function(thing) {
28 this.foi.forEach(function(field) { if (thing[field]()) need_one++; });
31 alert(localeStrings.POINT_NEEDS_ONE);
37 !thing.tag().match(/^\d{3}$/) ||
38 thing.subfield().length != 1 ||
39 !thing.subfield().match(/\S/) ||
40 thing.subfield().charCodeAt(0) < 32
42 alert(localeStrings.FAULTY_MARC);
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]);
57 this._consistent_controls.forEach(
58 function(node) { dojo.place(dojo.clone(node), tgt); }
61 "_factories_by_type": {
63 if (!self._svf_select_template) {
64 self._svf_select_template = dojo.create(
65 "select", {"fmfield": "svf"}
67 for (var i=0; i<_crads.length; i++) {
70 "value": _crads[i].name(),
71 "innerHTML": _crads[i].label()
72 }, self._svf_select_template
77 var select = dojo.clone(self._svf_select_template);
78 dojo.attr(select, "id", "svf-select");
79 var label = dojo.create(
81 "for": "svf-select", "innerHTML": localeStrings.SVF + ":"
85 var tr = dojo.create("tr");
86 dojo.place(label, dojo.create("td", null, tr));
87 dojo.place(select, dojo.create("td", null, tr));
92 var rows = [dojo.create("tr"), dojo.create("tr")];
95 "for": "tag-input", "innerHTML": "Tag:"
96 }, dojo.create("td", null, rows[0])
105 }, dojo.create("td", null, rows[0])
109 "for": "subfield-input", "innerHTML": "Subfield: \u2021"
110 }, dojo.create("td", null, rows[1])
114 "id": "subfield-input",
118 "fmfield": "subfield"
119 }, dojo.create("td", null, rows[1])
123 "bool_op": function() {
124 var tr = dojo.create("tr");
127 {"for": "operator-select", "innerHTML": "Operator:"},
128 dojo.create("td", null, tr)
130 var select = dojo.create(
131 "select", {"fmfield": "bool_op", "id": "operator-select"},
132 dojo.create("td", null, tr)
134 dojo.create("option", {"value": "AND", "innerHTML": "AND"}, select);
135 dojo.create("option", {"value": "OR", "innerHTML": "OR"}, select);
142 function apply_base_class(cls, basecls) {
143 openils.Util.objectProperties(basecls).forEach(
144 function(m) { cls[m] = basecls[m]; }
148 function QualityNodeEditor() {
150 this.foi = ["tag", "svf"]; /* Fields of Interest - starting points for UI */
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);
159 this.clear = function() {
161 "em", {"innerHTML": localeStrings.WORKING_QM_HERE},
162 this.qnode_editor_container, "only"
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);
176 if (!this.is_sensible(metric)) return null; /* will alert() */
180 this.add = function(type) {
183 /* these are the editing widgets */
184 var table = dojo.create("table", {"className": "node-editor"});
186 var nodes = this._factories_by_type[type]();
187 for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
189 this._add_consistent_controls(table);
191 var ok_cxl_td = dojo.create(
192 "td", {"colspan": 2, "align": "center", "className": "space-me"},
193 dojo.create("tr", null, table)
198 "type": "submit", "value": localeStrings.OK,
199 "onclick": function() {
200 var metric = self.build_vmsq();
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);
211 vmsq_grid.store.newItem(
212 fmObject.toStoreItem()
215 setTimeout(function() {
217 vmsq_grid.selection.select(vmsq_grid.rowCount-1);
218 vmsq_grid.views.views[0].getCellNode(vmsq_grid.rowCount-1, 1).focus();
230 "type": "reset", "value": localeStrings.CANCEL,
231 "onclick": function() { self.clear(); }
235 dojo.place(table, this.qnode_editor_container, "only");
238 try { dojo.query("select, input", table)[0].focus(); }
239 catch(E) { console.log(String(E)); }
243 apply_base_class(self, NodeEditorAbstract);
244 this._init.apply(this, arguments);
247 function NodeEditor() {
249 this.foi = ["tag", "svf", "bool_op"]; /* Fields of Interest - starting points for UI */
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);
258 this.clear = function() {
259 this.dnd_source.selectAll().deleteSelectedNodes();
261 "em", {"innerHTML": localeStrings.WORKING_MP_HERE},
262 this.node_editor_container, "only"
264 this.dnd_source._ready = false;
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);
276 if (!this.is_sensible(match_point)) return null; /* will alert() */
277 else return match_point;
280 this.update_draggable = function(draggable) {
283 if (!(mp = this.build_vmsp())) return; /* will alert() */
285 draggable.match_point = mp;
286 dojo.attr(draggable, "innerHTML", render_vmsp_label(mp));
287 this.dnd_source._ready = true;
290 this.add = function(type) {
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}
299 /* these are the editing widgets */
300 var table = dojo.create("table", {"className": "node-editor"});
302 var nodes = this._factories_by_type[type]();
303 for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
305 if (type != "bool_op")
306 this._add_consistent_controls(table);
310 "type": "submit", "value": localeStrings.OK,
311 "onclick": function() { self.update_draggable(draggable); }
313 "td", {"colspan": 2, "align": "center"},
314 dojo.create("tr", null, table)
318 dojo.place(table, this.node_editor_container, "only");
320 this.dnd_source.insertNodes(false, [draggable]);
323 try { dojo.query("select, input", table)[0].focus(); }
324 catch(E) { console.log(String(E)); }
328 apply_base_class(self, NodeEditorAbstract);
330 this._init.apply(this, arguments);
333 function find_crad_by_name(name) {
334 for (var i = 0; i < _crads.length; i++) {
335 if (_crads[i].name() == name)
341 function render_vmsp_label(point, minimal) {
342 /* "minimal" has these implications:
343 * for svf, only show the code, not the longer label.
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()]
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()]
366 function replace_mode(explicit) {
367 if (typeof explicit == "undefined")
368 tree.model.replace_mode ^= 1;
370 tree.model.replace_mode = explicit;
373 "replacer", "innerHTML",
375 (tree.model.replace_mode ? "EXIT" : "ENTER") + "_REPLACE_MODE"
378 dojo[tree.model.replace_mode ? "addClass" : "removeClass"](
379 "replacer", "replace-mode"
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(
388 if (item === tree.model.root)
389 alert(localeStrings.LEAVE_ROOT_ALONE);
391 tree.model.store.deleteItem(item);
396 function new_match_set_tree() {
397 var point = new vmsp();
398 point.bool_op("AND");
403 "name": render_vmsp_label(point),
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.,
415 * {'id': 'root', children:[{'_reference': '0'}, {'_reference': '1'}]},
416 * {'id': '0', children:[]},
417 * {'id': '1', children:[]}
421 function dojoize_match_set_tree(point, refgen) {
425 return new_match_set_tree();
431 var bathwater = point.children();
434 "id": (root ? "root" : refgen),
435 "name": render_vmsp_label(point),
436 "match_point": point.clone(),
439 point.children(bathwater);
441 var results = [item];
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)
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();
463 function redraw_expression_preview() {
466 tree.model.get_simple_tree(
471 render_expression_preview(r)
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) + " "
484 } else if (!r.bool_op()) {
485 return render_vmsp_label(r, true /* minimal */);
491 function save_tree() {
492 progress_dialog.show(true);
496 tree.model.get_simple_tree(
498 fieldmapper.standardRequest(
499 ["open-ils.vandelay",
500 "open-ils.vandelay.match_set.update"], {
502 openils.User.authtoken, match_set.id(), r
505 "oncomplete": function(r) {
506 progress_dialog.hide();
507 /* catch exceptions */
508 r = openils.Util.readResponse(r);
510 location.href = location.href;
520 function init_vmsq_grid() {
522 {"order_by": {"vmsq": "quality"}},
523 {"match_set": match_set.id()}
528 progress_dialog.show(true);
530 dojo.requireLocalization("openils.vandelay", "match_set");
531 localeStrings = dojo.i18n.getLocalization("openils.vandelay", "match_set");
533 pcrud = new openils.PermaCrud();
534 CGI = new openils.CGI();
536 if (!CGI.param("match_set")) {
537 alert(localeStrings.NO_CAN_DO);
538 progress_dialog.hide();
543 match_set = pcrud.retrieve("vms", CGI.param("match_set"))
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"}});
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")]
555 var store = new dojo.data.ItemFileWriteStore({
559 "items": dojoize_match_set_tree(match_set_tree)
563 var tree_model = new openils.vandelay.TreeStoreModel({
564 "store": store, "query": {"id": "root"}
567 var src = new dojo.dnd.Source("src-here");
568 tree = new dijit.Tree(
571 "dndController": openils.vandelay.TreeDndSource,
573 "betweenThreshold": 5,
578 node_editor = new NodeEditor(src, "node-editor-container");
579 qnode_editor = new QualityNodeEditor("qnode-editor-container");
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!
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
598 redraw_expression_preview();
603 progress_dialog.hide();
606 openils.Util.addOnLoad(my_init);