vinova_sunspot_autocomplete 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/LICENSE +7 -0
  2. data/README +91 -0
  3. data/README.rdoc +91 -0
  4. data/Rakefile +37 -0
  5. data/VERSION +1 -0
  6. data/assets/javascripts/jquery.js +6240 -0
  7. data/assets/javascripts/solr-autocomplete/ajax-solr/core/AbstractManager.js +182 -0
  8. data/assets/javascripts/solr-autocomplete/ajax-solr/core/Core.js +226 -0
  9. data/assets/javascripts/solr-autocomplete/ajax-solr/core/Parameter.js +161 -0
  10. data/assets/javascripts/solr-autocomplete/ajax-solr/core/ParameterStore.js +354 -0
  11. data/assets/javascripts/solr-autocomplete/ajax-solr/managers/Manager.jquery.js +20 -0
  12. data/assets/javascripts/solr-autocomplete/jquery-autocomplete/indicator.gif +0 -0
  13. data/assets/javascripts/solr-autocomplete/jquery-autocomplete/jquery.autocomplete.css +49 -0
  14. data/assets/javascripts/solr-autocomplete/jquery-autocomplete/jquery.autocomplete.js +867 -0
  15. data/lib/autocomplete_view_helpers.rb +126 -0
  16. data/lib/sunspot_autocomplete.rb +38 -0
  17. data/rails/init.rb +3 -0
  18. data/rdoc/classes/AutocompleteViewHelpers.html +322 -0
  19. data/rdoc/classes/Sunspot.html +111 -0
  20. data/rdoc/classes/Sunspot/Type.html +112 -0
  21. data/rdoc/classes/Sunspot/Type/AutocompleteType.html +117 -0
  22. data/rdoc/classes/Sunspot/Type/AutosuggestType.html +117 -0
  23. data/rdoc/created.rid +1 -0
  24. data/rdoc/files/README.html +238 -0
  25. data/rdoc/files/README_rdoc.html +238 -0
  26. data/rdoc/files/lib/autocomplete_view_helpers_rb.html +236 -0
  27. data/rdoc/files/lib/sunspot_autocomplete_rb.html +101 -0
  28. data/rdoc/fr_class_index.html +31 -0
  29. data/rdoc/fr_file_index.html +29 -0
  30. data/rdoc/fr_method_index.html +28 -0
  31. data/rdoc/index.html +24 -0
  32. data/rdoc/rdoc-style.css +208 -0
  33. data/tasks/tasks.rake +10 -0
  34. data/test/sunspot_autocomplete_test.rb +21 -0
  35. data/test/test_helper.rb +3 -0
  36. data/vinova_sunspot_autocomplete.gemspec +80 -0
  37. metadata +118 -0
@@ -0,0 +1,354 @@
1
+ // $Id$
2
+
3
+ /**
4
+ * The ParameterStore, as its name suggests, stores Solr parameters. Widgets
5
+ * expose some of these parameters to the user. Whenever the user changes the
6
+ * values of these parameters, the state of the application changes. In order to
7
+ * allow the user to move back and forth between these states with the browser's
8
+ * Back and Forward buttons, and to bookmark these states, each state needs to
9
+ * be stored. The easiest method is to store the exposed parameters in the URL
10
+ * hash (see the <tt>ParameterHashStore</tt> class). However, you may implement
11
+ * your own storage method by extending this class.
12
+ *
13
+ * <p>For a list of possible parameters, please consult the links below.</p>
14
+ *
15
+ * @see http://wiki.apache.org/solr/CoreQueryParameters
16
+ * @see http://wiki.apache.org/solr/CommonQueryParameters
17
+ * @see http://wiki.apache.org/solr/SimpleFacetParameters
18
+ * @see http://wiki.apache.org/solr/HighlightingParameters
19
+ * @see http://wiki.apache.org/solr/MoreLikeThis
20
+ * @see http://wiki.apache.org/solr/SpellCheckComponent
21
+ * @see http://wiki.apache.org/solr/StatsComponent
22
+ * @see http://wiki.apache.org/solr/TermsComponent
23
+ * @see http://wiki.apache.org/solr/TermVectorComponent
24
+ * @see http://wiki.apache.org/solr/LocalParams
25
+ *
26
+ * @param properties A map of fields to set. Refer to the list of public fields.
27
+ * @class ParameterStore
28
+ */
29
+ AjaxSolr.ParameterStore = AjaxSolr.Class.extend(
30
+ /** @lends AjaxSolr.ParameterStore.prototype */
31
+ {
32
+ /**
33
+ * The names of the exposed parameters. Any parameters that your widgets
34
+ * expose to the user, directly or indirectly, should be listed here.
35
+ *
36
+ * @field
37
+ * @public
38
+ * @type String[]
39
+ * @default []
40
+ */
41
+ exposed: [],
42
+
43
+ /**
44
+ * The Solr parameters.
45
+ *
46
+ * @field
47
+ * @private
48
+ * @type Object
49
+ * @default {}
50
+ */
51
+ params: {},
52
+
53
+ /**
54
+ * A reference to the parameter store's manager. For internal use only.
55
+ *
56
+ * @field
57
+ * @private
58
+ * @type AjaxSolr.AbstractManager
59
+ */
60
+ manager: null,
61
+
62
+ /**
63
+ * An abstract hook for child implementations.
64
+ *
65
+ * <p>This method should do any necessary one-time initializations.</p>
66
+ */
67
+ init: function () {},
68
+
69
+ /**
70
+ * Some Solr parameters may be specified multiple times. It is easiest to
71
+ * hard-code a list of such parameters. You may change the list by passing
72
+ * <code>{ multiple: /pattern/ }</code> as an argument to the constructor of
73
+ * this class or one of its children, e.g.:
74
+ *
75
+ * <p><code>new ParameterStore({ multiple: /pattern/ })</code>
76
+ *
77
+ * @param {String} name The name of the parameter.
78
+ * @returns {Boolean} Whether the parameter may be specified multiple times.
79
+ */
80
+ isMultiple: function (name) {
81
+ return name.match(/^(?:bf|bq|facet\.date|facet\.date\.other|facet\.field|facet\.query|fq|pf|qf)$/);
82
+ },
83
+
84
+ /**
85
+ * Returns a parameter. If the parameter doesn't exist, creates it.
86
+ *
87
+ * @param {String} name The name of the parameter.
88
+ * @returns {AjaxSolr.Parameter|AjaxSolr.Parameter[]} The parameter.
89
+ */
90
+ get: function (name) {
91
+ if (this.params[name] === undefined) {
92
+ var param = new AjaxSolr.Parameter({ name: name });
93
+ if (this.isMultiple(name)) {
94
+ this.params[name] = [ param ];
95
+ }
96
+ else {
97
+ this.params[name] = param;
98
+ }
99
+ }
100
+ return this.params[name];
101
+ },
102
+
103
+ /**
104
+ * If the parameter may be specified multiple times, returns the values of
105
+ * all identically-named parameters. If the parameter may be specified only
106
+ * once, returns the value of that parameter.
107
+ *
108
+ * @param {String} name The name of the parameter.
109
+ * @returns {String[]|Number[]} The value(s) of the parameter.
110
+ */
111
+ values: function (name) {
112
+ if (this.params[name] !== undefined) {
113
+ if (this.isMultiple(name)) {
114
+ var values = [];
115
+ for (var i = 0, l = this.params[name].length; i < l; i++) {
116
+ values.push(this.params[name][i].val());
117
+ }
118
+ return values;
119
+ }
120
+ else {
121
+ return [ this.params[name].val() ];
122
+ }
123
+ }
124
+ return [];
125
+ },
126
+
127
+ /**
128
+ * If the parameter may be specified multiple times, adds the given parameter
129
+ * to the list of identically-named parameters, unless one already exists with
130
+ * the same value. If it may be specified only once, replaces the parameter.
131
+ *
132
+ * @param {String} name The name of the parameter.
133
+ * @param {AjaxSolr.Parameter} [param] The parameter.
134
+ * @returns {AjaxSolr.Parameter|Boolean} The parameter, or false.
135
+ */
136
+ add: function (name, param) {
137
+ if (param === undefined) {
138
+ param = new AjaxSolr.Parameter({ name: name });
139
+ }
140
+ if (this.isMultiple(name)) {
141
+ if (this.params[name] === undefined) {
142
+ this.params[name] = [ param ];
143
+ }
144
+ else {
145
+ if (AjaxSolr.inArray(param.val(), this.values(name)) == -1) {
146
+ this.params[name].push(param);
147
+ }
148
+ else {
149
+ return false;
150
+ }
151
+ }
152
+ }
153
+ else {
154
+ this.params[name] = param;
155
+ }
156
+ return param;
157
+ },
158
+
159
+ /**
160
+ * Deletes a parameter.
161
+ *
162
+ * @param {String} name The name of the parameter.
163
+ * @param {Number} [index] The index of the parameter.
164
+ */
165
+ remove: function (name, index) {
166
+ if (index === undefined) {
167
+ delete this.params[name];
168
+ }
169
+ else {
170
+ this.params[name].splice(index, 1);
171
+ if (this.params[name].length == 0) {
172
+ delete this.params[name];
173
+ }
174
+ }
175
+ },
176
+
177
+ /**
178
+ * Finds all parameters with matching values.
179
+ *
180
+ * @param {String} name The name of the parameter.
181
+ * @param {String|Number|String[]|Number[]|RegExp} value The value.
182
+ * @returns {String|Number[]} The indices of the parameters found.
183
+ */
184
+ find: function (name, value) {
185
+ if (this.params[name] !== undefined) {
186
+ if (this.isMultiple(name)) {
187
+ var indices = [];
188
+ for (var i = 0, l = this.params[name].length; i < l; i++) {
189
+ if (AjaxSolr.equals(this.params[name][i].val(), value)) {
190
+ indices.push(i);
191
+ }
192
+ }
193
+ return indices.length ? indices : false;
194
+ }
195
+ else {
196
+ if (AjaxSolr.equals(this.params[name].val(), value)) {
197
+ return name;
198
+ }
199
+ }
200
+ }
201
+ return false;
202
+ },
203
+
204
+ /**
205
+ * If the parameter may be specified multiple times, creates a parameter using
206
+ * the given name and value, and adds it to the list of identically-named
207
+ * parameters, unless one already exists with the same value. If it may be
208
+ * specified only once, replaces the parameter.
209
+ *
210
+ * @param {String} name The name of the parameter.
211
+ * @param {String|Number|String[]|Number[]} value The value.
212
+ * @returns {AjaxSolr.Parameter|Boolean} The parameter, or false.
213
+ */
214
+ addByValue: function (name, value) {
215
+ if (this.isMultiple(name) && AjaxSolr.isArray(value)) {
216
+ var ret = [];
217
+ for (var i = 0, l = value.length; i < l; i++) {
218
+ ret.push(this.add(name, new AjaxSolr.Parameter({ name: name, value: value[i] })));
219
+ }
220
+ return ret;
221
+ }
222
+ else {
223
+ return this.add(name, new AjaxSolr.Parameter({ name: name, value: value }))
224
+ }
225
+ },
226
+
227
+ /**
228
+ * Deletes any parameter with a matching value.
229
+ *
230
+ * @param {String} name The name of the parameter.
231
+ * @param {String|Number|String[]|Number[]|RegExp} value The value.
232
+ * @returns {String|Number[]} The indices deleted.
233
+ */
234
+ removeByValue: function (name, value) {
235
+ var indices = this.find(name, value);
236
+ if (indices) {
237
+ if (AjaxSolr.isArray(indices)) {
238
+ for (var i = indices.length - 1; i >= 0; i--) {
239
+ this.remove(name, indices[i]);
240
+ }
241
+ }
242
+ else {
243
+ this.remove(indices);
244
+ }
245
+ }
246
+ return indices;
247
+ },
248
+
249
+ /**
250
+ * Returns the Solr parameters as a query string.
251
+ *
252
+ * <p>IE6 calls the default toString() if you write <tt>store.toString()
253
+ * </tt>. So, we need to choose another name for toString().</p>
254
+ */
255
+ string: function () {
256
+ var params = [];
257
+ for (var name in this.params) {
258
+ if (this.isMultiple(name)) {
259
+ for (var i = 0, l = this.params[name].length; i < l; i++) {
260
+ params.push(this.params[name][i].string());
261
+ }
262
+ }
263
+ else {
264
+ params.push(this.params[name].string());
265
+ }
266
+ }
267
+ return AjaxSolr.compact(params).join('&');
268
+ },
269
+
270
+ /**
271
+ * Parses a query string into Solr parameters.
272
+ *
273
+ * @param {String} str The string to parse.
274
+ */
275
+ parseString: function (str) {
276
+ var pairs = str.split('&');
277
+ for (var i = 0, l = pairs.length; i < l; i++) {
278
+ if (pairs[i]) { // ignore leading, trailing, and consecutive &'s
279
+ var param = new AjaxSolr.Parameter();
280
+ param.parseString(pairs[i]);
281
+ this.add(param.name, param);
282
+ }
283
+ }
284
+ },
285
+
286
+ /**
287
+ * Returns the exposed parameters as a query string.
288
+ *
289
+ * @returns {String} A string representation of the exposed parameters.
290
+ */
291
+ exposedString: function () {
292
+ var params = [];
293
+ for (var i = 0, l = this.exposed.length; i < l; i++) {
294
+ if (this.params[this.exposed[i]] !== undefined) {
295
+ if (this.isMultiple(this.exposed[i])) {
296
+ for (var j = 0, m = this.params[this.exposed[i]].length; j < m; j++) {
297
+ params.push(this.params[this.exposed[i]][j].string());
298
+ }
299
+ }
300
+ else {
301
+ params.push(this.params[this.exposed[i]].string());
302
+ }
303
+ }
304
+ }
305
+ return AjaxSolr.compact(params).join('&');
306
+ },
307
+
308
+ /**
309
+ * Resets the values of the exposed parameters.
310
+ */
311
+ exposedReset: function () {
312
+ for (var i = 0, l = this.exposed.length; i < l; i++) {
313
+ this.remove(this.exposed[i]);
314
+ }
315
+ },
316
+
317
+ /**
318
+ * Loads the values of exposed parameters from persistent storage. It is
319
+ * necessary, in most cases, to reset the values of exposed parameters before
320
+ * setting the parameters to the values in storage. This is to ensure that a
321
+ * parameter whose name is not present in storage is properly reset.
322
+ *
323
+ * @param {Boolean} [reset=true] Whether to reset the exposed parameters.
324
+ * before loading new values from persistent storage. Default: true.
325
+ */
326
+ load: function (reset) {
327
+ if (reset === undefined) {
328
+ reset = true;
329
+ }
330
+ if (reset) {
331
+ this.exposedReset();
332
+ }
333
+ this.parseString(this.storedString());
334
+ },
335
+
336
+ /**
337
+ * An abstract hook for child implementations.
338
+ *
339
+ * <p>Stores the values of the exposed parameters in persistent storage. This
340
+ * method should usually be called before each Solr request.</p>
341
+ */
342
+ save: function () {},
343
+
344
+ /**
345
+ * An abstract hook for child implementations.
346
+ *
347
+ * <p>Returns the string to parse from persistent storage.</p>
348
+ *
349
+ * @returns {String} The string from persistent storage.
350
+ */
351
+ storedString: function () {
352
+ return '';
353
+ }
354
+ });
@@ -0,0 +1,20 @@
1
+ // $Id$
2
+
3
+ /**
4
+ * @see http://wiki.apache.org/solr/SolJSON#JSON_specific_parameters
5
+ * @class Manager
6
+ * @augments AjaxSolr.AbstractManager
7
+ */
8
+ AjaxSolr.Manager = AjaxSolr.AbstractManager.extend(
9
+ /** @lends AjaxSolr.Manager.prototype */
10
+ {
11
+ executeRequest: function (servlet) {
12
+ var self = this;
13
+ if (this.proxyUrl) {
14
+ jQuery.post(this.proxyUrl, { query: this.store.string() }, function (data) { self.handleResponse(data); }, 'json');
15
+ }
16
+ else {
17
+ jQuery.getJSON(this.solrUrl + servlet + '?' + this.store.string() + '&wt=json&json.wrf=?', {}, function (data) { self.handleResponse(data); });
18
+ }
19
+ }
20
+ });
@@ -0,0 +1,49 @@
1
+ .ac_results {
2
+ padding: 0px;
3
+ border: 1px solid black;
4
+ background-color: white;
5
+ overflow: hidden;
6
+ z-index: 99999;
7
+ text-align: left;
8
+ }
9
+
10
+ .ac_results ul {
11
+ width: 100%;
12
+ list-style-position: outside;
13
+ list-style: none;
14
+ padding: 0;
15
+ margin: 0;
16
+ }
17
+
18
+ .ac_results li {
19
+ margin: 0px;
20
+ padding: 2px 5px;
21
+ cursor: default;
22
+ display: block;
23
+ /*
24
+ if width will be 100% horizontal scrollbar will apear
25
+ when scroll mode will be used
26
+ */
27
+ /*width: 100%;*/
28
+ font: menu;
29
+ font-size: 12px;
30
+ /*
31
+ it is very important, if line-height not setted or setted
32
+ in relative units scroll will be broken in firefox
33
+ */
34
+ line-height: 16px;
35
+ overflow: hidden;
36
+ }
37
+
38
+ .ac_loading {
39
+ background: white url('indicator.gif') right center no-repeat;
40
+ }
41
+
42
+ .ac_odd {
43
+ background-color: #eee;
44
+ }
45
+
46
+ .ac_over {
47
+ background-color: #0A246A;
48
+ color: white;
49
+ }
@@ -0,0 +1,867 @@
1
+ /*
2
+ * This part of the library is extended from jQuery Autocomplete plugin by Jörn Zaefferer
3
+ */
4
+
5
+
6
+ jQuery.extend({
7
+
8
+ get: function( url, data, callback, type ) {
9
+ // shift arguments if data argument was omited
10
+ if ( jQuery.isFunction( data ) ) {
11
+ type = type || callback;
12
+ callback = data;
13
+ data = null;
14
+ }
15
+
16
+ return jQuery.ajax({
17
+ type: "GET",
18
+ url: url,
19
+ data: data,
20
+ success: callback,
21
+ dataType: type,
22
+ jsonpCallback: "applyAjaxSolrCallback"
23
+ });
24
+ }
25
+ });
26
+
27
+
28
+ ;(function($) {
29
+
30
+ $.fn.extend({
31
+ autocomplete: function(urlOrData, fieldName, options) {
32
+ var isUrl = typeof urlOrData == "string";
33
+ options = $.extend({}, $.Autocompleter.defaults, {
34
+ url: isUrl ? urlOrData : null,
35
+ data: isUrl ? null : urlOrData,
36
+ delay: isUrl ? $.Autocompleter.defaults.delay : 10,
37
+ max: options && !options.scroll ? 10 : 150
38
+ }, options);
39
+
40
+ // if highlight is set to false, replace it with a do-nothing function
41
+ options.highlight = options.highlight || function(value) { return value; };
42
+
43
+ // if the formatMatch option is not specified, then use formatItem for backwards compatibility
44
+ options.formatMatch = options.formatMatch || options.formatItem;
45
+
46
+ return this.each(function() {
47
+ new $.Autocompleter(this, fieldName, options);
48
+ });
49
+ },
50
+ autosuggest: function(urlOrData, fieldName, options){
51
+ options = $.extend({}, {suggest: true}, options);
52
+ this.autocomplete(urlOrData, fieldName, options)
53
+ },
54
+ result: function(handler) {
55
+ return this.bind("result", handler);
56
+ },
57
+ search: function(handler) {
58
+ return this.trigger("search", [handler]);
59
+ },
60
+ flushCache: function() {
61
+ return this.trigger("flushCache");
62
+ },
63
+ setOptions: function(options){
64
+ return this.trigger("setOptions", [options]);
65
+ },
66
+ unautocomplete: function() {
67
+ return this.trigger("unautocomplete");
68
+ },
69
+ ajaxSolrCallback: null,
70
+ ajaxSolrFieldName: null
71
+ });
72
+
73
+ $.Autocompleter = function(input, fieldName, options) {
74
+
75
+ var KEY = {
76
+ UP: 38,
77
+ DOWN: 40,
78
+ DEL: 46,
79
+ TAB: 9,
80
+ RETURN: 13,
81
+ ESC: 27,
82
+ COMMA: 188,
83
+ PAGEUP: 33,
84
+ PAGEDOWN: 34,
85
+ BACKSPACE: 8
86
+ };
87
+
88
+ // Create $ object for input element
89
+ var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
90
+
91
+ var timeout;
92
+ var previousValue = "";
93
+ var cache = $.Autocompleter.Cache(options);
94
+ var hasFocus = 0;
95
+ var lastKeyPressCode;
96
+ var config = {
97
+ mouseDownOnSelect: false
98
+ };
99
+ var select = $.Autocompleter.Select(options, input, selectCurrent, config);
100
+
101
+ var blockSubmit;
102
+
103
+ var fieldName = fieldName;
104
+ var suggest = options.suggest;
105
+
106
+ var ajaxSolrManager = new AjaxSolr.Manager({
107
+ solrUrl: options.url
108
+ });
109
+ ajaxSolrManager.init();
110
+
111
+ // prevent form submit in opera when selecting with return key
112
+ $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
113
+ if (blockSubmit) {
114
+ blockSubmit = false;
115
+ return false;
116
+ }
117
+ });
118
+
119
+ // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
120
+ $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
121
+ // a keypress means the input has focus
122
+ // avoids issue where input had focus before the autocomplete was applied
123
+ hasFocus = 1;
124
+ // track last key pressed
125
+ lastKeyPressCode = event.keyCode;
126
+ switch(event.keyCode) {
127
+
128
+ case KEY.UP:
129
+ event.preventDefault();
130
+ if ( select.visible() ) {
131
+ select.prev();
132
+ } else {
133
+ onChange(0, true);
134
+ }
135
+ break;
136
+
137
+ case KEY.DOWN:
138
+ event.preventDefault();
139
+ if ( select.visible() ) {
140
+ select.next();
141
+ } else {
142
+ onChange(0, true);
143
+ }
144
+ break;
145
+
146
+ case KEY.PAGEUP:
147
+ event.preventDefault();
148
+ if ( select.visible() ) {
149
+ select.pageUp();
150
+ } else {
151
+ onChange(0, true);
152
+ }
153
+ break;
154
+
155
+ case KEY.PAGEDOWN:
156
+ event.preventDefault();
157
+ if ( select.visible() ) {
158
+ select.pageDown();
159
+ } else {
160
+ onChange(0, true);
161
+ }
162
+ break;
163
+
164
+ // matches also semicolon
165
+ case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
166
+ case KEY.TAB:
167
+ case KEY.RETURN:
168
+ if( selectCurrent() ) {
169
+ // stop default to prevent a form submit, Opera needs special handling
170
+ event.preventDefault();
171
+ blockSubmit = true;
172
+ return false;
173
+ }
174
+ break;
175
+
176
+ case KEY.ESC:
177
+ select.hide();
178
+ break;
179
+
180
+ default:
181
+ clearTimeout(timeout);
182
+ timeout = setTimeout(onChange, options.delay);
183
+ break;
184
+ }
185
+ }).focus(function(){
186
+ // track whether the field has focus, we shouldn't process any
187
+ // results if the field no longer has focus
188
+ hasFocus++;
189
+ }).blur(function() {
190
+ hasFocus = 0;
191
+ if (!config.mouseDownOnSelect) {
192
+ hideResults();
193
+ }
194
+ }).click(function() {
195
+ // show select when clicking in a focused field
196
+ if ( hasFocus++ > 1 && !select.visible() ) {
197
+ onChange(0, true);
198
+ }
199
+ }).bind("search", function() {
200
+ // TODO why not just specifying both arguments?
201
+ var fn = (arguments.length > 1) ? arguments[1] : null;
202
+ function findValueCallback(q, data) {
203
+ var result;
204
+ if( data && data.length ) {
205
+ for (var i=0; i < data.length; i++) {
206
+ if( data[i].result.toLowerCase() == q.toLowerCase() ) {
207
+ result = data[i];
208
+ break;
209
+ }
210
+ }
211
+ }
212
+ if( typeof fn == "function" ) fn(result);
213
+ else $input.trigger("result", result && [result.data, result.value]);
214
+ }
215
+ $.each(trimWords($input.val()), function(i, value) {
216
+ request(value, findValueCallback, findValueCallback);
217
+ });
218
+ }).bind("flushCache", function() {
219
+ cache.flush();
220
+ }).bind("setOptions", function() {
221
+ $.extend(options, arguments[1]);
222
+ // if we've updated the data, repopulate
223
+ if ( "data" in arguments[1] )
224
+ cache.populate();
225
+ }).bind("unautocomplete", function() {
226
+ select.unbind();
227
+ $input.unbind();
228
+ $(input.form).unbind(".autocomplete");
229
+ });
230
+
231
+
232
+ function selectCurrent() {
233
+ var selected = select.selected();
234
+ if( !selected )
235
+ return false;
236
+
237
+ var v = selected.result;
238
+ previousValue = v;
239
+
240
+ if ( options.multiple ) {
241
+ var words = trimWords($input.val());
242
+ if ( words.length > 1 ) {
243
+ var seperator = options.multipleSeparator.length;
244
+ var cursorAt = $(input).selection().start;
245
+ var wordAt, progress = 0;
246
+ $.each(words, function(i, word) {
247
+ progress += word.length;
248
+ if (cursorAt <= progress) {
249
+ wordAt = i;
250
+ return false;
251
+ }
252
+ progress += seperator;
253
+ });
254
+ words[wordAt] = v;
255
+ // TODO this should set the cursor to the right position, but it gets overriden somewhere
256
+ //$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
257
+ v = words.join( options.multipleSeparator );
258
+ }
259
+ v += options.multipleSeparator;
260
+ }
261
+
262
+ $input.val(v);
263
+ hideResultsNow();
264
+ $input.trigger("result", [selected.data, selected.value]);
265
+ return true;
266
+ }
267
+
268
+ function onChange(crap, skipPrevCheck) {
269
+ if( lastKeyPressCode == KEY.DEL ) {
270
+ select.hide();
271
+ return;
272
+ }
273
+
274
+ var currentValue = $input.val();
275
+
276
+ if ( !skipPrevCheck && currentValue == previousValue )
277
+ return;
278
+
279
+ previousValue = currentValue;
280
+
281
+ currentValue = lastWord(currentValue);
282
+ if ( currentValue.length >= options.minChars) {
283
+ $input.addClass(options.loadingClass);
284
+ if (!options.matchCase)
285
+ currentValue = currentValue.toLowerCase();
286
+ request(currentValue, receiveData, hideResultsNow);
287
+ } else {
288
+ stopLoading();
289
+ select.hide();
290
+ }
291
+ };
292
+
293
+ function trimWords(value) {
294
+ if (!value)
295
+ return [""];
296
+ if (!options.multiple)
297
+ return [$.trim(value)];
298
+ return $.map(value.split(options.multipleSeparator), function(word) {
299
+ return $.trim(value).length ? $.trim(word) : null;
300
+ });
301
+ }
302
+
303
+ function lastWord(value) {
304
+ if ( !options.multiple )
305
+ return value;
306
+ var words = trimWords(value);
307
+ if (words.length == 1)
308
+ return words[0];
309
+ var cursorAt = $(input).selection().start;
310
+ if (cursorAt == value.length) {
311
+ words = trimWords(value)
312
+ } else {
313
+ words = trimWords(value.replace(value.substring(cursorAt), ""));
314
+ }
315
+ return words[words.length - 1];
316
+ }
317
+
318
+ // fills in the input box w/the first match (assumed to be the best match)
319
+ // q: the term entered
320
+ // sValue: the first matching result
321
+ function autoFill(q, sValue){
322
+ // autofill in the complete box w/the first match as long as the user hasn't entered in more data
323
+ // if the last user key pressed was backspace, don't autofill
324
+ if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
325
+ // fill in the value (keep the case the user has typed)
326
+ $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
327
+ // select the portion of the value not typed by the user (so the next character will erase)
328
+ $(input).selection(previousValue.length, previousValue.length + sValue.length);
329
+ }
330
+ };
331
+
332
+ function hideResults() {
333
+ clearTimeout(timeout);
334
+ timeout = setTimeout(hideResultsNow, 200);
335
+ };
336
+
337
+ function hideResultsNow() {
338
+ var wasVisible = select.visible();
339
+ select.hide();
340
+ clearTimeout(timeout);
341
+ stopLoading();
342
+ if (options.mustMatch) {
343
+ // call search and run callback
344
+ $input.search(
345
+ function (result){
346
+ // if no value found, clear the input box
347
+ if( !result ) {
348
+ if (options.multiple) {
349
+ var words = trimWords($input.val()).slice(0, -1);
350
+ $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
351
+ }
352
+ else {
353
+ $input.val( "" );
354
+ $input.trigger("result", null);
355
+ }
356
+ }
357
+ }
358
+ );
359
+ }
360
+ };
361
+
362
+ function receiveData(q, data) {
363
+ var parsed = options.parse && options.parse(data) || parse(data);
364
+ cache.add(q, parsed);
365
+ if ( parsed && parsed.length && hasFocus ) {
366
+ stopLoading();
367
+ select.display(parsed, q);
368
+ autoFill(q, parsed[0].value);
369
+ select.show();
370
+ } else {
371
+ hideResultsNow();
372
+ }
373
+ };
374
+
375
+ function request(term, success, failure) {
376
+ if (!options.matchCase)
377
+ term = term.toLowerCase();
378
+ var data = cache.load(term);
379
+ // recieve the cached data
380
+ if (data && data.length) {
381
+ success(term, data);
382
+ // if an AJAX url has been supplied, try loading the data now
383
+ } else if( (typeof options.url == "string") && (options.url.length > 0) ){
384
+
385
+ var extraParams = {
386
+ timestamp: +new Date()
387
+ };
388
+ $.each(options.extraParams, function(key, param) {
389
+ extraParams[key] = typeof param == "function" ? param() : param;
390
+ });
391
+
392
+ // work out what fields to request from solr. the one
393
+ // required field is the value used for the autocomplete
394
+ // while the resultKey is optional and allows the result
395
+ // to be identified by other means e.g. primary key
396
+ var fl = solrFieldName();
397
+ if (options.resultKey)
398
+ fl = [fl, options.resultKey].join(" ");
399
+
400
+ ajaxSolrManager.store.addByValue('q', makeQuery(lastWord(term)));
401
+ ajaxSolrManager.store.addByValue('indent', 'on');
402
+ ajaxSolrManager.store.addByValue('sort', 'score desc');
403
+ ajaxSolrManager.store.addByValue('fl', fl);
404
+
405
+ $.ajaxSolrCallback = success;
406
+ $.ajaxSolrFieldName = solrFieldName();
407
+
408
+ ajaxSolrManager.doRequest();
409
+
410
+ } else {
411
+ // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
412
+ select.emptyList();
413
+ failure(term);
414
+ }
415
+ };
416
+
417
+ function solrFieldName(){
418
+ return fieldName + (suggest ? '_as' : '_ac' )
419
+ };
420
+
421
+ function makeQuery(phrase){
422
+ if (suggest){
423
+ return phrase.trim().split(/\s+/).map(function(word){
424
+ return solrFieldName() + ":" + word;
425
+ }).join (" AND ");
426
+ }else{
427
+ return solrFieldName() + ":" + phrase.trim().split(/\s+/).join("\\ ");
428
+ }
429
+ };
430
+
431
+ function parse(data) {
432
+ var parsed = [];
433
+ var rows = data.split("\n");
434
+ for (var i=0; i < rows.length; i++) {
435
+ var row = $.trim(rows[i]);
436
+ if (row) {
437
+ row = row.split("|");
438
+ parsed[parsed.length] = {
439
+ data: row,
440
+ value: row[0],
441
+ result: options.formatResult && options.formatResult(row, row[0]) || row[0]
442
+ };
443
+ }
444
+ }
445
+ return parsed;
446
+ };
447
+
448
+ function stopLoading() {
449
+ $input.removeClass(options.loadingClass);
450
+ };
451
+
452
+ };
453
+
454
+ $.Autocompleter.defaults = {
455
+ inputClass: "ac_input",
456
+ resultsClass: "ac_results",
457
+ loadingClass: "ac_loading",
458
+ minChars: 1,
459
+ delay: 400,
460
+ matchCase: false,
461
+ matchSubset: true,
462
+ matchContains: false,
463
+ cacheLength: 10,
464
+ max: 100,
465
+ mustMatch: false,
466
+ extraParams: {},
467
+ selectFirst: true,
468
+ formatItem: function(row) { return row[0]; },
469
+ formatMatch: null,
470
+ autoFill: false,
471
+ width: 0,
472
+ multiple: false,
473
+ multipleSeparator: ", ",
474
+ highlight: function(value, term) {
475
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong class='highlight'>$1</strong>");
476
+ },
477
+ scroll: true,
478
+ scrollHeight: 180,
479
+ suggest: false
480
+ };
481
+
482
+ $.Autocompleter.Cache = function(options) {
483
+
484
+ var data = {};
485
+ var length = 0;
486
+
487
+ function matchSubset(s, sub) {
488
+ if (!options.matchCase)
489
+ s = s.toLowerCase();
490
+ var i = s.indexOf(sub);
491
+ if (options.matchContains == "word"){
492
+ i = s.toLowerCase().search("\\b" + sub.toLowerCase());
493
+ }
494
+ if (i == -1) return false;
495
+ return i == 0 || options.matchContains;
496
+ };
497
+
498
+ function add(q, value) {
499
+ if (length > options.cacheLength){
500
+ flush();
501
+ }
502
+ if (!data[q]){
503
+ length++;
504
+ }
505
+ data[q] = value;
506
+ }
507
+
508
+ function populate(){
509
+ if( !options.data ) return false;
510
+ // track the matches
511
+ var stMatchSets = {},
512
+ nullData = 0;
513
+
514
+ // no url was specified, we need to adjust the cache length to make sure it fits the local data store
515
+ if( !options.url ) options.cacheLength = 1;
516
+
517
+ // track all options for minChars = 0
518
+ stMatchSets[""] = [];
519
+
520
+ // loop through the array and create a lookup structure
521
+ for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
522
+ var rawValue = options.data[i];
523
+ // if rawValue is a string, make an array otherwise just reference the array
524
+ rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
525
+
526
+ var value = options.formatMatch(rawValue, i+1, options.data.length);
527
+ if ( value === false )
528
+ continue;
529
+
530
+ var firstChar = value.charAt(0).toLowerCase();
531
+ // if no lookup array for this character exists, look it up now
532
+ if( !stMatchSets[firstChar] )
533
+ stMatchSets[firstChar] = [];
534
+
535
+ // if the match is a string
536
+ var row = {
537
+ value: value,
538
+ data: rawValue,
539
+ result: options.formatResult && options.formatResult(rawValue) || value
540
+ };
541
+
542
+ // push the current match into the set list
543
+ stMatchSets[firstChar].push(row);
544
+
545
+ // keep track of minChars zero items
546
+ if ( nullData++ < options.max ) {
547
+ stMatchSets[""].push(row);
548
+ }
549
+ };
550
+
551
+ // add the data items to the cache
552
+ $.each(stMatchSets, function(i, value) {
553
+ // increase the cache size
554
+ options.cacheLength++;
555
+ // add to the cache
556
+ add(i, value);
557
+ });
558
+ }
559
+
560
+ // populate any existing data
561
+ setTimeout(populate, 25);
562
+
563
+ function flush(){
564
+ data = {};
565
+ length = 0;
566
+ }
567
+
568
+ return {
569
+ flush: flush,
570
+ add: add,
571
+ populate: populate,
572
+ load: function(q) {
573
+ if (!options.cacheLength || !length)
574
+ return null;
575
+ /*
576
+ * if dealing w/local data and matchContains than we must make sure
577
+ * to loop through all the data collections looking for matches
578
+ */
579
+ if( !options.url && options.matchContains ){
580
+ // track all matches
581
+ var csub = [];
582
+ // loop through all the data grids for matches
583
+ for( var k in data ){
584
+ // don't search through the stMatchSets[""] (minChars: 0) cache
585
+ // this prevents duplicates
586
+ if( k.length > 0 ){
587
+ var c = data[k];
588
+ $.each(c, function(i, x) {
589
+ // if we've got a match, add it to the array
590
+ if (matchSubset(x.value, q)) {
591
+ csub.push(x);
592
+ }
593
+ });
594
+ }
595
+ }
596
+ return csub;
597
+ } else
598
+ // if the exact item exists, use it
599
+ if (data[q]){
600
+ return data[q];
601
+ } else
602
+ if (options.matchSubset) {
603
+ for (var i = q.length - 1; i >= options.minChars; i--) {
604
+ var c = data[q.substr(0, i)];
605
+ if (c) {
606
+ var csub = [];
607
+ $.each(c, function(i, x) {
608
+ if (matchSubset(x.value, q)) {
609
+ csub[csub.length] = x;
610
+ }
611
+ });
612
+ return csub;
613
+ }
614
+ }
615
+ }
616
+ return null;
617
+ }
618
+ };
619
+ };
620
+
621
+ $.Autocompleter.Select = function (options, input, select, config) {
622
+ var CLASSES = {
623
+ ACTIVE: "ac_over"
624
+ };
625
+
626
+ var listItems,
627
+ active = -1,
628
+ data,
629
+ term = "",
630
+ needsInit = true,
631
+ element,
632
+ list;
633
+
634
+ // Create results
635
+ function init() {
636
+ if (!needsInit)
637
+ return;
638
+ element = $("<div/>")
639
+ .hide()
640
+ .addClass(options.resultsClass)
641
+ .css("position", "absolute")
642
+ .appendTo(document.body);
643
+
644
+ list = $("<ul/>").appendTo(element).mouseover( function(event) {
645
+ if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
646
+ active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
647
+ $(target(event)).addClass(CLASSES.ACTIVE);
648
+ }
649
+ }).click(function(event) {
650
+ $(target(event)).addClass(CLASSES.ACTIVE);
651
+ select();
652
+ // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
653
+ input.focus();
654
+ return false;
655
+ }).mousedown(function() {
656
+ config.mouseDownOnSelect = true;
657
+ }).mouseup(function() {
658
+ config.mouseDownOnSelect = false;
659
+ });
660
+
661
+ if( options.width > 0 ) {
662
+ element.css("width", options.width);
663
+ list.css("width", options.width);
664
+ }
665
+
666
+ needsInit = false;
667
+ }
668
+
669
+ function target(event) {
670
+ var element = event.target;
671
+ while(element && element.tagName != "LI")
672
+ element = element.parentNode;
673
+ // more fun with IE, sometimes event.target is empty, just ignore it then
674
+ if(!element)
675
+ return [];
676
+ return element;
677
+ }
678
+
679
+ function moveSelect(step) {
680
+ listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
681
+ movePosition(step);
682
+ var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
683
+ if(options.scroll) {
684
+ var offset = 0;
685
+ listItems.slice(0, active).each(function() {
686
+ offset += this.offsetHeight;
687
+ });
688
+ if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
689
+ list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
690
+ } else if(offset < list.scrollTop()) {
691
+ list.scrollTop(offset);
692
+ }
693
+ }
694
+ };
695
+
696
+ function movePosition(step) {
697
+ active += step;
698
+ if (active < 0) {
699
+ active = listItems.size() - 1;
700
+ } else if (active >= listItems.size()) {
701
+ active = 0;
702
+ }
703
+ }
704
+
705
+ function limitNumberOfItems(available) {
706
+ return options.max && options.max < available
707
+ ? options.max
708
+ : available;
709
+ }
710
+
711
+ function fillList() {
712
+ list.empty();
713
+ var max = limitNumberOfItems(data.length);
714
+ for (var i=0; i < max; i++) {
715
+ if (!data[i]) { continue; }
716
+ var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
717
+ if ( formatted === false ) { continue; }
718
+ str = options.highlight(formatted, $(input).val());
719
+ // /* DEBUG */ console.log(input, $(input).val());
720
+ var li = $("<li/>").html( str ).addClass(i % 2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
721
+ $.data(li, "ac_data", data[i]);
722
+ }
723
+ listItems = list.find("li");
724
+ if ( options.selectFirst ) {
725
+ listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
726
+ active = 0;
727
+ }
728
+ // apply bgiframe if available
729
+ if ( $.fn.bgiframe )
730
+ list.bgiframe();
731
+ }
732
+
733
+ return {
734
+ display: function(d, q) {
735
+ init();
736
+ data = d;
737
+ term = q;
738
+ fillList();
739
+ },
740
+ next: function() {
741
+ moveSelect(1);
742
+ },
743
+ prev: function() {
744
+ moveSelect(-1);
745
+ },
746
+ pageUp: function() {
747
+ if (active != 0 && active - 8 < 0) {
748
+ moveSelect( -active );
749
+ } else {
750
+ moveSelect(-8);
751
+ }
752
+ },
753
+ pageDown: function() {
754
+ if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
755
+ moveSelect( listItems.size() - 1 - active );
756
+ } else {
757
+ moveSelect(8);
758
+ }
759
+ },
760
+ hide: function() {
761
+ element && element.hide();
762
+ listItems && listItems.removeClass(CLASSES.ACTIVE);
763
+ active = -1;
764
+ },
765
+ visible : function() {
766
+ return element && element.is(":visible");
767
+ },
768
+ current: function() {
769
+ return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
770
+ },
771
+ show: function() {
772
+ var offset = $(input).offset();
773
+ element.css({
774
+ width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
775
+ top: offset.top + input.offsetHeight,
776
+ left: offset.left
777
+ }).show();
778
+ if(options.scroll) {
779
+ list.scrollTop(0);
780
+ list.css({
781
+ maxHeight: options.scrollHeight,
782
+ overflow: 'auto'
783
+ });
784
+
785
+ if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
786
+ var listHeight = 0;
787
+ listItems.each(function() {
788
+ listHeight += this.offsetHeight;
789
+ });
790
+ var scrollbarsVisible = listHeight > options.scrollHeight;
791
+ list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
792
+ if (!scrollbarsVisible) {
793
+ // IE doesn't recalculate width when scrollbar disappears
794
+ listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
795
+ }
796
+ }
797
+
798
+ }
799
+ },
800
+ selected: function() {
801
+ var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
802
+ return selected && selected.length && $.data(selected[0], "ac_data");
803
+ },
804
+ emptyList: function (){
805
+ list && list.empty();
806
+ },
807
+ unbind: function() {
808
+ element && element.remove();
809
+ }
810
+ };
811
+ };
812
+
813
+ $.fn.selection = function(start, end) {
814
+ if (start !== undefined) {
815
+ return this.each(function() {
816
+ if( this.createTextRange ){
817
+ var selRange = this.createTextRange();
818
+ if (end === undefined || start == end) {
819
+ selRange.move("character", start);
820
+ selRange.select();
821
+ } else {
822
+ selRange.collapse(true);
823
+ selRange.moveStart("character", start);
824
+ selRange.moveEnd("character", end);
825
+ selRange.select();
826
+ }
827
+ } else if( this.setSelectionRange ){
828
+ this.setSelectionRange(start, end);
829
+ } else if( this.selectionStart ){
830
+ this.selectionStart = start;
831
+ this.selectionEnd = end;
832
+ }
833
+ });
834
+ }
835
+ var field = this[0];
836
+ if ( field.createTextRange ) {
837
+ var range = document.selection.createRange(),
838
+ orig = field.value,
839
+ teststring = "<->",
840
+ textLength = range.text.length;
841
+ range.text = teststring;
842
+ var caretAt = field.value.indexOf(teststring);
843
+ field.value = orig;
844
+ this.selection(caretAt, caretAt + textLength);
845
+ return {
846
+ start: caretAt,
847
+ end: caretAt + textLength
848
+ }
849
+ } else if( field.selectionStart !== undefined ){
850
+ return {
851
+ start: field.selectionStart,
852
+ end: field.selectionEnd
853
+ }
854
+ }
855
+ };
856
+
857
+ })(jQuery);
858
+
859
+ //// callback is not encapsulated in any objects
860
+ //// to avoid the (.) anomaly in the url
861
+ var applyAjaxSolrCallback = function(result){
862
+ var items = "";
863
+ $(result.response.docs).each(function(index, doc){
864
+ items = items + doc[$.ajaxSolrFieldName] + "\n";
865
+ });
866
+ $.ajaxSolrCallback(result.responseHeader.params.q, items);
867
+ };