Acq: add search-by-file-of-terms to unified search
authorsenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Tue, 13 Apr 2010 20:17:55 +0000 (20:17 +0000)
committersenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Tue, 13 Apr 2010 20:17:55 +0000 (20:17 +0000)
This feature was present in the now deprecated lineitem search interface.
It works, but it is severely limited at the moment in the number of search
terms you can use, because search queries are made into URIs before the
search actually happens (in order to make backing up to old search results
possible), and at a certain point, very large URIs become infeasible.

Some adjustments to how search history is made naviagable may be in order.

git-svn-id: svn://svn.open-ils.org/ILS/trunk@16230 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/web/css/skin/default/acq.css
Open-ILS/web/js/dojo/openils/widget/XULTermLoader.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/widget/nls/XULTermLoader.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/search/unified.js
Open-ILS/web/templates/default/acq/search/unified.tt2

index 6cbc1fa..96eaeaa 100644 (file)
@@ -202,6 +202,12 @@ span[name="notes_alert_flag"] {color: #c00;font-weight: bold;font-size: 110%;mar
 #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-inoice-item-extra-info { padding-left: 10px; }
+.acq-inoice-item-info { font-weight: bold; }
+.acq-invoice-row td { border-bottom: 1px solid #e0e0e0; }
+.acq-invoice-invalid-amount input { color: red; font-weight: bold; }
+.acq-link-invoice-dialog td,.acq-link-invoice-dialog th {padding-top: 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; }
@@ -218,8 +224,3 @@ option[disabled="disabled"] { font-style: italic; }
 .acq-unified-terms-match { width: 15%; }
 .acq-unified-terms-remove { width: 5%; text-align: right; }
 .acq-unified-remover { color: #c00; }
-.acq-inoice-item-extra-info { padding-left: 10px; }
-.acq-inoice-item-info { font-weight: bold; }
-.acq-invoice-row td { border-bottom: 1px solid #e0e0e0; }
-.acq-invoice-invalid-amount input { color: red; font-weight: bold; }
-.acq-link-invoice-dialog td,.acq-link-invoice-dialog th {padding-top: 10px;}
diff --git a/Open-ILS/web/js/dojo/openils/widget/XULTermLoader.js b/Open-ILS/web/js/dojo/openils/widget/XULTermLoader.js
new file mode 100644 (file)
index 0000000..fb4770d
--- /dev/null
@@ -0,0 +1,96 @@
+if (!dojo._hasResource["openils.widget.XULTermLoader"]) {
+    dojo._hasResource["openils.widget.XULTermLoader"] = true;
+
+    dojo.provide("openils.widget.XULTermLoader");
+    dojo.require("openils.XUL");
+    dojo.requireLocalization("openils.widget", "XULTermLoader");
+
+    dojo.declare(
+        "openils.widget.XULTermLoader", [dijit.layout.ContentPane], {
+            "constructor": function(args) {
+                this.args = args;
+                this.terms = [];
+                this._ = openils.widget.XULTermLoader.localeStrings;
+
+                /* XXX Totally arbitrary defaults. Keeping them low for now
+                 * since all search terms have to be turned into a URL.
+                 * There will need to be a better long term solution.
+                 */
+                if (!this.args.fileSizeLimit)
+                    this.args.fileSizeLimit = 2048;
+                if (!this.args.termLimit)
+                    this.args.termLimit = 100;
+            },
+            "build": function(callback) {
+                var self = this;
+
+                this.domNode = dojo.create("span");
+                this.labelNode = dojo.create(
+                    "span", {
+                        "innerHTML": this._.LABEL_TEXT,
+                        "style": "padding-right: 8px;"
+                    }, this.domNode, "last"
+                );
+                this.countNode = dojo.create(
+                    "span", {"innerHTML": this.terms.length},
+                    this.labelNode, "first"
+                );
+                this.buttonNode = dojo.create(
+                    "button", {
+                        "innerHTML": this._.BUTTON_TEXT,
+                        "onclick": function() { self.loadTerms(); }
+                    },
+                    this.domNode, "last"
+                );
+
+                if (this.args.parentNode)
+                    dojo.place(this.domNode, this.args.parentNode, "last");
+
+                callback(this);
+            },
+            "updateCount": function() {
+                var value = this.attr("value");
+                if (dojo.isArray(value))
+                    this.terms = this.attr("value");
+                this.countNode.innerHTML = this.terms.length;
+            },
+            "focus": function() {
+                this.buttonNode.focus();
+            },
+            "loadTerms": function() {
+                try {
+                    if (this.terms.length >= this.args.termLimit) {
+                        alert(this._.TERM_LIMIT);
+                        return;
+                    }
+                    var data = this.parseUnimaginatively(
+                        openils.XUL.contentFromFileOpenDialog(
+                            this._.CHOOSE_FILE, this.args.fileSizeLimit
+                        )
+                    );
+                    if (data.length + this.terms.length >=
+                        this.args.termLimit) {
+                        alert(this._.TERM_LIMIT_SOME);
+                        var can = this.args.termLimit - this.terms.length;
+                        if (can > 0)
+                            this.terms = this.terms.concat(data.slice(0, can));
+                    } else {
+                        this.terms = this.terms.concat(data);
+                    }
+                    this.attr("value", this.terms);
+                    this.updateCount();
+                } catch(E) {
+                    alert(E);
+                }
+            },
+            "parseUnimaginatively": function(data) {
+                return data.split(/[\n, ]/).filter(
+                    function(o) { return o.length > 0; }
+                );
+            }
+        }
+    );
+
+    openils.widget.XULTermLoader.localeStrings =
+        dojo.i18n.getLocalization("openils.widget", "XULTermLoader");
+}
diff --git a/Open-ILS/web/js/dojo/openils/widget/nls/XULTermLoader.js b/Open-ILS/web/js/dojo/openils/widget/nls/XULTermLoader.js
new file mode 100644 (file)
index 0000000..71881a3
--- /dev/null
@@ -0,0 +1,7 @@
+{
+    'LABEL_TEXT': " term(s) loaded",
+    'BUTTON_TEXT': "Load more terms",
+    'TERM_LIMIT': "You have already loaded the maximum number of terms.",
+    'TERM_LIMIT_SOME': "Could not load all terms from the file without exceeding maximum number of terms. Some data not included.",
+    'CHOOSE_FILE': "Choose a file from which to read search terms."
+}
index 1413238..67114c1 100644 (file)
@@ -2,6 +2,7 @@ dojo.require("dojo.date.stamp");
 dojo.require("dojox.encoding.base64");
 dojo.require("openils.widget.AutoGrid");
 dojo.require("openils.widget.AutoWidget");
+dojo.require("openils.widget.XULTermLoader");
 dojo.require("openils.PermaCrud");
 
 var termSelectorFactory;
@@ -19,7 +20,7 @@ HTMLSelectElement.prototype.getValue = function() {
 /* only sets the selected value if such an option is actually available */
 HTMLSelectElement.prototype.setValue = function(s) {
     for (var i = 0; i < this.options.length; i++) {
-        if (s == this.options[i].value && !this.options[i].disabled) {
+        if (s == this.options[i].value) {
             this.selectedIndex = i;
             break;
         }
@@ -88,26 +89,47 @@ function TermSelectorFactory(terms) {
                 "datatype": self.terms[parts[0]][parts[1]].datatype
             };
         },
-        "makeWidget": function(parentNode, wStore, callback) {
+        "makeWidget": function(parentNode, wStore, matchHow, value, callback) {
             var term = this.getTerm();
             var widgetKey = this.uniq;
-            if (term.hint == "acqlia") {
+            if (matchHow.getValue() == "__in") {
+                new openils.widget.XULTermLoader({
+                    "parentNode": parentNode
+                }).build(
+                    function(w) {
+                        wStore[widgetKey] = w;
+                        if (typeof(callback) == "function")
+                            callback(term, widgetKey);
+                        if (typeof(value) != "undefined")
+                            w.attr("value", value);
+                        /* I would love for the following call not to be
+                         * necessary, so that updating the value of the dijit
+                         * would lead to this automatically, but I can't yet
+                         * figure out the correct way to do this in Dojo.
+                         */
+                        w.updateCount();
+                    }
+                );
+            } else if (term.hint == "acqlia") {
                 wStore[widgetKey] = dojo.create(
                     "input", {"type": "text"}, parentNode, "only"
                 );
+                if (typeof(value) != "undefined")
+                    wStore[widgetKey].attr("value", value);
                 wStore[widgetKey].focus();
                 if (typeof(callback) == "function")
                     callback(term, widgetKey);
             } else {
-                var widget = new openils.widget.AutoFieldWidget({
+                new openils.widget.AutoFieldWidget({
                     "fmClass": term.hint,
                     "fmField": term.field,
                     "noDisablePkey": true,
                     "parentNode": dojo.create("span", null, parentNode, "only")
-                });
-                widget.build(
+                }).build(
                     function(w) {
                         wStore[widgetKey] = w;
+                        if (typeof(value) != "undefined")
+                            w.attr("value", value);
                         w.focus();
                         if (typeof(callback) == "function")
                             callback(term, widgetKey);
@@ -199,6 +221,40 @@ function TermManager() {
     this._selector = function(id) { return dojo.byId("term-" + id); };
     this._match_how = function(id) { return dojo.byId("term-match-" + id); };
 
+    this._updateMatchHowForField = function(term, key) {
+        /* NOTE important to use self, not this, in this function.
+         *
+         * Based on the selected field (its datatype and the kind of widget
+         * that AutoFieldWidget provides for it) we update the possible
+         * choices in the mach_how selector.
+         */
+        var w = self.widgets[key];
+        var can_do_fuzzy, can_do_in;
+        if (term.datatype == "id") {
+            can_do_fuzzy = false;
+            can_do_in = true;
+        } else if (term.datatype == "link") {
+            can_do_fuzzy = (self.getLinkTarget(term) == "au");
+            can_do_in = false; /* XXX might revise later */
+        } else if (typeof(w.declaredClass) != "undefined") {
+            can_do_fuzzy = can_do_in =
+                Boolean(w.declaredClass.match(/form\.Text/));
+        } else {
+            var type = dojo.attr(w, "type");
+            if (type)
+                can_do_fuzzy = can_do_in = (type == "text");
+            else
+                can_do_fuzzy = can_do_in = false;
+        }
+
+        self.matchHowAllow(key, "__fuzzy", can_do_fuzzy);
+        self.matchHowAllow(key, "__in", can_do_in);
+
+        var inequalities = (term.datatype == "timestamp");
+        self.matchHowAllow(key, "__gte", inequalities);
+        self.matchHowAllow(key, "__lte", inequalities);
+    };
+
     this.removerButton = function(n) {
         return dojo.create("button", {
             "innerHTML": "X",
@@ -225,36 +281,8 @@ function TermManager() {
         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);
-
-                var inequalities = (term.datatype == "timestamp");
-                self.matchHowAllow(id, "__gte", inequalities);
-                self.matchHowAllow(id, "__lte", inequalities);
-
-                if (value) {
-                    if (typeof(w.attr) == "function")
-                        w.attr("value", value);
-                    else
-                        w.value = value;
-                }
-            }
+            where, this.widgets, this._match_how(id), value,
+            this._updateMatchHowForField
         );
     };
 
@@ -308,6 +336,13 @@ function TermManager() {
         dojo.attr(
             match_how, "onchange",
             function() {
+                if (this.getValue() == "__in") {
+                    self.updateRowWidget(uniq);
+                    this.was_in = true;
+                } else if (this.was_in) {
+                    self.updateRowWidget(uniq);
+                    this.was_in = false;
+                }
                 if (self.widgets[uniq]) self.widgets[uniq].focus();
             }
         );
@@ -320,11 +355,13 @@ function TermManager() {
         if (term && hint) {
             var field = hint + ":" + this._term_reverse_selector_field(term);
             selector.setValue(field);
-            this.updateRowWidget(uniq, this._term_reverse_selector_value(term));
 
             var match_how_value = this._term_reverse_match_how(term);
             if (match_how_value)
                 match_how.setValue(match_how_value);
+
+            this.updateRowWidget(uniq, this._term_reverse_selector_value(term));
+
         }
     }
 
index dcc14fc..f5722e2 100644 (file)
                             <select>
                                 <option value="">is</option>
                                 <option value="__not">is NOT</option>
-                                <option value="__fuzzy">contains</option>
-                                <option value="__not,__fuzzy">
+                                <option value="__fuzzy" disabled="disabled">
+                                    contains
+                                </option>
+                                <option value="__not,__fuzzy"
+                                    disabled="disabled">
                                     does NOT contain
                                 </option>
-                                <option value="__lte">
+                                <option value="__lte" disabled="disabled">
                                     is on or BEFORE
                                 </option>
-                                <option value="__gte">
+                                <option value="__gte" disabled="disabled">
                                     is on or AFTER
                                 </option>
+                                <option value="__in" disabled="disabled">
+                                    matches a term from a file
+                                </option>
                             </select>
                         </td>
                         <td name="widget"