jQuery Plugin: Auto Complete
Auto-complete takes input from the user, and tries to form a list of words that match the users input. The function attaches itself to the input field selected, and also creates the UL drop down from within so all you need is have the styles for it ready.
Basic UsageNo parameters are required, but the path to your ajax script should be correct, and you will need styles set for the UL drop down.
$(function(){
$('input[name=auto-complete]').autoComplete({ajax: '/path/to/ajax-script'});
});
And thanks to esldesks's most common mispelled words we have some test data to use like so:
Source
/*!
* Auto Complete 5.0
* November 22, 2009
* Corey Hart @ http://www.codenothing.com
*/
(function($, undefined){
// Expose autoComplete to the jQuery chain
$.fn.autoComplete = function(){
// Force array of arguments
var args = Slice.call(arguments),
self = this,
first = args.shift(),
isMethod = (typeof first === 'string');
// Deep namespacing is not supported in jQuery, a mistake I made in v4.1
if (isMethod) first = first.replace('.', '-');
// Allow for passing array of arguments, or multiple arguments
// Eg: .autoComplete('trigger', [arg1, arg2, arg3...]) or .autoComplete('trigger', arg1, arg2, arg3...)
// Mainly to allow for .autoComplete('trigger', arguments) to work
// Note*: button.supply passes an array as the first param, so check against that first
args = first === 'button-supply' || first === 'direct-supply' ? $.isArray(args[0]) && $.isArray(args[0][0]) ? args[0] : args :
args[1] === undefined && $.isArray(args[0]) ? args[0] : args;
// Autocomplete special triggers
return isMethod ?
// The only chain breaking operation is option, which gets passed back the
// settings/value it requested, otherwise trigger the event and don't break the chain!
$(self)[ first === 'option' && args.length < 2 ? 'triggerHandler' : 'trigger' ]('autoComplete.'+first, args) :
// Allow passing a jquery event special object {from $.Event()}
first && first[$.expando] ? $(self).trigger(first, args) :
// Initiate the autocomplete (Only takes a single argument, the options object)
AutoCompleteFunction.call(self, first);
};
// bgiframe is needed to fix z-index problem for IE6 users.
$.fn.bgiframe = $.fn.bgiframe ? $.fn.bgiframe : $.fn.bgIframe ? $.fn.bgIframe : function(){
// For applications that don't have bgiframe plugin installed, create a useless
// function that doesn't break the chain
return this;
};
// The expando won't get attached to the jQuery object until 1.4 release(or so it seems in the nightlies)
// To get the expando, we must create an event through jQuery, and filter it out.
$.expando = $.expando !== undefined ? $.expando : (function(){
var event = $.Event('keyup'), i;
for (i in event)
if (i.indexOf('jQuery') === 0)
return i;
// Use the event's timestamp on instances where
// expando isn't attached to the event object
// (is it ever not?)
return 'jQuery'+event.timeStamp;
})();
// Current timestamp
function now(){
return (new Date).getTime();
}
// Internals
var
// Munging
TRUE = true,
FALSE = false,
// Copy of the slice prototype
Slice = Array.prototype.slice,
// Attach global aspects to jQuery itself
AutoComplete = $.autoComplete = {
// Index Counter
counter: 0,
// Attach length of stack to object
length: 0,
// Storage of elements
stack: {},
// Storage order of uid's
order: [],
// Global access to elements in use
hasFocus: FALSE,
// Callback methods for getting focus element
getFocus: function(){
return this.order[0] ? this.stack[ this.order[0] ] : undefined;
},
getPrevious: function(){
// Removing elements cause some indexs on the order stack
// to become undefined, so loop until one is found
for ( var i=1, l=this.order.length; i < l; i++ )
if (this.order[i])
return this.stack[ this.order[i] ];
// If none are found, return undefined
return undefined;
},
// Attempts to remove element from the stack
remove: function(i){
for ( var k=0, l=this.order.length; k < l; k++ )
if (this.order[k] === i)
this.order[k] = undefined;
this.stack[i] = undefined;
this.length--;
delete this.stack[i];
},
// Returns full stack in jQuery form
getAll: function(){
for ( var i = 0, l = this.counter, stack = []; i < l; i++ )
if (this.stack[i])
stack.push(this.stack[i]);
return $(stack);
},
defaults: {
// To smooth upgrade process to 5.0, set backwardsCompatible to true
backwardsCompatible: FALSE,
// Server Script Path
ajax: 'ajax.php',
ajaxCache: $.ajaxSettings.cache,
// Data Configuration
dataSupply: [],
dataFn: undefined,
dataName: 'ac-data',
// Drop List CSS
list: 'auto-complete-list',
rollover: 'auto-complete-list-rollover',
width: undefined, // Defined as inputs width when extended (can be overridden with this global/options/meta)
striped: undefined,
maxHeight: undefined,
newList: FALSE,
// Post Data
postVar: 'value',
postData: {},
// Limitations
minChars: 1,
maxItems: -1,
maxRequests: 0,
requestType: 'POST',
// Input
inputControl: undefined,
autoFill: FALSE,
nonInput: undefined,
multiple: FALSE,
multipleSeparator: ' ',
// Events
onBlur: undefined,
onFocus: undefined,
onHide: undefined,
onLoad: undefined,
onMaxRequest: function(){},
onRollover: undefined,
onSelect: undefined,
onShow: undefined,
onSubmit: function(){return TRUE;},
spinner: undefined,
preventEnterSubmit: TRUE,
delay: 0,
// Caching Options
useCache: TRUE,
cacheLimit: 50
}
},
// Autocomplete function
AutoCompleteFunction = function(options){
return this.each(function(){
var
// Cache a copy of the input element
self = this,
// Cache Input Object
$input = $(self).attr('autocomplete', 'off'),
// autoComplete enabled/disabled
Active = TRUE,
// Track every event triggered
LastEvent = {},
// String of current input value
inputval = '',
// Place holder for all list elements
$elems = {length:0},
// Place holder for the list element in focus
$li,
// View and heights for scrolling
view, ulHeight, liHeight, liPerView,
// Harcoded value for ul visiblity
ulOpen = FALSE,
// Timer for delay
timeid,
// Ajax requests holder
xhr,
// li element in focus during key up/down, and its data
liFocus = -1, liData,
// For multiple selections
separator,
// Index of current input
inputIndex = (function(){ AutoComplete.length++; return ++AutoComplete.counter; })(),
// Number of requests made
requests = 0,
// Internal Per Input Cache
cache = {
length: 0,
val: undefined,
list: {}
},
// Merge defaults with passed options and metadata options
settings = $.extend(
{ width: $input.outerWidth() },
AutoComplete.defaults,
options||{},
$.metadata ? $input.metadata() : {}
),
// Create the drop list (Use an existing one if possible)
$ul = !settings.newList && $('ul.'+settings.list)[0] ?
$('ul.'+settings.list).eq(0).bgiframe().data('autoComplete', TRUE) :
$('<ul/>').appendTo('body').addClass(settings.list).bgiframe().hide().data('ac-selfmade', TRUE).data('autoComplete', TRUE),
// Attach document click to force blur event
$doc = $(document).bind('click.autoComplete-'+inputIndex, function(event){
var $elem;
// Make sure input is active and list is open
if (Active && ulOpen &&
// Double check the event timestamps to ensure there isn't
// a delayed reaction from a button
(!LastEvent || event.timeStamp - LastEvent.timeStamp > 200) &&
// Check the target after all other checks are passed (less processing)
( $elem = $(event.target) ).closest('ul').data('ac-input-index') !== inputIndex &&
// Also ensure that the input it's being clicked on either
$elem.data('ac-input-index') !== inputIndex){
$ul.hide(event);
// We want to trigger all blur events, so don't
// pass special autoComplete flags here through the
// trigger function
$input.blur();
}
LastEvent = event;
});
// Attach special fn's to ul
newUl();
// Upper case requestType now instead of on every call
settings.requestType = settings.requestType.toUpperCase();
// Set separator to local variable for munging
separator = settings.multiple ? settings.multipleSeparator : undefined;
// Add input to stack
AutoComplete.stack[inputIndex] = self;
/**
* Input Central
*/
// Show autocomplete has been initialized on this element
$input.data('autoComplete', TRUE)
// Attach input index and initial settings
.data('ac-input-index', inputIndex)
// autoComplete Activity
.data('ac-active', Active)
// Attach settings to initail and current states
.data('ac-initial-settings', $.extend(TRUE, {}, settings)).data('ac-settings', settings)
// Central autoComplete specific function
// Opera uses keypress as it has problems with keydown
.bind(window.opera ? 'keypress.autoComplete' : 'keydown.autoComplete', function(event){
// If autoComplete has been disabled, prevent input events
if (!Active) return TRUE;
// Track last event and store code for munging
var key = (LastEvent = event).keyCode, enter = FALSE;
// Tab Key
if (key == 9 && ulOpen){
select(event);
}
// Enter Key
else if (key == 13 && $li){
// IE needs keydown to return false on 'enter' so the element doesn't
// lose focus. The problem with returning false is that it prevents bubbling,
// and most importantly, form submission. To allow for most flexibility,
// preventEnterSubmit is used along with activity of drop down UL list to
// determine whether focus is on the drop list or is just on the input.
//
// Furthermore, preventEnterSubmit will now be defaulted to true, so as
// to affect as few implementations as possible, and the ones that need
// form submission on 'enter' can just set this flag to false for it to
// work as needed.
enter = settings.preventEnterSubmit && ulOpen ? FALSE : TRUE;
select(event);
}
// Up Arrow
else if (key == 38){
if (liFocus > 0){
liFocus--;
up(event);
}else{
liFocus = -1;
$input.val(inputval);
$ul.hide(event);
}
}
// Down Arrow
else if (key == 40){
if (liFocus < $elems.length-1){
liFocus++;
down(event);
}
}
// Page Up
else if (key == 33){
if (liFocus > 0){
liFocus -= liPerView;
if (liFocus < 0) liFocus = 0;
up(event);
}
}
// Page Down
else if (key == 34){
if (liFocus < $elems.length-1){
liFocus += liPerView;
if (liFocus > $elems.length-1) liFocus = $elems.length-1;
down(event);
}
}
// Check for non input values defined by user
else if (settings.nonInput && $.inArray(key, settings.nonInput)){
$ul.html('').hide(event);
}
// Everything else is considered possible input, so
// return before keyup prevention flag is set
else{
return TRUE;
}
// Prevent autoComplete keyup event's from triggering by
// attaching a flag to the last event
LastEvent[$.expando + '_autoComplete_keydown'] = TRUE;
return enter;
})
// Run a keydown event to specifically catch the tab key
.bind('keyup.autoComplete', function(event){
// If autoComplete has been disabled or keyup prevention
// flag has be set, prevent input events
if (!Active || LastEvent[$.expando + '_autoComplete_keydown']) return TRUE;
/**
* If no special operations were run on keydown,
* allow for regular text searching
*/
inputval = $input.val();
var key = (LastEvent = event).keyCode,
val = separator ? inputval.split(separator).pop() : inputval;
// Still check to make sure 'enter' wasn't pressed
if (key != 13){
// Caching key value
cache.val = settings.inputControl === undefined ? val :
settings.inputControl.apply(self, settings.backwardsCompatible ?
[val, key, $ul, event] : [event, {val: val, key: key, ul: $ul}]);
// Only send request if character length passes
if (cache.val.length >= settings.minChars)
sendRequest(event, settings, cache, (key==8||key==32));
// Remove list on backspace of small string
else if (key == 8)
$ul.html('').hide(event);
}
})
// Bind specific Blur Actions
.bind('blur.autoComplete', function(event){
// If autoComplete has been disabled or the drop list
// is still open, prevent input events
if (!Active || ulOpen) return TRUE;
// Store event
LastEvent = event;
$input.data('ac-hasFocus', FALSE);
liFocus = -1;
// Only push undefined index onto order stack
// if not already there (incase multiple blur events occur)
if (AutoComplete.order[0] !== undefined)
AutoComplete.order.unshift(undefined);
// Expose focus
AutoComplete.hasFocus = FALSE;
$ul.hide(event);
// Trigger blur callback last
if (settings.onBlur) settings.onBlur.apply(self, settings.backwardsCompatible ?
[inputval, $ul, event] : [event, {val: inputval, ul: $ul}]);
})
// Bind specific focus actions
.bind('focus.autoComplete', function(event, flag){
// If autoComplete has been disabled but not destoyed, just return true
if (!Active ||
// Prevent inner focus events if caused by autoComplete inner functionality
(AutoComplete.focus === inputIndex && flag === $.expando + '_autoComplete') ||
// Because IE triggers focus AND closes the drop list before form submission,
// prevent inner function focus functionality & pass on the select flag
LastEvent[$.expando + '_autoComplete_enter'])
return TRUE;
// Store event
LastEvent = event;
// If ul is not associated with current input, clear it
if (inputIndex != $ul.data('ac-input-index'))
$ul.html('').hide(event);
// Store focus into input
$input.data('ac-hasFocus', TRUE);
// Overwrite undefined index pushed on by the blur event
if (AutoComplete.order[0] === undefined){
if (AutoComplete.order[1] === inputIndex)
AutoComplete.order.shift();
else
AutoComplete.order[0] = inputIndex;
}
// Only push another uid if it's not the current one
else if (AutoComplete.order[0] != inputIndex && AutoComplete.order[1] != inputIndex)
AutoComplete.order.unshift(inputIndex);
// Keep the order array to within the global cacheLimit size
if (AutoComplete.order.length > AutoComplete.defaults.cacheLimit)
AutoComplete.order.pop();
// Expose focus
AutoComplete.hasFocus = TRUE;
// Trigger focus callback last
if (settings.onFocus) settings.onFocus.apply(self, settings.backwardsCompatible ? [$ul, event] : [event, {ul: $ul}]);
})
/**
* Autocomplete Methods
* -Extensions off autoComplete event
*/
// Allows for change of settings at any point
.bind('autoComplete.settings', function(event, newSettings){
// If autoComplete has been disabled, prevent input events
if (!Active) return TRUE;
// Give access to current settings and cache
if ($.isFunction(newSettings)){
var ret = newSettings.apply(self, settings.backwardsCompatible ?
[settings, cache, $ul, event] : [event, {settings: settings, cache: cache, ul: $ul}]);
// Allow for extending of settings/cache based off function return values
if ($.isArray(ret) && ret[0] !== undefined){
settings = $.extend(TRUE, {}, settings, ret[0]||settings);
cache = $.extend(TRUE, {}, cache, ret[1]||cache);
}
}else{
// Extend deep so settings are kept
settings = $.extend(TRUE, {}, settings, newSettings||{});
}
// Upper case requestType now instead of on every call
settings.requestType = settings.requestType.toUpperCase();
// Reassign local separator
separator = settings.multiple ? settings.multipleSeparator : undefined;
// Restablish current settings onto the inputs data
$input.data('ac-settings', settings);
// Change the drop down if user want's a differen't class attached
$ul = !settings.newList && $ul.hasClass(settings.list) ? $ul :
!settings.newList && $('ul.'+settings.list)[0] ? $('ul.'+settings.list).bgiframe().data('autoComplete', TRUE) :
$('<ul/>').appendTo('body').addClass(settings.list).bgiframe().hide()
.data('ac-selfmade', TRUE).data('autoComplete', TRUE);
// Attach special ul fn's
newUl();
// Return & Store event
return LastEvent = event;
})
// Clears the Cache & requests (requests can be blocked on request)
.bind('autoComplete.flush', function(event, cacheOnly){
// If autoComplete has been disabled, prevent input events
if (!Active) return TRUE;
cache = {length:0, val:undefined, list:{}};
if (!cacheOnly) requests = 0;
// Store & return event
return LastEvent = event;
})
// External button trigger for ajax requests
.bind('autoComplete.button-ajax', function(event, postData, cacheName){
// If autoComplete has been disabled, prevent input events
if (!Active) return TRUE;
// Store event
LastEvent = event;
// Refocus the input box and pass flag to prevent inner focus events
$input.trigger('focus', [$.expando + '_autoComplete']);
// Allow for just passing the cache name
if (typeof postData === 'string'){
cacheName = postData;
postData = {};
}
// If no cache name is given, supply a non-common word
cache.val = cacheName||'NON_404_<>!@$^&';
// Timer is done within sendRequest
return sendRequest(
event,
$.extend(TRUE, {}, settings, {maxItems: -1, postData: postData||{}}),
cache
);
})
// External button trigger for supplied data
.bind('autoComplete.button-supply', function(event, data, cacheName){
// If autoComplete has been disabled, prevent input events
if (!Active) return TRUE;
// Store event
LastEvent = event;
// Refocus the input box and pass flag to prevent inner focus events
$input.trigger('focus', [$.expando + '_autoComplete']);
// Allow for just passing of cacheName
if (typeof data === 'string'){
cacheName = data;
data = undefined;
}
// If no cache name is given, supply a non-common word
cache.val = cacheName||'NON_404_SUPPLY_<>!@$^&';
// If no data is supplied, use data in settings
data = $.isArray(data) && data.length ? data : settings.dataSupply;
// Timer done within sendRequest
return sendRequest(
event,
$.extend(TRUE, {}, settings, {maxItems: -1, dataSupply: data, dataFn: function(){ return TRUE; } }),
cache
);
})
// Supply list directly into the result function
.bind('autoComplete.direct-supply', function(event, data, cacheName){
// If autoComplete has been disabled, prevent input events
if (!Active) return TRUE;
// Store event
LastEvent = event;
// Refocus the input box and pass flag to prevent inner focus events
$input.trigger('focus', [$.expando + '_autoComplete']);
// Allow for just passing of cacheName
if (typeof data === 'string'){
cacheName = data;
data = undefined;
}
// If no cache name is given, supply a non-common word
cache.val = cacheName||'NON_404_SUPPLY_<>!@$^&';
// If no data is supplied, use data in settings
data = $.isArray(data) && data.length ? data : settings.dataSupply;
// Load the results directly into the results function
// bypassing error checks (Only do)
return loadResults(
event,
data,
$.extend(TRUE, {}, settings, {maxItems: -1, dataSupply: data, dataFn: function(){ return TRUE; } }),
cache
);
})
// Triggering autocomplete programatically
.bind('autoComplete.search', function(event, value){
// If autoComplete has been disabled, prevent input events
if (!Active) return TRUE;
cache.val = value||'';
// Timer done within sendRequest
return sendRequest(LastEvent = event, settings, cache);
})
// Add jquery-ui like option access
.bind('autoComplete.option', function(event){
// If autoComplete has been disabled, prevent input events
if (!Active) return TRUE;
// Store event
LastEvent = event;
var args = Slice.call(arguments), length = args.length;
return length == 3 ? (function(){settings[ args[1] ] = args[2]; $input.data('ac-settings', settings); return args[2];})() :
length == 2 ? (function(){
switch (args[1]){
case 'ul': return $ul;
case 'cache': return cache;
case 'xhr': return xhr;
case 'input': return $input;
default: return settings[ args[1] ] || undefined;
}
})() :
settings;
})
// Add enabling event (only applicable after disable)
.bind('autoComplete.enable', function(event){
$input.data('ac-active', Active = TRUE);
// Store & return event
return LastEvent = event;
})
// Add disable event
.bind('autoComplete.disable', function(event){
// Store event
$input.data('ac-active', Active = FALSE);
$ul.html('').hide(event);
// Store & return event
return LastEvent = event;
})
// Add a destruction function
.bind('autoComplete.destroy', function(event){
// Break down the input
$input
// Remove all autoComplete Specific Data
.removeData('autoComplete')
.removeData('ac-input-index')
.removeData('ac-initial-settings')
.removeData('ac-settings')
.removeData('ac-active')
// Remove all autoComplete specific events
.unbind('.autoComplete')
// jQuery requires every namespace attached to
// a made up event to be removed separately
.unbind( 'autoComplete.' + [
'settings',
'flush',
'button-ajax',
'button-supply',
'direct-supply',
'search',
'option',
'enable',
'disable',
'destroy'
].join(' autoComplete.') )
// Unbind the form submission event
.parents('form').eq(0).unbind('submit.autoComplete-'+inputIndex);
// Remove document click event
$doc.unbind('click.autoComplete-'+inputIndex);
// Remove from stack
AutoComplete.remove(inputIndex);
// Disable Activity
Active = FALSE;
// Clean the UL
var list = $ul.html('').hide(event).data('ac-inputs'), i;
list[inputIndex] = undefined;
for (i in list)
if (list[i] === TRUE)
return LastEvent = event;
// Remove the element from the DOM if self created no other input is using it
if ($ul.data('ac-selfmade') === TRUE) $ul.remove();
// Store & return event
return LastEvent = event;
})
// Back to normal events
// Prevent form submission if defined in settings
.parents('form').eq(0).bind('submit.autoComplete-'+inputIndex, function(event){
// If autoComplete has been disabled, prevent input events
if (!Active) return TRUE;
// Because IE triggers focus AND closes the drop list before form submission, store the flag if any
var flag = LastEvent[$.expando + '_autoComplete_enter']||FALSE;
// Store event
LastEvent = event;
return settings.preventEnterSubmit ?
(ulOpen || flag) ? FALSE : settings.onSubmit.call(self, event, {form: this, ul: $ul}) :
settings.onSubmit.call(self, event, {form: this, ul: $ul});
});
// Ajax/Cache Request
function sendRequest(event, settings, cache, backSpace, timeout){
// Pass spinner enabler
if (settings.spinner) settings.spinner.call(self, event, {active: TRUE, ul: $ul});
// Centralize the timer request
if (timeid) timeid = clearTimeout(timeid);
// Call send request again with timeout flag if on delay
if (settings.delay > 0 && timeout === undefined) return timeid = setTimeout(function(){
sendRequest(event, settings, cache, backSpace, TRUE);
timeid = clearTimeout(timeid);
}, settings.delay);
// Abort previous request incase it's still running
if (xhr) xhr.abort();
// Load from cache if possible
if (settings.useCache && cache.list[cache.val])
return loadResults(event, cache.list[cache.val], settings, cache, backSpace);
// Use user supplied data when defined
if (settings.dataSupply.length)
return userSuppliedData(event, settings, cache, backSpace);
// Check Max requests first before sending request
if (settings.maxRequests && ++requests >= settings.maxRequests){
$ul.html('').hide(event);
if (settings.spinner) settings.spinner.call(self, event, {active: FALSE, ul: $ul});
return requests > settings.maxRequests ?
FALSE : settings.onMaxRequest.apply(self, settings.backwardsCompatible ?
[cache.val, $ul, event, inputval] : [event, {search: cache.val, val: inputval, ul: $ul}]);
}
// Send request server side
settings.postData[settings.postVar] = cache.val
// Switched to base ajax request to remove list on errors
return xhr = $.ajax({
type: settings.requestType,
url: settings.ajax,
data: settings.postData,
dataType: 'json',
cache: settings.ajaxCache,
success: function(list){
loadResults(event, list, settings, cache, backSpace);
},
error: function(){
$ul.html('').hide(event);
if (settings.spinner) settings.spinner.call(self, event, {active: FALSE, ul: $ul});
}
});
}
// Parse User Supplied Data
function userSuppliedData(event, settings, cache, backSpace){
var list = [], // Result list
args = [], // Backwards Compatibility
fn = $.isFunction(settings.dataFn), // User supplied function
regex = fn ? undefined : new RegExp('^'+cache.val, 'i'), // Only compile regex if needed
k = 0, entry, i=0, l=settings.dataSupply.length; // Looping vars
// Loop through each entry and find matches
for ( ; i < l; i++ ){
entry = settings.dataSupply[i];
// Force object
entry = typeof entry === 'object' && entry.value ? entry : {value: entry};
// Setup arguments for dataFn in a backwards compatible way if needed
args = settings.backwardsCompatible ?
[cache.val, entry.value, list, i, settings.dataSupply, $ul, event] :
[event, {val: cache.val, entry: entry.value, list: list, i: i, supply: settings.dataSupply, ul: $ul}];
// If user supplied function, use that, otherwise test with default regex
if ((fn && settings.dataFn.apply(self, args)) || (!fn && entry.value.match(regex))){
// Reduce browser load by breaking on limit if it exists
if (settings.maxItems > -1 && ++k > settings.maxItems)
break;
list.push(entry);
}
}
// Use normal load functionality
return loadResults(event, list, settings, cache, backSpace);
}
// Key element Selection
function select(event){
// Ensure the select function only gets fired when list of open
if (ulOpen){
if (settings.onSelect) settings.onSelect.apply(self, settings.backwardsCompatible ?
[liData, $li, $ul, event] : [event, {data: liData, li: $li, ul: $ul}]);
autoFill(undefined);
inputval = $input.val();
// Because IE triggers focus AND closes the drop list before form submission
// attach a flag on 'enter' selection
if (LastEvent.type=='keydown') LastEvent[$.expando + '_autoComplete_enter'] = TRUE;
}
$ul.hide(event);
return $li;
}
// Key direction up
function up(event){
if ($li) $li.removeClass(settings.rollover);
$ul.show(event);
$li = $elems.eq(liFocus).addClass(settings.rollover);
liData = $li.data(settings.dataName);
if (!$li.length || !liData) return FALSE;
autoFill( liData.value||'' );
if (settings.onRollover) settings.onRollover.apply(self, settings.backwardsCompatible ?
[liData, $li, $ul, event] : [event, {data: liData, li: $li, ul: $ul}]);
// Scrolling
var v = liFocus*liHeight;
if (v < view-ulHeight){
view = v+ulHeight
$ul.scrollTop( v );
}
return $li;
}
// Key direction down
function down(event){
if ($li) $li.removeClass(settings.rollover);
$ul.show(event);
$li = $elems.eq( liFocus ).addClass( settings.rollover );
liData = $li.data( settings.dataName );
if (!$li.length || !liData) return FALSE;
autoFill( liData.value||'' );
// Scrolling
var v = (liFocus+1)*liHeight;
if (v > view)
$ul.scrollTop( (view = v) - ulHeight );
// Callback
if (settings.onRollover) settings.onRollover.apply(self, settings.backwardsCompatible ?
[liData, $li, $ul, event] : [event, {data: liData, li: $li, ul: $ul}]);
return $li;
}
// Attach new show/hide functionality to only the
// ul object (so not to infect all of jQuery)
function newUl(){
if (! $ul[$.expando + '_autoComplete']){
// Make a copy of the old show/hide
var hide = $ul.hide, show = $ul.show;
$ul.hide = function(event, speed, callback){
if (settings.onHide && ulOpen){
settings.onHide.call(self, event, {ul: $ul});
LastEvent[$.expando + '_autoComplete_hide'] = TRUE;
}
ulOpen = FALSE;
return hide.call($ul, speed, callback);
};
$ul.show = function(event, speed, callback){
if (settings.onShow && !ulOpen) settings.onShow.call(self, event, {ul: $ul});
ulOpen = TRUE;
return show.call($ul, speed, callback);
};
// A flag must be attached to the $ul cached object
$ul[$.expando + '_autoComplete'] = TRUE;
}
var list = $ul.data('ac-inputs')||{};
list[inputIndex] = TRUE;
return $ul.data('ac-inputs', list);
}
// Auto-fill the input
// Credit to Jörn Zaefferer @ http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/
// and http://www.pengoworks.com/workshop/jquery/autocomplete.htm for this functionality
function autoFill(val){
// Set starting and ending points based on values
if (val === undefined){
var start, end; start = end = $input.val().length;
}else{
if (separator) val = inputval.substr( 0, inputval.length-inputval.split(separator).pop().length ) + val + separator;
var start = inputval.length, end = val.length;
$input.val(val);
}
// Create selection if allowed
if (! settings.autoFill || start > end){
return FALSE;
}
else if (self.createTextRange){
var range = self.createTextRange();
if (val === undefined) {
range.move('character', start);
range.select();
}else{
range.collapse(TRUE);
range.moveStart("character", start);
range.moveEnd("character", end);
range.select();
}
}
else if (self.setSelectionRange){
self.setSelectionRange(start, end);
}
else if (self.selectionStart){
self.selectionStart = start;
self.selectionEnd = end;
}
return TRUE;
}
// List Functionality
function loadResults(event, list, settings, cache, backSpace){
// Allow another level of result handling
if (settings.onLoad) list = settings.onLoad.call(self, event, {list: list, settings: settings, cache: cache, ul: $ul});
// Pass spinner killer as wait time is done in javascript processing
if (settings.spinner) settings.spinner.call(self, event, {active: FALSE, ul: $ul});
// Store results into the cache if allowed
if (settings.useCache && cache.list[cache.val] === undefined){
cache.length++;
cache.list[cache.val] = list;
// Clear cache if necessary
if (cache.length > settings.cacheLimit){
cache.list = {};
cache.length = 0;
}
}
// Ensure there is a list
if (!list || list.length < 1)
return $ul.html('').hide(event);
// Refocus list element
liFocus = -1;
// Initialize Vars together (save bytes)
var offset = $input.offset(), // Input position
container = [], // Container for list elements
aci=0,k=0,i=0,even=FALSE,length=list.length; // Loop Items
// Push items onto container
for (; i < length; i++){
if (list[i].value){
if (settings.maxItems > -1 && ++aci > settings.maxItems)
break;
container.push(
settings.striped && even ? '<li class="'+settings.striped+'">' : '<li>',
list[i].display||list[i].value,
'</li>'
);
even = !even;
}
}
// Load items into list
$elems = $ul.html( container.join('') ).children('li');
for ( length = $elems.length; k < length; k++ ){
$.data( $elems[k], settings.dataName, list[k] );
$.data( $elems[k], 'ac-index', k );
}
// Autofill input with first entry
if (settings.autoFill && ! backSpace){
liFocus = 0;
liData = list[0];
autoFill( liData.value||'' );
$li = $elems.eq(0).addClass( settings.rollover );
}
// Clear off old events and attach new ones
$ul.unbind('.autoComplete')
// Attach input index in focus
.data('ac-input-index', inputIndex)
// Remove focus elements hover class
.bind('mouseout.autoComplete', function(){
$li.removeClass(settings.rollover);
})
// Mouseover using event delegation
.bind('mouseover.autoComplete', function(event){
$li = $(event.target).closest('li');
// Ensure 'li' mouseover
if ($li.length < 1) return FALSE;
// Remove hover class from last rollover
$elems.filter('.'+settings.rollover).removeClass(settings.rollover);
liFocus = $li.addClass(settings.rollover).data('ac-index');
liData = $li.data( settings.dataName );
if (settings.onRollover) settings.onRollover.apply(self, settings.backwardsCompatible ?
[liData, $li, $ul, event] : [event, {data: liData, li: $li, ul: $ul}]);
})
// Click event using target from mouseover
.bind('click.autoComplete', function(event){
// Refocus the input box and pass flag to prevent inner focus events
$input.trigger('focus', [$.expando + '_autoComplete']);
liData = $li.data(settings.dataName);
// Check against separator for input value
$input.val( inputval = separator ?
inputval.substr( 0, inputval.length-inputval.split(separator).pop().length ) + liData.value + separator :
liData.value
);
$ul.hide(event);
autoFill(undefined);
if (settings.onSelect) settings.onSelect.apply(self, settings.backwardsCompatible ?
[liData, $li, $ul, event] : [event, {data: liData, li: $li, ul: $ul}]);
})
// Reposition list
.css({
top: offset.top + $input.outerHeight(),
left: offset.left,
width: settings.width
})
// Scroll to the top
.scrollTop(0);
// If Max Height specified, control it
if (settings.maxHeight) $ul.css({
height: liHeight*$elems.length > settings.maxHeight ? settings.maxHeight : 'auto',
overflow: 'auto'
});
// Apply list height to view for inital value,
// and show the list now so no jerkiness from css
// changes are shown to the user
ulHeight = $ul.show(event).outerHeight();
view = ulHeight;
// Log li height for less computation
liHeight = $elems.eq(0).outerHeight();
// Number of elements per viewport
liPerView = Math.floor(view/liHeight);
// Include amount of time it took
// to load the list
LastEvent.timeStamp = now();
// Every function needs to return something
return $ul;
}
});
};
})(jQuery);
Download
Latest: auto-complete-5.0.zipReleased: November 22, 2009
-Multiple word searches.
-Paging and Scrolling.
-Removed timer dependency. Now mainly event based.
-Event delegation used on mouse actions of the drop list.
Past Release's:
October 5, 2009: auto-complete-4.1.zip
September 28, 2009: auto-complete-4.0.zip
September 17, 2009: auto-complete-3.2.zip
August 22, 2009: auto-complete-3.1.zip
July 26, 2009: auto-complete-3.0.zip
June 11, 2009: auto-complete-2.1.zip
June 10, 2009: auto-complete-2.0.zip
June 5, 2009: auto-complete-1.0.zip
RSS