|
1 /* |
|
2 * Autocomplete - jQuery plugin 1.0.2 |
|
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 5747 2008-06-25 18:30:55Z 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 (i == -1) return false; |
|
425 return i == 0 || options.matchContains; |
|
426 }; |
|
427 |
|
428 function add(q, value) { |
|
429 if (length > options.cacheLength){ |
|
430 flush(); |
|
431 } |
|
432 if (!data[q]){ |
|
433 length++; |
|
434 } |
|
435 data[q] = value; |
|
436 } |
|
437 |
|
438 function populate(){ |
|
439 if( !options.data ) return false; |
|
440 // track the matches |
|
441 var stMatchSets = {}, |
|
442 nullData = 0; |
|
443 |
|
444 // no url was specified, we need to adjust the cache length to make sure it fits the local data store |
|
445 if( !options.url ) options.cacheLength = 1; |
|
446 |
|
447 // track all options for minChars = 0 |
|
448 stMatchSets[""] = []; |
|
449 |
|
450 // loop through the array and create a lookup structure |
|
451 for ( var i = 0, ol = options.data.length; i < ol; i++ ) { |
|
452 var rawValue = options.data[i]; |
|
453 // if rawValue is a string, make an array otherwise just reference the array |
|
454 rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; |
|
455 |
|
456 var value = options.formatMatch(rawValue, i+1, options.data.length); |
|
457 if ( value === false ) |
|
458 continue; |
|
459 |
|
460 var firstChar = value.charAt(0).toLowerCase(); |
|
461 // if no lookup array for this character exists, look it up now |
|
462 if( !stMatchSets[firstChar] ) |
|
463 stMatchSets[firstChar] = []; |
|
464 |
|
465 // if the match is a string |
|
466 var row = { |
|
467 value: value, |
|
468 data: rawValue, |
|
469 result: options.formatResult && options.formatResult(rawValue) || value |
|
470 }; |
|
471 |
|
472 // push the current match into the set list |
|
473 stMatchSets[firstChar].push(row); |
|
474 |
|
475 // keep track of minChars zero items |
|
476 if ( nullData++ < options.max ) { |
|
477 stMatchSets[""].push(row); |
|
478 } |
|
479 }; |
|
480 |
|
481 // add the data items to the cache |
|
482 $.each(stMatchSets, function(i, value) { |
|
483 // increase the cache size |
|
484 options.cacheLength++; |
|
485 // add to the cache |
|
486 add(i, value); |
|
487 }); |
|
488 } |
|
489 |
|
490 // populate any existing data |
|
491 setTimeout(populate, 25); |
|
492 |
|
493 function flush(){ |
|
494 data = {}; |
|
495 length = 0; |
|
496 } |
|
497 |
|
498 return { |
|
499 flush: flush, |
|
500 add: add, |
|
501 populate: populate, |
|
502 load: function(q) { |
|
503 if (!options.cacheLength || !length) |
|
504 return null; |
|
505 /* |
|
506 * if dealing w/local data and matchContains than we must make sure |
|
507 * to loop through all the data collections looking for matches |
|
508 */ |
|
509 if( !options.url && options.matchContains ){ |
|
510 // track all matches |
|
511 var csub = []; |
|
512 // loop through all the data grids for matches |
|
513 for( var k in data ){ |
|
514 // don't search through the stMatchSets[""] (minChars: 0) cache |
|
515 // this prevents duplicates |
|
516 if( k.length > 0 ){ |
|
517 var c = data[k]; |
|
518 $.each(c, function(i, x) { |
|
519 // if we've got a match, add it to the array |
|
520 if (matchSubset(x.value, q)) { |
|
521 csub.push(x); |
|
522 } |
|
523 }); |
|
524 } |
|
525 } |
|
526 return csub; |
|
527 } else |
|
528 // if the exact item exists, use it |
|
529 if (data[q]){ |
|
530 return data[q]; |
|
531 } else |
|
532 if (options.matchSubset) { |
|
533 for (var i = q.length - 1; i >= options.minChars; i--) { |
|
534 var c = data[q.substr(0, i)]; |
|
535 if (c) { |
|
536 var csub = []; |
|
537 $.each(c, function(i, x) { |
|
538 if (matchSubset(x.value, q)) { |
|
539 csub[csub.length] = x; |
|
540 } |
|
541 }); |
|
542 return csub; |
|
543 } |
|
544 } |
|
545 } |
|
546 return null; |
|
547 } |
|
548 }; |
|
549 }; |
|
550 |
|
551 $.Autocompleter.Select = function (options, input, select, config) { |
|
552 var CLASSES = { |
|
553 ACTIVE: "ac_over" |
|
554 }; |
|
555 |
|
556 var listItems, |
|
557 active = -1, |
|
558 data, |
|
559 term = "", |
|
560 needsInit = true, |
|
561 element, |
|
562 list; |
|
563 |
|
564 // Create results |
|
565 function init() { |
|
566 if (!needsInit) |
|
567 return; |
|
568 element = $("<div/>") |
|
569 .hide() |
|
570 .addClass(options.resultsClass) |
|
571 .css("position", "absolute") |
|
572 .appendTo(document.body); |
|
573 |
|
574 list = $("<ul/>").appendTo(element).mouseover( function(event) { |
|
575 if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { |
|
576 active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); |
|
577 $(target(event)).addClass(CLASSES.ACTIVE); |
|
578 } |
|
579 }).click(function(event) { |
|
580 $(target(event)).addClass(CLASSES.ACTIVE); |
|
581 select(); |
|
582 // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus |
|
583 input.focus(); |
|
584 return false; |
|
585 }).mousedown(function() { |
|
586 config.mouseDownOnSelect = true; |
|
587 }).mouseup(function() { |
|
588 config.mouseDownOnSelect = false; |
|
589 }); |
|
590 |
|
591 if( options.width > 0 ) |
|
592 element.css("width", options.width); |
|
593 |
|
594 needsInit = false; |
|
595 } |
|
596 |
|
597 function target(event) { |
|
598 var element = event.target; |
|
599 while(element && element.tagName != "LI") |
|
600 element = element.parentNode; |
|
601 // more fun with IE, sometimes event.target is empty, just ignore it then |
|
602 if(!element) |
|
603 return []; |
|
604 return element; |
|
605 } |
|
606 |
|
607 function moveSelect(step) { |
|
608 listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); |
|
609 movePosition(step); |
|
610 var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); |
|
611 if(options.scroll) { |
|
612 var offset = 0; |
|
613 listItems.slice(0, active).each(function() { |
|
614 offset += this.offsetHeight; |
|
615 }); |
|
616 if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { |
|
617 list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); |
|
618 } else if(offset < list.scrollTop()) { |
|
619 list.scrollTop(offset); |
|
620 } |
|
621 } |
|
622 }; |
|
623 |
|
624 function movePosition(step) { |
|
625 active += step; |
|
626 if (active < 0) { |
|
627 active = listItems.size() - 1; |
|
628 } else if (active >= listItems.size()) { |
|
629 active = 0; |
|
630 } |
|
631 } |
|
632 |
|
633 function limitNumberOfItems(available) { |
|
634 return options.max && options.max < available |
|
635 ? options.max |
|
636 : available; |
|
637 } |
|
638 |
|
639 function fillList() { |
|
640 list.empty(); |
|
641 var max = limitNumberOfItems(data.length); |
|
642 for (var i=0; i < max; i++) { |
|
643 if (!data[i]) |
|
644 continue; |
|
645 var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); |
|
646 if ( formatted === false ) |
|
647 continue; |
|
648 var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; |
|
649 $.data(li, "ac_data", data[i]); |
|
650 } |
|
651 listItems = list.find("li"); |
|
652 if ( options.selectFirst ) { |
|
653 listItems.slice(0, 1).addClass(CLASSES.ACTIVE); |
|
654 active = 0; |
|
655 } |
|
656 // apply bgiframe if available |
|
657 if ( $.fn.bgiframe ) |
|
658 list.bgiframe(); |
|
659 } |
|
660 |
|
661 return { |
|
662 display: function(d, q) { |
|
663 init(); |
|
664 data = d; |
|
665 term = q; |
|
666 fillList(); |
|
667 }, |
|
668 next: function() { |
|
669 moveSelect(1); |
|
670 }, |
|
671 prev: function() { |
|
672 moveSelect(-1); |
|
673 }, |
|
674 pageUp: function() { |
|
675 if (active != 0 && active - 8 < 0) { |
|
676 moveSelect( -active ); |
|
677 } else { |
|
678 moveSelect(-8); |
|
679 } |
|
680 }, |
|
681 pageDown: function() { |
|
682 if (active != listItems.size() - 1 && active + 8 > listItems.size()) { |
|
683 moveSelect( listItems.size() - 1 - active ); |
|
684 } else { |
|
685 moveSelect(8); |
|
686 } |
|
687 }, |
|
688 hide: function() { |
|
689 element && element.hide(); |
|
690 listItems && listItems.removeClass(CLASSES.ACTIVE); |
|
691 active = -1; |
|
692 }, |
|
693 visible : function() { |
|
694 return element && element.is(":visible"); |
|
695 }, |
|
696 current: function() { |
|
697 return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); |
|
698 }, |
|
699 show: function() { |
|
700 var offset = $(input).offset(); |
|
701 element.css({ |
|
702 width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), |
|
703 top: offset.top + input.offsetHeight, |
|
704 left: offset.left |
|
705 }).show(); |
|
706 if(options.scroll) { |
|
707 list.scrollTop(0); |
|
708 list.css({ |
|
709 maxHeight: options.scrollHeight, |
|
710 overflow: 'auto' |
|
711 }); |
|
712 |
|
713 if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { |
|
714 var listHeight = 0; |
|
715 listItems.each(function() { |
|
716 listHeight += this.offsetHeight; |
|
717 }); |
|
718 var scrollbarsVisible = listHeight > options.scrollHeight; |
|
719 list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); |
|
720 if (!scrollbarsVisible) { |
|
721 // IE doesn't recalculate width when scrollbar disappears |
|
722 listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); |
|
723 } |
|
724 } |
|
725 |
|
726 } |
|
727 }, |
|
728 selected: function() { |
|
729 var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); |
|
730 return selected && selected.length && $.data(selected[0], "ac_data"); |
|
731 }, |
|
732 emptyList: function (){ |
|
733 list && list.empty(); |
|
734 }, |
|
735 unbind: function() { |
|
736 element && element.remove(); |
|
737 } |
|
738 }; |
|
739 }; |
|
740 |
|
741 $.Autocompleter.Selection = function(field, start, end) { |
|
742 if( field.createTextRange ){ |
|
743 var selRange = field.createTextRange(); |
|
744 selRange.collapse(true); |
|
745 selRange.moveStart("character", start); |
|
746 selRange.moveEnd("character", end); |
|
747 selRange.select(); |
|
748 } else if( field.setSelectionRange ){ |
|
749 field.setSelectionRange(start, end); |
|
750 } else { |
|
751 if( field.selectionStart ){ |
|
752 field.selectionStart = start; |
|
753 field.selectionEnd = end; |
|
754 } |
|
755 } |
|
756 field.focus(); |
|
757 }; |
|
758 |
|
759 })(jQuery); |