Hi there!
A couple of days ago I started to develop something that needed a fancy, unobtrusive jquery autocomplete plugin, that could accept JSON as data source and created light HTML and I found this
one.
But then, it missed two simple functionalities that I added:
One -> the for each of your result items, isn't controlled by you, it expects that your JSON contains a value property, and uses it, but my JSON was a little more complex than that, so I coded a setting to the autocomplete plugin, so you can define, or not, your drawing function!
Two -> If you needed to force your autocomplete list to show up, you couldn't, and I coded that two.
So here's the result
or here:
/*!
* Auto Complete 4.1
* October 5, 2009
* Corey Hart @ http://www.codenothing.com
* Ricardo Rodrigues @ http://sharpdevpt.blogspot.com
*/
; (function($, undefined) {
// Expose autoComplete to the jQuery chain
$.fn.autoComplete = function() {
// Force array of arguments
var args = Array.prototype.slice.call(arguments);
// Autocomplete special triggers
if (typeof args[0] === 'string')
// Trigger the requested function, and dont break the chain!
return $(this).trigger('autoComplete.' + args.shift(), args);
// Initiate the autocomplete
return autoComplete.call(this, args[0]);
};
// 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;
};
// Autocomplete function
var inputIndex = 0, autoComplete = function(options) {
return this.each(function() {
// Cache objects
var $input = $(this).attr('autocomplete', 'off'), $li, timeid, timeid2, blurid,
// Internal Per Input Cache
cache = {
length: 0,
val: undefined,
list: {}
},
// Set defaults and include metadata support
settings = $.extend({
// Inner Function Defaults (Best to leave alone)
opt: -1,
inputval: undefined,
mouseClick: false,
dataName: 'ac-data',
inputIndex: ++inputIndex,
// Server Script Path
ajax: 'ajax.php',
dataSupply: [],
dataFn: undefined,
// Drop List CSS
list: 'auto-complete-list',
rollover: 'auto-complete-list-rollover',
width: $input.outerWidth(),
// Post Data
postVar: 'value',
postData: {},
// Limitations
minChars: 1,
maxItems: -1,
maxRequests: 0,
requestType: 'post',
requests: 0, // Inner Function Default
// Events
onMaxRequest: function() { },
onSelect: function() { },
onRollover: function() { },
onBlur: function() { },
onFocus: function() { },
inputControl: function(v) { return v; },
preventEnterSubmit: false,
enter: true, // Inner Function Default
delay: 100,
selectFuncFire: true, // Inner Function Default
// Caching Options
useCache: true,
cacheLimit: 50,
htmlCustomFormatter: undefined
}, options || {}, $.metadata ? $input.metadata() : {}),
// Create the drop list (Use an existing one if possible)
$ul = $('ul.' + settings.list)[0] ?
$('ul.' + settings.list).bgiframe() :
$('
').appendTo('body').addClass(settings.list).bgiframe().hide();
// Input Events
$input.data('ac-input-index', settings.inputIndex) // Attach input index
// Central autoComplete specific function
.bind('keyup.autoComplete', function(event) {
var key = event.keyCode;
settings.mouseClick = false;
// Enter Key
if (key == 13 && $li) {
settings.opt = -1;
// Ensure the select function only gets fired once
if (settings.selectFuncFire) {
settings.selectFuncFire = false;
settings.onSelect.call($input[0], $li.data(settings.dataName), $li, $ul);
if (timeid2) clearTimeout(timeid2);
timeid2 = setTimeout(function() { settings.selectFuncFire = true; }, 1000);
}
$ul.hide();
}
// Up Arrow
else if (key == 38) {
if (settings.opt > 0) {
settings.opt--;
$li = $('li', $ul).removeClass(settings.rollover).eq(settings.opt).addClass(settings.rollover);
$input.val($li.data(settings.dataName).value || '');
settings.onRollover.call($input[0], $li.data(settings.dataName), $li, $ul);
} else {
settings.opt = -1;
$input.val(settings.inputval);
$ul.hide();
}
}
// Down Arrow
else if (key == 40) {
if (settings.opt < $('li', $ul).length - 1) {
settings.opt++;
$li = $('li', $ul.show()).removeClass(settings.rollover).eq(settings.opt).addClass(settings.rollover);
$input.val($li.data(settings.dataName).value || '');
settings.onRollover.call($input[0], $li.data(settings.dataName), $li, $ul);
}
}
// Everything else is possible input
else {
settings.opt = -1;
settings.inputval = $input.val();
cache.val = settings.inputControl.call($input, settings.inputval, key);
if (cache.val.length >= settings.minChars) {
// Send request on timer so fast typing doesn't overload requests
if (timeid) clearTimeout(timeid);
timeid = setTimeout(function() { sendRequest(settings, cache); clearTimeout(timeid); }, settings.delay);
} else if (key == 8) { // Remove list on backspace of small string
$ul.html('').hide();
}
}
})
// Bind specific Blur Actions
.bind('blur.autoComplete', function() {
settings.enter = true;
blurid = setTimeout(function() {
if (settings.mouseClick)
return false;
settings.opt = -1;
settings.onBlur.call($input[0], settings.inputval, $ul);
$ul.hide();
}, 150);
})
// Bind specific focus actions
.bind('focus.autoComplete', function() {
settings.enter = false;
// If ul is not associated with current input, clear it
if (settings.inputIndex != $ul.data('ac-input-index'))
$ul.html('').hide();
settings.onFocus.call($input[0], $ul);
})
/**
* Autocomplete Special Triggers
* -Extensions off autoComplete event
*/
// Allows for change of settings at any point
.bind('autoComplete.settings', function(event, newSettings) {
// Give access to current settings and cache
if ($.isFunction(newSettings)) {
var ret = newSettings.call($input[0], settings, cache);
// Allow for extending of settings/cache based off function return values
if ($.isArray(ret) && ret.length) {
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 || {});
}
})
// Clears the Cache & requests (requests can be blocked on request)
.bind('autoComplete.flush', function(event, cacheOnly) {
cache = { length: 0, val: undefined, list: {} };
if (!cacheOnly) settings.requests = 0;
})
// External button trigger for ajax requests
.bind('autoComplete.button.ajax', function(event, postData, cacheName) {
// Refocus the input box
$input.focus();
// Remove blur trigger
if (blurid) clearTimeout(blurid);
// 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_<>!@$^&';
// Send request on timer so focus event doesn't override
if (timeid) clearTimeout(timeid);
timeid = setTimeout(function() {
sendRequest($.extend(true, {}, settings, { opt: -1, maxItems: -1, postData: postData || {} }), cache);
clearTimeout(timeid);
}, settings.delay);
})
// External button trigger for supplied data
.bind('autoComplete.button.supply', function(event, data, cacheName) {
// Refocus the input box
$input.focus();
// Remove blur trigger
if (blurid) clearTimeout(blurid);
// 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 : settings.dataSupply;
// Send request on timer so focus event doesn't override
if (timeid) clearTimeout(timeid);
timeid = setTimeout(function() {
sendRequest($.extend(true, {}, settings, { opt: -1, maxItems: -1, dataSupply: data, dataFn: function() { return true; } }), cache);
clearTimeout(timeid);
}, settings.delay);
})
// Add a destruction function
.bind('autoComplete.destroy', function() {
// Unbind input events
$input.unbind('keyup.autoComplete blur.autoComplete focus.autoComplete autoComplete')
// Unbind the form submission event
.parents('form').eq(0).unbind('submit.autoComplete.' + settings.inputIndex);
})
// Add a show list function
.bind('autoComplete.showlist', function() {
$ul.show();
})
// Back to normal events
// Prevent form submission if defined in settings
.parents('form').eq(0).bind('submit.autoComplete.' + settings.inputIndex, function() {
return settings.preventEnterSubmit ? settings.enter : true;
});
// Ajax/Cache Request
function sendRequest(settings, cache) {
// Check Max reqests first
if (settings.maxRequests && ++settings.requests >= settings.maxRequests)
return settings.requests > settings.maxRequests ?
false : settings.onMaxRequest.call($input[0], settings.inputval, $ul);
// Load from cache if possible
if (settings.useCache && cache.list[cache.val])
return loadResults(cache.list[cache.val], settings, cache);
// Use user supplied data when defined
if (settings.dataSupply.length)
return userSuppliedData(settings, cache);
// Send request server side
settings.postData[settings.postVar] = cache.val
$[settings.requestType](settings.ajax, settings.postData, function(json) {
// Show the list if there is a return, else hide it
loadResults(json, settings, cache);
// Use jQuery's method of json evaluation
// (thus, can only send 'get' or 'post' jQuery requests)
}, 'json');
}
// Parse User Supplied Data
function userSuppliedData(settings, cache) {
var json = [], // Result list
fn = $.isFunction(settings.dataFn), // User supplied function
regex = fn ? undefined : new RegExp('^' + cache.val, 'i'), // Only compile regex if needed
k = 0, entry, i; // Looping vars
// Loop through each entry and find matches
for (i in settings.dataSupply) {
entry = settings.dataSupply[i];
// Force object
entry = typeof entry === 'object' && entry.value ? entry : { value: entry };
// If user supplied function, use that, otherwise test with default regex
if ((fn && settings.dataFn.call($input[0], cache.val, entry.value, json, i, settings.dataSupply)) ||
(!fn && entry.value.match(regex))) {
// Reduce browser load by breaking on limit if it exists
if (settings.maxItems > -1 && ++k > settings.maxItems)
break;
json.push(entry);
}
}
// Use normal load functionality
loadResults(json, settings, cache);
}
// List Functionality
function loadResults(list, settings, cache) {
// Store results into the cache if need be
if (settings.useCache) {
cache.length++;
cache.list[cache.val] = list;
// Clear cache if necessary
if (settings.cacheLength > settings.cacheLimit) {
cache.list = {};
cache.length = 0;
}
}
// Ensure there is a list
if (!list || list.length < 1)
return $ul.html('').hide();
// Initialize Vars together (save bytes)
var offset = $input.offset(), // Store offsets
aci = 0, i; // Index list items
// Clear the List and align it properly
$ul.data('ac-input-index', settings.inputIndex).html('').css({
top: offset.top + $input.outerHeight(),
left: offset.left,
width: settings.width
});
// Add new rows to the list
for (i in list) {
var fn = $.isFunction(settings.htmlCustomFormatter);
if (list[i].value || fn) {
if (settings.maxItems > -1 && ++aci > settings.maxItems) {
break;
}
var appendHTML = list[i].display || list[i].value;
//user custom function to compose HTML
if (fn) {
appendHTML = settings.htmlCustomFormatter(list[i]);
}
$('
').appendTo($ul).html(appendHTML)
.data(settings.dataName, list[i]).data('ac-index', aci);
}
}
// Remove old mouseout event and return orignal val when not hovering
$ul.show().unbind('mouseout.autoComplete').bind('mouseout.autoComplete', function() {
$('li.' + settings.rollover, $ul).removeClass(settings.rollover);
if (!settings.mouseClick && settings.selectFuncFire)
$input.val(settings.inputval);
// Unbind any events that linger from previous drops
// I don't understand why this helps yet, because the old li elements are
// removed and new ones created/added to the ul element; but for now, it works
}).children('li').unbind('mouseover.autoComplete').unbind('click.autoComplete')
// New mouseover and click events
.bind('mouseover.autoComplete', function() {
$li = $(this);
$('li.' + settings.rollover, $ul).removeClass(settings.rollover);
$input.val($li.addClass(settings.rollover).data(settings.dataName).value);
settings.onRollover.call($input[0], $li.data(settings.dataName), $li, $ul);
settings.opt = $li.data('ac-index');
}).bind('click.autoComplete', function() {
settings.mouseClick = true;
if (blurid) clearTimeout(blurid);
settings.onSelect.call($input[0], $li.data(settings.dataName), $li, $ul);
$ul.hide();
// Bring the focus back to the input when clicking a list member
$input.focus();
});
}
});
};
})(jQuery);
Make it good use! :)
No comments:
Post a Comment