1 dojo.require("dojo.date.stamp");
2 dojo.require("openils.widget.AutoGrid");
3 dojo.require("openils.widget.AutoWidget");
4 dojo.require("openils.PermaCrud");
5 dojo.require("openils.Util");
7 var termSelectorFactory;
10 var pcrud = new openils.PermaCrud();
12 /* typing save: add getValue() to all HTML <select> elements */
13 HTMLSelectElement.prototype.getValue = function() {
14 return this.options[this.selectedIndex].value;
17 /* quickly find elements by the value of a "name" attribute */
18 function nodeByName(name, root) {
19 return dojo.query("[name='" + name + "']", root)[0];
23 openils.Util.hide("acq-unified-hide-form");
24 openils.Util.show("acq-unified-reveal-form", "inline");
25 openils.Util.hide("acq-unified-form");
28 function revealForm() {
29 openils.Util.hide("acq-unified-reveal-form");
30 openils.Util.show("acq-unified-hide-form", "inline");
31 openils.Util.show("acq-unified-form");
34 /* The TermSelectorFactory will be instantiated by the TermManager. It
35 * provides HTML select controls whose options are all the searchable
36 * fields. Selecting a field from one of these controls will create the
37 * appopriate type of corresponding widget for the user to enter a search
38 * term against the selected field.
40 function TermSelectorFactory(terms) {
44 this.template = dojo.create("select");
45 this.template.appendChild(
46 dojo.create("option", {
47 "disabled": "disabled",
48 "selected": "selected",
50 "innerHTML": "Select Search Field" // XXX i18n
54 /* Create abbreviations for class names to make field categories
55 * more readable in field selector control. */
56 this._abbreviate = function(s) {
58 for (var i = 0; i < s.length; i++) {
60 if (!i) result = s[i];
61 else if (last == " ") result += s[i];
68 var selectorMethods = {
69 /* Important: within the following functions, "this" refers to one
70 * HTMLSelect object, and "self" refers to the TermSelectorFactory. */
71 "getTerm": function() {
72 var parts = this.getValue().split(":");
76 "datatype": self.terms[parts[0]][parts[1]].datatype
79 "makeWidget": function(parentNode, wStore, callback) {
80 var term = this.getTerm();
81 var widgetKey = this.uniq;
82 if (term.hint == "acqlia") {
83 wStore[widgetKey] = dojo.create(
84 "input", {"type": "text"}, parentNode, "only"
86 wStore[widgetKey].focus();
87 if (typeof(callback) == "function")
88 callback(term, widgetKey);
90 var widget = new openils.widget.AutoFieldWidget({
92 "fmField": term.field,
93 "noDisablePkey": true,
94 "parentNode": dojo.create("span", null, parentNode, "only")
98 wStore[widgetKey] = w;
100 if (typeof(callback) == "function")
101 callback(term, widgetKey);
108 for (var hint in this.terms) {
109 var optgroup = dojo.create(
110 "optgroup", {"value": "", "label": this.terms[hint].__label}
112 var prefix = this._abbreviate(this.terms[hint].__label);
114 for (var field in this.terms[hint]) {
115 if (!/^__/.test(field)) {
116 optgroup.appendChild(
117 dojo.create("option", {
118 "class": "acq-unified-option-regular",
119 "value": hint + ":" + field,
120 "innerHTML": prefix + " - " +
121 this.terms[hint][field].label
127 this.template.appendChild(optgroup);
130 this.make = function(n) {
131 var node = dojo.clone(this.template);
133 dojo.attr(node, "id", "term-" + n);
134 for (var name in selectorMethods)
135 node[name] = selectorMethods[name];
140 /* The term manager retrieves information from the IDL about all the fields
141 * in the classes that we consider searchable for our purpose. It maintains
142 * a dynamic HTML table of search terms, using the TermSelectorFactory
143 * to generate search field selectors, which in turn provide appropriate
144 * widgets for entering search terms. The TermManager provides search term
145 * modifiers (fuzzy searching, not searching). The TermManager also handles
146 * adding and removing rows of search terms, as well as building the search
147 * query to pass to the middle layer from the search term widgets.
149 function TermManager() {
153 ["jub", "acqpl", "acqpo"].forEach(
156 o.__label = fieldmapper.IDL.fmclasses[hint].label;
157 fieldmapper.IDL.fmclasses[hint].fields.forEach(
159 if (!field.virtual) {
161 "label": field.label, "datatype": field.datatype
166 self.terms[hint] = o;
170 this.terms.acqlia = {"__label": fieldmapper.IDL.fmclasses.acqlia.label};
171 pcrud.retrieveAll("acqliad", {"order_by": {"acqliad": "id"}}).forEach(
173 self.terms.acqlia[def.id()] =
174 {"label": def.description(), "datatype": "text"}
178 this.selectorFactory = new TermSelectorFactory(this.terms);
179 this.template = dojo.byId("acq-unified-terms-tbody").
180 removeChild(dojo.byId("acq-unified-terms-row-tmpl"));
181 dojo.attr(this.template, "id");
186 this._row = function(id) { return dojo.byId("term-row-" + id); };
187 this._selector = function(id) { return dojo.byId("term-" + id); };
188 this._match_how = function(id) { return dojo.byId("term-match-" + id); };
190 this.removerButton = function(n) {
191 return dojo.create("button", {
193 "class": "acq-unified-remover",
194 "onclick": function() { self.removeRow(n); }
198 this.matchHowAllow = function(id, what, which) {
200 "option[value*='" + what + "']", this._match_how(id)
201 ).forEach(function(o) { o.disabled = !which; });
204 this.getLinkTarget = function(term) {
205 return fieldmapper.IDL.fmclasses[term.hint].
206 field_map[term.field]["class"];
209 this.updateRowWidget = function(id) {
210 var where = nodeByName("widget", this._row(id));
212 delete this.widgets[id];
215 this._selector(id).makeWidget(
217 function(term, key) {
218 var w = self.widgets[key];
220 if (term.datatype == "id") {
221 can_do_fuzzy = false;
222 } else if (term.datatype == "link") {
223 can_do_fuzzy = (self.getLinkTarget(term) == "au");
224 } else if (typeof(w.declaredClass) != "undefined") {
225 can_do_fuzzy = Boolean(w.declaredClass.match(/form\.Text/));
227 var type = dojo.attr(w, "type");
229 can_do_fuzzy = (type == "text");
231 can_do_fuzzy = false;
233 self.matchHowAllow(id, "__fuzzy", can_do_fuzzy);
238 this.addRow = function() {
239 var uniq = (this.rowId)++;
241 var row = dojo.clone(this.template);
242 dojo.attr(row, "id", "term-row-" + uniq);
244 var selector = this.selectorFactory.make(uniq);
248 function() { self.updateRowWidget(uniq); }
251 var match_how = dojo.query("select", nodeByName("match", row))[0];
252 dojo.attr(match_how, "id", "term-match-" + uniq);
253 dojo.attr(match_how, "selectedIndex", 0);
255 nodeByName("selector", row).appendChild(selector);
256 nodeByName("remove", row).appendChild(this.removerButton(uniq));
258 dojo.place(row, "acq-unified-terms-tbody", "last");
261 this.removeRow = function(id) {
262 delete this.widgets[id];
263 dojo.destroy(this._row(id));
266 this.buildSearchObject = function() {
269 for (var id in this.widgets) {
270 var attr_parts = this._selector(id).getValue().split(":");
271 if (attr_parts.length != 2)
274 var hint = attr_parts[0];
275 var attr = attr_parts[1];
277 this._match_how(id).getValue().split(",").filter(Boolean);
280 if (typeof(this.widgets[id].declaredClass) != "undefined") {
281 if (this.widgets[id].declaredClass.match(/Date/)) {
283 dojo.date.stamp.toISOString(this.widgets[id].value).
286 value = this.widgets[id].attr("value");
289 value = this.widgets[id].value;
297 match_how.forEach(function(key) { unit[key] = true; });
298 if (this.terms[hint][attr].datatype == "timestamp")
299 unit.__castdate = true;
307 /* The result manager is used primarily when the users submits a search. It
308 * consults the termManager to get the search query to send to the middl
309 * layer, and it chooses which ML method to call as well as what widgets to use
310 * to display the results.
312 function ResultManager(liTable, poGrid, plGrid) {
315 this.liTable = liTable;
316 this.poGrid = poGrid;
317 this.plGrid = plGrid;
321 this.result_types = {
325 "flesh_cancel_reason": true,
328 "revealer": function() {
329 self.liTable.reset();
330 self.liTable.show("list");
335 "no_flesh_cancel_reason": true
337 "revealer": function() {
338 self.poGrid.resetStore();
344 "flesh_lineitem_count": true,
347 "revealer": function() {
348 self.plGrid.resetStore();
353 "revealer": function() { alert(localeStrings.NO_RESULTS); }
357 this._add_lineitem = function(li) {
358 this.liTable.addLineitem(li);
361 this._add_purchase_order = function(po) {
362 this.poCache[po.id()] = po;
363 this.poGrid.store.newItem(acqpo.toStoreItem(po));
366 this._add_picklist = function(pl) {
367 this.plCache[pl.id()] = pl;
368 this.plGrid.store.newItem(acqpl.toStoreItem(pl));
371 this._finish_purchase_order = function() {
372 this.poGrid.hideLoadProgressIndicator();
375 this._finish_picklist = function() {
376 this.plGrid.hideLoadProgressIndicator();
379 this.add = function(which, what) {
380 var name = "_add_" + which;
381 if (this[name]) this[name](what);
384 this.finish = function(which) {
385 var name = "_finish_" + which;
386 if (this[name]) this[name]();
389 this.show = function(which) {
390 openils.Util.objectProperties(this.result_types).forEach(
392 openils.Util[rt == which ? "show" : "hide"](
393 "acq-unified-results-" + rt
397 this.result_types[which].revealer();
400 this.search = function(search_obj) {
401 var count_results = 0;
402 var result_type = dojo.byId("acq-unified-result-type").getValue();
403 var conjunction = dojo.byId("acq-unified-conjunction").getValue();
405 /* XXX TODO when result_type can be "lineitem_and_bib" there may be a
406 * totally different ML method to call; not sure how that will work
408 var method_name = "open-ils.acq." + result_type + ".unified_search";
410 openils.User.authtoken,
412 this.result_types[result_type].search_options
415 params[conjunction == "and" ? 1 : 2] = search_obj;
417 fieldmapper.standardRequest(
418 ["open-ils.acq", method_name], {
421 "onresponse": function(r) {
422 if (r = openils.Util.readResponse(r)) {
423 if (!count_results++)
424 self.show(result_type);
425 self.add(result_type, r);
428 "oncomplete": function() {
430 self.show("no_results");
432 self.finish(result_type);
439 /* The rest of the functions below handle the relatively unorganized
440 * miscellany of the search interface.
444 openils.Util.addOnLoad(
446 termManager = new TermManager();
447 termManager.addRow();
448 resultManager = new ResultManager(
450 dijit.byId("acq-unified-po-grid"),
451 dijit.byId("acq-unified-pl-grid")
453 openils.Util.show("acq-unified-body");