1 if (!dojo._hasResource["openils.AutoSuggestStore"]) {
2 dojo._hasResource["openils.AutoSuggestStore"] = true;
4 dojo.provide("openils.AutoSuggestStore");
6 dojo.require("dojo.cookie");
7 dojo.require("DojoSRF");
8 dojo.require("openils.Util");
10 /* Here's an exception class specific to openils.AutoSuggestStore */
11 function AutoSuggestStoreError(message) { this.message = message; }
12 AutoSuggestStoreError.prototype.toString = function() {
13 return "openils.AutoSuggestStore: " + this.message;
16 function TermString(str, field) { this.str = str; this.field = field; }
17 /* It doesn't seem to be possible to subclass builtins like String, but
18 * these are the only methods of String we should actually need */
19 TermString.prototype.toString=function(){return this.str;};
20 TermString.prototype.toLowerCase=function(){return this.str.toLowerCase();};
21 TermString.prototype.substr=function(){return this.str.substr(arguments);};
23 var _autosuggest_fields = ["id", "match", "term", "field"];
26 "openils.AutoSuggestStore", null, {
28 "_last_fetch": null, /* used internally */
30 /* Everything between here and the constructor can be specified in
31 * the constructor's args object. */
33 "type_selector": null, /* HTMLSelect object w/ options whose values
34 are search_classes (required) */
35 "org_unit_getter": null, /* function that returns int (OU ID) */
37 "limit": 10, /* number of suggestions at once */
38 "highlight_max": null, /* TS_HEADLINE()'s MaxWords option */
39 "highlight_min": null, /* TS_HEADLINE()'s MinWords option */
40 "short_word_length": null, /* TS_HEADLINE()'s ShortWord option */
41 "normalization": null, /* TS_RANK_CD()'s normalization argument */
43 "constructor": function(/* object */ args) {
44 dojo.mixin(this, args); /* XXX very sloppy */
45 this._current_items = {};
46 this._setup_config_metabib_caches();
49 "_setup_config_metabib_cache": function(key, field_list, oncomplete) {
52 if (this.cm_cache[key]) return;
54 var cookie = dojo.cookie("OILS_AS" + key);
56 this.cm_cache[key] = dojo.fromJson(cookie);
60 /* now try to get it from open-ils.searcher */
62 /* openils.widget.Searcher may not even be loaded;
63 * that's ok; just try. */
65 openils.widget.Searcher._cache.obj[key];
66 /* Don't try to set a cookie here; o.w.Searcher has
67 * tried and failed. */
72 if (this.cm_cache[key]) return oncomplete();
74 /* now try talking to fielder ourselves, and cache the result */
75 var pkey = field_list[0];
77 query[pkey] = {"!=": null};
79 OpenSRF.CachedClientSession("open-ils.fielder").request({
80 "method": "open-ils.fielder." + key + ".atomic",
81 "params": [{"query": query, "fields": field_list}],
83 "oncomplete": function(r) {
84 /* XXX check for failure? */
85 var result_arr = r.recv().content();
87 self.cm_cache[key] = {};
90 function(o) { self.cm_cache[key][o[pkey]] = o; }
93 "OILS_AS" + key, dojo.toJson(self.cm_cache[key])
100 "_setup_config_metabib_caches": function() {
106 "cmf": ["id", "field_class", "name", "label"],
107 "cmc": ["name", "label"]
109 var class_list = openils.Util.objectProperties(field_lists);
111 var is_done = function(k) { return Boolean(self.cm_cache[k]); };
114 class_list, function(key) {
115 self._setup_config_metabib_cache(
116 key, field_lists[key], function() {
117 if (dojo.every(class_list, is_done))
118 self.cm_cache.is_done = true;
125 "_prepare_match_for_display": function(match, field) {
127 "<div class='oils_AS_match'><div class='oils_AS_match_term'>" +
128 match + "</div><div class='oils_AS_match_field'>" +
129 this.get_field_label(field) + "</div></div>"
133 "_prepare_autosuggest_url": function(req) {
134 var term = req.query.term; /* affected by searchAttr on widget */
135 var limit = (!isNaN(req.count) && req.count != Infinity) ?
136 req.count : this.limit;
138 if (!term || term.length < 1 || term == "*") return null;
139 if (term.match(/[^\s*]$/)) term += " ";
140 term = term.replace(/\*$/, "");
143 "query=" + encodeURI(term),
144 "search_class=" + this.type_selector.value,
148 if (typeof this.org_unit_getter == "function")
149 params.push("org_unit=" + this.org_unit_getter());
152 ["highlight_max", "highlight_min",
153 "short_word_length", "normalization"],
154 dojo.hitch(this, function(arg) {
155 if (this[arg] != null)
156 params.push(arg + "=" + this[arg]);
160 return "/opac/extras/autosuggest?" + params.join("&");
163 "get_field_label": function(field_id) {
164 var mfield = this.cm_cache.cmf[field_id];
165 var mclass = this.cm_cache.cmc[mfield.field_class];
166 return mfield.label + " (" + mclass.label + ")";
169 /* *** Begin dojo.data.api.Read methods *** */
171 "getValue": function(
173 /* string */ attribute,
174 /* anything */ defaultValue) {
175 if (!this.isItem(item))
176 throw new AutoSuggestStoreError("getValue(): bad item " + item);
177 else if (typeof attribute != "string")
178 throw new AutoSuggestStoreError("getValue(): bad attribute");
180 var value = item[attribute];
181 return (typeof value == "undefined") ? defaultValue : value;
184 "getValues": function(/* object */ item, /* string */ attribute) {
185 if (!this.isItem(item) || typeof attribute != "string")
186 throw new AutoSuggestStoreError("bad arguments");
188 var result = this.getValue(item, attribute, []);
189 return dojo.isArray(result) ? result : [result];
192 "getAttributes": function(/* object */ item) {
193 if (!this.isItem(item))
194 throw new AutoSuggestStoreError("getAttributes(): bad args");
196 return _autosuggest_fields;
199 "hasAttribute": function(/* object */ item, /* string */ attribute) {
200 if (!this.isItem(item) || typeof attribute != "string") {
201 throw new AutoSuggestStoreError("hasAttribute(): bad args");
203 return (dojo.indexOf(_autosuggest_fields, attribute) >= 0);
207 "containsValue": function(
209 /* string */ attribute,
210 /* anything */ value) {
211 if (!this.isItem(item) || typeof attribute != "string")
212 throw new AutoSuggestStoreError("bad data");
215 dojo.indexOf(this.getValues(item, attribute), value) != -1
219 "isItem": function(/* anything */ something) {
220 if (typeof something != "object" || something === null)
223 for (var i = 0; i < _autosuggest_fields.length; i++) {
224 var cur = _autosuggest_fields[i];
225 if (typeof something[cur] == "undefined")
231 "isItemLoaded": function(/* anything */ something) {
232 return this.isItem(something); /* for this store,
233 items are always loaded */
236 "close": function(/* object */ request) { /* no-op */ return; },
237 "getLabel": function(/* object */ item) { return "match"; },
238 "getLabelAttributes": function(/* object */ item) { return ["match"]; },
240 "loadItem": function(/* object */ keywordArgs) {
241 if (!this.isItem(keywordArgs.item))
242 throw new AutoSuggestStoreError("not an item; can't load it");
244 keywordArgs.identity = this.getIdentity(item);
245 return this.fetchItemByIdentity(keywordArgs);
248 "fetch": function(/* request-object */ req) {
249 // Respect the following properties of the *req* object:
251 // query a dojo-style query, which will need modest
252 // translation for our server-side service
254 // onBegin a callback that takes the number of items
255 // that this call to fetch() will return, but
256 // we always give it -1 (i.e. unknown)
257 // onItem a callback that takes each item as we get it
258 // onComplete a callback that takes the list of items
259 // after they're all fetched
261 // The onError callback is ignored for now (haven't thought
262 // of anything useful to do with it yet).
264 // The Read API also charges this method with adding an abort
265 // callback to the *req* object for the caller's use, but
266 // the one we provide does nothing but issue an alert().
268 if (!this.cm_cache.is_done) {
269 if (typeof req.onComplete == "function")
270 req.onComplete.call(callback_scope, [], req);
273 this._current_items = {};
275 var callback_scope = req.scope || dojo.global;
276 var url = this._prepare_autosuggest_url(req);
279 if (typeof req.onComplete == "function")
280 req.onComplete.call(callback_scope, [], req);
285 var process_fetch = function(obj, when) {
286 if (when < self._last_fetch) /* Stale response. Discard. */
292 item.id = item.field + "_" + item.term;
293 item.term = new TermString(item.term, item.field);
295 item.match = self._prepare_match_for_display(
296 item.match, item.field
298 self._current_items[item.id] = item;
300 if (typeof req.onItem == "function")
301 req.onItem.call(callback_scope, item, req);
305 if (typeof req.onComplete == "function") {
308 openils.Util.objectValues(self._current_items),
314 req.abort = function() {
315 alert("The 'abort' operation is not supported");
318 if (typeof req.onBegin == "function")
319 req.onBegin.call(callback_scope, -1, req);
321 var fetch_time = this._last_fetch = (new Date().getTime());
327 "preventCache": true,
328 "headers": {"Accept": "application/json"},
329 "load": function(obj) { process_fetch(obj, fetch_time); }
332 /* as for onError: what to do? */
337 /* *** Begin dojo.data.api.Identity methods *** */
339 "getIdentity": function(/* object */ item) {
340 if (!this.isItem(item))
341 throw new AutoSuggestStoreError("not an item");
346 "getIdentityAttributes": function(/* object */ item) { return ["id"]; },
348 "fetchItemByIdentity": function(/* object */ keywordArgs) {
349 if (keywordArgs.identity == undefined)
350 return null; // Identity API spec unclear whether error callback
351 // would need to be run, so we won't.
352 var callback_scope = keywordArgs.scope || dojo.global;
355 if (item = this._current_items[keywordArgs.identity]) {
356 if (typeof keywordArgs.onItem == "function")
357 keywordArgs.onItem.call(callback_scope, item);
361 if (typeof keywordArgs.onError == "function")
362 keywordArgs.onError.call(callback_scope, E);
368 /* *** Classes implementing any Dojo APIs do this to list which
369 * APIs they're implementing. *** */
371 "getFeatures": function() {
373 "dojo.data.api.Read": true,
374 "dojo.data.api.Identity": true