[open-ils-commits] r16140 - in trunk/Open-ILS: examples src/perlmods/OpenILS/Application/Acq web/css/skin/default web/js/dojo/openils/acq/nls web/js/dojo/openils/widget web/js/ui/default/acq web/js/ui/default/acq/search web/opac/locale/en-US web/templates/default/acq web/templates/default/acq/search xul/staff_client/chrome/content/main xul/staff_client/chrome/locale/en-US (senator)

svn at svn.open-ils.org svn at svn.open-ils.org
Tue Apr 6 12:40:36 EDT 2010


Author: senator
Date: 2010-04-06 12:40:33 -0400 (Tue, 06 Apr 2010)
New Revision: 16140

Added:
   trunk/Open-ILS/web/js/ui/default/acq/search/
   trunk/Open-ILS/web/js/ui/default/acq/search/unified.js
   trunk/Open-ILS/web/templates/default/acq/search/
   trunk/Open-ILS/web/templates/default/acq/search/unified.tt2
Modified:
   trunk/Open-ILS/examples/fm_IDL.xml
   trunk/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm
   trunk/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm
   trunk/Open-ILS/web/css/skin/default/acq.css
   trunk/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
   trunk/Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
   trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
   trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js
   trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
   trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
Log:
Acq: unified search interface for LI, PO, and PL. Usable but not 100% finished

Still to come:
    Paging results (very important with large result sets)
    Searchable timestamp fields (those don't work yet)
    Search terms interpreted from URI (to enable returning to search later)
    Misc PO and PL controls to enable acting on search results
    ** Bib record searching


Modified: trunk/Open-ILS/examples/fm_IDL.xml
===================================================================
--- trunk/Open-ILS/examples/fm_IDL.xml	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/examples/fm_IDL.xml	2010-04-06 16:40:33 UTC (rev 16140)
@@ -5238,7 +5238,7 @@
 		<fields oils_persist:primary="id" oils_persist:sequence="acq.picklist_id_seq">
 			<field reporter:label="Picklist ID" name="id" reporter:datatype="id" />
 			<field reporter:label="Owner" name="owner" reporter:datatype="link" />
-			<field reporter:label="Org Unit" name="org_unit" reporter:datatype="link" />
+			<field reporter:label="Org Unit" name="org_unit" reporter:datatype="org_unit" />
 			<field reporter:label="Name" name="name" reporter:datatype="text" oils_persist:i18n="true" />
 			<field reporter:label="Creation Time" name="create_time" reporter:datatype="timestamp" />
 			<field reporter:label="Edit Time" name="edit_time" reporter:datatype="timestamp" />

Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm	2010-04-06 16:40:33 UTC (rev 16140)
@@ -899,19 +899,23 @@
 sub retrieve_purchase_order_impl {
     my($e, $po_id, $options) = @_;
 
-    # let's just always flesh this if it's there. what the hey.
-    my $flesh = {
-        "flesh" => 1, "flesh_fields" => {"acqpo" => ["cancel_reason"]}
-    };
+    my $flesh = {"flesh" => 1, "flesh_fields" => {"acqpo" => []}};
 
     $options ||= {};
+    unless ($options->{"no_flesh_cancel_reason"}) {
+        push @{$flesh->{"flesh_fields"}->{"acqpo"}}, "cancel_reason";
+    }
     if ($options->{"flesh_notes"}) {
         push @{$flesh->{"flesh_fields"}->{"acqpo"}}, "notes";
     }
     if ($options->{"flesh_provider"}) {
         push @{$flesh->{"flesh_fields"}->{"acqpo"}}, "provider";
     }
-    my $po = $e->retrieve_acq_purchase_order([$po_id, $flesh])
+
+    my $args = (@{$flesh->{"flesh_fields"}->{"acqpo"}}) ?
+        [$po_id, $flesh] : $po_id;
+
+    my $po = $e->retrieve_acq_purchase_order($args)
         or return $e->event;
 
     if($$options{flesh_lineitems}) {

Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm	2010-04-06 16:40:33 UTC (rev 16140)
@@ -156,37 +156,39 @@
 # alias, given names and family name.
 sub prepare_au_terms {
     my ($terms, $join_num) = @_;
+
     my @joins = ();
+    my $nots = 0;
     $join_num ||= 0;
 
     foreach my $conj (qw/-and -or/) {
         next unless exists $terms->{$conj};
 
         my @new_outer_terms = ();
-        foreach my $hint_unit (@{$terms->{$conj}}) {
+        HINT_UNIT: foreach my $hint_unit (@{$terms->{$conj}}) {
             my $hint = (keys %$hint_unit)[0];
             (my $plain_hint = $hint) =~ y/+//d;
+            if ($hint eq "-not") {
+                $hint_unit = $hint_unit->{$hint};
+                $nots++;
+                redo HINT_UNIT;
+            }
 
             if (my $links = get_fm_links_by_hint($plain_hint) and
                 $plain_hint ne "acqlia") {
                 my @new_terms = ();
-                foreach my $pair (@{$hint_unit->{$hint}}) {
-                    my ($attr, $value) = breakdown_term($pair);
-                    if ($links->{$attr} and
-                        $links->{$attr}->{"class"} eq "au") {
-                        push @joins, [$plain_hint, $attr, $join_num];
-                        push @new_outer_terms, gen_au_term($value, $join_num);
-                        $join_num++;
-                    } else {
-                        push @new_terms, $pair;
-                    }
-                }
-                if (@new_terms) {
-                    $hint_unit->{$hint} = [ @new_terms ];
-                } else {
+                my ($attr, $value) = breakdown_term($hint_unit->{$hint});
+                if ($links->{$attr} and
+                    $links->{$attr}->{"class"} eq "au") {
+                    push @joins, [$plain_hint, $attr, $join_num];
+                    my $au_term = gen_au_term($value, $join_num);
+                    $au_term = {"-not" => $au_term} if $nots--;
+                    push @new_outer_terms, $au_term;
+                    $join_num++;
                     delete $hint_unit->{$hint};
                 }
             }
+            $hint_unit = {"-not" => $hint_unit} if $nots--;
             push @new_outer_terms, $hint_unit if scalar keys %$hint_unit;
         }
         $terms->{$conj} = [ @new_outer_terms ];
@@ -203,7 +205,6 @@
     foreach my $class (qw/acqpo acqpl jub/) {
         next if not exists $terms->{$class};
 
-        my $clause = [];
         $outer_clause->{$conj} = [] unless $outer_clause->{$conj};
         foreach my $unit (@{$terms->{$class}}) {
             my ($k, $v, $fuzzy, $between, $not) = breakdown_term($unit);
@@ -218,9 +219,10 @@
                 next;
             }
 
-            push @$clause, $not ? {"-not" => $term_clause} : $term_clause;
+            my $clause = {"+" . $class => $term_clause};
+            $clause = {"-not" => $clause} if $not;
+            push @{$outer_clause->{$conj}}, $clause;
         }
-        push @{$outer_clause->{$conj}}, {"+" . $class => $clause};
     }
 
     if ($terms->{"acqlia"}) {
@@ -328,12 +330,12 @@
         "from" => {
             "jub" => {
                 "acqpo" => {
-                    "type" => "left",
+                    "type" => "full",
                     "field" => "id",
                     "fkey" => "purchase_order"
                 },
                 "acqpl" => {
-                    "type" => "left",
+                    "type" => "full",
                     "field" => "id",
                     "fkey" => "picklist"
                 }
@@ -352,6 +354,9 @@
         };
     };
 
+    # TODO find instances of fields of type "timestamp" and massage the
+    # comparison to match search input (which is only at date precision,
+    # not timestamp).
     my $offset = add_au_joins($query->{"from"}, prepare_au_terms($and_terms));
     add_au_joins($query->{"from"}, prepare_au_terms($or_terms, $offset));
 
@@ -369,7 +374,16 @@
     }
 
     my $results = $e->json_query($query) or return $e->die_event;
-    $conn->respond($retriever->($e, $_->{"id"}, $options)) foreach (@$results);
+    if ($options->{"id_list"}) {
+        foreach (@$results) {
+            $conn->respond($_->{"id"}) if $_->{"id"};
+        }
+    } else {
+        foreach (@$results) {
+            $conn->respond($retriever->($e, $_->{"id"}, $options))
+                if $_->{"id"};
+        }
+    }
     $e->disconnect;
     undef;
 }

Modified: trunk/Open-ILS/web/css/skin/default/acq.css
===================================================================
--- trunk/Open-ILS/web/css/skin/default/acq.css	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/web/css/skin/default/acq.css	2010-04-06 16:40:33 UTC (rev 16140)
@@ -195,9 +195,24 @@
     border:1px solid #888;
 }
 
-
 /* INVOICING */
 #oils-acq-invoice-table td { padding: 5px; }
 #acq-invoice-new-msg { font-weight: bold; margin: 10px;}
 #acq-invoice-li-details { padding: 10px; font-weight: bold; border: 1px solid #888; margin: 10px; }
 #acq-invoice-create { margin: 10px; }
+#acq-unified-heading { margin-bottom: 10px; }
+#acq-unified-heading-actual { float: left; width: 50%; font-size: 120%; font-weight: bold; }
+#acq-unified-heading-controls { float: right; width: 50%; text-align: right; }
+#acq-unified-form { margin-bottom: 10px; border-bottom: 1px dashed #666; padding-bottom: 10px; }
+#acq-unified-form > div { margin: 6px 0; }
+option[disabled="disabled"] { font-style: italic; }
+#acq-unified-terms-table { width: 100%; }
+#acq-unified-terms-table td { padding: 4px 0; }
+#acq-unified-add-term { padding-bottom: 20px; }
+.acq-unified-option-header { padding-left: 12px; }
+.acq-unified-option-regular { padding-left: 24px; }
+.acq-unified-terms-selector { width: 20%; }
+.acq-unified-terms-widget { width: 60%; }
+.acq-unified-terms-match { width: 15%; }
+.acq-unified-terms-remove { width: 5%; text-align: right; }
+.acq-unified-remover { color: #c00; }

Modified: trunk/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
===================================================================
--- trunk/Open-ILS/web/js/dojo/openils/acq/nls/acq.js	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/web/js/dojo/openils/acq/nls/acq.js	2010-04-06 16:40:33 UTC (rev 16140)
@@ -57,4 +57,5 @@
     'CONFIRM_FUNDS_AT_STOP': "One or more of the selected funds has a balance below its stop level.\nYou may not be able to activate purchase orders incorporating these copies.\nContinue?",
     'CONFIRM_FUNDS_AT_WARNING': "One or more of the selected funds has a balance below its warning level.\nContinue?",
     'INVOICE_ITEM_DETAILS' : "${0} <br/> ${1} <br/> ${2}. <br/> Estimated Price: $${3}. <br/> Lineitem ID: ${4} <br/> PO: ${5} <br/> Order Date: ${6}",
+    'UNNAMED': "Unnamed"
 }

Modified: trunk/Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
===================================================================
--- trunk/Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js	2010-04-06 16:40:33 UTC (rev 16140)
@@ -494,7 +494,7 @@
             } else {
 
                 this.baseWidgetValue(this.widgetValue);
-                if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && !this.selfReference)
+                if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
                     this.widget.attr('disabled', true); 
                 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
                     this.widget.attr('disabled', true); 

Added: trunk/Open-ILS/web/js/ui/default/acq/search/unified.js
===================================================================
--- trunk/Open-ILS/web/js/ui/default/acq/search/unified.js	                        (rev 0)
+++ trunk/Open-ILS/web/js/ui/default/acq/search/unified.js	2010-04-06 16:40:33 UTC (rev 16140)
@@ -0,0 +1,445 @@
+dojo.require("openils.widget.AutoGrid");
+dojo.require("openils.widget.AutoWidget");
+dojo.require("openils.PermaCrud");
+dojo.require("openils.Util");
+
+var termSelectorFactory;
+var termManager;
+var resultManager;
+var pcrud = new openils.PermaCrud();
+
+/* typing save: add getValue() to all HTML <select> elements */
+HTMLSelectElement.prototype.getValue = function() {
+    return this.options[this.selectedIndex].value;
+}
+
+/* quickly find elements by the value of a "name" attribute */
+function nodeByName(name, root) {
+    return dojo.query("[name='" + name + "']", root)[0];
+}
+
+function hideForm() {
+    openils.Util.hide("acq-unified-hide-form");
+    openils.Util.show("acq-unified-reveal-form", "inline");
+    openils.Util.hide("acq-unified-form");
+}
+
+function revealForm() {
+    openils.Util.hide("acq-unified-reveal-form");
+    openils.Util.show("acq-unified-hide-form", "inline");
+    openils.Util.show("acq-unified-form");
+}
+
+/* The TermSelectorFactory will be instantiated by the TermManager. It
+ * provides HTML select controls whose options are all the searchable
+ * fields.  Selecting a field from one of these controls will create the
+ * appopriate type of corresponding widget for the user to enter a search
+ * term against the selected field.
+ */
+function TermSelectorFactory(terms) {
+    var self = this;
+    this.terms = terms;
+
+    this.template = dojo.create("select");
+    this.template.appendChild(
+        dojo.create("option", {
+            "disabled": "disabled",
+            "selected": "selected",
+            "value": "",
+            "innerHTML": "Select Search Field" // XXX i18n
+        })
+    );
+
+    /* Create abbreviations for class names to make field categories
+     * more readable in field selector control. */
+    this._abbreviate = function(s) {
+        var last, result;
+        for (var i = 0; i < s.length; i++) {
+            if (s[i] != " ") {
+                if (!i) result = s[i];
+                else if (last == " ") result += s[i];
+            }
+            last = s[i];
+        }
+        return result;
+    };
+
+    var selectorMethods = {
+        /* Important: within the following functions, "this" refers to one
+         * HTMLSelect object, and "self" refers to the TermSelectorFactory. */
+        "getTerm": function() {
+            var parts = this.getValue().split(":");
+            return {
+                "hint": parts[0],
+                "field": parts[1],
+                "datatype": self.terms[parts[0]][parts[1]].datatype
+            };
+        },
+        "makeWidget": function(parentNode, wStore, callback) {
+            var term = this.getTerm();
+            var widgetKey = this.uniq;
+            if (term.hint == "acqlia") {
+                wStore[widgetKey] = dojo.create(
+                    "input", {"type": "text"}, parentNode, "only"
+                );
+                wStore[widgetKey].focus();
+                if (typeof(callback) == "function")
+                    callback(term, widgetKey);
+            } else {
+                var widget = new openils.widget.AutoFieldWidget({
+                    "fmClass": term.hint,
+                    "fmField": term.field,
+                    "noDisablePkey": true,
+                    "parentNode": dojo.create("span", null, parentNode, "only")
+                });
+                widget.build(
+                    function(w) {
+                        wStore[widgetKey] = w;
+                        w.focus();
+                        if (typeof(callback) == "function")
+                            callback(term, widgetKey);
+                    }
+                );
+            }
+        }
+    }
+
+    for (var hint in this.terms) {
+        var optgroup = dojo.create(
+            "optgroup", {"value": "", "label": this.terms[hint].__label}
+        );
+        var prefix = this._abbreviate(this.terms[hint].__label);
+
+        for (var field in this.terms[hint]) {
+            if (!/^__/.test(field)) {
+                optgroup.appendChild(
+                    dojo.create("option", {
+                        "class": "acq-unified-option-regular",
+                        "value": hint + ":" + field,
+                        "innerHTML": prefix + " - " +
+                            this.terms[hint][field].label
+                    })
+                );
+            }
+        }
+
+        this.template.appendChild(optgroup);
+    }
+
+    this.make = function(n) {
+        var node = dojo.clone(this.template);
+        node.uniq = n;
+        dojo.attr(node, "id", "term-" + n);
+        for (var name in selectorMethods)
+            node[name] = selectorMethods[name];
+        return node;
+    };
+}
+
+/* The term manager retrieves information from the IDL about all the fields
+ * in the classes that we consider searchable for our purpose.  It maintains
+ * a dynamic HTML table of search terms, using the TermSelectorFactory
+ * to generate search field selectors, which in turn provide appropriate
+ * widgets for entering search terms.  The TermManager provides search term
+ * modifiers (fuzzy searching, not searching). The TermManager also handles
+ * adding and removing rows of search terms, as well as building the search
+ * query to pass to the middle layer from the search term widgets.
+ */
+function TermManager() {
+    var self = this;
+
+    this.terms = {};
+    ["jub", "acqpl", "acqpo"].forEach(
+        function(hint) {
+            var o = {};
+            o.__label = fieldmapper.IDL.fmclasses[hint].label;
+            fieldmapper.IDL.fmclasses[hint].fields.forEach(
+                function(field) {
+                    if (!field.virtual) {
+                        o[field.name] = {
+                            "label": field.label, "datatype": field.datatype
+                        };
+                    }
+                }
+            );
+            self.terms[hint] = o;
+        }
+    );
+
+    this.terms.acqlia = {"__label": fieldmapper.IDL.fmclasses.acqlia.label};
+    pcrud.retrieveAll("acqliad", {"order_by": {"acqliad": "id"}}).forEach(
+        function(def) {
+            self.terms.acqlia[def.id()] =
+                {"label": def.description(), "datatype": "text"}
+        }
+    );
+
+    this.selectorFactory = new TermSelectorFactory(this.terms);
+    this.template = dojo.byId("acq-unified-terms-tbody").
+        removeChild(dojo.byId("acq-unified-terms-row-tmpl"));
+    dojo.attr(this.template, "id");
+
+    this.rowId = 0;
+    this.widgets = {};
+
+    this._row = function(id) { return dojo.byId("term-row-" + id); };
+    this._selector = function(id) { return dojo.byId("term-" + id); };
+    this._match_how = function(id) { return dojo.byId("term-match-" + id); };
+
+    this.removerButton = function(n) {
+        return dojo.create("button", {
+            "innerHTML": "X",
+            "class": "acq-unified-remover",
+            "onclick": function() { self.removeRow(n); }
+        });
+    };
+
+    this.matchHowAllow = function(id, what, which) {
+        dojo.query(
+            "option[value*='" + what + "']", this._match_how(id)
+        ).forEach(function(o) { o.disabled = !which; });
+    };
+
+    this.getLinkTarget = function(term) {
+        return fieldmapper.IDL.fmclasses[term.hint].
+            field_map[term.field]["class"];
+    };
+
+    this.updateRowWidget = function(id) {
+        var where = nodeByName("widget", this._row(id));
+
+        delete this.widgets[id];
+        dojo.empty(where);
+
+        this._selector(id).makeWidget(
+            where, this.widgets,
+            function(term, key) {
+                var w = self.widgets[key];
+                var can_do_fuzzy;
+                if (term.datatype == "id") {
+                    can_do_fuzzy = false;
+                } else if (term.datatype == "link") {
+                    can_do_fuzzy = (self.getLinkTarget(term) == "au");
+                } else if (typeof(w.declaredClass) != "undefined") {
+                    can_do_fuzzy = Boolean(w.declaredClass.match(/form\.Text/));
+                } else {
+                    var type = dojo.attr(w, "type");
+                    if (type)
+                        can_do_fuzzy = (type == "text");
+                    else
+                        can_do_fuzzy = false;
+                }
+                self.matchHowAllow(id, "__fuzzy", can_do_fuzzy);
+            }
+        );
+    };
+
+    this.addRow = function() {
+        var uniq = (this.rowId)++;
+
+        var row = dojo.clone(this.template);
+        dojo.attr(row, "id", "term-row-" + uniq);
+
+        var selector = this.selectorFactory.make(uniq);
+        dojo.attr(
+            selector,
+            "onchange",
+            function() { self.updateRowWidget(uniq); }
+        );
+
+        var match_how = dojo.query("select", nodeByName("match", row))[0];
+        dojo.attr(match_how, "id", "term-match-" + uniq);
+        dojo.attr(match_how, "selectedIndex", 0);
+
+        nodeByName("selector", row).appendChild(selector);
+        nodeByName("remove", row).appendChild(this.removerButton(uniq));
+
+        dojo.place(row, "acq-unified-terms-tbody", "last");
+    }
+
+    this.removeRow = function(id) {
+        delete this.widgets[id];
+        dojo.destroy(this._row(id));
+    };
+
+    this.buildSearchObject = function() {
+        var so = {};
+
+        for (var id in this.widgets) {
+            var attr_parts = this._selector(id).getValue().split(":");
+            if (attr_parts.length != 2)
+                continue;
+
+            var hint = attr_parts[0];
+            var attr = attr_parts[1];
+            var match_how =
+                this._match_how(id).getValue().split(",").filter(Boolean);
+
+            var value;
+            try {
+                value = this.widgets[id].attr("value");
+            } catch (E) {
+                value = this.widgets[id].value;
+            }
+
+            if (!so[hint])
+                so[hint] = [];
+            var unit = {};
+            match_how.forEach(function(key) { unit[key] = true; });
+            unit[attr] = value;
+
+            so[hint].push(unit);
+        }
+        return so;
+    };
+}
+
+/* The result manager is used primarily when the users submits a search.  It
+ * consults the termManager to get the search query to send to the middl
+ * layer, and it chooses which ML method to call as well as what widgets to use
+ * to display the results.
+ */
+function ResultManager(liTable, poGrid, plGrid) {
+    var self = this;
+
+    this.liTable = liTable;
+    this.poGrid = poGrid;
+    this.plGrid = plGrid;
+    this.poCache = {};
+    this.plCache = {};
+
+    this.result_types = {
+        "lineitem": {
+            "search_options": {
+                "flesh_attrs": true,
+                "flesh_cancel_reason": true,
+                "flesh_notes": true
+            },
+            "revealer": function() {
+                self.liTable.reset();
+                self.liTable.show("list");
+            }
+        },
+        "purchase_order": {
+            "search_options": {
+                "no_flesh_cancel_reason": true
+            },
+            "revealer": function() {
+                self.poGrid.resetStore();
+                self.poCache = {};
+            }
+        },
+        "picklist": {
+            "search_options": {
+                "flesh_lineitem_count": true,
+                "flesh_owner": true
+            },
+            "revealer": function() {
+                self.plGrid.resetStore();
+                self.plCache = {};
+            }
+        },
+        "no_results": {
+            "revealer": function() { alert(localeStrings.NO_RESULTS); }
+        }
+    };
+
+    this._add_lineitem = function(li) {
+        this.liTable.addLineitem(li);
+    };
+
+    this._add_purchase_order = function(po) {
+        this.poCache[po.id()] = po;
+        this.poGrid.store.newItem(acqpo.toStoreItem(po));
+    };
+
+    this._add_picklist = function(pl) {
+        this.plCache[pl.id()] = pl;
+        this.plGrid.store.newItem(acqpl.toStoreItem(pl));
+    };
+
+    this._finish_purchase_order = function() {
+        this.poGrid.hideLoadProgressIndicator();
+    };
+
+    this._finish_picklist = function() {
+        this.plGrid.hideLoadProgressIndicator();
+    };
+
+    this.add = function(which, what) {
+        var name = "_add_" + which;
+        if (this[name]) this[name](what);
+    };
+
+    this.finish = function(which) {
+        var name = "_finish_" + which;
+        if (this[name]) this[name]();
+    };
+
+    this.show = function(which) {
+        openils.Util.objectProperties(this.result_types).forEach(
+            function(rt) {
+                openils.Util[rt == which ? "show" : "hide"](
+                    "acq-unified-results-" + rt
+                );
+            }
+        );
+        this.result_types[which].revealer();
+    };
+
+    this.search = function(search_obj) {
+        var count_results = 0;
+        var result_type = dojo.byId("acq-unified-result-type").getValue();
+        var conjunction = dojo.byId("acq-unified-conjunction").getValue();
+
+        /* XXX TODO when result_type can be "lineitem_and_bib" there may be a
+         * totally different ML method to call; not sure how that will work
+         * yet. */
+        var method_name = "open-ils.acq." + result_type + ".unified_search";
+        var params = [
+            openils.User.authtoken,
+            null, null, null,
+            this.result_types[result_type].search_options
+        ];
+
+        params[conjunction == "and" ? 1 : 2] = search_obj;
+
+        fieldmapper.standardRequest(
+            ["open-ils.acq", method_name], {
+                "params": params,
+                "async": true,
+                "onresponse": function(r) {
+                    if (r = openils.Util.readResponse(r)) {
+                        if (!count_results++)
+                            self.show(result_type);
+                        self.add(result_type, r);
+                    }
+                },
+                "oncomplete": function() {
+                    if (!count_results)
+                        self.show("no_results");
+                    else
+                        self.finish(result_type);
+                }
+            }
+        );
+    }
+}
+
+/* The rest of the functions below handle the relatively unorganized
+ * miscellany of the search interface.
+ */
+
+/* onload */
+openils.Util.addOnLoad(
+    function() {
+        termManager = new TermManager();
+        termManager.addRow();
+        resultManager = new ResultManager(
+            new AcqLiTable(),
+            dijit.byId("acq-unified-po-grid"),
+            dijit.byId("acq-unified-pl-grid")
+        );
+        openils.Util.show("acq-unified-body");
+    }
+);

Modified: trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
===================================================================
--- trunk/Open-ILS/web/opac/locale/en-US/lang.dtd	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/web/opac/locale/en-US/lang.dtd	2010-04-06 16:40:33 UTC (rev 16140)
@@ -787,6 +787,8 @@
 <!ENTITY staff.main.menu.acq.bib_search.accesskey "T">
 <!ENTITY staff.main.menu.acq.li_search.label "Lineitem Search">
 <!ENTITY staff.main.menu.acq.li_search.accesskey "I">
+<!ENTITY staff.main.menu.acq.unified_search.label "Acquisitions Search">
+<!ENTITY staff.main.menu.acq.unified_search.accesskey "A">
 <!ENTITY staff.main.menu.acq.brief_record.label "New Brief Record">
 <!ENTITY staff.main.menu.acq.brief_record.accesskey "B">
 <!ENTITY staff.main.menu.acq.upload.label "Load Order Record">

Added: trunk/Open-ILS/web/templates/default/acq/search/unified.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/acq/search/unified.tt2	                        (rev 0)
+++ trunk/Open-ILS/web/templates/default/acq/search/unified.tt2	2010-04-06 16:40:33 UTC (rev 16140)
@@ -0,0 +1,167 @@
+[% WRAPPER "default/base.tt2" %]
+[% ctx.page_title = "Acquisitions Search" %]
+<script src="[% ctx.media_prefix %]/js/ui/default/acq/search/unified.js">
+</script>
+<script>
+    /* The only functions in this <script> element are for
+     * formatting/getting fields for autogrids. */
+    function getName(rowIndex, item) {
+        if (item) {
+            return {
+                "name": this.grid.store.getValue(item, "name") ||
+                    localeStrings.UNNAMED,
+                "id": this.grid.store.getValue(item, "id")
+            };
+        }
+    }
+
+    function getPlOwnerName(rowIndex, item) {
+        try {
+            return resultManager.plCache[this.grid.store.getValue(item, "id")].
+                owner().usrname();
+        } catch (E) {
+            return "";
+        }
+    }
+
+    function formatPoName(po) {
+        if (po) {
+            return "<a href='" + oilsBasePath + "/acq/po/view/" + po.id +
+                "'>" + po.name + "</a>";
+        }
+    }
+
+    function formatPlName(pl) {
+        if (pl) {
+            return "<a href='" + oilsBasePath + "/acq/picklist/view/" +
+                pl.id + "'>" + pl.name + "</a>";
+        }
+    }
+</script>
+<!-- later: "[% ctx.page_args.0 %]" -->
+<div id="acq-unified-body" class="hidden">
+    <div id="acq-unified-heading">
+        <span id="acq-unified-heading-actual">Acquisitions Search</span>
+        <span id="acq-unified-heading-controls">
+            <button id="acq-unified-hide-form" onclick="hideForm();">
+                Hide Search Form
+            </button>
+            <button id="acq-unified-reveal-form" onclick="revealForm();"
+                class="hidden">
+                Reveal Search Form
+            </button>
+        </span>
+        <div style="clear: both;"><!-- layout; don't remove --></div>
+    </div>
+    <div id="acq-unified-form">
+        <div>
+            <label for="acq-unified-result-type">Search for</label>
+            <select id="acq-unified-result-type">
+                <option value="lineitem">line items</option>
+                <option value="lineitem_and_bib" disabled="disabled">
+                    <!-- XXX not yet implemented -->
+                    line items &amp; catalog records
+                </option>
+                <option value="picklist">pick lists</option>
+                <option value="purchase_order">purchase orders</option>
+            </select>
+            <label for="acq-unified-conjunction">matching</label>
+            <select id="acq-unified-conjunction">
+                <option value="or">any</option>
+                <option value="and">all</option>
+            </select>
+            <label for="acq-unified-conjunction">
+                of the following terms:
+            </label>
+        </div>
+        <div id="acq-unified-terms">
+            <table id="acq-unified-terms-table">
+                <tbody id="acq-unified-terms-tbody">
+                    <tr id="acq-unified-terms-row-tmpl"
+                        class="acq-unified-terms-row">
+                        <td name="selector"
+                            class="acq-unified-terms-selector"></td>
+                        <td name="match"
+                            class="acq-unified-terms-match">
+                            <select>
+                                <option value="">matches exactly</option>
+                                <option value="__not">
+                                    does NOT match exactly
+                                </option>
+                                <option value="__fuzzy">contains</option>
+                                <option value="__not,__fuzzy">
+                                    does NOT contain
+                                </option>
+                            </select>
+                        </td>
+                        <td name="widget"
+                            class="acq-unified-terms-widget"></td>
+                        <td name="remove"
+                            class="acq-unified-terms-remove"></td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>
+        <div id="acq-unified-add-term">
+            <button onclick="termManager.addRow()">Add Search Term</button>
+        </div>
+        <div>
+            <button
+                onclick="resultManager.search(termManager.buildSearchObject())">
+                Search
+            </button>
+        </div>
+    </div>
+    <div id="acq-unified-results-purchase_order" class="hidden">
+        <table
+            id="acq-unified-po-grid"
+            autoHeight="true"
+            dojoType="openils.widget.AutoGrid"
+            query="{id: '*'}"
+            fieldOrder="['name', 'owner', 'ordering_agency', 'provider',
+                'create_time', 'edit_time', 'state']"
+            suppressFields="['owner', 'editor', 'creator']"
+            defaultCellWidth="'auto'"
+            showPaginator="true"
+            fmClass="acqpo">
+            <thead>
+                <tr>
+                    <th field="name" get="getName" formatter="formatPoName">
+                        Name
+                    </th>
+                </tr>
+            </thead>
+        </table>
+    </div>
+    <div id="acq-unified-results-picklist" class="hidden">
+        <table
+            id="acq-unified-pl-grid"
+            autoHeight="true"
+            dojoType="openils.widget.AutoGrid"
+            query="{id: '*'}"
+            fieldOrder="['name', 'owner', 'entry_count',
+                'create_time', 'edit_time']"
+            suppressFields="['editor', 'creator']"
+            defaultCellWidth="'auto'"
+            showPaginator="true"
+            fmClass="acqpl">
+            <thead>
+                <tr>
+                    <th field="name" get="getName" formatter="formatPlName">
+                        Name
+                    </th>
+                    <th field="owner" get="getPlOwnerName">Owner</th>
+                    <th field="entry_count">Entry Count</th>
+                </tr>
+            </thead>
+        </table>
+    </div>
+    <div id="acq-unified-results-no_results" class="hidden">
+        There are no results from your search.
+    </div>
+    <div id="acq-unified-results-lineitem" class="hidden">
+        [% INCLUDE "default/acq/common/li_table.tt2" %]
+    </div>
+</div>
+
+[% END %]

Modified: trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js
===================================================================
--- trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/xul/staff_client/chrome/content/main/menu.js	2010-04-06 16:40:33 UTC (rev 16140)
@@ -691,6 +691,10 @@
                 ['oncommand'],
                 function() { open_eg_web_page('acq/picklist/bib_search', 'menu.cmd_acq_bib_search.tab'); }
             ],
+            'cmd_acq_unified_search' : [
+                ['oncommand'],
+                function() { open_eg_web_page('acq/search/unified', 'menu.cmd_acq_unified_search.tab'); }
+            ],
             'cmd_acq_li_search' : [
                 ['oncommand'],
                 function() { open_eg_web_page('acq/lineitem/search', 'menu.cmd_acq_li_search.tab'); }

Modified: trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul	2010-04-06 16:40:33 UTC (rev 16140)
@@ -83,6 +83,7 @@
     <command id="cmd_acq_user_requests" />
     <command id="cmd_acq_bib_search" />
     <command id="cmd_acq_li_search" />
+    <command id="cmd_acq_unified_search" />
     <command id="cmd_acq_new_brief_record" />
     <command id="cmd_acq_view_fund" />
     <command id="cmd_acq_view_funding_source" />
@@ -253,13 +254,14 @@
 <!-- The Acquisitions menu on the main menu -->
 <menu id="main.menu.acq" label="&staff.main.menu.acq.label;" accesskey="&staff.main.menu.acq.accesskey;">
     <menupopup id="main.menu.acq.popup">
-        <menuitem label="&staff.main.menu.acq.picklist.label;" accesskey="&staff.main.menu.acq.picklist.accesskey;" command="cmd_acq_view_picklist"/>
+        <menuitem label="&staff.main.menu.acq.unified_search.label;" accesskey="&staff.main.menu.acq.unified_search.accesskey;" command="cmd_acq_unified_search"/>
         <menuitem label="&staff.main.menu.acq.bib_search.label;" accesskey="&staff.main.menu.acq.bib_search.accesskey;" command="cmd_acq_bib_search"/>
         <menuitem label="&staff.main.menu.acq.li_search.label;" accesskey="&staff.main.menu.acq.li_search.accesskey;" command="cmd_acq_li_search"/>
+        <menuitem label="&staff.main.menu.acq.po.label;" accesskey="&staff.main.menu.acq.po.accesskey;" command="cmd_acq_view_po" />
+        <menuitem label="&staff.main.menu.acq.picklist.label;" accesskey="&staff.main.menu.acq.picklist.accesskey;" command="cmd_acq_view_picklist"/>
+        <menuseparator />
         <menuitem label="&staff.main.menu.acq.upload.label;" accesskey="&staff.main.menu.acq.upload.accesskey;" command="cmd_acq_upload"/>
         <menuitem label="&staff.main.menu.acq.brief_record.label;" accesskey="&staff.main.menu.acq.brief_record.accesskey;" command="cmd_acq_new_brief_record"/>
-        <menuseparator />
-        <menuitem label="&staff.main.menu.acq.po.label;" accesskey="&staff.main.menu.acq.po.accesskey;" command="cmd_acq_view_po" />
         <menuitem label="&staff.main.menu.acq.po_events.label;" accesskey="&staff.main.menu.acq.po_events.accesskey;" command="cmd_acq_view_po_events" />
         <menuitem label="&staff.main.menu.acq.user_requests.label;" accesskey="&staff.main.menu.acq.user_requests.accesskey;" command="cmd_acq_user_requests" />
         <menuseparator />

Modified: trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
===================================================================
--- trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties	2010-04-06 15:33:17 UTC (rev 16139)
+++ trunk/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties	2010-04-06 16:40:33 UTC (rev 16140)
@@ -222,6 +222,7 @@
 menu.cmd_local_admin_transit_list.tab=Transits
 menu.cmd_acq_view_picklist.tab=Selection Lists
 menu.cmd_acq_bib_search.tab=Title Search
+menu.cmd_acq_unified_search.tab=Acquisitions Search
 menu.cmd_acq_li_search.tab=Lineitem Search
 menu.cmd_acq_upload.tab=Load Order Record
 menu.cmd_acq_new_brief_record.tab=New Brief Record



More information about the open-ils-commits mailing list