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 /* Try to get cache of cmc's or cmf's from
55 * openils.widget.Searcher */
57 /* openils.widget.Searcher may not even be loaded;
58 * that's ok; just try. */
60 openils.widget.Searcher._cache.obj[key];
61 /* Don't try to set a cookie here; o.w.Searcher has
62 * tried and failed. */
67 if (this.cm_cache[key]) return oncomplete();
69 /* now try talking to fielder ourselves, and cache the result */
70 var pkey = field_list[0];
72 query[pkey] = {"!=": null};
74 OpenSRF.CachedClientSession("open-ils.fielder").request({
75 "method": "open-ils.fielder." + key + ".atomic",
76 "params": [{"query": query, "fields": field_list}],
79 "oncomplete": function(r) {
80 var result_arr = openils.Util.readResponse(r);
82 self.cm_cache[key] = {};
85 function(o) { self.cm_cache[key][o[pkey]] = o; }
92 "_setup_config_metabib_caches": function() {
98 "cmf": ["id", "field_class", "name", "label"],
99 "cmc": ["name", "label"]
101 var class_list = openils.Util.objectProperties(field_lists);
103 var is_done = function(k) { return Boolean(self.cm_cache[k]); };
106 class_list, function(key) {
107 self._setup_config_metabib_cache(
108 key, field_lists[key], function() {
109 if (dojo.every(class_list, is_done))
110 self.cm_cache.is_done = true;
117 "_prepare_match_for_display": function(match, field) {
119 "<div class='oils_AS_match'><div class='oils_AS_match_term'>" +
120 match + "</div><div class='oils_AS_match_field'>" +
121 this.get_field_label(field) + "</div></div>"
125 "_prepare_autosuggest_url": function(req) {
126 var term = req.query.term; /* affected by searchAttr on widget */
127 var limit = (!isNaN(req.count) && req.count != Infinity) ?
128 req.count : this.limit;
130 if (!term || term.length < 1 || term == "*") return null;
131 if (term.match(/[^\s*]$/)) term += " ";
132 term = term.replace(/\*$/, "");
135 "query=" + encodeURIComponent(term),
136 "search_class=" + this.type_selector.value,
140 if (typeof this.org_unit_getter == "function")
141 params.push("org_unit=" + this.org_unit_getter());
144 ["highlight_max", "highlight_min",
145 "short_word_length", "normalization"],
146 dojo.hitch(this, function(arg) {
147 if (this[arg] != null)
148 params.push(arg + "=" + this[arg]);
152 return "/opac/extras/autosuggest?" + params.join("&");
155 "get_field_label": function(field_id) {
156 var mfield = this.cm_cache.cmf[field_id];
157 var mclass = this.cm_cache.cmc[mfield.field_class];
158 return mfield.label + " (" + mclass.label + ")";
161 /* *** Begin dojo.data.api.Read methods *** */
163 "getValue": function(
165 /* string */ attribute,
166 /* anything */ defaultValue) {
167 if (!this.isItem(item))
168 throw new AutoSuggestStoreError("getValue(): bad item " + item);
169 else if (typeof attribute != "string")
170 throw new AutoSuggestStoreError("getValue(): bad attribute");
172 var value = item[attribute];
173 return (typeof value == "undefined") ? defaultValue : value;
176 "getValues": function(/* object */ item, /* string */ attribute) {
177 if (!this.isItem(item) || typeof attribute != "string")
178 throw new AutoSuggestStoreError("bad arguments");
180 var result = this.getValue(item, attribute, []);
181 return dojo.isArray(result) ? result : [result];
184 "getAttributes": function(/* object */ item) {
185 if (!this.isItem(item))
186 throw new AutoSuggestStoreError("getAttributes(): bad args");
188 return _autosuggest_fields;
191 "hasAttribute": function(/* object */ item, /* string */ attribute) {
192 if (!this.isItem(item) || typeof attribute != "string") {
193 throw new AutoSuggestStoreError("hasAttribute(): bad args");
195 return (dojo.indexOf(_autosuggest_fields, attribute) >= 0);
199 "containsValue": function(
201 /* string */ attribute,
202 /* anything */ value) {
203 if (!this.isItem(item) || typeof attribute != "string")
204 throw new AutoSuggestStoreError("bad data");
207 dojo.indexOf(this.getValues(item, attribute), value) != -1
211 "isItem": function(/* anything */ something) {
212 if (typeof something != "object" || something === null)
215 for (var i = 0; i < _autosuggest_fields.length; i++) {
216 var cur = _autosuggest_fields[i];
217 if (typeof something[cur] == "undefined")
223 "isItemLoaded": function(/* anything */ something) {
224 return this.isItem(something); /* for this store,
225 items are always loaded */
228 "close": function(/* object */ request) { /* no-op */ return; },
229 "getLabel": function(/* object */ item) { return "match"; },
230 "getLabelAttributes": function(/* object */ item) { return ["match"]; },
232 "loadItem": function(/* object */ keywordArgs) {
233 if (!this.isItem(keywordArgs.item))
234 throw new AutoSuggestStoreError("not an item; can't load it");
236 keywordArgs.identity = this.getIdentity(item);
237 return this.fetchItemByIdentity(keywordArgs);
240 "fetch": function(/* request-object */ req) {
241 // Respect the following properties of the *req* object:
243 // query a dojo-style query, which will need modest
244 // translation for our server-side service
246 // onBegin a callback that takes the number of items
247 // that this call to fetch() will return, but
248 // we always give it -1 (i.e. unknown)
249 // onItem a callback that takes each item as we get it
250 // onComplete a callback that takes the list of items
251 // after they're all fetched
253 // The onError callback is ignored for now (haven't thought
254 // of anything useful to do with it yet).
256 // The Read API also charges this method with adding an abort
257 // callback to the *req* object for the caller's use, but
258 // the one we provide does nothing but issue an alert().
260 if (!this.cm_cache.is_done) {
261 if (typeof req.onComplete == "function")
262 req.onComplete.call(callback_scope, [], req);
265 this._current_items = {};
267 var callback_scope = req.scope || dojo.global;
268 var url = this._prepare_autosuggest_url(req);
271 if (typeof req.onComplete == "function")
272 req.onComplete.call(callback_scope, [], req);
277 var process_fetch = function(obj, when) {
278 if (when < self._last_fetch) /* Stale response. Discard. */
284 item.id = item.field + "_" + item.term;
285 item.term = new TermString(item.term, item.field);
287 item.match = self._prepare_match_for_display(
288 item.match, item.field
290 self._current_items[item.id] = item;
292 if (typeof req.onItem == "function")
293 req.onItem.call(callback_scope, item, req);
297 if (typeof req.onComplete == "function") {
300 openils.Util.objectValues(self._current_items),
306 req.abort = function() {
307 alert("The 'abort' operation is not supported");
310 if (typeof req.onBegin == "function")
311 req.onBegin.call(callback_scope, -1, req);
313 var fetch_time = this._last_fetch = (new Date().getTime());
319 "preventCache": true,
320 "headers": {"Accept": "application/json"},
321 "load": function(obj) { process_fetch(obj, fetch_time); }
324 /* as for onError: what to do? */
329 /* *** Begin dojo.data.api.Identity methods *** */
331 "getIdentity": function(/* object */ item) {
332 if (!this.isItem(item))
333 throw new AutoSuggestStoreError("not an item");
338 "getIdentityAttributes": function(/* object */ item) { return ["id"]; },
340 "fetchItemByIdentity": function(/* object */ keywordArgs) {
341 if (keywordArgs.identity == undefined)
342 return null; // Identity API spec unclear whether error callback
343 // would need to be run, so we won't.
344 var callback_scope = keywordArgs.scope || dojo.global;
347 if (item = this._current_items[keywordArgs.identity]) {
348 if (typeof keywordArgs.onItem == "function")
349 keywordArgs.onItem.call(callback_scope, item);
353 if (typeof keywordArgs.onError == "function")
354 keywordArgs.onError.call(callback_scope, E);
360 /* *** Classes implementing any Dojo APIs do this to list which
361 * APIs they're implementing. *** */
363 "getFeatures": function() {
365 "dojo.data.api.Read": true,
366 "dojo.data.api.Identity": true