[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 * Autocomplete - jQuery plugin 1.0.2 3 * 4 * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, 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,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $ 11 * 12 */ 13 14 ;(function($) { 15 16 $.fn.extend({ 17 autocomplete: function(urlOrData, options) { 18 var isUrl = typeof urlOrData == "string"; 19 options = $.extend({}, $.Autocompleter.defaults, { 20 url: isUrl ? urlOrData : null, 21 data: isUrl ? null : urlOrData, 22 delay: isUrl ? $.Autocompleter.defaults.delay : 10, 23 max: options && !options.scroll ? 10 : 150 24 }, options); 25 26 // if highlight is set to false, replace it with a do-nothing function 27 options.highlight = options.highlight || function(value) { return value; }; 28 29 // if the formatMatch option is not specified, then use formatItem for backwards compatibility 30 options.formatMatch = options.formatMatch || options.formatItem; 31 32 return this.each(function() { 33 new $.Autocompleter(this, options); 34 }); 35 }, 36 result: function(handler) { 37 return this.bind("result", handler); 38 }, 39 search: function(handler) { 40 return this.trigger("search", [handler]); 41 }, 42 flushCache: function() { 43 return this.trigger("flushCache"); 44 }, 45 setOptions: function(options){ 46 return this.trigger("setOptions", [options]); 47 }, 48 unautocomplete: function() { 49 return this.trigger("unautocomplete"); 50 } 51 }); 52 53 $.Autocompleter = function(input, options) { 54 55 var KEY = { 56 UP: 38, 57 DOWN: 40, 58 DEL: 46, 59 TAB: 9, 60 RETURN: 13, 61 ESC: 27, 62 COMMA: 188, 63 PAGEUP: 33, 64 PAGEDOWN: 34, 65 BACKSPACE: 8 66 }; 67 68 // Create $ object for input element 69 var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); 70 71 var timeout; 72 var previousValue = ""; 73 var cache = $.Autocompleter.Cache(options); 74 var hasFocus = 0; 75 var lastKeyPressCode; 76 var config = { 77 mouseDownOnSelect: false 78 }; 79 var select = $.Autocompleter.Select(options, input, selectCurrent, config); 80 81 var blockSubmit; 82 83 // prevent form submit in opera when selecting with return key 84 $.browser.opera && $(input.form).bind("submit.autocomplete", function() { 85 if (blockSubmit) { 86 blockSubmit = false; 87 return false; 88 } 89 }); 90 91 // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all 92 $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { 93 // track last key pressed 94 lastKeyPressCode = event.keyCode; 95 switch(event.keyCode) { 96 97 case KEY.UP: 98 event.preventDefault(); 99 if ( select.visible() ) { 100 select.prev(); 101 } else { 102 onChange(0, true); 103 } 104 break; 105 106 case KEY.DOWN: 107 event.preventDefault(); 108 if ( select.visible() ) { 109 select.next(); 110 } else { 111 onChange(0, true); 112 } 113 break; 114 115 case KEY.PAGEUP: 116 event.preventDefault(); 117 if ( select.visible() ) { 118 select.pageUp(); 119 } else { 120 onChange(0, true); 121 } 122 break; 123 124 case KEY.PAGEDOWN: 125 event.preventDefault(); 126 if ( select.visible() ) { 127 select.pageDown(); 128 } else { 129 onChange(0, true); 130 } 131 break; 132 133 // matches also semicolon 134 case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: 135 case KEY.TAB: 136 case KEY.RETURN: 137 if( selectCurrent() ) { 138 // stop default to prevent a form submit, Opera needs special handling 139 event.preventDefault(); 140 blockSubmit = true; 141 return false; 142 } 143 break; 144 145 case KEY.ESC: 146 select.hide(); 147 break; 148 149 default: 150 clearTimeout(timeout); 151 timeout = setTimeout(onChange, options.delay); 152 break; 153 } 154 }).focus(function(){ 155 // track whether the field has focus, we shouldn't process any 156 // results if the field no longer has focus 157 hasFocus++; 158 }).blur(function() { 159 hasFocus = 0; 160 if (!config.mouseDownOnSelect) { 161 hideResults(); 162 } 163 }).click(function() { 164 // show select when clicking in a focused field 165 if ( hasFocus++ > 1 && !select.visible() ) { 166 onChange(0, true); 167 } 168 }).bind("search", function() { 169 // TODO why not just specifying both arguments? 170 var fn = (arguments.length > 1) ? arguments[1] : null; 171 function findValueCallback(q, data) { 172 var result; 173 if( data && data.length ) { 174 for (var i=0; i < data.length; i++) { 175 if( data[i].result.toLowerCase() == q.toLowerCase() ) { 176 result = data[i]; 177 break; 178 } 179 } 180 } 181 if( typeof fn == "function" ) fn(result); 182 else $input.trigger("result", result && [result.data, result.value]); 183 } 184 $.each(trimWords($input.val()), function(i, value) { 185 request(value, findValueCallback, findValueCallback); 186 }); 187 }).bind("flushCache", function() { 188 cache.flush(); 189 }).bind("setOptions", function() { 190 $.extend(options, arguments[1]); 191 // if we've updated the data, repopulate 192 if ( "data" in arguments[1] ) 193 cache.populate(); 194 }).bind("unautocomplete", function() { 195 select.unbind(); 196 $input.unbind(); 197 $(input.form).unbind(".autocomplete"); 198 }); 199 200 201 function selectCurrent() { 202 var selected = select.selected(); 203 if( !selected ) 204 return false; 205 206 var v = selected.result; 207 previousValue = v; 208 209 if ( options.multiple ) { 210 var words = trimWords($input.val()); 211 if ( words.length > 1 ) { 212 v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v; 213 } 214 v += options.multipleSeparator; 215 } 216 217 $input.val(v); 218 hideResultsNow(); 219 $input.trigger("result", [selected.data, selected.value]); 220 return true; 221 } 222 223 function onChange(crap, skipPrevCheck) { 224 if( lastKeyPressCode == KEY.DEL ) { 225 select.hide(); 226 return; 227 } 228 229 var currentValue = $input.val(); 230 231 if ( !skipPrevCheck && currentValue == previousValue ) 232 return; 233 234 previousValue = currentValue; 235 236 currentValue = lastWord(currentValue); 237 if ( currentValue.length >= options.minChars) { 238 $input.addClass(options.loadingClass); 239 if (!options.matchCase) 240 currentValue = currentValue.toLowerCase(); 241 request(currentValue, receiveData, hideResultsNow); 242 } else { 243 stopLoading(); 244 select.hide(); 245 } 246 }; 247 248 function trimWords(value) { 249 if ( !value ) { 250 return [""]; 251 } 252 var words = value.split( options.multipleSeparator ); 253 var result = []; 254 $.each(words, function(i, value) { 255 if ( $.trim(value) ) 256 result[i] = $.trim(value); 257 }); 258 return result; 259 } 260 261 function lastWord(value) { 262 if ( !options.multiple ) 263 return value; 264 var words = trimWords(value); 265 return words[words.length - 1]; 266 } 267 268 // fills in the input box w/the first match (assumed to be the best match) 269 // q: the term entered 270 // sValue: the first matching result 271 function autoFill(q, sValue){ 272 // autofill in the complete box w/the first match as long as the user hasn't entered in more data 273 // if the last user key pressed was backspace, don't autofill 274 if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { 275 // fill in the value (keep the case the user has typed) 276 $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); 277 // select the portion of the value not typed by the user (so the next character will erase) 278 $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length); 279 } 280 }; 281 282 function hideResults() { 283 clearTimeout(timeout); 284 timeout = setTimeout(hideResultsNow, 200); 285 }; 286 287 function hideResultsNow() { 288 var wasVisible = select.visible(); 289 select.hide(); 290 clearTimeout(timeout); 291 stopLoading(); 292 if (options.mustMatch) { 293 // call search and run callback 294 $input.search( 295 function (result){ 296 // if no value found, clear the input box 297 if( !result ) { 298 if (options.multiple) { 299 var words = trimWords($input.val()).slice(0, -1); 300 $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); 301 } 302 else 303 $input.val( "" ); 304 } 305 } 306 ); 307 } 308 if (wasVisible) 309 // position cursor at end of input field 310 $.Autocompleter.Selection(input, input.value.length, input.value.length); 311 }; 312 313 function receiveData(q, data) { 314 if ( data && data.length && hasFocus ) { 315 stopLoading(); 316 select.display(data, q); 317 autoFill(q, data[0].value); 318 select.show(); 319 } else { 320 hideResultsNow(); 321 } 322 }; 323 324 function request(term, success, failure) { 325 if (!options.matchCase) 326 term = term.toLowerCase(); 327 var data = cache.load(term); 328 // recieve the cached data 329 if (data && data.length) { 330 success(term, data); 331 // if an AJAX url has been supplied, try loading the data now 332 } else if( (typeof options.url == "string") && (options.url.length > 0) ){ 333 334 var extraParams = { 335 timestamp: +new Date() 336 }; 337 $.each(options.extraParams, function(key, param) { 338 extraParams[key] = typeof param == "function" ? param() : param; 339 }); 340 341 $.ajax({ 342 // try to leverage ajaxQueue plugin to abort previous requests 343 mode: "abort", 344 // limit abortion to this input 345 port: "autocomplete" + input.name, 346 dataType: options.dataType, 347 url: options.url, 348 data: $.extend({ 349 q: lastWord(term), 350 limit: options.max 351 }, extraParams), 352 success: function(data) { 353 var parsed = options.parse && options.parse(data) || parse(data); 354 cache.add(term, parsed); 355 success(term, parsed); 356 } 357 }); 358 } else { 359 // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match 360 select.emptyList(); 361 failure(term); 362 } 363 }; 364 365 function parse(data) { 366 var parsed = []; 367 var rows = data.split("\n"); 368 for (var i=0; i < rows.length; i++) { 369 var row = $.trim(rows[i]); 370 if (row) { 371 row = row.split("|"); 372 parsed[parsed.length] = { 373 data: row, 374 value: row[0], 375 result: options.formatResult && options.formatResult(row, row[0]) || row[0] 376 }; 377 } 378 } 379 return parsed; 380 }; 381 382 function stopLoading() { 383 $input.removeClass(options.loadingClass); 384 }; 385 386 }; 387 388 $.Autocompleter.defaults = { 389 inputClass: "ac_input", 390 resultsClass: "ac_results", 391 loadingClass: "ac_loading", 392 minChars: 1, 393 delay: 400, 394 matchCase: false, 395 matchSubset: true, 396 matchContains: false, 397 cacheLength: 10, 398 max: 100, 399 mustMatch: false, 400 extraParams: {}, 401 selectFirst: true, 402 formatItem: function(row) { return row[0]; }, 403 formatMatch: null, 404 autoFill: false, 405 width: 0, 406 multiple: false, 407 multipleSeparator: ", ", 408 highlight: function(value, term) { 409 return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>"); 410 }, 411 scroll: true, 412 scrollHeight: 180 413 }; 414 415 $.Autocompleter.Cache = function(options) { 416 417 var data = {}; 418 var length = 0; 419 420 function matchSubset(s, sub) { 421 if (!options.matchCase) 422 s = s.toLowerCase(); 423 var i = s.indexOf(sub); 424 if (i == -1) return false; 425 return i == 0 || options.matchContains; 426 }; 427 428 function add(q, value) { 429 if (length > options.cacheLength){ 430 flush(); 431 } 432 if (!data[q]){ 433 length++; 434 } 435 data[q] = value; 436 } 437 438 function populate(){ 439 if( !options.data ) return false; 440 // track the matches 441 var stMatchSets = {}, 442 nullData = 0; 443 444 // no url was specified, we need to adjust the cache length to make sure it fits the local data store 445 if( !options.url ) options.cacheLength = 1; 446 447 // track all options for minChars = 0 448 stMatchSets[""] = []; 449 450 // loop through the array and create a lookup structure 451 for ( var i = 0, ol = options.data.length; i < ol; i++ ) { 452 var rawValue = options.data[i]; 453 // if rawValue is a string, make an array otherwise just reference the array 454 rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; 455 456 var value = options.formatMatch(rawValue, i+1, options.data.length); 457 if ( value === false ) 458 continue; 459 460 var firstChar = value.charAt(0).toLowerCase(); 461 // if no lookup array for this character exists, look it up now 462 if( !stMatchSets[firstChar] ) 463 stMatchSets[firstChar] = []; 464 465 // if the match is a string 466 var row = { 467 value: value, 468 data: rawValue, 469 result: options.formatResult && options.formatResult(rawValue) || value 470 }; 471 472 // push the current match into the set list 473 stMatchSets[firstChar].push(row); 474 475 // keep track of minChars zero items 476 if ( nullData++ < options.max ) { 477 stMatchSets[""].push(row); 478 } 479 }; 480 481 // add the data items to the cache 482 $.each(stMatchSets, function(i, value) { 483 // increase the cache size 484 options.cacheLength++; 485 // add to the cache 486 add(i, value); 487 }); 488 } 489 490 // populate any existing data 491 setTimeout(populate, 25); 492 493 function flush(){ 494 data = {}; 495 length = 0; 496 } 497 498 return { 499 flush: flush, 500 add: add, 501 populate: populate, 502 load: function(q) { 503 if (!options.cacheLength || !length) 504 return null; 505 /* 506 * if dealing w/local data and matchContains than we must make sure 507 * to loop through all the data collections looking for matches 508 */ 509 if( !options.url && options.matchContains ){ 510 // track all matches 511 var csub = []; 512 // loop through all the data grids for matches 513 for( var k in data ){ 514 // don't search through the stMatchSets[""] (minChars: 0) cache 515 // this prevents duplicates 516 if( k.length > 0 ){ 517 var c = data[k]; 518 $.each(c, function(i, x) { 519 // if we've got a match, add it to the array 520 if (matchSubset(x.value, q)) { 521 csub.push(x); 522 } 523 }); 524 } 525 } 526 return csub; 527 } else 528 // if the exact item exists, use it 529 if (data[q]){ 530 return data[q]; 531 } else 532 if (options.matchSubset) { 533 for (var i = q.length - 1; i >= options.minChars; i--) { 534 var c = data[q.substr(0, i)]; 535 if (c) { 536 var csub = []; 537 $.each(c, function(i, x) { 538 if (matchSubset(x.value, q)) { 539 csub[csub.length] = x; 540 } 541 }); 542 return csub; 543 } 544 } 545 } 546 return null; 547 } 548 }; 549 }; 550 551 $.Autocompleter.Select = function (options, input, select, config) { 552 var CLASSES = { 553 ACTIVE: "ac_over" 554 }; 555 556 var listItems, 557 active = -1, 558 data, 559 term = "", 560 needsInit = true, 561 element, 562 list; 563 564 // Create results 565 function init() { 566 if (!needsInit) 567 return; 568 element = $("<div/>") 569 .hide() 570 .addClass(options.resultsClass) 571 .css("position", "absolute") 572 .appendTo(document.body); 573 574 list = $("<ul/>").appendTo(element).mouseover( function(event) { 575 if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { 576 active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); 577 $(target(event)).addClass(CLASSES.ACTIVE); 578 } 579 }).click(function(event) { 580 $(target(event)).addClass(CLASSES.ACTIVE); 581 select(); 582 // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus 583 input.focus(); 584 return false; 585 }).mousedown(function() { 586 config.mouseDownOnSelect = true; 587 }).mouseup(function() { 588 config.mouseDownOnSelect = false; 589 }); 590 591 if( options.width > 0 ) 592 element.css("width", options.width); 593 594 needsInit = false; 595 } 596 597 function target(event) { 598 var element = event.target; 599 while(element && element.tagName != "LI") 600 element = element.parentNode; 601 // more fun with IE, sometimes event.target is empty, just ignore it then 602 if(!element) 603 return []; 604 return element; 605 } 606 607 function moveSelect(step) { 608 listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); 609 movePosition(step); 610 var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); 611 if(options.scroll) { 612 var offset = 0; 613 listItems.slice(0, active).each(function() { 614 offset += this.offsetHeight; 615 }); 616 if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { 617 list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); 618 } else if(offset < list.scrollTop()) { 619 list.scrollTop(offset); 620 } 621 } 622 }; 623 624 function movePosition(step) { 625 active += step; 626 if (active < 0) { 627 active = listItems.size() - 1; 628 } else if (active >= listItems.size()) { 629 active = 0; 630 } 631 } 632 633 function limitNumberOfItems(available) { 634 return options.max && options.max < available 635 ? options.max 636 : available; 637 } 638 639 function fillList() { 640 list.empty(); 641 var max = limitNumberOfItems(data.length); 642 for (var i=0; i < max; i++) { 643 if (!data[i]) 644 continue; 645 var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); 646 if ( formatted === false ) 647 continue; 648 var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; 649 $.data(li, "ac_data", data[i]); 650 } 651 listItems = list.find("li"); 652 if ( options.selectFirst ) { 653 listItems.slice(0, 1).addClass(CLASSES.ACTIVE); 654 active = 0; 655 } 656 // apply bgiframe if available 657 if ( $.fn.bgiframe ) 658 list.bgiframe(); 659 } 660 661 return { 662 display: function(d, q) { 663 init(); 664 data = d; 665 term = q; 666 fillList(); 667 }, 668 next: function() { 669 moveSelect(1); 670 }, 671 prev: function() { 672 moveSelect(-1); 673 }, 674 pageUp: function() { 675 if (active != 0 && active - 8 < 0) { 676 moveSelect( -active ); 677 } else { 678 moveSelect(-8); 679 } 680 }, 681 pageDown: function() { 682 if (active != listItems.size() - 1 && active + 8 > listItems.size()) { 683 moveSelect( listItems.size() - 1 - active ); 684 } else { 685 moveSelect(8); 686 } 687 }, 688 hide: function() { 689 element && element.hide(); 690 listItems && listItems.removeClass(CLASSES.ACTIVE); 691 active = -1; 692 }, 693 visible : function() { 694 return element && element.is(":visible"); 695 }, 696 current: function() { 697 return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); 698 }, 699 show: function() { 700 var offset = $(input).offset(); 701 element.css({ 702 width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), 703 top: offset.top + input.offsetHeight, 704 left: offset.left 705 }).show(); 706 if(options.scroll) { 707 list.scrollTop(0); 708 list.css({ 709 maxHeight: options.scrollHeight, 710 overflow: 'auto' 711 }); 712 713 if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { 714 var listHeight = 0; 715 listItems.each(function() { 716 listHeight += this.offsetHeight; 717 }); 718 var scrollbarsVisible = listHeight > options.scrollHeight; 719 list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); 720 if (!scrollbarsVisible) { 721 // IE doesn't recalculate width when scrollbar disappears 722 listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); 723 } 724 } 725 726 } 727 }, 728 selected: function() { 729 var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); 730 return selected && selected.length && $.data(selected[0], "ac_data"); 731 }, 732 emptyList: function (){ 733 list && list.empty(); 734 }, 735 unbind: function() { 736 element && element.remove(); 737 } 738 }; 739 }; 740 741 $.Autocompleter.Selection = function(field, start, end) { 742 if( field.createTextRange ){ 743 var selRange = field.createTextRange(); 744 selRange.collapse(true); 745 selRange.moveStart("character", start); 746 selRange.moveEnd("character", end); 747 selRange.select(); 748 } else if( field.setSelectionRange ){ 749 field.setSelectionRange(start, end); 750 } else { 751 if( field.selectionStart ){ 752 field.selectionStart = start; 753 field.selectionEnd = end; 754 } 755 } 756 field.focus(); 757 }; 758 759 })(jQuery);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |