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 "heading" : function() {
124 var tr = dojo.create("tr");
127 "for": "heading-input",
128 "innerHTML": localeStrings.HEADING_MATCH
129 }, dojo.create("td", null, tr)
134 "id": "heading-input",
137 "disabled": true, // if you don't want it, don't use it.
139 }, dojo.create("td", null, tr)
144 "bool_op": function() {
145 var tr = dojo.create("tr");
148 {"for": "operator-select", "innerHTML": "Operator:"},
149 dojo.create("td", null, tr)
151 var select = dojo.create(
152 "select", {"fmfield": "bool_op", "id": "operator-select"},
153 dojo.create("td", null, tr)
155 dojo.create("option", {"value": "AND", "innerHTML": "AND"}, select);
156 dojo.create("option", {"value": "OR", "innerHTML": "OR"}, select);
163 function apply_base_class(cls, basecls) {
164 openils.Util.objectProperties(basecls).forEach(
165 function(m) { cls[m] = basecls[m]; }
169 function QualityNodeEditor() {
171 this.foi = ["tag", "svf"]; /* Fields of Interest - starting points for UI */
173 this._init = function(qnode_editor_container) {
174 this._consistent_controls_query =
175 "[consistent-controls], [quality-controls]";
176 this.qnode_editor_container = dojo.byId(qnode_editor_container);
180 this.clear = function() {
182 "em", {"innerHTML": localeStrings.WORKING_QM_HERE},
183 this.qnode_editor_container, "only"
187 this.build_vmsq = function() {
188 var metric = new vmsq();
189 metric.match_set(match_set.id()); /* using global */
190 var controls = dojo.query("[fmfield]", this.qnode_editor_container);
191 for (var i = 0; i < controls.length; i++) {
192 var field = dojo.attr(controls[i], "fmfield");
193 var value = this._simple_value_getter(controls[i]);
194 metric[field](value);
197 if (!this.is_sensible(metric)) return null; /* will alert() */
201 this.add = function(type) {
204 /* these are the editing widgets */
205 var table = dojo.create("table", {"className": "node-editor"});
207 var nodes = this._factories_by_type[type]();
208 for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
210 this._add_consistent_controls(table);
212 var ok_cxl_td = dojo.create(
213 "td", {"colspan": 2, "align": "center", "className": "space-me"},
214 dojo.create("tr", null, table)
219 "type": "submit", "value": localeStrings.OK,
220 "onclick": function() {
221 var metric = self.build_vmsq();
226 /* borrowed from openils.widget.AutoGrid */
227 "oncomplete": function(req, cudResults) {
228 var fmObject = cudResults[0];
229 if (vmsq_grid.onPostCreate)
230 vmsq_grid.onPostCreate(fmObject);
232 vmsq_grid.store.newItem(
233 fmObject.toStoreItem()
236 setTimeout(function() {
238 vmsq_grid.selection.select(vmsq_grid.rowCount-1);
239 vmsq_grid.views.views[0].getCellNode(vmsq_grid.rowCount-1, 1).focus();
251 "type": "reset", "value": localeStrings.CANCEL,
252 "onclick": function() { self.clear(); }
256 dojo.place(table, this.qnode_editor_container, "only");
259 try { dojo.query("select, input", table)[0].focus(); }
260 catch(E) { console.log(String(E)); }
264 apply_base_class(self, NodeEditorAbstract);
265 this._init.apply(this, arguments);
268 function NodeEditor() {
270 this.foi = ["tag", "svf", "heading", "bool_op"]; /* Fields of Interest - starting points for UI */
272 this._init = function(dnd_source, node_editor_container) {
273 this._consistent_controls_query =
274 "[consistent-controls], [point-controls]";
275 this.dnd_source = dnd_source;
276 this.node_editor_container = dojo.byId(node_editor_container);
278 // hide match point types which are not relevent to
279 // the current record type
280 if (match_set.mtype() == 'authority') {
281 openils.Util.hide('record-attr-btn');
283 openils.Util.hide('heading-match-btn');
287 this.clear = function() {
288 this.dnd_source.selectAll().deleteSelectedNodes();
290 "em", {"innerHTML": localeStrings.WORKING_MP_HERE},
291 this.node_editor_container, "only"
293 this.dnd_source._ready = false;
296 this.build_vmsp = function() {
297 var match_point = new vmsp();
298 var controls = dojo.query("[fmfield]", this.node_editor_container);
299 for (var i = 0; i < controls.length; i++) {
300 var field = dojo.attr(controls[i], "fmfield");
301 var value = this._simple_value_getter(controls[i]);
302 match_point[field](value);
305 if (!this.is_sensible(match_point)) return null; /* will alert() */
306 else return match_point;
309 this.update_draggable = function(draggable) {
312 if (!(mp = this.build_vmsp())) return; /* will alert() */
314 draggable.match_point = mp;
315 dojo.attr(draggable, "innerHTML", render_vmsp_label(mp));
316 this.dnd_source._ready = true;
319 this.add = function(type) {
322 /* a representation, not the editing widgets, but will also carry
323 * the fieldmapper object when dragged to the tree */
324 var draggable = dojo.create(
325 "li", {"innerHTML": localeStrings.DEFINE_MP}
328 /* these are the editing widgets */
329 var table = dojo.create("table", {"className": "node-editor"});
331 var nodes = this._factories_by_type[type]();
332 for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
334 if (type != "bool_op")
335 this._add_consistent_controls(table);
339 "type": "submit", "value": localeStrings.OK,
340 "onclick": function() { self.update_draggable(draggable); }
342 "td", {"colspan": 2, "align": "center"},
343 dojo.create("tr", null, table)
347 dojo.place(table, this.node_editor_container, "only");
349 this.dnd_source.insertNodes(false, [draggable]);
352 try { dojo.query("select, input", table)[0].focus(); }
353 catch(E) { console.log(String(E)); }
357 apply_base_class(self, NodeEditorAbstract);
359 this._init.apply(this, arguments);
362 function find_crad_by_name(name) {
363 for (var i = 0; i < _crads.length; i++) {
364 if (_crads[i].name() == name)
370 function render_vmsp_label(point, minimal) {
371 /* "minimal" has these implications:
372 * for svf, only show the code, not the longer label.
375 if (point.bool_op()) {
376 return point.bool_op();
377 } else if (point.svf()) {
378 return (openils.Util.isTrue(point.negate()) ? "NOT " : "") + (
379 minimal ? point.svf() :
380 (point.svf() + " / " + find_crad_by_name(point.svf()).label()) +
381 " | " + dojo.string.substitute(
382 localeStrings.MATCH_SCORE, [point.quality()]
385 } else if (point.heading() === true || point.heading() == 't') {
386 return localeStrings.HEADING_MATCH +
387 " | " + dojo.string.substitute(
388 localeStrings.MATCH_SCORE, [point.quality()]);
390 return (openils.Util.isTrue(point.negate()) ? "NOT " : "") +
391 point.tag() + " \u2021" + point.subfield() + (minimal ? "" : " | " +
392 dojo.string.substitute(
393 localeStrings.MATCH_SCORE, [point.quality()]
399 function replace_mode(explicit) {
400 if (typeof explicit == "undefined")
401 tree.model.replace_mode ^= 1;
403 tree.model.replace_mode = explicit;
406 "replacer", "innerHTML",
408 (tree.model.replace_mode ? "EXIT" : "ENTER") + "_REPLACE_MODE"
411 dojo[tree.model.replace_mode ? "addClass" : "removeClass"](
412 "replacer", "replace-mode"
416 function delete_selected_in_tree() {
417 /* relies on the fact that we only have one tree that would have
418 * registered a dnd controller. */
419 _tree_dnd_controllers[0].getSelectedItems().forEach(
421 if (item === tree.model.root)
422 alert(localeStrings.LEAVE_ROOT_ALONE);
424 tree.model.store.deleteItem(item);
429 function new_match_set_tree() {
430 var point = new vmsp();
431 point.bool_op("AND");
436 "name": render_vmsp_label(point),
442 /* dojoize_match_set_tree() takes an argument, "point", that is actually a
443 * vmsp fieldmapper object with descendants fleshed hierarchically. It turns
444 * that into a syntactically flat array but preserving the hierarchy
445 * semantically in the language used by dojo data stores, i.e.,
448 * {'id': 'root', children:[{'_reference': '0'}, {'_reference': '1'}]},
449 * {'id': '0', children:[]},
450 * {'id': '1', children:[]}
454 function dojoize_match_set_tree(point, depth) {
458 return new_match_set_tree();
464 var bathwater = point.children();
467 "id": (root ? "root" : point.id()),
468 "name": render_vmsp_label(point),
469 "match_point": point.clone(),
472 point.children(bathwater);
474 var results = [item];
476 if (point.children()) {
477 for (var i = 0; i < point.children().length; i++) {
478 var child = point.children()[i];
479 item.children.push({"_reference": child.id()});
480 results = results.concat(
481 dojoize_match_set_tree(child, ++depth)
489 function render_vms_metadata(match_set) {
490 dojo.byId("vms-name").innerHTML = match_set.name();
491 dojo.byId("vms-owner").innerHTML =
492 aou.findOrgUnit(match_set.owner()).name();
493 dojo.byId("vms-mtype").innerHTML = match_set.mtype();
496 function redraw_expression_preview() {
499 tree.model.get_simple_tree(
504 render_expression_preview(r)
512 function render_expression_preview(r) {
513 if (r.children().length) {
514 return "(" + r.children().map(render_expression_preview).join(
515 " " + render_vmsp_label(r) + " "
517 } else if (!r.bool_op()) {
518 return render_vmsp_label(r, true /* minimal */);
524 function save_tree() {
525 progress_dialog.show(true);
529 tree.model.get_simple_tree(
531 fieldmapper.standardRequest(
532 ["open-ils.vandelay",
533 "open-ils.vandelay.match_set.update"], {
535 openils.User.authtoken, match_set.id(), r
538 "oncomplete": function(r) {
539 progress_dialog.hide();
540 /* catch exceptions */
541 r = openils.Util.readResponse(r);
543 location.href = location.href;
553 function init_vmsq_grid() {
555 {"order_by": {"vmsq": "quality"}},
556 {"match_set": match_set.id()}
561 progress_dialog.show(true);
563 dojo.requireLocalization("openils.vandelay", "match_set");
564 localeStrings = dojo.i18n.getLocalization("openils.vandelay", "match_set");
566 pcrud = new openils.PermaCrud();
567 CGI = new openils.CGI();
569 if (!CGI.param("match_set")) {
570 alert(localeStrings.NO_CAN_DO);
571 progress_dialog.hide();
576 match_set = pcrud.retrieve("vms", CGI.param("match_set"))
579 /* No-one should have hundreds of these or anything, but theoretically
580 * this could be problematic with a big enough list of crad objects. */
581 _crads = match_set.mtype() == 'authority' ? [] :
582 pcrud.retrieveAll("crad", {"order_by": {"crad": "label"}});
584 var match_set_tree = fieldmapper.standardRequest(
585 ["open-ils.vandelay", "open-ils.vandelay.match_set.get_tree"],
586 [openils.User.authtoken, CGI.param("match_set")]
589 var store = new dojo.data.ItemFileWriteStore({
593 "items": dojoize_match_set_tree(match_set_tree)
597 var tree_model = new openils.vandelay.TreeStoreModel({
598 "store": store, "query": {"id": "root"}
601 var src = new dojo.dnd.Source("src-here");
602 tree = new dijit.Tree(
605 "dndController": openils.vandelay.TreeDndSource,
607 "betweenThreshold": 5,
612 node_editor = new NodeEditor(src, "node-editor-container");
613 qnode_editor = new QualityNodeEditor("qnode-editor-container");
618 src, "onDndDrop", null,
619 function(source, nodes, copy, target) {
620 /* Because of the... interesting... characteristics of DnD
621 * design in dojo/dijit (at least as of 1.3), this callback will
622 * fire both for our working node dndSource and for the tree!
625 node_editor.clear(); /* ... because otherwise this acts like a
626 copy operation no matter what the user
627 does, even though we really want a
632 redraw_expression_preview();
637 progress_dialog.hide();
640 openils.Util.addOnLoad(my_init);