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.
- data/LICENSE +7 -0
- data/README +91 -0
- data/README.rdoc +91 -0
- data/Rakefile +37 -0
- data/VERSION +1 -0
- data/assets/javascripts/jquery.js +6240 -0
- data/assets/javascripts/solr-autocomplete/ajax-solr/core/AbstractManager.js +182 -0
- data/assets/javascripts/solr-autocomplete/ajax-solr/core/Core.js +226 -0
- data/assets/javascripts/solr-autocomplete/ajax-solr/core/Parameter.js +161 -0
- data/assets/javascripts/solr-autocomplete/ajax-solr/core/ParameterStore.js +354 -0
- data/assets/javascripts/solr-autocomplete/ajax-solr/managers/Manager.jquery.js +20 -0
- data/assets/javascripts/solr-autocomplete/jquery-autocomplete/indicator.gif +0 -0
- data/assets/javascripts/solr-autocomplete/jquery-autocomplete/jquery.autocomplete.css +49 -0
- data/assets/javascripts/solr-autocomplete/jquery-autocomplete/jquery.autocomplete.js +867 -0
- data/lib/autocomplete_view_helpers.rb +126 -0
- data/lib/sunspot_autocomplete.rb +38 -0
- data/rails/init.rb +3 -0
- data/rdoc/classes/AutocompleteViewHelpers.html +322 -0
- data/rdoc/classes/Sunspot.html +111 -0
- data/rdoc/classes/Sunspot/Type.html +112 -0
- data/rdoc/classes/Sunspot/Type/AutocompleteType.html +117 -0
- data/rdoc/classes/Sunspot/Type/AutosuggestType.html +117 -0
- data/rdoc/created.rid +1 -0
- data/rdoc/files/README.html +238 -0
- data/rdoc/files/README_rdoc.html +238 -0
- data/rdoc/files/lib/autocomplete_view_helpers_rb.html +236 -0
- data/rdoc/files/lib/sunspot_autocomplete_rb.html +101 -0
- data/rdoc/fr_class_index.html +31 -0
- data/rdoc/fr_file_index.html +29 -0
- data/rdoc/fr_method_index.html +28 -0
- data/rdoc/index.html +24 -0
- data/rdoc/rdoc-style.css +208 -0
- data/tasks/tasks.rake +10 -0
- data/test/sunspot_autocomplete_test.rb +21 -0
- data/test/test_helper.rb +3 -0
- data/vinova_sunspot_autocomplete.gemspec +80 -0
- 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
|
+
};
|