// if there is a data array supplied if( options.data !=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 options.url !="string" ) options.cacheLength =1;
// loop through the array and create a lookup structure for( var i=0; i < options.data.length; i++ ){ // if row is a string, make an array otherwise just reference the array row = ((typeof options.data[i] =="string") ? [options.data[i]] : options.data[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 options.cacheLength++; // add to the cache addToCache(k, stMatchSets[k]); } }
$input .keydown(function(e) { // track last key pressed lastKeyPressCode = e.keyCode; switch(e.keyCode) { case38: // up e.preventDefault(); moveSelect(-1); break; case40: // down e.preventDefault(); moveSelect(1); break; case9: // tab case13: // return if( selectCurrent() ){ // make sure to blur off the current field $input.get(0).blur(); e.preventDefault(); } break; default: active =-1; if (timeout) clearTimeout(timeout); timeout = setTimeout(function(){onChange();}, options.delay); 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(); });
hideResultsNow();
function onChange() { // ignore if the following keys are pressed: [del] [shift] [capslock] if( lastKeyPressCode ==46|| (lastKeyPressCode >8&& lastKeyPressCode <32) ) return $results.hide(); var v = $input.val(); if (v == prev) return; prev = v; if (v.length >= options.minChars) { $input.addClass(options.loadingClass); requestData(v); } else { $input.removeClass(options.loadingClass); $results.hide(); } };
function moveSelect(step) {
var lis = $("li", results); if (!lis) return;
active += step;
if (active <0) { active =0; } elseif (active >= lis.size()) { active = lis.size() -1; }
lis.removeClass("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 (options.selectOnly) { if ($li.length ==1) li = $li[0]; } elseif (options.selectFirst) { li = $li[0]; } } if (li) { selectItem(li); returntrue; } else { returnfalse; } };
function selectItem(li) { if (!li) { li = document.createElement("li"); li.extra = []; li.selectValue =""; } //sey 2009/08/04 change 為滿足需求 更改 var v =$.trim(li.extra[0]); //$.trim(li.selectValue ? li.selectValue : li.innerHTML); input.lastSelected = v; prev = v; $results.html(""); $input.val(v); hideResultsNow(); if (options.onItemSelect) setTimeout(function() { options.onItemSelect(li) }, 1); };
// selects a portion of the input string function createSelection(start, end){ // get a reference to the input element var field = $input.get(0); if( field.createTextRange ){ var selRange = field.createTextRange(); selRange.collapse(true); selRange.moveStart("character", start); selRange.moveEnd("character", end); selRange.select(); } elseif( field.setSelectionRange ){ field.setSelectionRange(start, end); } else { if( field.selectionStart ){ field.selectionStart = start; field.selectionEnd = end; } } field.focus(); };
// 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) $input.val($input.val() + sValue.substring(prev.length)); // select the portion of the value not typed by the user (so the next character will erase) createSelection(prev.length, sValue.length); } };
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 = (options.width >0) ? options.width : $input.width(); // reposition $results.css({ width: parseInt(iWidth) +"px", top: (pos.y + input.offsetHeight) +"px", left: pos.x +"px" }).show(); };
function hideResults() { if (timeout) clearTimeout(timeout); timeout = setTimeout(hideResultsNow, 200); };
function hideResultsNow() { if (timeout) clearTimeout(timeout); $input.removeClass(options.loadingClass); if ($results.is(":visible")) { $results.hide(); } if (options.mustMatch) { var v = $input.val(); if (v != input.lastSelected) { selectItem(null); } } };
function receiveData(q, data) { if (data) { $input.removeClass(options.loadingClass); results.innerHTML ="";
// if the field no longer has focus or if there are no matches, do not display the drop down if( !hasFocus || data.length ==0 ) return hideResultsNow();
if ($.browser.msie) { // we put a styled iframe behind the calendar so HTML SELECT elements don't show through $results.append(document.createElement('iframe')); } results.appendChild(dataToDom(data)); // autofill in the complete box w/the first match as long as the user hasn't entered in more data if( options.autoFill && ($input.val().toLowerCase() == q.toLowerCase()) ) autoFill(data[0][0]); showResults(); } else { hideResultsNow(); } };
function parseData(data) { if (!data) returnnull; var parsed = []; var rows = data.split(options.lineSeparator); for (var i=0; i < rows.length; i++) { var row = $.trim(rows[i]); if (row) { parsed[parsed.length] = row.split(options.cellSeparator); } } return parsed; };
function dataToDom(data) { var ul = document.createElement("ul"); var num = data.length;
// limited results to a max number if( (options.maxItemsToShow >0) && (options.maxItemsToShow < num) ) num = options.maxItemsToShow;
for (var i=0; i < num; i++) { var row = data[i]; if (!row) continue; var li = document.createElement("li"); if (options.formatItem) { li.innerHTML = options.formatItem(row, i, num); li.selectValue = row[0]; } else { li.innerHTML = row[0]; li.selectValue = row[0]; } var extra =null; if (row.length >1) { extra = []; for (var j=1; j < row.length; j++) { extra[extra.length] = row[j]; } } li.extra = extra; ul.appendChild(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) { e.preventDefault(); e.stopPropagation(); selectItem(this) }); } return ul; };
function requestData(q) { if (!options.matchCase) q = q.toLowerCase(); var data = options.cacheLength ? loadFromCache(q) : null; // recieve the cached data if (data) { receiveData(q, data); // if an AJAX url has been supplied, try loading the data now } elseif( (typeof options.url =="string") && (options.url.length >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 { $input.removeClass(options.loadingClass); } };
function makeUrl(q) { //將encodeURL更改為 escape的編碼格式 就可以支持中文 var url = options.url +"?q="+ escape(q); for (var i in options.extraParams) { url +="&"+ i +"="+ escape(options.extraParams[i]); } return url; };
function loadFromCache(q) { if (!q) returnnull; if (cache.data[q]) return cache.data[q]; if (options.matchSubset) { for (var i = q.length -1; i >= options.minChars; i--) { var qs = q.substr(0, i); var c = cache.data[qs]; if (c) { var csub = []; for (var j =0; j < c.length; j++) { var x = c[j]; var x0 = x[0]; if (matchSubset(x0, q)) { csub[csub.length] = x; } } return csub; } } } returnnull; };
function matchSubset(s, sub) { if (!options.matchCase) s = s.toLowerCase(); var i = s.indexOf(sub); if (i ==-1) returnfalse; return i ==0|| options.matchContains; };
function addToCache(q, data) { if (!data ||!q ||!options.cacheLength) return; if (!cache.length || cache.length > options.cacheLength) { flushCache(); cache.length++; } elseif (!cache[q]) { cache.length++; } cache.data[q] = data; };
function findPos(obj) { var curleft = obj.offsetLeft ||0; var curtop = obj.offsetTop ||0; while (obj = obj.offsetParent) { curleft += obj.offsetLeft curtop += obj.offsetTop } return {x:curleft,y:curtop}; } }
jQuery.fn.autocomplete =function(url, options, data) { // Make sure options exists options = options || {}; // Set url as option options.url = url; // set some bulk local data options.data = ((typeof data =="object") && (data.constructor == Array)) ? data : null;