3 jQuery
.autocomplete = function(input
, options
) {
4 // Create a link to self
7 // Create jQuery object for input element
8 var $input
= $(input
).attr("autocomplete", "off");
10 // Apply inputClass if necessary
11 if (options
.inputClass
) $input
.addClass(options
.inputClass
);
14 if(!options
.resultElem
){
15 var results
= document
.createElement("div");
16 // Create jQuery object for results
17 var $results
= $(results
);
18 // Add to body element
19 $("body").append(results
);
20 $results
.hide().addClass(options
.resultsClass
).css("position", "absolute");
21 if( options
.width
> 0 ) $results
.css("width", options
.width
);
23 var results
= $j(options
.resultElem
).get(0);
24 var $results
= $j(options
.resultElem
);
29 input
.autocompleter
= me
;
37 var lastKeyPressCode
= null;
40 function flushCache(){
49 // if there is a data array supplied
50 if( options
.data
!= null ){
51 var sFirstChar
= "", stMatchSets
= {}, row
= [];
53 // no url was specified, we need to adjust the cache length to make sure it fits the local data store
54 if( typeof options
.url
!= "string" ) options
.cacheLength
= 1;
56 // loop through the array and create a lookup structure
57 for( var i
=0; i
< options
.data
.length
; i
++ ){
58 // if row is a string, make an array otherwise just reference the array
59 row
= ((typeof options
.data
[i
] == "string") ? [options
.data
[i
]] : options
.data
[i
]);
61 // if the length is zero, don't add to list
62 if( row
[0].length
> 0 ){
63 // get the first character
64 sFirstChar
= row
[0].substring(0, 1).toLowerCase();
65 // if no lookup array for this character exists, look it up now
66 if( !stMatchSets
[sFirstChar
] ) stMatchSets
[sFirstChar
] = [];
67 // if the match is a string
68 stMatchSets
[sFirstChar
].push(row
);
72 // add the data items to the cache
73 for( var k
in stMatchSets
){
74 // increase the cache size
75 options
.cacheLength
++;
77 addToCache(k
, stMatchSets
[k
]);
82 .keydown(function(e
) {
83 // track last key pressed
84 lastKeyPressCode
= e
.keyCode
;
96 if( selectCurrent() ){
97 // make sure to blur off the current field
104 if (timeout
) clearTimeout(timeout
);
105 timeout
= setTimeout(function(){onChange();}, options
.delay
);
110 // track whether the field has focus, we shouldn't process any results if the field no longer has focus
114 // track whether the field has focus
121 function onChange() {
122 // ignore if the following keys are pressed: [del] [shift] [capslock]
123 if( lastKeyPressCode
== 46 || (lastKeyPressCode
> 8 && lastKeyPressCode
< 32) ) return $results
.hide();
124 var v
= $input
.val();
125 if (v
== prev
) return;
127 if (v
.length
>= options
.minChars
) {
128 $input
.addClass(options
.loadingClass
);
131 $input
.removeClass(options
.loadingClass
);
136 function moveSelect(step
) {
137 var lis
= $("li", results
);
144 } else if (active
>= lis
.size()) {
145 active
= lis
.size() - 1;
148 lis
.removeClass("ac_over");
150 $(lis
[active
]).addClass("ac_over");
152 // Weird behaviour in IE
153 // if (lis[active] && lis[active].scrollIntoView) {
154 // lis[active].scrollIntoView(false);
158 function selectCurrent() {
159 var li
= $("li.ac_over", results
)[0];
161 var $li
= $("li", results
);
162 if (options
.selectOnly
) {
163 if ($li
.length
== 1) li
= $li
[0];
164 } else if (options
.selectFirst
) {
176 function selectItem(li
) {
178 li
= document
.createElement("li");
182 var v
= $.trim(li
.selectValue
? li
.selectValue
: li
.innerHTML
);
183 input
.lastSelected
= v
;
188 if (options
.onItemSelect
) setTimeout(function() { options
.onItemSelect(li
) }, 1);
191 // selects a portion of the input string
192 function createSelection(start
, end
){
193 // get a reference to the input element
194 var field
= $input
.get(0);
195 if( field
.createTextRange
){
196 var selRange
= field
.createTextRange();
197 selRange
.collapse(true);
198 selRange
.moveStart("character", start
);
199 selRange
.moveEnd("character", end
);
201 } else if( field
.setSelectionRange
){
202 field
.setSelectionRange(start
, end
);
204 if( field
.selectionStart
){
205 field
.selectionStart
= start
;
206 field
.selectionEnd
= end
;
212 // fills in the input box w/the first match (assumed to be the best match)
213 function autoFill(sValue
){
214 // if the last user key pressed was backspace, don't autofill
215 if( lastKeyPressCode
!= 8 ){
216 // fill in the value (keep the case the user has typed)
217 $input
.val($input
.val() + sValue
.substring(prev
.length
));
218 // select the portion of the value not typed by the user (so the next character will erase)
219 createSelection(prev
.length
, sValue
.length
);
223 function showResults() {
224 // get the position of the input field right now (in case the DOM is shifted)
225 var pos
= findPos(input
);
226 // either use the specified width, or autocalculate based on form element
227 var iWidth
= (options
.width
> 0) ? options
.width
: $input
.width();
229 if(!options
.resultElem
){
231 width
: parseInt(iWidth
) + "px",
232 top
: (pos
.y
+ input
.offsetHeight
) + "px",
238 if(options
.resultContainer
){
239 $(options
.resultContainer
).css({top
: (pos
.y
+ input
.offsetHeight
) + "px",
240 left
: (pos
.x
- parseInt(iWidth
)) + "px"}).show();
244 function hideResults() {
245 if (timeout
) clearTimeout(timeout
);
246 timeout
= setTimeout(hideResultsNow
, 200);
249 function hideResultsNow() {
250 if (timeout
) clearTimeout(timeout
);
251 $input
.removeClass(options
.loadingClass
);
252 if ($results
.is(":visible")) {
255 if(options
.resultContainer
){
256 $(options
.resultContainer
).hide();
258 if (options
.mustMatch
) {
259 var v
= $input
.val();
260 if (v
!= input
.lastSelected
) {
266 function receiveData(q
, data
) {
268 $input
.removeClass(options
.loadingClass
);
269 results
.innerHTML
= "";
271 // if the field no longer has focus or if there are no matches, do not display the drop down
272 if( !hasFocus
|| data
.length
== 0 ) return hideResultsNow();
274 //messes with layout & ie7 does not have this problem
275 /*if ($.browser.msie) {
276 // we put a styled iframe behind the calendar so HTML SELECT elements don't show through
277 $results.append(document.createElement('iframe'));
279 results
.appendChild(dataToDom(data
));
280 // autofill in the complete box w/the first match as long as the user hasn't entered in more data
281 if( options
.autoFill
&& ($input
.val().toLowerCase() == q
.toLowerCase()) ) autoFill(data
[0][0]);
288 function parseData(data
) {
289 if (!data
) return null;
291 var rows
= data
.split(options
.lineSeparator
);
292 for (var i
=0; i
< rows
.length
; i
++) {
293 var row
= $.trim(rows
[i
]);
295 parsed
[parsed
.length
] = row
.split(options
.cellSeparator
);
301 function dataToDom(data
) {
302 var ul
= document
.createElement("ul");
303 if(options
.ul_class
)$(ul
).addClass(options
.ul_class
);
305 var num
= data
.length
;
307 // limited results to a max number
308 if( (options
.maxItemsToShow
> 0) && (options
.maxItemsToShow
< num
) ) num
= options
.maxItemsToShow
;
310 for (var i
=0; i
< num
; i
++) {
313 var li
= document
.createElement("li");
314 if (options
.formatItem
) {
315 li
.innerHTML
= options
.formatItem(row
, i
, num
);
316 li
.selectValue
= row
[0];
318 li
.innerHTML
= row
[0];
319 li
.selectValue
= row
[0];
322 if (row
.length
> 1) {
324 for (var j
=1; j
< row
.length
; j
++) {
325 extra
[extra
.length
] = row
[j
];
331 function() { $("li", ul
).removeClass("ac_over"); $(this).addClass("ac_over"); active
= $("li", ul
).indexOf($(this).get(0)); },
332 function() { $(this).removeClass("ac_over"); }
333 ).click(function(e
) { e
.preventDefault(); e
.stopPropagation(); selectItem(this) });
338 function requestData(q
) {
339 if (!options
.matchCase
) q
= q
.toLowerCase();
340 //var data = options.cacheLength ? loadFromCache(q) : null;
342 // recieve the cached data
344 receiveData(q
, data
);
345 // if an AJAX url has been supplied, try loading the data now
346 } else if( (typeof options
.url
== "string") && (options
.url
.length
> 0) ){
347 $.get(makeUrl(q
), function(data
) {
348 data
= parseData(data
);
350 receiveData(q
, data
);
352 // if there's been no data found, remove the loading class
354 $input
.removeClass(options
.loadingClass
);
358 function makeUrl(q
) {
359 var url
= options
.url
+ "?"+options
.paramName
+'='+ encodeURI(q
);
360 for (var i
in options
.extraParams
) {
361 url
+= "&" + i
+ "=" + encodeURI(options
.extraParams
[i
]);
366 function loadFromCache(q
) {
368 if (typeof cache
.data
[q
]!='undefined'){
369 return cache
.data
[q
];
371 if (options
.matchSubset
) {
372 for (var i
= q
.length
- 1; i
>= options
.minChars
; i
--) {
373 var qs
= q
.substr(0, i
);
374 var c
= cache
.data
[qs
];
377 for (var j
= 0; j
< c
.length
; j
++) {
380 if (matchSubset(x0
, q
)) {
381 csub
[csub
.length
] = x
;
391 function matchSubset(s
, sub
) {
392 if (!options
.matchCase
) s
= s
.toLowerCase();
393 var i
= s
.indexOf(sub
);
394 if (i
== -1) return false;
395 return i
== 0 || options
.matchContains
;
398 this.flushCache = function() {
402 this.setExtraParams = function(p
) {
403 options
.extraParams
= p
;
406 this.findValue = function(){
407 var q
= $input
.val();
409 if (!options
.matchCase
) q
= q
.toLowerCase();
410 var data
= options
.cacheLength
? loadFromCache(q
) : null;
412 findValueCallback(q
, data
);
413 } else if( (typeof options
.url
== "string") && (options
.url
.length
> 0) ){
414 $.get(makeUrl(q
), function(data
) {
415 data
= parseData(data
)
417 findValueCallback(q
, data
);
421 findValueCallback(q
, null);
425 function findValueCallback(q
, data
){
426 if (data
) $input
.removeClass(options
.loadingClass
);
428 var num
= (data
) ? data
.length
: 0;
431 for (var i
=0; i
< num
; i
++) {
434 if( row
[0].toLowerCase() == q
.toLowerCase() ){
435 li
= document
.createElement("li");
436 if (options
.formatItem
) {
437 li
.innerHTML
= options
.formatItem(row
, i
, num
);
438 li
.selectValue
= row
[0];
440 li
.innerHTML
= row
[0];
441 li
.selectValue
= row
[0];
444 if( row
.length
> 1 ){
446 for (var j
=1; j
< row
.length
; j
++) {
447 extra
[extra
.length
] = row
[j
];
454 if( options
.onFindValue
) setTimeout(function() { options
.onFindValue(li
) }, 1);
457 function addToCache(q
, data
) {
458 if (!data
|| !q
|| !options
.cacheLength
) return;
459 if (!cache
.length
|| cache
.length
> options
.cacheLength
) {
462 } else if (!cache
[q
]) {
465 cache
.data
[q
] = data
;
468 function findPos(obj
) {
469 var curleft
= obj
.offsetLeft
|| 0;
470 var curtop
= obj
.offsetTop
|| 0;
471 while (obj
= obj
.offsetParent
) {
472 curleft
+= obj
.offsetLeft
473 curtop
+= obj
.offsetTop
475 return {x
:curleft
,y
:curtop
};
480 jQuery
.fn
.autocomplete = function(url
, options
, data
) {
481 // Make sure options exists
482 options
= options
|| {};
485 // set some bulk local data
486 options
.data
= ((typeof data
== "object") && (data
.constructor == Array
)) ? data
: null;
488 // Set default values for required options
489 options
.resultElem
= options
.resultElem
|| null;
490 options
.paramName
= options
.paramName
|| 'q';
492 options
.inputClass
= options
.inputClass
|| "ac_input";
493 options
.resultsClass
= options
.resultsClass
|| "ac_results";
494 options
.lineSeparator
= options
.lineSeparator
|| "\n";
495 options
.cellSeparator
= options
.cellSeparator
|| "|";
496 options
.minChars
= options
.minChars
|| 1;
497 options
.delay
= options
.delay
|| 400;
498 options
.matchCase
= options
.matchCase
|| 0;
499 options
.matchSubset
= options
.matchSubset
|| 1;
500 options
.matchContains
= options
.matchContains
|| 0;
501 options
.cacheLength
= options
.cacheLength
|| 1;
502 options
.mustMatch
= options
.mustMatch
|| 0;
503 options
.extraParams
= options
.extraParams
|| {};
504 options
.loadingClass
= options
.loadingClass
|| "ac_loading";
505 options
.selectFirst
= options
.selectFirst
|| false;
506 options
.selectOnly
= options
.selectOnly
|| false;
507 options
.maxItemsToShow
= options
.maxItemsToShow
|| -1;
508 options
.autoFill
= options
.autoFill
|| false;
509 options
.width
= parseInt(options
.width
, 10) || 0;
511 this.each(function() {
513 new jQuery
.autocomplete(input
, options
);
516 // Don't break the chain
520 jQuery
.fn
.autocompleteArray = function(data
, options
) {
521 return this.autocomplete(null, options
, data
);
524 jQuery
.fn
.indexOf = function(e
){
525 for( var i
=0; i
<this.length
; i
++ ){
526 if( this[i
] == e
) return i
;