= function(input, options) {
// Create a link to self
var me = this;
// Create jQuery object for input element
var $input = $(input).attr("autocomplete", "off");
// Apply inputClass if necessary
if () $();
// Create results
var results = ("div");
// Create jQuery object for results
var $results = $(results);
$().addClass().css("position", "absolute");
if ( > 0) $("width", );
// Add to body element
$("body").append(results);
= me;
var timeout = null;
var prev = "";
var active = -1;
var cache = {};
var keyb = false;
var hasFocus = false;
var lastKeyPressCode = null;
// flush cache
function flushCache() {
cache = {};
= {};
= 0;
};
// flush cache
flushCache();
// if there is a data array supplied
if ( != null) {
var sFirstChar = "", stMatchSets = {}, row = [];
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
if (typeof != "string") = 1;
// loop through the array and create a lookup structure
for (var i = 0; i < ; i++) {
// if row is a string, make an array otherwise just reference the array
row = ((typeof [i] == "string") ? [[i]] : [i]);
// if the length is zero, don't add to list
if (row[0].length > 0) {
// get the first character
sFirstChar = row[0].substring(0, 1).toLowerCase();
// if no lookup array for this character exists, look it up now
if (!stMatchSets[sFirstChar]) stMatchSets[sFirstChar] = [];
// if the match is a string
stMatchSets[sFirstChar].push(row);
}
}
// add the data items to the cache
for (var k in stMatchSets) {
// increase the cache size
++;
// add to the cache
addToCache(k, stMatchSets[k]);
}
}
$input
.keydown(function(e) {
// track last key pressed
lastKeyPressCode = ;
switch () {
case 38: // up
();
moveSelect(-1);
break;
case 40: // down
();
moveSelect(1);
break;
case 9: // tab
case 13: // return
if (selectCurrent()) {
// make sure to blur off the current field
$(0).blur();
();
}
break;
default:
active = -1;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(function() { onChange(); }, );
break;
}
})
.focus(function() {
// track whether the field has focus, we shouldn't process any results if the field no longer has focus
hasFocus = true;
})
.blur(function() {
// track whether the field has focus
hasFocus = false;
hideResults();
})
.bind("input", function() {
// @hack:support for inputing chinese characters in firefox
onChange(0, true);
});
hideResultsNow();
function onChange() {
// ignore if the following keys are pressed: [del] [shift] [capslock]
if (lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32)) return $();
var v = $();
if (v == prev) return;
prev = v;
if ( >= ) {
$();
requestData(v);
} else {
$();
$();
}
};
function moveSelect(step) {
var lis = $("li", results);
if (!lis) return;
active += step;
if (active < 0) {
active = 0;
} else if (active >= ()) {
active = () - 1;
}
("ac_over");
$(lis[active]).addClass("ac_over");
// Weird behaviour in IE
// if (lis[active] && lis[active].scrollIntoView) {
// lis[active].scrollIntoView(false);
// }
};
function selectCurrent() {
var li = $("li.ac_over", results)[0];
if (!li) {
var $li = $("li", results);
if () {
if ($ == 1) li = $li[0];
} else if () {
li = $li[0];
}
}
if (li) {
selectItem(li);
return true;
} else {
return false;
}
};
function selectItem(li) {
if (!li) {
li = ("li");
= [];
= "";
}
var v = $.trim( ? : );
= v;
prev = v;
$("");
$(v);
hideResultsNow();
if () setTimeout(function() { (li) }, 1);
};
// selects a portion of the input string
function createSelection(start, end) {
// get a reference to the input element
var field = $(0);
if () {
var selRange = ();
(true);
("character", start);
("character", end);
();
} else if () {
(start, end);
} else {
if () {
= start;
= end;
}
}
();
};
// fills in the input box w/the first match (assumed to be the best match)
function autoFill(sValue) {
// if the last user key pressed was backspace, don't autofill
if (lastKeyPressCode != 8) {
// fill in the value (keep the case the user has typed)
$($() + ());
// select the portion of the value not typed by the user (so the next character will erase)
createSelection(, );
}
};
function showResults() {
// get the position of the input field right now (in case the DOM is shifted)
var pos = findPos(input);
// either use the specified width, or autocalculate based on form element
var iWidth = ( > 0) ? : $();
// reposition
$({
width: parseInt(iWidth) + "px",
top: ( + ) + "px",
left: + "px"
}).show();
};
function hideResults() {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(hideResultsNow, 200);
};
function hideResultsNow() {
if (timeout) clearTimeout(timeout);
$();
if ($(":visible")) {
$();
}
if () {
var v = $();
if (v != ) {
selectItem(null);
}
}
};
function receiveData(q, data) {
if (data) {
$();
= "";
// if the field no longer has focus or if there are no matches, do not display the drop down
if (!hasFocus || == 0) return hideResultsNow();
if ($.) {
// we put a styled iframe behind the calendar so HTML SELECT elements don't show through
$(('iframe'));
}
(dataToDom(data));
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
if ( && ($().toLowerCase() == ())) autoFill(data[0][0]);
showResults();
} else {
hideResultsNow();
}
};
function parseData(data) {
if (!data) return null;
var parsed = [];
var rows = ();
for (var i = 0; i < ; i++) {
var row = $.trim(rows[i]);
if (row) {
parsed[] = ();
}
}
return parsed;
};
function dataToDom(data) {
var ul = ("ul");
var num = ;
// limited results to a max number
if (( > 0) && ( < num)) num = ;
for (var i = 0; i < num; i++) {
var row = data[i];
if (!row) continue;
var li = ("li");
if () {
= (row, i, num);
= row[0];
} else {
= row[0];
= row[0];
}
var extra = null;
if ( > 1) {
extra = [];
for (var j = 1; j < ; j++) {
extra[] = row[j];
}
}
= extra;
(li);
$(li).hover(
function() { $("li", ul).removeClass("ac_over"); $(this).addClass("ac_over"); active = $("li", ul).indexOf($(this).get(0)); },
function() { $(this).removeClass("ac_over"); }
).click(function(e) { (); (); selectItem(this) });
}
return ul;
};
function requestData(q) {
if (!) q = ();
var data = ? loadFromCache(q) : null;
// recieve the cached data
if (data) {
receiveData(q, data);
// if an AJAX url has been supplied, try loading the data now
} else if ((typeof == "string") && ( > 0)) {
$.get(makeUrl(q), function(data) {
data = parseData(data);
addToCache(q, data);
receiveData(q, data);
});
// if there's been no data found, remove the loading class
} else {
$();
}
};
function makeUrl(q) {
var url = + "?q=" + escape(q);
for (var i in ) {
url += "&" + i + "=" + escape([i]);
}
return url;
};
function loadFromCache(q) {
if (!q) return null;
if ([q]) return [q];
if () {
for (var i = - 1; i >= ; i--) {
var qs = (0, i);
var c = [qs];
if (c) {
var csub = [];
for (var j = 0; j < ; j++) {
var x = c[j];
var x0 = x[0];
if (matchSubset(x0, q)) {
csub[] = x;
}
}
return csub;
}
}
}
return null;
};
function matchSubset(s, sub) {
if (!) s = ();
var i = (sub);
if (i == -1) return false;
return i == 0 || ;
};
= function() {
flushCache();
};
= function(p) {
= p;
};
= function() {
var q = $();
if (!) q = ();
var data = ? loadFromCache(q) : null;
if (data) {
findValueCallback(q, data);
} else if ((typeof == "string") && ( > 0)) {
$.get(makeUrl(q), function(data) {
data = parseData(data)
addToCache(q, data);
findValueCallback(q, data);
});
} else {
// no matches
findValueCallback(q, null);
}
}
function findValueCallback(q, data) {
if (data) $();
var num = (data) ? : 0;
var li = null;
for (var i = 0; i < num; i++) {
var row = data[i];
if (row[0].toLowerCase() == ()) {
li = ("li");
if () {
= (row, i, num);
= row[0];
} else {
= row[0];
= row[0];
}
var extra = null;
if ( > 1) {
extra = [];
for (var j = 1; j < ; j++) {
extra[] = row[j];
}
}
= extra;
}
}
if () setTimeout(function() { (li) }, 1);
}
function addToCache(q, data) {
if (!data || !q || !) return;
if (! || > ) {
flushCache();
++;
} else if (!cache[q]) {
++;
}
[q] = data;
};
function findPos(obj) {
var curleft = || 0;
var curtop = || 0;
while (obj = ) {
curleft +=
curtop +=
}
return { x: curleft, y: curtop };
}
}
= function(url, options, data) {
// Make sure options exists
options = options || {};
// Set url as option
= url;
// set some bulk local data
= ((typeof data == "object") && ( == Array)) ? data : null;
// Set default values for required options
= || "ac_input";
= || "ac_results";
= || "\n";
= || "|";
= || 1;
= || 400;
= || 0;
= || 1;
= || 0;
= || 1;
= || 0;
= || {};
= || "ac_loading";
= || false;
= || false;
= || -1;
= || false;
= parseInt(, 10) || 0;
(function() {
var input = this;
new (input, options);
});
// Don't break the chain
return this;
}
= function(data, options) {
return (null, options, data);
}
= function(e) {
for (var i = 0; i < ; i++) {
if (this[i] == e) return i;
}
return -1;
};