supernova 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/Gemfile +1 -0
  2. data/Gemfile.lock +2 -0
  3. data/VERSION +1 -1
  4. data/lib/supernova/criteria.rb +6 -2
  5. data/lib/supernova/solr.rb +1 -0
  6. data/lib/supernova/solr_criteria.rb +10 -1
  7. data/lib/supernova/solr_indexer.rb +71 -0
  8. data/solr/conf/admin-extra.html +31 -0
  9. data/solr/conf/elevate.xml +36 -0
  10. data/solr/conf/mapping-FoldToASCII.txt +3813 -0
  11. data/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
  12. data/solr/conf/protwords.txt +21 -0
  13. data/solr/conf/schema.xml +475 -0
  14. data/solr/conf/scripts.conf +24 -0
  15. data/solr/conf/solrconfig.xml +1508 -0
  16. data/solr/conf/spellings.txt +2 -0
  17. data/solr/conf/stopwords.txt +58 -0
  18. data/solr/conf/synonyms.txt +29 -0
  19. data/solr/conf/velocity/VM_global_library.vm +184 -0
  20. data/solr/conf/velocity/browse.vm +45 -0
  21. data/solr/conf/velocity/cluster.vm +26 -0
  22. data/solr/conf/velocity/clusterResults.vm +29 -0
  23. data/solr/conf/velocity/doc.vm +29 -0
  24. data/solr/conf/velocity/facet_dates.vm +0 -0
  25. data/solr/conf/velocity/facet_fields.vm +12 -0
  26. data/solr/conf/velocity/facet_queries.vm +3 -0
  27. data/solr/conf/velocity/facet_ranges.vm +30 -0
  28. data/solr/conf/velocity/facets.vm +7 -0
  29. data/solr/conf/velocity/footer.vm +17 -0
  30. data/solr/conf/velocity/head.vm +45 -0
  31. data/solr/conf/velocity/header.vm +3 -0
  32. data/solr/conf/velocity/hit.vm +5 -0
  33. data/solr/conf/velocity/jquery.autocomplete.css +48 -0
  34. data/solr/conf/velocity/jquery.autocomplete.js +762 -0
  35. data/solr/conf/velocity/layout.vm +20 -0
  36. data/solr/conf/velocity/main.css +184 -0
  37. data/solr/conf/velocity/query.vm +56 -0
  38. data/solr/conf/velocity/querySpatial.vm +40 -0
  39. data/solr/conf/velocity/suggest.vm +3 -0
  40. data/solr/conf/velocity/tabs.vm +22 -0
  41. data/solr/conf/xslt/example.xsl +132 -0
  42. data/solr/conf/xslt/example_atom.xsl +67 -0
  43. data/solr/conf/xslt/example_rss.xsl +66 -0
  44. data/solr/conf/xslt/luke.xsl +337 -0
  45. data/spec/integration/solr_spec.rb +6 -0
  46. data/spec/spec_helper.rb +1 -1
  47. data/spec/supernova/solr_criteria_spec.rb +16 -0
  48. data/spec/supernova/solr_indexer_spec.rb +167 -0
  49. data/supernova.gemspec +45 -3
  50. metadata +91 -36
@@ -0,0 +1,7 @@
1
+ #parse('facet_fields.vm')
2
+ #parse('facet_queries.vm')
3
+ #parse('facet_ranges.vm')
4
+ #parse('cluster.vm')
5
+
6
+
7
+
@@ -0,0 +1,17 @@
1
+ <hr/>
2
+ <div>
3
+ <span>Options:</span>
4
+ #if($request.params.get('debugQuery'))
5
+ <a href="#url_for_home?#q#if($list.size($request.params.getParams('fq')) > 0)&#fqs($request.params.getParams('fq'))#end">disable debug</a>
6
+ #else
7
+ <a href="#url_for_lens&debugQuery=true">enable debug</a>
8
+ #end
9
+ #if($annotate)
10
+ <a href="#url_for_home?#q#if($list.size($request.params.getParams('fq')) > 0)&#fqs($request.params.getParams('fq'))#end#boostPrice">disable annotation</a>
11
+ #else
12
+ <a href="#url_for_lens&annotateBrowse=true">enable annotation</a>
13
+ #end
14
+ <a #annTitle("Click to switch to an XML response: &wt=xml") href="#url_for_lens&wt=xml#if($request.params.get('debugQuery'))&debugQuery=true#end">XML</a></div>
15
+ <div>Generated by <a href="http://wiki.apache.org/solr/VelocityResponseWriter">VelocityResponseWriter</a></div>
16
+ <div><span>Documentation: </span> <a href="http://lucene.apache.org/solr">Solr Home Page</a>, <a href="http://wiki.apache.org/solr">Solr Wiki</a></div>
17
+ <div>Disclaimer: The locations displayed in this demonstration are purely fictional. It is more than likely that no store with the items listed actually exists at that location!</div>
@@ -0,0 +1,45 @@
1
+
2
+ ## An example of using an arbitrary request parameter
3
+ <!--
4
+ Licensed to the Apache Software Foundation (ASF) under one or more
5
+ contributor license agreements. See the NOTICE file distributed with
6
+ this work for additional information regarding copyright ownership.
7
+ The ASF licenses this file to You under the Apache License, Version 2.0
8
+ (the "License"); you may not use this file except in compliance with
9
+ the License. You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+ -->
19
+
20
+ <title>#param('title')</title>
21
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
22
+
23
+ <script type="text/javascript" src="#{url_for_solr}/admin/jquery-1.4.3.min.js"></script>
24
+ <link rel="stylesheet" type="text/css" href="#{url_for_solr}/admin/file?file=/velocity/main.css&contentType=text/css"/>
25
+ <link rel="stylesheet" href="#{url_for_solr}/admin/file?file=/velocity/jquery.autocomplete.css&contentType=text/css" type="text/css" />
26
+ <script type="text/javascript" src="#{url_for_solr}/admin/file?file=/velocity/jquery.autocomplete.js&contentType=text/javascript"></script>
27
+
28
+
29
+ <script>
30
+ $(document).ready(function(){
31
+ $("\#q").autocomplete('#{url_for_solr}/terms', { ## backslash escaped #q as that is a macro defined in VM_global_library.vm
32
+ extraParams:{
33
+ 'terms.prefix': function() { return $("\#q").val();},
34
+ 'terms.sort': 'count',
35
+ 'terms.fl': 'name',
36
+ 'wt': 'velocity',
37
+ 'v.template': 'suggest'
38
+ }
39
+ }
40
+ );
41
+
42
+ // http://localhost:8983/solr/terms?terms.fl=name&terms.prefix=i&terms.sort=count
43
+ });
44
+
45
+ </script>
@@ -0,0 +1,3 @@
1
+ <div id="head">
2
+ <span ><a href="#url_for_home#if($request.params.get('debugQuery'))?debugQuery=true#end"><img src="#{url_for_solr}/admin/solr_small.png" id="logo"/></a></span>
3
+ </div>
@@ -0,0 +1,5 @@
1
+ #set($docId = $doc.getFieldValue('id'))
2
+
3
+ <div class="result-document">
4
+ #parse("doc.vm")
5
+ </div>
@@ -0,0 +1,48 @@
1
+ .ac_results {
2
+ padding: 0px;
3
+ border: 1px solid black;
4
+ background-color: white;
5
+ overflow: hidden;
6
+ z-index: 99999;
7
+ }
8
+
9
+ .ac_results ul {
10
+ width: 100%;
11
+ list-style-position: outside;
12
+ list-style: none;
13
+ padding: 0;
14
+ margin: 0;
15
+ }
16
+
17
+ .ac_results li {
18
+ margin: 0px;
19
+ padding: 2px 5px;
20
+ cursor: default;
21
+ display: block;
22
+ /*
23
+ if width will be 100% horizontal scrollbar will apear
24
+ when scroll mode will be used
25
+ */
26
+ /*width: 100%;*/
27
+ font: menu;
28
+ font-size: 12px;
29
+ /*
30
+ it is very important, if line-height not setted or setted
31
+ in relative units scroll will be broken in firefox
32
+ */
33
+ line-height: 16px;
34
+ overflow: hidden;
35
+ }
36
+
37
+ .ac_loading {
38
+ background: white url('indicator.gif') right center no-repeat;
39
+ }
40
+
41
+ .ac_odd {
42
+ background-color: #eee;
43
+ }
44
+
45
+ .ac_over {
46
+ background-color: #0A246A;
47
+ color: white;
48
+ }
@@ -0,0 +1,762 @@
1
+ /*
2
+ * Autocomplete - jQuery plugin 1.1pre
3
+ *
4
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
5
+ *
6
+ * Dual licensed under the MIT and GPL licenses:
7
+ * http://www.opensource.org/licenses/mit-license.php
8
+ * http://www.gnu.org/licenses/gpl.html
9
+ *
10
+ * Revision: $Id: jquery.autocomplete.js 5785 2008-07-12 10:37:33Z joern.zaefferer $
11
+ *
12
+ */
13
+
14
+ ;(function($) {
15
+
16
+ $.fn.extend({
17
+ autocomplete: function(urlOrData, options) {
18
+ var isUrl = typeof urlOrData == "string";
19
+ options = $.extend({}, $.Autocompleter.defaults, {
20
+ url: isUrl ? urlOrData : null,
21
+ data: isUrl ? null : urlOrData,
22
+ delay: isUrl ? $.Autocompleter.defaults.delay : 10,
23
+ max: options && !options.scroll ? 10 : 150
24
+ }, options);
25
+
26
+ // if highlight is set to false, replace it with a do-nothing function
27
+ options.highlight = options.highlight || function(value) { return value; };
28
+
29
+ // if the formatMatch option is not specified, then use formatItem for backwards compatibility
30
+ options.formatMatch = options.formatMatch || options.formatItem;
31
+
32
+ return this.each(function() {
33
+ new $.Autocompleter(this, options);
34
+ });
35
+ },
36
+ result: function(handler) {
37
+ return this.bind("result", handler);
38
+ },
39
+ search: function(handler) {
40
+ return this.trigger("search", [handler]);
41
+ },
42
+ flushCache: function() {
43
+ return this.trigger("flushCache");
44
+ },
45
+ setOptions: function(options){
46
+ return this.trigger("setOptions", [options]);
47
+ },
48
+ unautocomplete: function() {
49
+ return this.trigger("unautocomplete");
50
+ }
51
+ });
52
+
53
+ $.Autocompleter = function(input, options) {
54
+
55
+ var KEY = {
56
+ UP: 38,
57
+ DOWN: 40,
58
+ DEL: 46,
59
+ TAB: 9,
60
+ RETURN: 13,
61
+ ESC: 27,
62
+ COMMA: 188,
63
+ PAGEUP: 33,
64
+ PAGEDOWN: 34,
65
+ BACKSPACE: 8
66
+ };
67
+
68
+ // Create $ object for input element
69
+ var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
70
+
71
+ var timeout;
72
+ var previousValue = "";
73
+ var cache = $.Autocompleter.Cache(options);
74
+ var hasFocus = 0;
75
+ var lastKeyPressCode;
76
+ var config = {
77
+ mouseDownOnSelect: false
78
+ };
79
+ var select = $.Autocompleter.Select(options, input, selectCurrent, config);
80
+
81
+ var blockSubmit;
82
+
83
+ // prevent form submit in opera when selecting with return key
84
+ $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
85
+ if (blockSubmit) {
86
+ blockSubmit = false;
87
+ return false;
88
+ }
89
+ });
90
+
91
+ // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
92
+ $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
93
+ // track last key pressed
94
+ lastKeyPressCode = event.keyCode;
95
+ switch(event.keyCode) {
96
+
97
+ case KEY.UP:
98
+ event.preventDefault();
99
+ if ( select.visible() ) {
100
+ select.prev();
101
+ } else {
102
+ onChange(0, true);
103
+ }
104
+ break;
105
+
106
+ case KEY.DOWN:
107
+ event.preventDefault();
108
+ if ( select.visible() ) {
109
+ select.next();
110
+ } else {
111
+ onChange(0, true);
112
+ }
113
+ break;
114
+
115
+ case KEY.PAGEUP:
116
+ event.preventDefault();
117
+ if ( select.visible() ) {
118
+ select.pageUp();
119
+ } else {
120
+ onChange(0, true);
121
+ }
122
+ break;
123
+
124
+ case KEY.PAGEDOWN:
125
+ event.preventDefault();
126
+ if ( select.visible() ) {
127
+ select.pageDown();
128
+ } else {
129
+ onChange(0, true);
130
+ }
131
+ break;
132
+
133
+ // matches also semicolon
134
+ case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
135
+ case KEY.TAB:
136
+ case KEY.RETURN:
137
+ if( selectCurrent() ) {
138
+ // stop default to prevent a form submit, Opera needs special handling
139
+ event.preventDefault();
140
+ blockSubmit = true;
141
+ return false;
142
+ }
143
+ break;
144
+
145
+ case KEY.ESC:
146
+ select.hide();
147
+ break;
148
+
149
+ default:
150
+ clearTimeout(timeout);
151
+ timeout = setTimeout(onChange, options.delay);
152
+ break;
153
+ }
154
+ }).focus(function(){
155
+ // track whether the field has focus, we shouldn't process any
156
+ // results if the field no longer has focus
157
+ hasFocus++;
158
+ }).blur(function() {
159
+ hasFocus = 0;
160
+ if (!config.mouseDownOnSelect) {
161
+ hideResults();
162
+ }
163
+ }).click(function() {
164
+ // show select when clicking in a focused field
165
+ if ( hasFocus++ > 1 && !select.visible() ) {
166
+ onChange(0, true);
167
+ }
168
+ }).bind("search", function() {
169
+ // TODO why not just specifying both arguments?
170
+ var fn = (arguments.length > 1) ? arguments[1] : null;
171
+ function findValueCallback(q, data) {
172
+ var result;
173
+ if( data && data.length ) {
174
+ for (var i=0; i < data.length; i++) {
175
+ if( data[i].result.toLowerCase() == q.toLowerCase() ) {
176
+ result = data[i];
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ if( typeof fn == "function" ) fn(result);
182
+ else $input.trigger("result", result && [result.data, result.value]);
183
+ }
184
+ $.each(trimWords($input.val()), function(i, value) {
185
+ request(value, findValueCallback, findValueCallback);
186
+ });
187
+ }).bind("flushCache", function() {
188
+ cache.flush();
189
+ }).bind("setOptions", function() {
190
+ $.extend(options, arguments[1]);
191
+ // if we've updated the data, repopulate
192
+ if ( "data" in arguments[1] )
193
+ cache.populate();
194
+ }).bind("unautocomplete", function() {
195
+ select.unbind();
196
+ $input.unbind();
197
+ $(input.form).unbind(".autocomplete");
198
+ });
199
+
200
+
201
+ function selectCurrent() {
202
+ var selected = select.selected();
203
+ if( !selected )
204
+ return false;
205
+
206
+ var v = selected.result;
207
+ previousValue = v;
208
+
209
+ if ( options.multiple ) {
210
+ var words = trimWords($input.val());
211
+ if ( words.length > 1 ) {
212
+ v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
213
+ }
214
+ v += options.multipleSeparator;
215
+ }
216
+
217
+ $input.val(v);
218
+ hideResultsNow();
219
+ $input.trigger("result", [selected.data, selected.value]);
220
+ return true;
221
+ }
222
+
223
+ function onChange(crap, skipPrevCheck) {
224
+ if( lastKeyPressCode == KEY.DEL ) {
225
+ select.hide();
226
+ return;
227
+ }
228
+
229
+ var currentValue = $input.val();
230
+
231
+ if ( !skipPrevCheck && currentValue == previousValue )
232
+ return;
233
+
234
+ previousValue = currentValue;
235
+
236
+ currentValue = lastWord(currentValue);
237
+ if ( currentValue.length >= options.minChars) {
238
+ $input.addClass(options.loadingClass);
239
+ if (!options.matchCase)
240
+ currentValue = currentValue.toLowerCase();
241
+ request(currentValue, receiveData, hideResultsNow);
242
+ } else {
243
+ stopLoading();
244
+ select.hide();
245
+ }
246
+ };
247
+
248
+ function trimWords(value) {
249
+ if ( !value ) {
250
+ return [""];
251
+ }
252
+ var words = value.split( options.multipleSeparator );
253
+ var result = [];
254
+ $.each(words, function(i, value) {
255
+ if ( $.trim(value) )
256
+ result[i] = $.trim(value);
257
+ });
258
+ return result;
259
+ }
260
+
261
+ function lastWord(value) {
262
+ if ( !options.multiple )
263
+ return value;
264
+ var words = trimWords(value);
265
+ return words[words.length - 1];
266
+ }
267
+
268
+ // fills in the input box w/the first match (assumed to be the best match)
269
+ // q: the term entered
270
+ // sValue: the first matching result
271
+ function autoFill(q, sValue){
272
+ // autofill in the complete box w/the first match as long as the user hasn't entered in more data
273
+ // if the last user key pressed was backspace, don't autofill
274
+ if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
275
+ // fill in the value (keep the case the user has typed)
276
+ $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
277
+ // select the portion of the value not typed by the user (so the next character will erase)
278
+ $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
279
+ }
280
+ };
281
+
282
+ function hideResults() {
283
+ clearTimeout(timeout);
284
+ timeout = setTimeout(hideResultsNow, 200);
285
+ };
286
+
287
+ function hideResultsNow() {
288
+ var wasVisible = select.visible();
289
+ select.hide();
290
+ clearTimeout(timeout);
291
+ stopLoading();
292
+ if (options.mustMatch) {
293
+ // call search and run callback
294
+ $input.search(
295
+ function (result){
296
+ // if no value found, clear the input box
297
+ if( !result ) {
298
+ if (options.multiple) {
299
+ var words = trimWords($input.val()).slice(0, -1);
300
+ $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
301
+ }
302
+ else
303
+ $input.val( "" );
304
+ }
305
+ }
306
+ );
307
+ }
308
+ if (wasVisible)
309
+ // position cursor at end of input field
310
+ $.Autocompleter.Selection(input, input.value.length, input.value.length);
311
+ };
312
+
313
+ function receiveData(q, data) {
314
+ if ( data && data.length && hasFocus ) {
315
+ stopLoading();
316
+ select.display(data, q);
317
+ autoFill(q, data[0].value);
318
+ select.show();
319
+ } else {
320
+ hideResultsNow();
321
+ }
322
+ };
323
+
324
+ function request(term, success, failure) {
325
+ if (!options.matchCase)
326
+ term = term.toLowerCase();
327
+ var data = cache.load(term);
328
+ // recieve the cached data
329
+ if (data && data.length) {
330
+ success(term, data);
331
+ // if an AJAX url has been supplied, try loading the data now
332
+ } else if( (typeof options.url == "string") && (options.url.length > 0) ){
333
+
334
+ var extraParams = {
335
+ timestamp: +new Date()
336
+ };
337
+ $.each(options.extraParams, function(key, param) {
338
+ extraParams[key] = typeof param == "function" ? param() : param;
339
+ });
340
+
341
+ $.ajax({
342
+ // try to leverage ajaxQueue plugin to abort previous requests
343
+ mode: "abort",
344
+ // limit abortion to this input
345
+ port: "autocomplete" + input.name,
346
+ dataType: options.dataType,
347
+ url: options.url,
348
+ data: $.extend({
349
+ q: lastWord(term),
350
+ limit: options.max
351
+ }, extraParams),
352
+ success: function(data) {
353
+ var parsed = options.parse && options.parse(data) || parse(data);
354
+ cache.add(term, parsed);
355
+ success(term, parsed);
356
+ }
357
+ });
358
+ } else {
359
+ // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
360
+ select.emptyList();
361
+ failure(term);
362
+ }
363
+ };
364
+
365
+ function parse(data) {
366
+ var parsed = [];
367
+ var rows = data.split("\n");
368
+ for (var i=0; i < rows.length; i++) {
369
+ var row = $.trim(rows[i]);
370
+ if (row) {
371
+ row = row.split("|");
372
+ parsed[parsed.length] = {
373
+ data: row,
374
+ value: row[0],
375
+ result: options.formatResult && options.formatResult(row, row[0]) || row[0]
376
+ };
377
+ }
378
+ }
379
+ return parsed;
380
+ };
381
+
382
+ function stopLoading() {
383
+ $input.removeClass(options.loadingClass);
384
+ };
385
+
386
+ };
387
+
388
+ $.Autocompleter.defaults = {
389
+ inputClass: "ac_input",
390
+ resultsClass: "ac_results",
391
+ loadingClass: "ac_loading",
392
+ minChars: 1,
393
+ delay: 400,
394
+ matchCase: false,
395
+ matchSubset: true,
396
+ matchContains: false,
397
+ cacheLength: 10,
398
+ max: 100,
399
+ mustMatch: false,
400
+ extraParams: {},
401
+ selectFirst: true,
402
+ formatItem: function(row) { return row[0]; },
403
+ formatMatch: null,
404
+ autoFill: false,
405
+ width: 0,
406
+ multiple: false,
407
+ multipleSeparator: ", ",
408
+ highlight: function(value, term) {
409
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
410
+ },
411
+ scroll: true,
412
+ scrollHeight: 180
413
+ };
414
+
415
+ $.Autocompleter.Cache = function(options) {
416
+
417
+ var data = {};
418
+ var length = 0;
419
+
420
+ function matchSubset(s, sub) {
421
+ if (!options.matchCase)
422
+ s = s.toLowerCase();
423
+ var i = s.indexOf(sub);
424
+ if (options.matchContains == "word"){
425
+ i = s.toLowerCase().search("\\b" + sub.toLowerCase());
426
+ }
427
+ if (i == -1) return false;
428
+ return i == 0 || options.matchContains;
429
+ };
430
+
431
+ function add(q, value) {
432
+ if (length > options.cacheLength){
433
+ flush();
434
+ }
435
+ if (!data[q]){
436
+ length++;
437
+ }
438
+ data[q] = value;
439
+ }
440
+
441
+ function populate(){
442
+ if( !options.data ) return false;
443
+ // track the matches
444
+ var stMatchSets = {},
445
+ nullData = 0;
446
+
447
+ // no url was specified, we need to adjust the cache length to make sure it fits the local data store
448
+ if( !options.url ) options.cacheLength = 1;
449
+
450
+ // track all options for minChars = 0
451
+ stMatchSets[""] = [];
452
+
453
+ // loop through the array and create a lookup structure
454
+ for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
455
+ var rawValue = options.data[i];
456
+ // if rawValue is a string, make an array otherwise just reference the array
457
+ rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
458
+
459
+ var value = options.formatMatch(rawValue, i+1, options.data.length);
460
+ if ( value === false )
461
+ continue;
462
+
463
+ var firstChar = value.charAt(0).toLowerCase();
464
+ // if no lookup array for this character exists, look it up now
465
+ if( !stMatchSets[firstChar] )
466
+ stMatchSets[firstChar] = [];
467
+
468
+ // if the match is a string
469
+ var row = {
470
+ value: value,
471
+ data: rawValue,
472
+ result: options.formatResult && options.formatResult(rawValue) || value
473
+ };
474
+
475
+ // push the current match into the set list
476
+ stMatchSets[firstChar].push(row);
477
+
478
+ // keep track of minChars zero items
479
+ if ( nullData++ < options.max ) {
480
+ stMatchSets[""].push(row);
481
+ }
482
+ };
483
+
484
+ // add the data items to the cache
485
+ $.each(stMatchSets, function(i, value) {
486
+ // increase the cache size
487
+ options.cacheLength++;
488
+ // add to the cache
489
+ add(i, value);
490
+ });
491
+ }
492
+
493
+ // populate any existing data
494
+ setTimeout(populate, 25);
495
+
496
+ function flush(){
497
+ data = {};
498
+ length = 0;
499
+ }
500
+
501
+ return {
502
+ flush: flush,
503
+ add: add,
504
+ populate: populate,
505
+ load: function(q) {
506
+ if (!options.cacheLength || !length)
507
+ return null;
508
+ /*
509
+ * if dealing w/local data and matchContains than we must make sure
510
+ * to loop through all the data collections looking for matches
511
+ */
512
+ if( !options.url && options.matchContains ){
513
+ // track all matches
514
+ var csub = [];
515
+ // loop through all the data grids for matches
516
+ for( var k in data ){
517
+ // don't search through the stMatchSets[""] (minChars: 0) cache
518
+ // this prevents duplicates
519
+ if( k.length > 0 ){
520
+ var c = data[k];
521
+ $.each(c, function(i, x) {
522
+ // if we've got a match, add it to the array
523
+ if (matchSubset(x.value, q)) {
524
+ csub.push(x);
525
+ }
526
+ });
527
+ }
528
+ }
529
+ return csub;
530
+ } else
531
+ // if the exact item exists, use it
532
+ if (data[q]){
533
+ return data[q];
534
+ } else
535
+ if (options.matchSubset) {
536
+ for (var i = q.length - 1; i >= options.minChars; i--) {
537
+ var c = data[q.substr(0, i)];
538
+ if (c) {
539
+ var csub = [];
540
+ $.each(c, function(i, x) {
541
+ if (matchSubset(x.value, q)) {
542
+ csub[csub.length] = x;
543
+ }
544
+ });
545
+ return csub;
546
+ }
547
+ }
548
+ }
549
+ return null;
550
+ }
551
+ };
552
+ };
553
+
554
+ $.Autocompleter.Select = function (options, input, select, config) {
555
+ var CLASSES = {
556
+ ACTIVE: "ac_over"
557
+ };
558
+
559
+ var listItems,
560
+ active = -1,
561
+ data,
562
+ term = "",
563
+ needsInit = true,
564
+ element,
565
+ list;
566
+
567
+ // Create results
568
+ function init() {
569
+ if (!needsInit)
570
+ return;
571
+ element = $("<div/>")
572
+ .hide()
573
+ .addClass(options.resultsClass)
574
+ .css("position", "absolute")
575
+ .appendTo(document.body);
576
+
577
+ list = $("<ul/>").appendTo(element).mouseover( function(event) {
578
+ if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
579
+ active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
580
+ $(target(event)).addClass(CLASSES.ACTIVE);
581
+ }
582
+ }).click(function(event) {
583
+ $(target(event)).addClass(CLASSES.ACTIVE);
584
+ select();
585
+ // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
586
+ input.focus();
587
+ return false;
588
+ }).mousedown(function() {
589
+ config.mouseDownOnSelect = true;
590
+ }).mouseup(function() {
591
+ config.mouseDownOnSelect = false;
592
+ });
593
+
594
+ if( options.width > 0 )
595
+ element.css("width", options.width);
596
+
597
+ needsInit = false;
598
+ }
599
+
600
+ function target(event) {
601
+ var element = event.target;
602
+ while(element && element.tagName != "LI")
603
+ element = element.parentNode;
604
+ // more fun with IE, sometimes event.target is empty, just ignore it then
605
+ if(!element)
606
+ return [];
607
+ return element;
608
+ }
609
+
610
+ function moveSelect(step) {
611
+ listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
612
+ movePosition(step);
613
+ var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
614
+ if(options.scroll) {
615
+ var offset = 0;
616
+ listItems.slice(0, active).each(function() {
617
+ offset += this.offsetHeight;
618
+ });
619
+ if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
620
+ list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
621
+ } else if(offset < list.scrollTop()) {
622
+ list.scrollTop(offset);
623
+ }
624
+ }
625
+ };
626
+
627
+ function movePosition(step) {
628
+ active += step;
629
+ if (active < 0) {
630
+ active = listItems.size() - 1;
631
+ } else if (active >= listItems.size()) {
632
+ active = 0;
633
+ }
634
+ }
635
+
636
+ function limitNumberOfItems(available) {
637
+ return options.max && options.max < available
638
+ ? options.max
639
+ : available;
640
+ }
641
+
642
+ function fillList() {
643
+ list.empty();
644
+ var max = limitNumberOfItems(data.length);
645
+ for (var i=0; i < max; i++) {
646
+ if (!data[i])
647
+ continue;
648
+ var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
649
+ if ( formatted === false )
650
+ continue;
651
+ var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
652
+ $.data(li, "ac_data", data[i]);
653
+ }
654
+ listItems = list.find("li");
655
+ if ( options.selectFirst ) {
656
+ listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
657
+ active = 0;
658
+ }
659
+ // apply bgiframe if available
660
+ if ( $.fn.bgiframe )
661
+ list.bgiframe();
662
+ }
663
+
664
+ return {
665
+ display: function(d, q) {
666
+ init();
667
+ data = d;
668
+ term = q;
669
+ fillList();
670
+ },
671
+ next: function() {
672
+ moveSelect(1);
673
+ },
674
+ prev: function() {
675
+ moveSelect(-1);
676
+ },
677
+ pageUp: function() {
678
+ if (active != 0 && active - 8 < 0) {
679
+ moveSelect( -active );
680
+ } else {
681
+ moveSelect(-8);
682
+ }
683
+ },
684
+ pageDown: function() {
685
+ if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
686
+ moveSelect( listItems.size() - 1 - active );
687
+ } else {
688
+ moveSelect(8);
689
+ }
690
+ },
691
+ hide: function() {
692
+ element && element.hide();
693
+ listItems && listItems.removeClass(CLASSES.ACTIVE);
694
+ active = -1;
695
+ },
696
+ visible : function() {
697
+ return element && element.is(":visible");
698
+ },
699
+ current: function() {
700
+ return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
701
+ },
702
+ show: function() {
703
+ var offset = $(input).offset();
704
+ element.css({
705
+ width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
706
+ top: offset.top + input.offsetHeight,
707
+ left: offset.left
708
+ }).show();
709
+ if(options.scroll) {
710
+ list.scrollTop(0);
711
+ list.css({
712
+ maxHeight: options.scrollHeight,
713
+ overflow: 'auto'
714
+ });
715
+
716
+ if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
717
+ var listHeight = 0;
718
+ listItems.each(function() {
719
+ listHeight += this.offsetHeight;
720
+ });
721
+ var scrollbarsVisible = listHeight > options.scrollHeight;
722
+ list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
723
+ if (!scrollbarsVisible) {
724
+ // IE doesn't recalculate width when scrollbar disappears
725
+ listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
726
+ }
727
+ }
728
+
729
+ }
730
+ },
731
+ selected: function() {
732
+ var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
733
+ return selected && selected.length && $.data(selected[0], "ac_data");
734
+ },
735
+ emptyList: function (){
736
+ list && list.empty();
737
+ },
738
+ unbind: function() {
739
+ element && element.remove();
740
+ }
741
+ };
742
+ };
743
+
744
+ $.Autocompleter.Selection = function(field, start, end) {
745
+ if( field.createTextRange ){
746
+ var selRange = field.createTextRange();
747
+ selRange.collapse(true);
748
+ selRange.moveStart("character", start);
749
+ selRange.moveEnd("character", end);
750
+ selRange.select();
751
+ } else if( field.setSelectionRange ){
752
+ field.setSelectionRange(start, end);
753
+ } else {
754
+ if( field.selectionStart ){
755
+ field.selectionStart = start;
756
+ field.selectionEnd = end;
757
+ }
758
+ }
759
+ field.focus();
760
+ };
761
+
762
+ })(jQuery);