return "openils.AutoSuggestStore: " + this.message;
};
- /* XXX TODO make this smarter */
+ function TermString(str, field) { this.str = str; this.field = field; }
+ TermString.prototype.toString = function() { return this.str; };
+
var _autosuggest_fields = ["id", "match", "term", "field"];
+ var _cmf_cache, _cmc_cache;
dojo.declare(
"openils.AutoSuggestStore", null, {
},
"_label_field": function(field_id) {
- /* It seems to be possible to catch openils.widget.Searcher
- * in various states of unreadiness, so we have to be gentle with
- * it if we want to ask for its cached cmc and cmf objects.
- */
- var cmf_cache, cmc_cache;
- try {
- cmf_cache = openils.widget.Searcher._cache.obj.cmf;
- } catch (E) {
- console.log("openils.widget.Searcher cmf cache not ready:" + E);
- return field_id;
+ /* It seems to be possible (well, I think I got it to happen once)
+ * to catch openils.widget.Searcher while it's still loading.
+ * So let's be gentle with it when we ask for its cached cmc and
+ * cmf objects. */
+ if (!_cmf_cache) {
+ try {
+ _cmf_cache = openils.widget.Searcher._cache.obj.cmf;
+ } catch (E) {
+ console.log("o.w.Searcher's cmf cache not ready:" + E);
+ return field_id;
+ }
}
- try {
- cmc_cache = openils.widget.Searcher._cache.obj.cmc;
- } catch (E) {
- console.log("openils.widget.Searcher cmc cache not ready:" + E);
- return cmf_cache[field_id].label;
+ if (!_cmc_cache) {
+ try {
+ _cmc_cache = openils.widget.Searcher._cache.obj.cmc;
+ } catch (E) {
+ console.log("o.w.Searcher's cmc cache not ready:" + E);
+ return _cmf_cache[field_id].label;
+ }
}
var mfield, mclass;
- mfield = cmf_cache[field_id];
- mclass = cmc_cache[mfield.field_class];
+ mfield = _cmf_cache[field_id];
+ mclass = _cmc_cache[mfield.field_class];
return mfield.label + " (" + mclass.label + ")";
},
);
},
- /* req will have attribute 'query' and possibly 'limit', for now */
"_prepare_autosuggest_url": function(req) {
- var term = req.query.term;
+ var term = req.query.term; /* affected by searchAttr on widget */
if (!term || term.length < 1 || term == "*")
return null;
/* object */ item,
/* string */ attribute,
/* anything */ defaultValue) {
- // Given an *item* and the name of an *attribute* on that item,
- // return that attribute's value.
-// console.log("getValue(" + item + ", " + attribute + ")");
if (!this.isItem(item))
throw new AutoSuggestStoreError("getValue(): bad item: " + item);
else if (typeof attribute != "string")
throw new AutoSuggestStoreError("getValue(): bad attribute");
var value = item[attribute];
-
return (typeof value == "undefined") ? defaultValue : value;
},
"getValues": function(/* object */ item, /* string */ attribute) {
- // Same as getValue(), except the result is always an array
- // and there is no way to specify a default value.
-// console.log("getValues()");
if (!this.isItem(item) || typeof attribute != "string")
throw new AutoSuggestStoreError("bad arguments");
},
"getAttributes": function(/* object */ item) {
- // Return an array of all of the given *item*'s *attribute*s.
-// console.log("getAttributes()");
if (!this.isItem(item))
throw new AutoSuggestStoreError("getAttributes(): bad arguments");
else
},
"hasAttribute": function(/* object */ item, /* string */ attribute) {
- // Return true or false based on whether *item* has an
- // attribute by the name specified in *attribute*.
-// console.log("hasAttribute()");
if (!this.isItem(item) || typeof attribute != "string") {
throw new AutoSuggestStoreError("hasAttribute(): bad arguments");
} else {
/* object */ item,
/* string */ attribute,
/* anything */ value) {
- // Return true or false based on whether *item* has any value
- // matching *value* for *attribute*.
-// console.log("containsValue(" + item + ", " + attribute + ", " + value + ")");
if (!this.isItem(item) || typeof attribute != "string")
throw new AutoSuggestStoreError("bad data");
else
},
"isItem": function(/* anything */ something) {
- // Return true if *something* is an item (loaded or not
- // because to use everything is loaded, really.
-// console.log("isItem(" + something + ")");
if (typeof something != "object" || something === null)
return false;
},
"isItemLoaded": function(/* anything */ something) {
- // Return true if *something* is an item. It's always
- // "loaded" in this store.
-// console.log("isItemLoaded()");
return this.isItem(something);
},
"close": function(/* object */ request) {
- // summary:
- // This is a no-op.
return;
},
"getLabel": function(/* object */ item) {
- // Return the name of the attribute that should serve as the
- // label for objects of the same class as *item*.
-// console.log("getLabel(" + item + ")");
return "match";
},
"getLabelAttributes": function(/* object */ item) {
-// console.log("getLabelAttributes(" + item + ")");
return ["match"];
},
"loadItem": function(/* object */ keywordArgs) {
- // Fully load the item specified in the *item* property of
- // *keywordArgs*
-// console.log("loadItem(" + dojo.toJson(keywordArgs) + ")");
if (!this.isItem(keywordArgs.item))
throw new AutoSuggestStoreError("that's not an item; can't load it");
},
"fetch": function(/* request-object */ req) {
- // Basically, fetch objects matching the *query* property of
- // the *req* parameter.
+ // Respect the following properties of the *req*
+ // object:
//
- // Translate the *query* into a call we make to the
- // autosuggest webserver module, which yields JSON for us,
- // and translate those results into items, storing them
- // in our internal cache.
+ // query a dojo-style query, which will need modest
+ // translation for our server-side service
+ // limit an int
+ // onBegin a callback that takes the number of items
+ // that this call to fetch() will return, but
+ // we always give it -1 (i.e. unknown)
+ // onItem a callback that takes each item as we get it
+ // onComplete a callback that takes the list of items
+ // after they're all fetched
//
- // We also respect the following properties of the *req*
- // object (all optional):
+ // The onError callback is ignored for now (haven't thought
+ // of anything useful to do with it yet).
//
- // limit an int
- // onBegin a callback that takes the number of items
- // that this call to fetch() will return, but
- // we always give it -1 (i.e. unknown)
- // onItem a callback that takes each item as we get it
- // onComplete a callback that takes the list of items
- // after they're all fetched
- //
- // The onError callback is ignored for now (haven't thought
- // of anything useful to do with it yet).
- //
- // The Read API also charges this method with adding an abort
- // callback to the *req* object for the caller's use, but
- // the one we provide does nothing but issue an alert().
- //console.log("fetch(" + dojo.toJson(openils.Util.objectProperties(req)) + ")");
+ // The Read API also charges this method with adding an abort
+ // callback to the *req* object for the caller's use, but
+ // the one we provide does nothing but issue an alert().
var callback_scope = req.scope || dojo.global;
var url = this._prepare_autosuggest_url(req);
var self = this;
var process_fetch = function(obj) {
- obj.val.forEach(
+ dojo.forEach(obj.val,
function(item) {
- /* XXX May not need this id field ultimately; still
+ /* XXX may not need this id field ultimately; still
* trying to use it to solve misc problems. */
item.id = item.field + "_" + item.term;
+ item.term = new TermString(item.term, item.field);
item.match = self._prepare_match_for_display(
item.match, item.field
if (typeof req.onBegin == "function")
req.onBegin.call(callback_scope, -1, req);
- dojo.xhrGet(
- {
- "url": url,
- "handleAs": "json",
- "sync": false,
- "preventCache": true,
- "headers": {"Accept": "application/json"},
- "load": process_fetch
- }
- );
+ dojo.xhrGet({
+ "url": url,
+ "handleAs": "json",
+ "sync": false,
+ "preventCache": true,
+ "headers": {"Accept": "application/json"},
+ "load": process_fetch
+ });
/* as for onError: what to do? */
/* *** Begin dojo.data.api.Identity methods *** */
"getIdentity": function(/* object */ item) {
- // Given an *item* return its unique identifier (the value
- // of its primary key).
-// console.log("getIdentity(" + item + " [" + item.id + "])");
if (!this.isItem(item))
throw new AutoSuggestStoreError("not an item");
},
"getIdentityAttributes": function(/* object */ item) {
- // Given an *item* return the list of the name of the fields
- // that constitute the item's unique identifier.
-// console.log("getIdentityAttributes()");
return ["id"];
},
"fetchItemByIdentity": function(/* object */ keywordArgs) {
-// console.log("fetchItemByIdentity(" + dojo.toJson(keywordArgs) + ")");
if (keywordArgs.identity == undefined)
return null; // Identity API spec unclear whether error callback
// would need to be run, so we won't.
var item;
if (item = this._current_items[keywordArgs.identity]) {
-// console.log(
-// "fetchItemByIdentity(): already have " +
-// keywordArgs.identity
-// );
if (typeof keywordArgs.onItem == "function")
keywordArgs.onItem.call(callback_scope, item);
dojo.require("dijit.form.ComboBox");
dojo.require("openils.AutoSuggestStore");
+function updateSearchTypeSelector(id) {
+ /* Fail somewhat gracefully if a race condition, which I'm not /certain/
+ * actually exists, lets us get here before openils.widget.Search has
+ * fully initialized. */
+ var f;
+ try {
+ f = openils.widget.Searcher._cache.obj.cmf[id];
+ } catch (E) {
+ console.log("o.w.Searcher couldn't help us with field #" + id);
+ return;
+ }
+
+ var selector = G.ui.searchbar.type_selector;
+ var search_class = f.field_class + "|" + f.name;
+ var exact = dojo.indexOf(
+ dojo.map(selector.options, function(o) { return o.value; }),
+ search_class
+ );
+
+ if (exact > 0) {
+ selector.selectedIndex = exact;
+ } else { /* settle for class match if we can get it */
+ for (var i = 0; i < selector.options.length; i++) {
+ /* XXX make sure IE can handle this syntax */
+ if (selector.options[i].value.split("|")[0] == f.field_class) {
+ selector.selectedIndex = i;
+ break;
+ }
+ }
+ }
+}
+
function autoSuggestInit() {
var as_store = new openils.AutoSuggestStore(
- {"type_selector": G.ui.searchbar.type_selector}
+ {
+ "type_selector": G.ui.searchbar.type_selector
+ }
);
- var widg = new dijit.form.ComboBox({
- "store": as_store,
- "labelAttr": "match",
- "labelType": "html",
- "searchAttr": "term",
- "hasDownArrow": false,
- "autoComplete": false,
- "style": dojo.attr("search_box", "style")
- }, "search_box");
-
- widg.validate = function() { return true; } /* sic! */
+
+ var widg = new dijit.form.ComboBox(
+ {
+ "store": as_store,
+ "labelAttr": "match",
+ "labelType": "html",
+ "searchAttr": "term",
+ "hasDownArrow": false,
+ "autoComplete": false,
+ "searchDelay": 200,
+ "onChange": function(value) {
+ if (typeof value.field == "number")
+ updateSearchTypeSelector(value.field);
+ },
+ "onKeyPress": function(event) {
+ if (event.charOrCode == dojo.keys.ENTER)
+ searchBarSubmit();
+ },
+ "style": dojo.attr("search_box", "style")
+ }, "search_box"
+ );
+
+ G.ui.searchbar.text = widg.textbox;
+ widg.focus();
}
function searchBarInit() {