/* * jquery autocomplete plugin 1.1 * * copyright (c) 2009 j喔`笘rn zaefferer * * dual licensed under the mit and gpl licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * * revision: $id: jquery.autocomplete.js 15 2009-08-22 10:30:27z joern.zaefferer $ */; $.browser = navigator.useragent; (function($) { $.fn.extend({ autocomplete: function(urlordata, options) { var isurl = typeof urlordata == "string"; options = $.extend({}, $.autocompleter.defaults, { url: isurl ? urlordata : null, data: isurl ? null : urlordata, delay: isurl ? $.autocompleter.defaults.delay : 10, max: options && !options.scroll ? 10 : 150 }, options); options.highlight = options.highlight || function(value) { return value; }; options.formatmatch = options.formatmatch || options.formatitem; return this.each(function() { new $.autocompleter(this, options); }); }, result: function(handler) { return this.bind("result", handler); }, search: function(handler) { return this.trigger("search", [handler]); }, flushcache: function() { return this.trigger("flushcache"); }, setoptions: function(options) { return this.trigger("setoptions", [options]); }, unautocomplete: function() { return this.trigger("unautocomplete"); } }); $.autocompleter = function(input, options) { var key = { up: 38, down: 40, del: 46, tab: 9, return: 13, esc: 27, comma: 188, pageup: 33, pagedown: 34, backspace: 8 }; var $input = $(input).attr("autocomplete", "off").addclass(options.inputclass); var timeout; var previousvalue = ""; var cache = $.autocompleter.cache(options); var hasfocus = 0; var lastkeypresscode; var config = { mousedownonselect: false }; var select = $.autocompleter.select(options, input, selectcurrent, config); var blocksubmit; $.browser.opera && $(input.form).bind("submit.autocomplete", function() { if (blocksubmit) { blocksubmit = false; return false; } }); $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { hasfocus = 1; lastkeypresscode = event.keycode; switch (event.keycode) { case key.up: event.preventdefault(); if (select.visible()) { select.prev(); } else { onchange(0, true); } break; case key.down: event.preventdefault(); if (select.visible()) { select.next(); } else { onchange(0, true); } break; case key.pageup: event.preventdefault(); if (select.visible()) { select.pageup(); } else { onchange(0, true); } break; case key.pagedown: event.preventdefault(); if (select.visible()) { select.pagedown(); } else { onchange(0, true); } break; case options.multiple && $.trim(options.multipleseparator) == "," && key.comma: case key.tab: if( selectcurrent() ) { // stop default to prevent a form submit, opera needs special handling event.preventdefault(); blocksubmit = true; return false; } break; case key.return: cleartimeout(timeout); timeout = settimeout(onchange, options.delay); break; case key.esc: select.hide(); break; default: cleartimeout(timeout); timeout = settimeout(onchange, options.delay); break; } }).focus(function() { hasfocus++; }).blur(function() { hasfocus = 0; if (!config.mousedownonselect) { hideresults(); } }).click(function() { if (hasfocus++ > 1 && !select.visible()) { onchange(0, true); } }).bind("search", function() { var fn = (arguments.length > 1) ? arguments[1] : null; function findvaluecallback(q, data) { var result; if (data && data.length) { for (var i = 0; i < data.length; i++) { if (data[i].result.tolowercase() == q.tolowercase()) { result = data[i]; break; } } } if (typeof fn == "function") fn(result); else $input.trigger("result", result && [result.data, result.value]); } $.each(trimwords($input.val()), function(i, value) { request(value, findvaluecallback, findvaluecallback); }); }).bind("flushcache", function() { cache.flush(); }).bind("setoptions", function() { $.extend(options, arguments[1]); if ("data" in arguments[1]) cache.populate(); }).bind("unautocomplete", function() { select.unbind(); $input.unbind(); $(input.form).unbind(".autocomplete"); }); function selectcurrent() { var selected = select.selected(); if (!selected) return false; var v = selected.result; previousvalue = v; if (options.multiple) { var words = trimwords($input.val()); if (words.length > 1) { var seperator = options.multipleseparator.length; var cursorat = $(input).selection().start; var wordat, progress = 0; $.each(words, function(i, word) { progress += word.length; if (cursorat <= progress) { wordat = i; return false; } progress += seperator; }); words[wordat] = v; v = words.join(options.multipleseparator); } v += options.multipleseparator; } $input.val(v); hideresultsnow(); $input.trigger("result", [selected.data, selected.value]); return true; } function onchange(crap, skipprevcheck) { if (lastkeypresscode == key.del) { select.hide(); return; } var currentvalue = $input.val(); if (!skipprevcheck && currentvalue == previousvalue) return; previousvalue = currentvalue; currentvalue = lastword(currentvalue); if (currentvalue.length >= options.minchars) { $input.addclass(options.loadingclass); if (!options.matchcase) currentvalue = currentvalue.tolowercase(); request(currentvalue, receivedata, hideresultsnow); } else { stoploading(); select.hide(); } }; function trimwords(value) { if (!value) return [""]; if (!options.multiple) return [$.trim(value)]; return $.map(value.split(options.multipleseparator), function(word) { return $.trim(value).length ? $.trim(word) : null; }); } function lastword(value) { if (!options.multiple) return value; var words = trimwords(value); if (words.length == 1) return words[0]; var cursorat = $(input).selection().start; if (cursorat == value.length) { words = trimwords(value) } else { words = trimwords(value.replace(value.substring(cursorat), "")); } return words[words.length - 1]; } function autofill(q, svalue) { if (options.autofill && (lastword($input.val()).tolowercase() == q.tolowercase()) && lastkeypresscode != key.backspace) { $input.val($input.val() + svalue.substring(lastword(previousvalue).length)); $(input).selection(previousvalue.length, previousvalue.length + svalue.length); } }; function hideresults() { cleartimeout(timeout); timeout = settimeout(hideresultsnow, 200); }; function hideresultsnow() { var wasvisible = select.visible(); select.hide(); cleartimeout(timeout); stoploading(); if (options.mustmatch) { $input.search(function(result) { if (!result) { if (options.multiple) { var words = trimwords($input.val()).slice(0, -1); $input.val(words.join(options.multipleseparator) + (words.length ? options.multipleseparator : "")); } else { $input.val(""); $input.trigger("result", null); } } }); } }; function receivedata(q, data) { if (data && data.length && hasfocus) { stoploading(); select.display(data, q); autofill(q, data[0].value); select.show(); } else { hideresultsnow(); } }; function request(term, success, failure) { if (!options.matchcase) term = term.tolowercase(); var data = cache.load(term); if (data && data.length) { success(term, data); } else if ((typeof options.url == "string") && (options.url.length > 0)) { var extraparams = { timestamp: +new date() }; $.each(options.extraparams, function(key, param) { extraparams[key] = typeof param == "function" ? param() : param; }); $.ajax({ mode: "abort", port: "autocomplete" + input.name, datatype: options.datatype, url: options.url, data: $.extend({ wd: lastword(term), limit: options.max }, extraparams), success: function(data) { var parsed = options.parse && options.parse(data) || parse(data); cache.add(term, parsed); success(term, parsed); } }); } else { select.emptylist(); failure(term); } }; function parse(data) { var parsed = []; var rows = data.split("\n"); for (var i = 0; i < rows.length; i++) { var row = $.trim(rows[i]); if (row) { row = row.split("|"); parsed[parsed.length] = { data: row, value: row[0], result: options.formatresult && options.formatresult(row, row[0]) || row[0] }; } } return parsed; }; function stoploading() { $input.removeclass(options.loadingclass); }; }; $.autocompleter.defaults = { inputclass: "ac_input", resultsclass: "ac_results", loadingclass: "ac_loading", minchars: 1, delay: 400, matchcase: false, matchsubset: true, matchcontains: false, cachelength: 10, max: 100, mustmatch: false, extraparams: {}, selectfirst: true, formatitem: function(row) { return row[0]; }, formatmatch: null, autofill: false, width: 0, multiple: false, multipleseparator: ", ", highlight: function(value, term) { return value.replace(new regexp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); }, scroll: true, scrollheight: 180 }; $.autocompleter.cache = function(options) { var data = {}; var length = 0; function matchsubset(s, sub) { if (!options.matchcase) s = s.tolowercase(); var i = s.indexof(sub); if (options.matchcontains == "word") { i = s.tolowercase().search("\\b" + sub.tolowercase()); } if (i == -1) return false; return i == 0 || options.matchcontains; }; function add(q, value) { if (length > options.cachelength) { flush(); } if (!data[q]) { length++; } data[q] = value; } function populate() { if (!options.data) return false; var stmatchsets = {}, nulldata = 0; if (!options.url) options.cachelength = 1; stmatchsets[""] = []; for (var i = 0, ol = options.data.length; i < ol; i++) { var rawvalue = options.data[i]; rawvalue = (typeof rawvalue == "string") ? [rawvalue] : rawvalue; var value = options.formatmatch(rawvalue, i + 1, options.data.length); if (value === false) continue; var firstchar = value.charat(0).tolowercase(); if (!stmatchsets[firstchar]) stmatchsets[firstchar] = []; var row = { value: value, data: rawvalue, result: options.formatresult && options.formatresult(rawvalue) || value }; stmatchsets[firstchar].push(row); if (nulldata++ < options.max) { stmatchsets[""].push(row); } }; $.each(stmatchsets, function(i, value) { options.cachelength++; add(i, value); }); } settimeout(populate, 25); function flush() { data = {}; length = 0; } return { flush: flush, add: add, populate: populate, load: function(q) { if (!options.cachelength || !length) return null; if (!options.url && options.matchcontains) { var csub = []; for (var k in data) { if (k.length > 0) { var c = data[k]; $.each(c, function(i, x) { if (matchsubset(x.value, q)) { csub.push(x); } }); } } return csub; } else if (data[q]) { return data[q]; } else if (options.matchsubset) { for (var i = q.length - 1; i >= options.minchars; i--) { var c = data[q.substr(0, i)]; if (c) { var csub = []; $.each(c, function(i, x) { if (matchsubset(x.value, q)) { csub[csub.length] = x; } }); return csub; } } } return null; } }; }; $.autocompleter.select = function(options, input, select, config) { var classes = { active: "ac_over" }; var listitems, active = -1, data, term = "", needsinit = true, element, list; function init() { if (!needsinit) return; element = $("
").hide().addclass(options.resultsclass).css("position", "absolute").appendto(document.body); list = $("