[jQuery] New plugin: Autocompleter
Here's a new plugin I wrote called Autocompleter. Guess what it does?
I can't upload it to my demo site now, so I'll just give you the source code.
Usage $("select_an_input_element").autocomplete(url, options);
url = url to autocomplete function, will receive a get parameter named
"q" (sans quotes) with the partial text to be completed. the page
should output each result on a single lines (empty lines are ignored),
any extra information should be preceded by a pipe | symbol.
options = lots of stuff for caching, extra parameters, delays, min
number of keys, etc, all optional
known issues:
1) i'm stripping all whitespace from the response because I was
squashing a bug and din't need whitespace. this should be improved on
:-)
2) I had trouble with the input element so I used a hack to get a DIV around it
3) probably some other stuff, since I just got it working like i
wanted to today, and haven't really tested it
Have fun, and please let me know what you think of it.
Dylan
suggested css:
.ac_wrapper {
width: 200px;
}
.ac_wrapper input {
width: 100%;
}
.ac_wrapper ul {
list-style: none;
padding: 0;
margin: 0;
}
.ac_results {
background: #ccc;
cursor: pointer;
position: absolute;
}
.ac_results li {
padding: 2px 5px;
}
.ac_loading {
background : url('../img/indicator.gif') right center no-repeat;
}
.over {
background: yellow;
}
source code:
$.autocomplete = function(wrapper, options) {
$(wrapper).removeClass(options.removeClass);
var results = document.createElement("div");
$(results).hide();
wrapper.appendChild(results);
$(results).set("class", options.resultsClass);
var input = $(wrapper).find("input").get(0);
input.autocomplete = this;
var timeout = null;
var prev = "";
var active = -1;
var cache = {};
$(input).keypress(function(e) {
switch(e.keyCode) {
case 38: // up
e.preventDefault();
moveSelect(-1);
break;
case 40: // down
e.preventDefault();
moveSelect(1);
break;
case 9: // tab
case 13: // return
if (selectCurrent()) {
e.preventDefault();
selectCurrent();
}
break;
default:
if (timeout) clearTimeout(timeout);
timeout = setTimeout(onChange, options.delay);
break;
}
});
$(input).blur(hideResults);
// $(document).click(hideResults);
hideResultsNow();
function onChange() {
var v = $(input).val();
if (v == prev) return;
if (prev = v && v.length >= options.minChars) {
$(input).addClass(options.loadingClass);
requestData(v);
} else {
$(input).removeClass(options.loadingClass);
$(results).hide();
}
};
function moveSelect(step) {
var lis = $(results).find("li");
if (!lis) return;
active += step;
if (active < 0) {
active = 0;
} else if (active >= lis.size()) {
active = lis.size() - 1;
}
lis.removeClass("over");
$(lis.get(active)).addClass("over");
};
function selectCurrent() {
var li = $(results).find("li.over").get(0);
if (li) {
selectItem(li);
return true;
} else {
return false;
}
};
function selectItem(li) {
var v = $.cleanSpaces(li.innerHTML);
input.lastSelected = v;
prev = v;
$(results).html("");
$(input).val(v);
hideResultsNow();
if (options.onItemSelect) setTimeout(function() {
options.onItemSelect(li) }, 1);
};
function hideResults() {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(hideResultsNow, 100);
};
function hideResultsNow() {
if (timeout) clearTimeout(timeout);
if ($(results).is(":visible")) {
$(results).hide();
if (options.mustMatch) {
var v = $(input).val();
if (v != input.lastSelected) {
selectItem(document.createElement("li"));
}
}
}
};
function receiveData(q, data) {
if (data) {
$(input).removeClass(options.loadingClass);
results.innerHTML = "";
results.appendChild(dataToDom(data));
$(results).show();
} else {
hideResultsNow();
}
};
function parseData(data) {
if (!data) return null;
var parsed = [];
var rows = data.split(options.lineSeparator);
for (var i=0; i < rows.length; i++) {
var row = rows[i].replace(/\s/g, "");
if (row) parsed[parsed.length] = row.split(options.cellSeparator);
}
return parsed;
};
function dataToDom(data) {
var ul = document.createElement("ul");
for (var i=0; i < data.length; i++) {
var row = data[i];
if (!row) continue;
var li = document.createElement("li");
li.innerHTML = row[0];
li.extra = row.length > 1 ? row[1] : null;
$(li).hover(
function() { $(this).addClass("over"); },
function() { $(this).removeClass("over"); }
).click(function(e) { selectItem(this) });
ul.appendChild(li);
}
return ul;
};
function requestData(q) {
if (!options.matchCase) q = q.toLowerCase();
var data = options.cacheLength ? loadFromCache(q) : null;
if (data) {
receiveData(q, data);
} else {
$.get(makeUrl(q), function(data) {
data = parseData(data)
addToCache(q, data);
receiveData(q, data);
});
}
};
function makeUrl(q) {
var url = options.url + "?q=" + q;
for (var i in options.extraParams) {
url += "&" + i + "=" + options.extraParams[i];
}
return url;
};
function loadFromCache(q) {
if (!q) return null;
if (cache[q]) return cache[q];
if (options.matchSubset) {
for (var i = q.length - 1; i >= options.minChars; i--) {
var qs = q.substr(0, i);
var c = cache[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;
}
}
}
return null;
};
function matchSubset(s, sub) {
if (!options.matchCase) s = s.toLowerCase();
var i = s.indexOf(sub);
if (i == -1) return false;
return i == 0 || options.matchContains;
};
function flushCache() {
cache = {};
};
function addToCache(q, data) {
if (!data || !q || !options.cacheLength) return;
if (!cache.length || cache.length > options.cacheLength) {
flushCache();
cache.length = 1; // we know we're adding something
} else if (!cache[q]) {
cache.length++;
}
cache[q] = data;
};
}
$.fn.autocomplete = function(url, options) {
// Make sure options exists
options = options || {};
// Set url as option
options.url = url;
// Set removeClass as option
options.removeClass = "ac___remove___this___class";
// Set default values for required options
options.wrapperClass = options.wrapperClass || "ac_wrapper";
options.resultsClass = options.resultsClass || "ac_results";
options.lineSeparator = options.lineSeparator || "\n";
options.cellSeparator = options.cellSeparator || "|";
options.minChars = options.minChars || 1;
options.delay = options.delay || 200;
options.matchCase = options.matchCase || 0;
options.matchSubset = options.matchSubset || 1;
options.matchContains = options.matchContains || 0;
options.cacheLength = options.cacheLength || 1;
options.mustMatch = options.mustMatch || 0;
options.extraParams = options.extraParams || {};
options.loadingClass = options.loadingClass || "ac_loading";
// Wrap our input elements with a DIV
this.wrap("<div class='" + options.wrapperClass + " " +
options.removeClass + "'></div>");
// Find all the newly created DIVs and create an autcompleter for each of them
$("." + options.removeClass).each(function() { new
$.autocomplete(this, options); });
// Don't break the chain
return this;
}
_______________________________________________
jQuery mailing list
discuss@jquery.com
http://jquery.com/discuss/