1/* 2 * jQuery Autocomplete plugin 1.1 3 * 4 * Copyright (c) 2009 Jörn Zaefferer 5 * 6 * Dual licensed under the MIT and GPL licenses: 7 * http://www.opensource.org/licenses/mit-license.php 8 * http://www.gnu.org/licenses/gpl.html 9 * 10 * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $ 11 */; 12$.browser = navigator.userAgent; 13(function($) { 14 $.fn.extend({ 15 autocomplete: function(urlOrData, options) { 16 var isUrl = typeof urlOrData == "string"; 17 options = $.extend({}, $.Autocompleter.defaults, { 18 url: isUrl ? urlOrData : null, 19 data: isUrl ? null : urlOrData, 20 delay: isUrl ? $.Autocompleter.defaults.delay : 10, 21 max: options && !options.scroll ? 10 : 150 22 }, options); 23 options.highlight = options.highlight || 24 function(value) { 25 return value; 26 }; 27 options.formatMatch = options.formatMatch || options.formatItem; 28 return this.each(function() { 29 new $.Autocompleter(this, options); 30 }); 31 }, 32 result: function(handler) { 33 return this.bind("result", handler); 34 }, 35 search: function(handler) { 36 return this.trigger("search", [handler]); 37 }, 38 flushCache: function() { 39 return this.trigger("flushCache"); 40 }, 41 setOptions: function(options) { 42 return this.trigger("setOptions", [options]); 43 }, 44 unautocomplete: function() { 45 return this.trigger("unautocomplete"); 46 } 47 }); 48 $.Autocompleter = function(input, options) { 49 var KEY = { 50 UP: 38, 51 DOWN: 40, 52 DEL: 46, 53 TAB: 9, 54 RETURN: 13, 55 ESC: 27, 56 COMMA: 188, 57 PAGEUP: 33, 58 PAGEDOWN: 34, 59 BACKSPACE: 8 60 }; 61 var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); 62 var timeout; 63 var previousValue = ""; 64 var cache = $.Autocompleter.Cache(options); 65 var hasFocus = 0; 66 var lastKeyPressCode; 67 var config = { 68 mouseDownOnSelect: false 69 }; 70 var select = $.Autocompleter.Select(options, input, selectCurrent, config); 71 var blockSubmit; 72 $.browser.opera && $(input.form).bind("submit.autocomplete", function() { 73 if (blockSubmit) { 74 blockSubmit = false; 75 return false; 76 } 77 }); 78 $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { 79 hasFocus = 1; 80 lastKeyPressCode = event.keyCode; 81 switch (event.keyCode) { 82 case KEY.UP: 83 event.preventDefault(); 84 if (select.visible()) { 85 select.prev(); 86 } else { 87 onChange(0, true); 88 } 89 break; 90 case KEY.DOWN: 91 event.preventDefault(); 92 if (select.visible()) { 93 select.next(); 94 } else { 95 onChange(0, true); 96 } 97 break; 98 case KEY.PAGEUP: 99 event.preventDefault(); 100 if (select.visible()) { 101 select.pageUp(); 102 } else { 103 onChange(0, true); 104 } 105 break; 106 case KEY.PAGEDOWN: 107 event.preventDefault(); 108 if (select.visible()) { 109 select.pageDown(); 110 } else { 111 onChange(0, true); 112 } 113 break; 114 case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: 115 case KEY.TAB: 116 case KEY.RETURN: 117 if (selectCurrent()) { 118 event.preventDefault(); 119 blockSubmit = true; 120 return false; 121 } 122 break; 123 case KEY.ESC: 124 select.hide(); 125 break; 126 default: 127 clearTimeout(timeout); 128 timeout = setTimeout(onChange, options.delay); 129 break; 130 } 131 }).focus(function() { 132 hasFocus++; 133 }).blur(function() { 134 hasFocus = 0; 135 if (!config.mouseDownOnSelect) { 136 hideResults(); 137 } 138 }).click(function() { 139 if (hasFocus++ > 1 && !select.visible()) { 140 onChange(0, true); 141 } 142 }).bind("search", function() { 143 var fn = (arguments.length > 1) ? arguments[1] : null; 144 145 function findValueCallback(q, data) { 146 var result; 147 if (data && data.length) { 148 for (var i = 0; i < data.length; i++) { 149 if (data[i].result.toLowerCase() == q.toLowerCase()) { 150 result = data[i]; 151 break; 152 } 153 } 154 } 155 if (typeof fn == "function") fn(result); 156 else $input.trigger("result", result && [result.data, result.value]); 157 } 158 $.each(trimWords($input.val()), function(i, value) { 159 request(value, findValueCallback, findValueCallback); 160 }); 161 }).bind("flushCache", function() { 162 cache.flush(); 163 }).bind("setOptions", function() { 164 $.extend(options, arguments[1]); 165 if ("data" in arguments[1]) cache.populate(); 166 }).bind("unautocomplete", function() { 167 select.unbind(); 168 $input.unbind(); 169 $(input.form).unbind(".autocomplete"); 170 }); 171 172 function selectCurrent() { 173 var selected = select.selected(); 174 if (!selected) return false; 175 var v = selected.result; 176 previousValue = v; 177 if (options.multiple) { 178 var words = trimWords($input.val()); 179 if (words.length > 1) { 180 var seperator = options.multipleSeparator.length; 181 var cursorAt = $(input).selection().start; 182 var wordAt, progress = 0; 183 $.each(words, function(i, word) { 184 progress += word.length; 185 if (cursorAt <= progress) { 186 wordAt = i; 187 return false; 188 } 189 progress += seperator; 190 }); 191 words[wordAt] = v; 192 v = words.join(options.multipleSeparator); 193 } 194 v += options.multipleSeparator; 195 } 196 $input.val(v); 197 hideResultsNow(); 198 $input.trigger("result", [selected.data, selected.value]); 199 return true; 200 } 201 function onChange(crap, skipPrevCheck) { 202 if (lastKeyPressCode == KEY.DEL) { 203 select.hide(); 204 return; 205 } 206 var currentValue = $input.val(); 207 if (!skipPrevCheck && currentValue == previousValue) return; 208 previousValue = currentValue; 209 currentValue = lastWord(currentValue); 210 if (currentValue.length >= options.minChars) { 211 $input.addClass(options.loadingClass); 212 if (!options.matchCase) currentValue = currentValue.toLowerCase(); 213 request(currentValue, receiveData, hideResultsNow); 214 } else { 215 stopLoading(); 216 select.hide(); 217 } 218 }; 219 220 function trimWords(value) { 221 if (!value) return [""]; 222 if (!options.multiple) return [$.trim(value)]; 223 return $.map(value.split(options.multipleSeparator), function(word) { 224 return $.trim(value).length ? $.trim(word) : null; 225 }); 226 } 227 function lastWord(value) { 228 if (!options.multiple) return value; 229 var words = trimWords(value); 230 if (words.length == 1) return words[0]; 231 var cursorAt = $(input).selection().start; 232 if (cursorAt == value.length) { 233 words = trimWords(value) 234 } else { 235 words = trimWords(value.replace(value.substring(cursorAt), "")); 236 } 237 return words[words.length - 1]; 238 } 239 function autoFill(q, sValue) { 240 if (options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE) { 241 $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); 242 $(input).selection(previousValue.length, previousValue.length + sValue.length); 243 } 244 }; 245 246 function hideResults() { 247 clearTimeout(timeout); 248 timeout = setTimeout(hideResultsNow, 200); 249 }; 250 251 function hideResultsNow() { 252 var wasVisible = select.visible(); 253 select.hide(); 254 clearTimeout(timeout); 255 stopLoading(); 256 if (options.mustMatch) { 257 $input.search(function(result) { 258 if (!result) { 259 if (options.multiple) { 260 var words = trimWords($input.val()).slice(0, -1); 261 $input.val(words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "")); 262 } else { 263 $input.val(""); 264 $input.trigger("result", null); 265 } 266 } 267 }); 268 } 269 }; 270 271 function receiveData(q, data) { 272 if (data && data.length && hasFocus) { 273 stopLoading(); 274 select.display(data, q); 275 autoFill(q, data[0].value); 276 select.show(); 277 } else { 278 hideResultsNow(); 279 } 280 }; 281 282 function request(term, success, failure) { 283 if (!options.matchCase) term = term.toLowerCase(); 284 var data = cache.load(term); 285 if (data && data.length) { 286 success(term, data); 287 } else if ((typeof options.url == "string") && (options.url.length > 0)) { 288 var extraParams = { 289 timestamp: +new Date() 290 }; 291 $.each(options.extraParams, function(key, param) { 292 extraParams[key] = typeof param == "function" ? param() : param; 293 }); 294 $.ajax({
295 mode: "abort", 296 port: "autocomplete" + input.name, 297 dataType: options.dataType, 298 url: options.url, 299 data: $.extend({ 300 wd: lastWord(term), 301 limit: options.max 302 }, extraParams), 303 success: function(data) { 304 var parsed = options.parse && options.parse(data) || parse(data); 305 cache.add(term, parsed); 306 success(term, parsed); 307 } 308 }); 309 } else { 310 select.emptyList(); 311 failure(term); 312 } 313 }; 314 315 function parse(data) { 316 var parsed = []; 317 var rows = data.split("\n"); 318 for (var i = 0; i < rows.length; i++) { 319 var row = $.trim(rows[i]); 320 if (row) { 321 row = row.split("|"); 322 parsed[parsed.length] = { 323 data: row, 324 value: row[0], 325 result: options.formatResult && options.formatResult(row, row[0]) || row[0] 326 }; 327 } 328 } 329 return parsed; 330 }; 331 332 function stopLoading() { 333 $input.removeClass(options.loadingClass); 334 }; 335 }; 336 $.Autocompleter.defaults = { 337 inputClass: "ac_input", 338 resultsClass: "ac_results", 339 loadingClass: "ac_loading", 340 minChars: 1, 341 delay: 400, 342 matchCase: false, 343 matchSubset: false, 344 matchContains: false, 345 cacheLength: 10, 346 max: 100, 347 mustMatch: false, 348 extraParams: {}, 349 selectFirst: true, 350 formatItem: function(row) { 351 return row[0]; 352 }, 353 formatMatch: null, 354 autoFill: false, 355 width: 0, 356 multiple: false, 357 multipleSeparator: ", ", 358 highlight: function(value, term) { 359 return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>"); 360 }, 361 scroll: true, 362 scrollHeight: 180 363 }; 364 $.Autocompleter.Cache = function(options) { 365 var data = {}; 366 var length = 0; 367 368 function matchSubset(s, sub) { 369 if (!options.matchCase) s = s.toLowerCase(); 370 var i = s.indexOf(sub); 371 if (options.matchContains == "word") { 372 i = s.toLowerCase().search("\\b" + sub.toLowerCase()); 373 } 374 if (i == -1) return false; 375 return i == 0 || options.matchContains; 376 }; 377 378 function add(q, value) { 379 if (length > options.cacheLength) { 380 flush(); 381 } 382 if (!data[q]) { 383 length++; 384 } 385 data[q] = value; 386 } 387 function populate() { 388 if (!options.data) return false; 389 var stMatchSets = {}, 390 nullData = 0; 391 if (!options.url) options.cacheLength = 1; 392 stMatchSets[""] = []; 393 for (var i = 0, ol = options.data.length; i < ol; i++) { 394 var rawValue = options.data[i]; 395 rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; 396 var value = options.formatMatch(rawValue, i + 1, options.data.length); 397 if (value === false) continue; 398 var firstChar = value.charAt(0).toLowerCase(); 399 if (!stMatchSets[firstChar]) stMatchSets[firstChar] = []; 400 var row = { 401 value: value, 402 data: rawValue, 403 result: options.formatResult && options.formatResult(rawValue) || value 404 }; 405 stMatchSets[firstChar].push(row); 406 if (nullData++ < options.max) { 407 stMatchSets[""].push(row); 408 } 409 }; 410 $.each(stMatchSets, function(i, value) { 411 options.cacheLength++; 412 add(i, value); 413 }); 414 } 415 setTimeout(populate, 25); 416 417 function flush() { 418 data = {}; 419 length = 0; 420 } 421 return { 422 flush: flush, 423 add: add, 424 populate: populate, 425 load: function(q) { 426 if (!options.cacheLength || !length) return null; 427 if (!options.url && options.matchContains) { 428 var csub = []; 429 for (var k in data) { 430 if (k.length > 0) { 431 var c = data[k]; 432 $.each(c, function(i, x) { 433 if (matchSubset(x.value, q)) { 434 csub.push(x); 435 } 436 }); 437 } 438 } 439 return csub; 440 } else if (data[q]) { 441 return data[q]; 442 } else if (options.matchSubset) { 443 for (var i = q.length - 1; i >= options.minChars; i--) { 444 var c = data[q.substr(0, i)]; 445 if (c) { 446 var csub = []; 447 $.each(c, function(i, x) { 448 if (matchSubset(x.value, q)) { 449 csub[csub.length] = x; 450 } 451 }); 452 return csub; 453 } 454 } 455 } 456 return null; 457 } 458 }; 459 }; 460 $.Autocompleter.Select = function(options, input, select, config) { 461 var CLASSES = { 462 ACTIVE: "ac_over" 463 }; 464 var listItems, active = -1, 465 data, term = "", 466 needsInit = true, 467 element, list; 468
469 function init() { 470 if (!needsInit) return; 471 element = $("<div/>").hide().addClass(options.resultsClass).css("position", "absolute").appendTo(document.body); 472 list = $("<ul/>").appendTo(element).mouseover(function(event) { 473 if (target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { 474 active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); 475 $(target(event)).addClass(CLASSES.ACTIVE); 476 } 477 }).click(function(event) { 478 $(target(event)).addClass(CLASSES.ACTIVE); 479 select(); 480 input.focus(); 481 return false; 482 }).mousedown(function() { 483 config.mouseDownOnSelect = true; 484 }).mouseup(function() { 485 config.mouseDownOnSelect = false; 486 }); 487 if (options.width > 0) element.css("width", options.width); 488 needsInit = false; 489 } 490 function target(event) { 491 var element = event.target; 492 while (element && element.tagName != "LI") element = element.parentNode; 493 if (!element) return []; 494 return element; 495 } 496 function moveSelect(step) { 497 listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); 498 movePosition(step); 499 var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); 500 if (options.scroll) { 501 var offset = 0; 502 listItems.slice(0, active).each(function() { 503 offset += this.offsetHeight; 504 }); 505 if ((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { 506 list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); 507 } else if (offset < list.scrollTop()) { 508 list.scrollTop(offset); 509 } 510 } 511 }; 512 513 function movePosition(step) { 514 active += step; 515 if (active < 0) { 516 active = listItems.size() - 1; 517 } else if (active >= listItems.size()) { 518 active = 0; 519 } 520 } 521 function limitNumberOfItems(available) { 522 return options.max && options.max < available ? options.max : available; 523 } 524 function fillList() { 525 list.empty(); 526 var max = limitNumberOfItems(data.length); 527 for (var i = 0; i < max; i++) { 528 if (!data[i]) continue; 529 var formatted = options.formatItem(data[i].data, i + 1, max, data[i].value, term); 530 if (formatted === false) continue; 531 var li = $("<li/>").html(options.highlight(formatted, term)).addClass(i % 2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; 532 $.data(li, "ac_data", data[i]); 533 } 534 listItems = list.find("li"); 535 if (options.selectFirst) { 536 listItems.slice(0, 1).addClass(CLASSES.ACTIVE); 537 active = 0; 538 } 539 if ($.fn.bgiframe) list.bgiframe(); 540 } 541 return { 542 display: function(d, q) { 543 init(); 544 data = d; 545 term = q; 546 fillList(); 547 }, 548 next: function() { 549 moveSelect(1); 550 }, 551 prev: function() { 552 moveSelect(-1); 553 }, 554 pageUp: function() { 555 if (active != 0 && active - 8 < 0) { 556 moveSelect(-active); 557 } else { 558 moveSelect(-8); 559 } 560 }, 561 pageDown: function() { 562 if (active != listItems.size() - 1 && active + 8 > listItems.size()) { 563 moveSelect(listItems.size() - 1 - active); 564 } else { 565 moveSelect(8); 566 } 567 }, 568 hide: function() { 569 element && element.hide(); 570 listItems && listItems.removeClass(CLASSES.ACTIVE); 571 active = -1; 572 }, 573 visible: function() { 574 return element && element.is(":visible"); 575 }, 576 current: function() { 577 return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); 578 }, 579 show: function() { 580 var offset = $(input).offset(); 581 element.css({ 582 width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), 583 top: offset.top + input.offsetHeight, 584 left: offset.left 585 }).show(); 586 if (options.scroll) { 587 list.scrollTop(0); 588 list.css({ 589 maxHeight: options.scrollHeight, 590 overflow: 'auto' 591 }); 592 if ($.browser.msie && typeof document.body.style.maxHeight === "undefined") { 593 var listHeight = 0; 594 listItems.each(function() { 595 listHeight += this.offsetHeight; 596 }); 597 var scrollbarsVisible = listHeight > options.scrollHeight; 598 list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight); 599 if (!scrollbarsVisible) {
600 listItems.width(list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right"))); 601 } 602 } 603 } 604 }, 605 selected: function() { 606 var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); 607 return selected && selected.length && $.data(selected[0], "ac_data"); 608 }, 609 emptyList: function() { 610 list && list.empty(); 611 }, 612 unbind: function() { 613 element && element.remove(); 614 } 615 }; 616 }; 617 $.fn.selection = function(start, end) { 618 if (start !== undefined) { 619 return this.each(function() { 620 if (this.createTextRange) { 621 var selRange = this.createTextRange(); 622 if (end === undefined || start == end) { 623 selRange.move("character", start); 624 selRange.select(); 625 } else { 626 selRange.collapse(true); 627 selRange.moveStart("character", start); 628 selRange.moveEnd("character", end); 629 selRange.select(); 630 } 631 } else if (this.setSelectionRange) { 632 this.setSelectionRange(start, end); 633 } else if (this.selectionStart) { 634 this.selectionStart = start; 635 this.selectionEnd = end; 636 } 637 }); 638 } 639 var field = this[0]; 640 if (field.createTextRange) { 641 var range = document.selection.createRange(), 642 orig = field.value, 643 teststring = "<->", 644 textLength = range.text.length; 645 range.text = teststring; 646 var caretAt = field.value.indexOf(teststring); 647 field.value = orig; 648 this.selection(caretAt, caretAt + textLength); 649 return { 650 start: caretAt, 651 end: caretAt + textLength 652 } 653 } else if (field.selectionStart !== undefined) { 654 return { 655 start: field.selectionStart, 656 end: field.selectionEnd 657 } 658 } 659 }; 660})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.