From: senator Date: Mon, 11 Apr 2011 21:25:25 +0000 (-0400) Subject: a little progress on the match set editor: X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=3e034f6558d4c1e930ad131a1a1a6a66d8ed0995;p=evergreen%2Fequinox.git a little progress on the match set editor: now we can retrieve a tree from the server and use it as the basis of our dijit.Tree widget. Still work to be done. Can't save anything yet. Note to self: borrow dojo dnd's "copy" operation (as opposed to move) to mean replacing a node in the tree, rather than adding to the tree. Re the permissions I changed, actual users of Evergreen hate having as much granularity as there was before, and it just confuses people trying to figure out what perms to give to whom. Note to self 2: add ADMIN_IMPORT_MATCH_SET to ppl --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index ae41a471e6..b4891d3c81 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -540,10 +540,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - - - - + + + + @@ -557,6 +557,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -568,16 +569,16 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + - + - + - + @@ -599,10 +600,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - - - - + + + + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm index ca6963cfc5..024be0819f 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm @@ -1108,23 +1108,32 @@ sub respond_with_status { } __PACKAGE__->register_method( - api_name => "open-ils.vandelay.match_set_point.get_tree", - method => "match_set_point_get_tree", + api_name => "open-ils.vandelay.match_set.get_tree", + method => "match_set_get_tree", api_level => 1, argc => 1 ); -sub match_set_point_get_tree { - my ($self, $conn, $parent_id) = @_; +sub match_set_get_tree { + my ($self, $conn, $authtoken, $match_set_id) = @_; - $parent_id = int($parent_id) or return; + $match_set_id = int($match_set_id) or return; - my $tree = new_editor->search_vandelay_match_set_point([ - {"parent" => $parent_id}, - {"flesh" => -1, "flesh_fields" => ["children"]} + my $e = new_editor("authtoken" => $authtoken); + $e->checkauth or return $e->die_event; + + my $set = $e->retrieve_vandelay_match_set($match_set_id) or + return $e->die_event; + + $e->allowed("ADMIN_IMPORT_MATCH_SET", $set->owner) or + return $e->die_event; + + my $tree = $e->search_vandelay_match_set_point([ + {"match_set" => $match_set_id, "parent" => undef}, + {"flesh" => -1, "flesh_fields" => {"vmsp" => ["children"]}} ]) or return $e->die_event; - return $tree; + return pop @$tree; } diff --git a/Open-ILS/src/sql/Pg/012.schema.vandelay.sql b/Open-ILS/src/sql/Pg/012.schema.vandelay.sql index 804d8ad4a6..9bfd413e5e 100644 --- a/Open-ILS/src/sql/Pg/012.schema.vandelay.sql +++ b/Open-ILS/src/sql/Pg/012.schema.vandelay.sql @@ -23,6 +23,7 @@ CREATE TABLE vandelay.match_set_point ( svf TEXT REFERENCES config.record_attr_definition (name), tag TEXT, subfield TEXT, + negate BOOL DEFAULT FALSE, quality INT NOT NULL DEFAULT 1, -- higher is better CONSTRAINT vmsp_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL), CONSTRAINT vmsp_need_a_tag_or_a_ff_or_a_bo CHECK ( diff --git a/Open-ILS/web/js/ui/default/vandelay/match_set.js b/Open-ILS/web/js/ui/default/vandelay/match_set.js new file mode 100644 index 0000000000..1e51b1a67d --- /dev/null +++ b/Open-ILS/web/js/ui/default/vandelay/match_set.js @@ -0,0 +1,355 @@ +dojo.require("dojo.data.ItemFileWriteStore"); +dojo.require("dijit.Tree"); +dojo.require("dijit.form.Button"); +dojo.require("dojo.dnd.Source"); +dojo.require("dijit._tree.dndSource"); +//dojo.require("openils.vandelay.TreeDndSource"); +dojo.require("openils.vandelay.TreeStoreModel"); +dojo.require("openils.CGI"); +dojo.require("openils.User"); +dojo.require("openils.Util"); +dojo.require("openils.PermaCrud"); +dojo.require("openils.widget.ProgressDialog"); + +var node_editor; +var _crads; +var CGI; + +function _find_crad_by_name(name) { + for (var i = 0; i < _crads.length; i++) { + if (_crads[i].name() == name) + return _crads[i]; + } + return null; +} + +function NodeEditor() { + var self = this; + + var _svf_select_template = null; + var _factories_by_type = { + "svf": function() { + if (!_svf_select_template) { + _svf_select_template = dojo.create( + "select", {"fmfield": "svf"} + ); + for (var i=0; i<_crads.length; i++) { + dojo.create( + "option", { + "value": _crads[i].name(), + "innerHTML": _crads[i].label() + }, _svf_select_template + ); + } + } + + var select = dojo.clone(_svf_select_template); + dojo.attr(select, "id", "svf-select"); + var label = dojo.create( + "label", { + "for": "svf-select", "innerHTML": "Single-Value-Field:" + } + ); + + var tr = dojo.create("tr"); + dojo.place(label, dojo.create("td", null, tr)); + dojo.place(select, dojo.create("td", null, tr)); + + return [tr]; + }, + "tag": function() { + var rows = [dojo.create("tr"), dojo.create("tr")]; + dojo.create( + "label", { + "for": "tag-input", "innerHTML": "Tag:" + }, dojo.create("td", null, rows[0]) + ); + dojo.create( + "input", { + "id": "tag-input", + "type": "text", + "size": 4, + "maxlength": 3, + "fmfield": "tag" + }, dojo.create("td", null, rows[0]) + ); + dojo.create( + "label", { + "for": "subfield-input", "innerHTML": "Subfield: \u2021" + }, dojo.create("td", null, rows[1]) + ); + dojo.create( + "input", { + "id": "subfield-input", + "type": "text", + "size": 2, + "maxlength": 1, + "fmfield": "subfield" + }, dojo.create("td", null, rows[1]) + ); + return rows; + }, + "bool_op": function() { + var tr = dojo.create("tr"); + dojo.create( + "label", + {"for": "operator-select", "innerHTML": "Operator:"}, + dojo.create("td", null, tr) + ); + var select = dojo.create( + "select", {"fmfield": "bool_op", "id": "operator-select"}, + dojo.create("td", null, tr) + ); + dojo.create("option", {"value": "AND", "innerHTML": "AND"}, select); + dojo.create("option", {"value": "OR", "innerHTML": "OR"}, select); + + return [tr]; + } + }; + + function _simple_value_getter(control) { + if (typeof control.selectedIndex != "undefined") + return control.options[control.selectedIndex].value; + else if (dojo.attr(control, "type") == "checkbox") + return control.checked; + else + return control.value; + }; + + this._init = function(dnd_source, node_editor_container) { + this.dnd_source = dnd_source; + this.node_editor_container = dojo.byId(node_editor_container); + }; + + this.clear = function() { + this.dnd_source.selectAll().deleteSelectedNodes(); + dojo.empty(this.node_editor_container); + }; + + this.update_draggable = function(draggable) { + var s = ""; + draggable.data = {"match_point": new vmsp()}; + var had_op = false; + dojo.query("[fmfield]", this.node_editor_container).forEach( + function(control) { + var used_svf = null; + var field = dojo.attr(control, "fmfield"); + var value = _simple_value_getter(control); + draggable.data.match_point[field](value); + + if (field == "subfield") + s += " \u2021"; + if (field == "svf") + used_svf = value; + if (field == "quality") + return; + if (field == "bool_op") + had_op = true; + if (field == "negate") { + if (value) { + if (had_op) + s = "N" + s; + else + s = "NOT " + s; + } + } else { + s += value; + } + + if (used_svf !== null) { + var our_crad = _find_crad_by_name(used_svf); + /* XXX i18n, use fmtted strings */ + s += " / " + our_crad.label() + "
" + + (our_crad.description() || "") + "
"; + } + } + ); + dojo.attr(draggable, "innerHTML", s); + }; + + this._add_consistent_controls = function(tgt) { + if (!this._consistent_controls) { + var trs = dojo.query("[consistent-controls]"); + this._consistent_controls = []; + for (var i = 0; i < trs.length; i++) + this._consistent_controls[i] = dojo.clone(trs[i]); + dojo.empty(trs[0].parentNode); + } + + this._consistent_controls.forEach( + function(node) { dojo.place(dojo.clone(node), tgt); } + ); + }; + + this.add = function(type) { + this.clear(); + + /* a representation, not the editing widgets, but will also carry + * the fieldmapper object when dragged to the tree */ + var draggable = dojo.create( + "li", { + "innerHTML": "Define your match point and drag me
" + + "to the tree on the right" + } + ); + + /* these are the editing widgets */ + var table = dojo.create("table", {"className": "node-editor"}); + + var nodes = _factories_by_type[type](); + for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table); + + this._add_consistent_controls(table); + + dojo.create( + "input", { + "type": "submit", "value": "Ok", + "onclick": function() { self.update_draggable(draggable); } + }, dojo.create( + "td", {"colspan": 2, "align": "center"}, + dojo.create("tr", null, table) + ) + ); + + dojo.place(table, this.node_editor_container, "only"); + /* XXX around here attach other data structures to the node */ + this.dnd_source.insertNodes(false, [draggable]); + }; + + this._init.apply(this, arguments); +} + +/* XXX replace later with code that will suit this function's purpose + * as well as that of update_draggable. */ +function display_name_from_point(point) { + /* quick and dirty */ + if (point.bool_op()) { + return (point.negate() == "t" ? "N" : "") + point.bool_op(); + } else if (point.svf()) { + return (point.negate() == "t" ? "NOT " : "") + point.svf(); + } else { + return (point.negate() == "t" ? "NOT " : "") + point.tag() + + "\u2021" + point.subfield(); + } +} + +/* dojoize_match_set_tree() takes an argument, "point", that is actually a + * vmsp fieldmapper object with descendants fleshed hierarchically. It turns + * that into a syntactically flat array but preserving the hierarchy + * semantically in the language used by dojo data stores, i.e., + * + * [ + * {'id': 'root', children:[{'_reference': '0'}, {'_reference': '1'}]}, + * {'id': '0', children:[]}, + * {'id': '1', children:[]} + * ], + * + */ +function dojoize_match_set_tree(point, refgen) { + /* XXX TODO test with deeper trees! */ + var root = false; + if (!refgen) { + refgen = 0; + root = true; + } + + var bathwater = point.children(); + point.children([]); + var item = { + "id": (root ? "root" : refgen), + "name": display_name_from_point(point), + "match_point": point.clone(), + "children": [] + }; + point.children(bathwater); + + var results = [item]; + + if (point.children()) { + for (var i = 0; i < point.children().length; i++) { + var child = point.children()[i]; + item.children.push({"_reference": ++refgen}); + results = results.concat( + dojoize_match_set_tree(child, refgen) + ); + } + } + + return results; +} + +function init_test() { + progress_dialog.show(true); + + CGI = new openils.CGI(); + + /* XXX No-one should have hundreds of these or anything, but theoretically + * this could be problematic with a big enough list of crad objects. */ + _crads = new openils.PermaCrud().retrieveAll( + "crad", {"order_by": {"crad": "label"}} + ); + + var match_set_tree = fieldmapper.standardRequest( + ["open-ils.vandelay", "open-ils.vandelay.match_set.get_tree"], + [openils.User.authtoken, CGI.param("match_set")] + ); + +// { +// "identifier": "id", "label": "name", "items": [ +// { +// "id": "root", "name": "AND", +// "children": [ +// {"_reference": "leaf0"}, {"_reference": "leaf1"} +// ] +// }, +// {"id": "leaf0", "name": "nonsense test"}, +// {"id": "leaf1", "name": "more nonsense"} +// ] +// } + + var store = new dojo.data.ItemFileWriteStore({ + "data": { + "identifier": "id", + "label": "name", + "items": dojoize_match_set_tree(match_set_tree) + } + }); + + var treeModel = new openils.vandelay.TreeStoreModel({ + store: store, "query": {"id": "root"} + }); + + var tree = new dijit.Tree( + { + "model": treeModel, + "dndController": dijit._tree.dndSource, + "dragThreshold": 8, + "betweenThreshold": 5, + "persist": false + }, "treeOne" + ); + + node_editor = new NodeEditor(mysrc, "node-editor-container"); + + dojo.connect( + mysrc, "onDndDrop", null, + function(source, nodes, copy, target) { + if (source == this) { + var model = target.tree.model; + model.getRoot( + function(root) { + model.getSimpleTree( + root, function(results) { alert(js2JSON(results)); } + ); + } + ); + node_editor.clear(); /* because otherwise this acts like a copy! */ + } else { + alert("XXX [mysrc] nodes length is " + nodes.length); /* XXX DEBUG */ + } + } + ); + progress_dialog.hide(); +} + +openils.Util.addOnLoad(init_test); diff --git a/Open-ILS/web/js/ui/default/vandelay/treetest.js b/Open-ILS/web/js/ui/default/vandelay/treetest.js deleted file mode 100644 index 4d4b7b2d34..0000000000 --- a/Open-ILS/web/js/ui/default/vandelay/treetest.js +++ /dev/null @@ -1,281 +0,0 @@ -dojo.require("dojo.data.ItemFileWriteStore"); -dojo.require("dijit.Tree"); -dojo.require("dijit.form.Button"); -dojo.require("dojo.dnd.Source"); -dojo.require("dijit._tree.dndSource"); -//dojo.require("openils.vandelay.TreeDndSource"); -dojo.require("openils.vandelay.TreeStoreModel"); -dojo.require("openils.User"); -dojo.require("openils.Util"); -dojo.require("openils.PermaCrud"); -dojo.require("openils.widget.ProgressDialog"); - -var node_editor; -var _crads; - -function _find_crad_by_name(name) { - for (var i = 0; i < _crads.length; i++) { - if (_crads[i].name() == name) - return _crads[i]; - } - return null; -} - -function NodeEditor() { - var self = this; - - var _svf_select_template = null; - var _factories_by_type = { - "svf": function() { - if (!_svf_select_template) { - _svf_select_template = dojo.create( - "select", {"fmfield": "svf"} - ); - for (var i=0; i<_crads.length; i++) { - dojo.create( - "option", { - "value": _crads[i].name(), - "innerHTML": _crads[i].label() - }, _svf_select_template - ); - } - } - - var select = dojo.clone(_svf_select_template); - dojo.attr(select, "id", "svf-select"); - var label = dojo.create( - "label", { - "for": "svf-select", "innerHTML": "Single-Value-Field:" - } - ); - - var tr = dojo.create("tr"); - dojo.place(label, dojo.create("td", null, tr)); - dojo.place(select, dojo.create("td", null, tr)); - - return [tr]; - }, - "tag": function() { - var rows = [dojo.create("tr"), dojo.create("tr")]; - dojo.create( - "label", { - "for": "tag-input", "innerHTML": "Tag:" - }, dojo.create("td", null, rows[0]) - ); - dojo.create( - "input", { - "id": "tag-input", - "type": "text", - "size": 4, - "maxlength": 3, - "fmfield": "tag" - }, dojo.create("td", null, rows[0]) - ); - dojo.create( - "label", { - "for": "subfield-input", "innerHTML": "Subfield: \u2021" - }, dojo.create("td", null, rows[1]) - ); - dojo.create( - "input", { - "id": "subfield-input", - "type": "text", - "size": 2, - "maxlength": 1, - "fmfield": "subfield" - }, dojo.create("td", null, rows[1]) - ); - return rows; - }, - "bool_op": function() { - var tr = dojo.create("tr"); - dojo.create( - "label", - {"for": "operator-select", "innerHTML": "Operator:"}, - dojo.create("td", null, tr) - ); - var select = dojo.create( - "select", {"fmfield": "bool_op", "id": "operator-select"}, - dojo.create("td", null, tr) - ); - dojo.create("option", {"value": "AND", "innerHTML": "AND"}, select); - dojo.create("option", {"value": "OR", "innerHTML": "OR"}, select); - - return [tr]; - } - }; - - function _simple_value_getter(control) { - if (typeof control.selectedIndex != "undefined") - return control.options[control.selectedIndex].value; - else if (dojo.attr(control, "type") == "checkbox") - return control.checked; - else - return control.value; - }; - - this._init = function(dnd_source, node_editor_container) { - this.dnd_source = dnd_source; - this.node_editor_container = dojo.byId(node_editor_container); - }; - - this.clear = function() { - this.dnd_source.selectAll().deleteSelectedNodes(); - dojo.empty(this.node_editor_container); - }; - - this.update_draggable = function(draggable) { - var s = ""; -// draggable.data = {"match_point": new vmsp()}; - var had_op = false; - dojo.query("[fmfield]", this.node_editor_container).forEach( - function(control) { - var used_svf = null; - var field = dojo.attr(control, "fmfield"); - var value = _simple_value_getter(control); -// draggable.data.match_point[field](value); - - if (field == "subfield") - s += " \u2021"; - if (field == "svf") - used_svf = value; - if (field == "quality") - return; - if (field == "bool_op") - had_op = true; - if (field == "negate") { - if (value) { - if (had_op) - s = "N" + s; - else - s = "NOT " + s; - } - } else { - s += value; - } - - if (used_svf !== null) { - var our_crad = _find_crad_by_name(used_svf); - /* XXX i18n, use fmtted strings */ - s += " / " + our_crad.label() + "
" + - (our_crad.description() || "") + "
"; - } - } - ); - dojo.attr(draggable, "innerHTML", s); - }; - - this._add_consistent_controls = function(tgt) { - if (!this._consistent_controls) { - var trs = dojo.query("[consistent-controls]"); - this._consistent_controls = []; - for (var i = 0; i < trs.length; i++) - this._consistent_controls[i] = dojo.clone(trs[i]); - dojo.empty(trs[0].parentNode); - } - - this._consistent_controls.forEach( - function(node) { dojo.place(dojo.clone(node), tgt); } - ); - }; - - this.add = function(type) { - this.clear(); - - /* a representation, not the editing widgets, but will also carry - * the fieldmapper object when dragged to the tree */ - var draggable = dojo.create( - "li", { - "innerHTML": "Define your match point and drag me
" + - "to the tree on the right" - } - ); - - /* these are the editing widgets */ - var table = dojo.create("table", {"className": "node-editor"}); - - var nodes = _factories_by_type[type](); - for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table); - - this._add_consistent_controls(table); - - dojo.create( - "input", { - "type": "submit", "value": "Ok", - "onclick": function() { self.update_draggable(draggable); } - }, dojo.create( - "td", {"colspan": 2, "align": "center"}, - dojo.create("tr", null, table) - ) - ); - - dojo.place(table, this.node_editor_container, "only"); - /* XXX around here attach other data structures to the node */ - this.dnd_source.insertNodes(false, [draggable]); - }; - - this._init.apply(this, arguments); -} - -function init_test() { - progress_dialog.show(true); - - /* XXX No-one should have hundreds of these or anything, but theoretically - * this could be problematic with a big enough list of crad objects. */ - _crads = new openils.PermaCrud().retrieveAll( - "crad", {"order_by": {"crad": "label"}} - ); - - var store = new dojo.data.ItemFileWriteStore({ - "data": { - "identifier": "id", "label": "name", "items": [ - { - "id": "root", "name": "AND", - "children": [ - {"_reference": "leaf0"}, {"_reference": "leaf1"} - ] - }, - {"id": "leaf0", "name": "nonsense test"}, - {"id": "leaf1", "name": "more nonsense"} - ] - } - }); - - var treeModel = new openils.vandelay.TreeStoreModel({ - store: store, "query": {"id": "root"} - }); - - var tree = new dijit.Tree( - { - "model": treeModel, - "dndController": dijit._tree.dndSource, - "dragThreshold": 8, - "betweenThreshold": 5, - "persist": false - }, "treeOne" - ); - - node_editor = new NodeEditor(mysrc, "node-editor-container"); - - dojo.connect( - mysrc, "onDndDrop", null, - function(source, nodes, copy, target) { - if (source == this) { - var model = target.tree.model; - model.getRoot( - function(root) { - model.getSimpleTree( - root, function(results) { alert(js2JSON(results)); } - ); - } - ); - node_editor.clear(); /* because otherwise this acts like a copy! */ - } else { - alert("XXX [mysrc] nodes length is " + nodes.length); /* XXX DEBUG */ - } - } - ); - progress_dialog.hide(); -} - -openils.Util.addOnLoad(init_test); diff --git a/Open-ILS/web/templates/default/vandelay/match_set.tt2 b/Open-ILS/web/templates/default/vandelay/match_set.tt2 new file mode 100644 index 0000000000..7883cf445d --- /dev/null +++ b/Open-ILS/web/templates/default/vandelay/match_set.tt2 @@ -0,0 +1,59 @@ +[% WRAPPER 'default/base.tt2' %] +[% ctx.page_title = 'Vandelay Match Set' %] + +

[% ctx.page_title %]

+ + + + + + + + + + +
+
+
+ + + +
+
+
+
+
+
+
+
    +
    + +
    +
    Your Expression
    +
    +
    +
    +
    + +[% END %] diff --git a/Open-ILS/web/templates/default/vandelay/treetest.tt2 b/Open-ILS/web/templates/default/vandelay/treetest.tt2 deleted file mode 100644 index 32e72c9893..0000000000 --- a/Open-ILS/web/templates/default/vandelay/treetest.tt2 +++ /dev/null @@ -1,59 +0,0 @@ -[% WRAPPER 'default/base.tt2' %] -[% ctx.page_title = 'Vandelay Match Set Points' %] - -

    [% ctx.page_title %]

    - - - - - - - - - - -
    -
    -
    - - - -
    -
    -
    -
    -
    -
    -
    -
      -
      - -
      -
      Your Expression
      -
      -
      -
      -
      - -[% END %]