1/** 2* Ajax Autocomplete for jQuery, version %version% 3* (c) 2015 Tomas Kirda 4* 5* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. 6* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete 7*/ 8 9/*jslint browser: true, white: true, single: true, this: true, multivar: true */ 10/*global define, window, document, jQuery, exports, require */ 11 12// Expose plugin as an AMD module if AMD loader is present: 13(function (factory) { 14 "use strict"; 15 if (typeof define === 'function' && define.amd) { 16 // AMD. Register as an anonymous module. 17 define(['jquery'], factory); 18 } else if (typeof exports === 'object' && typeof require === 'function') { 19 // Browserify 20 factory(require('jquery')); 21 } else { 22 // Browser globals 23 factory(jQuery); 24 } 25}(function ($) { 26 'use strict'; 27 28 var 29 utils = (function () { 30 return { 31 escapeRegExChars: function (value) { 32 return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&"); 33 }, 34 createNode: function (containerClass) { 35 var div = document.createElement('div'); 36 div.className = containerClass; 37 div.style.position = 'absolute'; 38 div.style.display = 'none'; 39 return div; 40 } 41 }; 42 }()), 43 44 keys = { 45 ESC: 27, 46 TAB: 9, 47 RETURN: 13, 48 LEFT: 37, 49 UP: 38, 50 RIGHT: 39, 51 DOWN: 40 52 }; 53 54 function Autocomplete(el, options) { 55 var noop = $.noop, 56 that = this, 57 defaults = { 58 ajaxSettings: {}, 59 autoSelectFirst: false, 60 appendTo: document.body, 61 serviceUrl: null, 62 lookup: null, 63 onSelect: null, 64 width: 'auto', 65 minChars: 1, 66 maxHeight: 300, 67 deferRequestBy: 0, 68 params: {}, 69 formatResult: Autocomplete.formatResult, 70 delimiter: null, 71 zIndex: 9999, 72 type: 'GET', 73 noCache: false, 74 onSearchStart: noop, 75 onSearchComplete: noop, 76 onSearchError: noop, 77 preserveInput: false, 78 containerClass: 'autocomplete-suggestions', 79 tabDisabled: false, 80 dataType: 'text', 81 currentRequest: null, 82 triggerSelectOnValidInput: true, 83 preventBadQueries: true, 84 lookupFilter: function (suggestion, originalQuery, queryLowerCase) { 85 return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; 86 }, 87 paramName: 'query', 88 transformResult: function (response) { 89 return typeof response === 'string' ? $.parseJSON(response) : response; 90 }, 91 showNoSuggestionNotice: false, 92 noSuggestionNotice: 'No results', 93 orientation: 'bottom', 94 forceFixPosition: false 95 }; 96 97 // Shared variables: 98 that.element = el; 99 that.el = $(el); 100 that.suggestions = []; 101 that.badQueries = []; 102 that.selectedIndex = -1; 103 that.currentValue = that.element.value; 104 that.intervalId = 0; 105 that.cachedResponse = {}; 106 that.onChangeInterval = null; 107 that.onChange = null; 108 that.isLocal = false;
109 that.suggestionsContainer = null; 110 that.noSuggestionsContainer = null; 111 that.options = $.extend({}, defaults, options); 112 that.classes = { 113 selected: 'autocomplete-selected', 114 suggestion: 'autocomplete-suggestion' 115 }; 116 that.hint = null; 117 that.hintValue = ''; 118 that.selection = null; 119 120 // Initialize and set options: 121 that.initialize(); 122 that.setOptions(options); 123 } 124 125 Autocomplete.utils = utils; 126 127 $.Autocomplete = Autocomplete; 128 129 Autocomplete.formatResult = function (suggestion, currentValue) { 130 // Do not replace anything if there current value is empty 131 if (!currentValue) { 132 return suggestion.value; 133 } 134 135 var pattern = '(' + utils.escapeRegExChars(currentValue) + ')'; 136 137 return suggestion.value 138 .replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>') 139 .replace(/&/g, '&') 140 .replace(/</g, '<') 141 .replace(/>/g, '>') 142 .replace(/"/g, '"') 143 .replace(/<(\/?strong)>/g, '<$1>'); 144 }; 145 146 Autocomplete.prototype = { 147 148 killerFn: null, 149 150 initialize: function () { 151 var that = this, 152 suggestionSelector = '.' + that.classes.suggestion, 153 selected = that.classes.selected, 154 options = that.options, 155 container; 156 157 // Remove autocomplete attribute to prevent native suggestions: 158 that.element.setAttribute('autocomplete', 'off'); 159 160 that.killerFn = function (e) { 161 if (!$(e.target).closest('.' + that.options.containerClass).length) { 162 that.killSuggestions(); 163 that.disableKillerFn(); 164 } 165 }; 166 167 // html() deals with many types: htmlString or Element or Array or jQuery
168 that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>') 169 .html(this.options.noSuggestionNotice).get(0); 170 171 that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass); 172 173 container = $(that.suggestionsContainer); 174 175 container.appendTo(options.appendTo); 176 177 // Only set width if it was provided: 178 if (options.width !== 'auto') { 179 container.width(options.width); 180 } 181 182 // Listen for mouse over event on suggestions list: 183 container.on('mouseover.autocomplete', suggestionSelector, function () { 184 that.activate($(this).data('index')); 185 }); 186 187 // Deselect active element when mouse leaves suggestions container: 188 container.on('mouseout.autocomplete', function () { 189 that.selectedIndex = -1; 190 container.children('.' + selected).removeClass(selected); 191 }); 192 193 // Listen for click event on suggestions list: 194 container.on('click.autocomplete', suggestionSelector, function () { 195 that.select($(this).data('index')); 196 return false; 197 }); 198 199 that.fixPositionCapture = function () { 200 if (that.visible) { 201 that.fixPosition(); 202 } 203 }; 204 205 $(window).on('resize.autocomplete', that.fixPositionCapture); 206 207 that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); }); 208 that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); }); 209 that.el.on('blur.autocomplete', function () { that.onBlur(); }); 210 that.el.on('focus.autocomplete', function () { that.onFocus(); }); 211 that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); }); 212 that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); }); 213 }, 214 215 onFocus: function () { 216 var that = this; 217 218 that.fixPosition(); 219 220 if (that.el.val().length >= that.options.minChars) { 221 that.onValueChange(); 222 } 223 }, 224 225 onBlur: function () { 226 this.enableKillerFn(); 227 }, 228 229 abortAjax: function () { 230 var that = this; 231 if (that.currentRequest) { 232 that.currentRequest.abort(); 233 that.currentRequest = null; 234 } 235 }, 236 237 setOptions: function (suppliedOptions) { 238 var that = this, 239 options = that.options; 240 241 $.extend(options, suppliedOptions); 242 243 that.isLocal = $.isArray(options.lookup); 244 245 if (that.isLocal) { 246 options.lookup = that.verifySuggestionsFormat(options.lookup); 247 } 248 249 options.orientation = that.validateOrientation(options.orientation, 'bottom'); 250 251 // Adjust height, width and z-index: 252 $(that.suggestionsContainer).css({ 253 'max-height': options.maxHeight + 'px', 254 'width': options.width + 'px', 255 'z-index': options.zIndex 256 }); 257 }, 258 259 260 clearCache: function () { 261 this.cachedResponse = {}; 262 this.badQueries = []; 263 }, 264 265 clear: function () { 266 this.clearCache(); 267 this.currentValue = ''; 268 this.suggestions = []; 269 }, 270 271 disable: function () { 272 var that = this; 273 that.disabled = true; 274 clearInterval(that.onChangeInterval); 275 that.abortAjax(); 276 }, 277 278 enable: function () { 279 this.disabled = false; 280 }, 281 282 fixPosition: function () { 283 // Use only when container has already its content 284 285 var that = this, 286 $container = $(that.suggestionsContainer), 287 containerParent = $container.parent().get(0); 288 // Fix position automatically when appended to body. 289 // In other cases force parameter must be given. 290 if (containerParent !== document.body && !that.options.forceFixPosition) { 291 return; 292 } 293 294 // Choose orientation 295 var orientation = that.options.orientation, 296 containerHeight = $container.outerHeight(), 297 height = that.el.outerHeight(), 298 offset = that.el.offset(), 299 styles = { 'top': offset.top, 'left': offset.left }; 300 301 if (orientation === 'auto') { 302 var viewPortHeight = $(window).height(), 303 scrollTop = $(window).scrollTop(), 304 topOverflow = -scrollTop + offset.top - containerHeight, 305 bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight); 306 307 orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow) ? 'top' : 'bottom'; 308 } 309 310 if (orientation === 'top') { 311 styles.top += -containerHeight; 312 } else { 313 styles.top += height; 314 } 315 316 // If container is not positioned to body, 317 // correct its position using offset parent offset 318 if(containerParent !== document.body) { 319 var opacity = $container.css('opacity'), 320 parentOffsetDiff; 321 322 if (!that.visible){ 323 $container.css('opacity', 0).show(); 324 } 325 326 parentOffsetDiff = $container.offsetParent().offset(); 327 styles.top -= parentOffsetDiff.top; 328 styles.left -= parentOffsetDiff.left; 329 330 if (!that.visible){ 331 $container.css('opacity', opacity).hide(); 332 } 333 } 334 335 // -2px to account for suggestions border. 336 if (that.options.width === 'auto') { 337 styles.width = (that.el.outerWidth() - 2) + 'px'; 338 } 339 340 $container.css(styles); 341 }, 342 343 enableKillerFn: function () { 344 var that = this; 345 $(document).on('click.autocomplete', that.killerFn); 346 }, 347 348 disableKillerFn: function () { 349 var that = this; 350 $(document).off('click.autocomplete', that.killerFn); 351 }, 352 353 killSuggestions: function () { 354 var that = this; 355 that.stopKillSuggestions(); 356 that.intervalId = window.setInterval(function () { 357 if (that.visible) { 358 that.el.val(that.currentValue); 359 that.hide(); 360 } 361 362 that.stopKillSuggestions(); 363 }, 50); 364 }, 365 366 stopKillSuggestions: function () { 367 window.clearInterval(this.intervalId); 368 }, 369 370 isCursorAtEnd: function () { 371 var that = this, 372 valLength = that.el.val().length, 373 selectionStart = that.element.selectionStart, 374 range; 375 376 if (typeof selectionStart === 'number') { 377 return selectionStart === valLength; 378 } 379 if (document.selection) { 380 range = document.selection.createRange(); 381 range.moveStart('character', -valLength); 382 return valLength === range.text.length; 383 } 384 return true; 385 }, 386 387 onKeyPress: function (e) { 388 var that = this; 389 390 // If suggestions are hidden and user presses arrow down, display suggestions: 391 if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) { 392 that.suggest(); 393 return; 394 } 395 396 if (that.disabled || !that.visible) { 397 return; 398 } 399 400 switch (e.which) { 401 case keys.ESC: 402 that.el.val(that.currentValue); 403 that.hide(); 404 break; 405 case keys.RIGHT: 406 if (that.hint && that.options.onHint && that.isCursorAtEnd()) { 407 that.selectHint(); 408 break; 409 } 410 return; 411 case keys.TAB: 412 if (that.hint && that.options.onHint) { 413 that.selectHint(); 414 return; 415 } 416 if (that.selectedIndex === -1) { 417 that.hide(); 418 return; 419 } 420 that.select(that.selectedIndex); 421 if (that.options.tabDisabled === false) { 422 return; 423 } 424 break; 425 case keys.RETURN: 426 if (that.selectedIndex === -1) { 427 that.hide(); 428 return; 429 } 430 that.select(that.selectedIndex); 431 break; 432 case keys.UP: 433 that.moveUp(); 434 break; 435 case keys.DOWN: 436 that.moveDown(); 437 break; 438 default: 439 return; 440 } 441 442 // Cancel event if function did not return: 443 e.stopImmediatePropagation(); 444 e.preventDefault(); 445 }, 446 447 onKeyUp: function (e) { 448 var that = this; 449 450 if (that.disabled) { 451 return; 452 } 453 454 switch (e.which) { 455 case keys.UP: 456 case keys.DOWN: 457 return; 458 } 459 460 clearInterval(that.onChangeInterval); 461 462 if (that.currentValue !== that.el.val()) { 463 that.findBestHint(); 464 if (that.options.deferRequestBy > 0) { 465 // Defer lookup in case when value changes very quickly: 466 that.onChangeInterval = setInterval(function () { 467 that.onValueChange(); 468 }, that.options.deferRequestBy); 469 } else { 470 that.onValueChange(); 471 } 472 } 473 }, 474 475 onValueChange: function () { 476 var that = this, 477 options = that.options, 478 value = that.el.val(), 479 query = that.getQuery(value); 480 481 if (that.selection && that.currentValue !== query) { 482 that.selection = null; 483 (options.onInvalidateSelection || $.noop).call(that.element); 484 } 485 486 clearInterval(that.onChangeInterval); 487 that.currentValue = value; 488 that.selectedIndex = -1; 489 490 // Check existing suggestion for the match before proceeding: 491 if (options.triggerSelectOnValidInput && that.isExactMatch(query)) { 492 that.select(0); 493 return; 494 } 495 496 if (query.length < options.minChars) { 497 that.hide(); 498 } else { 499 that.getSuggestions(query); 500 } 501 }, 502 503 isExactMatch: function (query) { 504 var suggestions = this.suggestions; 505 506 return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase()); 507 }, 508 509 getQuery: function (value) { 510 var delimiter = this.options.delimiter, 511 parts; 512 513 if (!delimiter) { 514 return value; 515 } 516 parts = value.split(delimiter); 517 return $.trim(parts[parts.length - 1]); 518 }, 519 520 getSuggestionsLocal: function (query) { 521 var that = this, 522 options = that.options, 523 queryLowerCase = query.toLowerCase(), 524 filter = options.lookupFilter, 525 limit = parseInt(options.lookupLimit, 10), 526 data; 527 528 data = { 529 suggestions: $.grep(options.lookup, function (suggestion) { 530 return filter(suggestion, query, queryLowerCase); 531 }) 532 }; 533 534 if (limit && data.suggestions.length > limit) { 535 data.suggestions = data.suggestions.slice(0, limit); 536 } 537 538 return data; 539 }, 540 541 getSuggestions: function (q) { 542 var response, 543 that = this, 544 options = that.options, 545 serviceUrl = options.serviceUrl, 546 params, 547 cacheKey, 548 ajaxSettings; 549 550 options.params[options.paramName] = q; 551 params = options.ignoreParams ? null : options.params; 552 553 if (options.onSearchStart.call(that.element, options.params) === false) { 554 return; 555 } 556 557 if ($.isFunction(options.lookup)){ 558 options.lookup(q, function (data) { 559 that.suggestions = data.suggestions; 560 that.suggest(); 561 options.onSearchComplete.call(that.element, q, data.suggestions); 562 }); 563 return; 564 } 565 566 if (that.isLocal) { 567 response = that.getSuggestionsLocal(q); 568 } else { 569 if ($.isFunction(serviceUrl)) { 570 serviceUrl = serviceUrl.call(that.element, q); 571 } 572 cacheKey = serviceUrl + '?' + $.param(params || {}); 573 response = that.cachedResponse[cacheKey]; 574 } 575 576 if (response && $.isArray(response.suggestions)) { 577 that.suggestions = response.suggestions; 578 that.suggest(); 579 options.onSearchComplete.call(that.element, q, response.suggestions); 580 } else if (!that.isBadQuery(q)) { 581 that.abortAjax(); 582 583 ajaxSettings = { 584 url: serviceUrl, 585 data: params, 586 type: options.type, 587 dataType: options.dataType 588 }; 589 590 $.extend(ajaxSettings, options.ajaxSettings); 591 592 that.currentRequest = $.ajax(ajaxSettings).done(function (data) { 593 var result; 594 that.currentRequest = null; 595 result = options.transformResult(data, q); 596 that.processResponse(result, q, cacheKey); 597 options.onSearchComplete.call(that.element, q, result.suggestions); 598 }).fail(function (jqXHR, textStatus, errorThrown) { 599 options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown); 600 }); 601 } else { 602 options.onSearchComplete.call(that.element, q, []); 603 } 604 }, 605 606 isBadQuery: function (q) { 607 if (!this.options.preventBadQueries){ 608 return false;
609 } 610 611 var badQueries = this.badQueries, 612 i = badQueries.length; 613 614 while (i--) { 615 if (q.indexOf(badQueries[i]) === 0) { 616 return true; 617 } 618 } 619 620 return false; 621 }, 622 623 hide: function () { 624 var that = this, 625 container = $(that.suggestionsContainer); 626 627 if ($.isFunction(that.options.onHide) && that.visible) { 628 that.options.onHide.call(that.element, container); 629 } 630 631 that.visible = false; 632 that.selectedIndex = -1; 633 clearInterval(that.onChangeInterval); 634 $(that.suggestionsContainer).hide(); 635 that.signalHint(null); 636 }, 637 638 suggest: function () { 639 if (!this.suggestions.length) { 640 if (this.options.showNoSuggestionNotice) { 641 this.noSuggestions(); 642 } else { 643 this.hide(); 644 } 645 return; 646 } 647 648 var that = this, 649 options = that.options, 650 groupBy = options.groupBy, 651 formatResult = options.formatResult, 652 value = that.getQuery(that.currentValue), 653 className = that.classes.suggestion, 654 classSelected = that.classes.selected, 655 container = $(that.suggestionsContainer), 656 noSuggestionsContainer = $(that.noSuggestionsContainer), 657 beforeRender = options.beforeRender, 658 html = '', 659 category, 660 formatGroup = function (suggestion, index) { 661 var currentCategory = suggestion.data[groupBy]; 662 663 if (category === currentCategory){ 664 return ''; 665 } 666 667 category = currentCategory; 668 669 return '<div class="autocomplete-group"><strong>' + category + '</strong></div>'; 670 }; 671 672 if (options.triggerSelectOnValidInput && that.isExactMatch(value)) { 673 that.select(0); 674 return; 675 } 676 677 // Build suggestions inner HTML: 678 $.each(that.suggestions, function (i, suggestion) { 679 if (groupBy){ 680 html += formatGroup(suggestion, value, i); 681 } 682 683 html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value, i) + '</div>'; 684 }); 685 686 this.adjustContainerWidth(); 687 688 noSuggestionsContainer.detach(); 689 container.html(html); 690 691 if ($.isFunction(beforeRender)) { 692 beforeRender.call(that.element, container, that.suggestions); 693 } 694 695 that.fixPosition(); 696 container.show(); 697 698 // Select first value by default: 699 if (options.autoSelectFirst) { 700 that.selectedIndex = 0; 701 container.scrollTop(0); 702 container.children('.' + className).first().addClass(classSelected); 703 } 704 705 that.visible = true; 706 that.findBestHint(); 707 }, 708 709 noSuggestions: function() { 710 var that = this, 711 container = $(that.suggestionsContainer), 712 noSuggestionsContainer = $(that.noSuggestionsContainer); 713 714 this.adjustContainerWidth(); 715 716 // Some explicit steps. Be careful here as it easy to get 717 // noSuggestionsContainer removed from DOM if not detached properly. 718 noSuggestionsContainer.detach(); 719 container.empty(); // clean suggestions if any 720 container.append(noSuggestionsContainer); 721 722 that.fixPosition(); 723 724 container.show(); 725 that.visible = true; 726 }, 727 728 adjustContainerWidth: function() { 729 var that = this, 730 options = that.options, 731 width, 732 container = $(that.suggestionsContainer); 733
734 // If width is auto, adjust width before displaying suggestions, 735 // because if instance was created before input had width, it will be zero. 736 // Also it adjusts if input width has changed. 737 // -2px to account for suggestions border. 738 if (options.width === 'auto') { 739 width = that.el.outerWidth() - 2; 740 container.width(width > 0 ? width : 300); 741 } 742 }, 743 744 findBestHint: function () { 745 var that = this, 746 value = that.el.val().toLowerCase(), 747 bestMatch = null; 748 749 if (!value) { 750 return; 751 } 752 753 $.each(that.suggestions, function (i, suggestion) { 754 var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0; 755 if (foundMatch) { 756 bestMatch = suggestion; 757 } 758 return !foundMatch; 759 }); 760 761 that.signalHint(bestMatch); 762 }, 763 764 signalHint: function (suggestion) { 765 var hintValue = '', 766 that = this; 767 if (suggestion) { 768 hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length); 769 } 770 if (that.hintValue !== hintValue) { 771 that.hintValue = hintValue; 772 that.hint = suggestion; 773 (this.options.onHint || $.noop)(hintValue); 774 } 775 }, 776 777 verifySuggestionsFormat: function (suggestions) { 778 // If suggestions is string array, convert them to supported format: 779 if (suggestions.length && typeof suggestions[0] === 'string') { 780 return $.map(suggestions, function (value) { 781 return { value: value, data: null }; 782 }); 783 } 784 785 return suggestions; 786 }, 787 788 validateOrientation: function(orientation, fallback) { 789 orientation = $.trim(orientation || '').toLowerCase(); 790 791 if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){ 792 orientation = fallback; 793 } 794 795 return orientation; 796 }, 797 798 processResponse: function (result, originalQuery, cacheKey) { 799 var that = this, 800 options = that.options; 801 802 result.suggestions = that.verifySuggestionsFormat(result.suggestions); 803 804 // Cache results if cache is not disabled: 805 if (!options.noCache) { 806 that.cachedResponse[cacheKey] = result; 807 if (options.preventBadQueries && !result.suggestions.length) { 808 that.badQueries.push(originalQuery); 809 } 810 } 811 812 // Return if originalQuery is not matching current query: 813 if (originalQuery !== that.getQuery(that.currentValue)) { 814 return; 815 } 816 817 that.suggestions = result.suggestions; 818 that.suggest(); 819 }, 820 821 activate: function (index) { 822 var that = this, 823 activeItem, 824 selected = that.classes.selected, 825 container = $(that.suggestionsContainer), 826 children = container.find('.' + that.classes.suggestion); 827 828 container.find('.' + selected).removeClass(selected); 829 830 that.selectedIndex = index; 831 832 if (that.selectedIndex !== -1 && children.length > that.selectedIndex) { 833 activeItem = children.get(that.selectedIndex); 834 $(activeItem).addClass(selected); 835 return activeItem; 836 } 837 838 return null; 839 }, 840 841 selectHint: function () { 842 var that = this, 843 i = $.inArray(that.hint, that.suggestions); 844 845 that.select(i); 846 }, 847 848 select: function (i) { 849 var that = this; 850 that.hide(); 851 that.onSelect(i); 852 }, 853 854 moveUp: function () { 855 var that = this; 856 857 if (that.selectedIndex === -1) { 858 return; 859 } 860 861 if (that.selectedIndex === 0) { 862 $(that.suggestionsContainer).children().first().removeClass(that.classes.selected); 863 that.selectedIndex = -1; 864 that.el.val(that.currentValue); 865 that.findBestHint(); 866 return; 867 } 868 869 that.adjustScroll(that.selectedIndex - 1); 870 }, 871 872 moveDown: function () { 873 var that = this; 874 875 if (that.selectedIndex === (that.suggestions.length - 1)) { 876 return; 877 } 878 879 that.adjustScroll(that.selectedIndex + 1); 880 }, 881 882 adjustScroll: function (index) { 883 var that = this, 884 activeItem = that.activate(index); 885 886 if (!activeItem) { 887 return; 888 } 889 890 var offsetTop, 891 upperBound, 892 lowerBound, 893 heightDelta = $(activeItem).outerHeight(); 894 895 offsetTop = activeItem.offsetTop; 896 upperBound = $(that.suggestionsContainer).scrollTop(); 897 lowerBound = upperBound + that.options.maxHeight - heightDelta; 898 899 if (offsetTop < upperBound) { 900 $(that.suggestionsContainer).scrollTop(offsetTop); 901 } else if (offsetTop > lowerBound) { 902 $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta); 903 } 904 905 if (!that.options.preserveInput) { 906 that.el.val(that.getValue(that.suggestions[index].value)); 907 } 908 that.signalHint(null); 909 }, 910 911 onSelect: function (index) { 912 var that = this, 913 onSelectCallback = that.options.onSelect, 914 suggestion = that.suggestions[index]; 915 916 that.currentValue = that.getValue(suggestion.value); 917 918 if (that.currentValue !== that.el.val() && !that.options.preserveInput) { 919 that.el.val(that.currentValue); 920 } 921 922 that.signalHint(null); 923 that.suggestions = []; 924 that.selection = suggestion; 925 926 if ($.isFunction(onSelectCallback)) { 927 onSelectCallback.call(that.element, suggestion); 928 } 929 }, 930 931 getValue: function (value) { 932 var that = this, 933 delimiter = that.options.delimiter, 934 currentValue, 935 parts; 936 937 if (!delimiter) { 938 return value; 939 } 940 941 currentValue = that.currentValue; 942 parts = currentValue.split(delimiter); 943 944 if (parts.length === 1) { 945 return value; 946 } 947 948 return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value; 949 }, 950 951 dispose: function () { 952 var that = this; 953 that.el.off('.autocomplete').removeData('autocomplete'); 954 that.disableKillerFn(); 955 $(window).off('resize.autocomplete', that.fixPositionCapture); 956 $(that.suggestionsContainer).remove(); 957 } 958 }; 959 960 // Create chainable jQuery plugin: 961 $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) { 962 var dataKey = 'autocomplete'; 963 // If function invoked without argument return 964 // instance of the first matched element: 965 if (!arguments.length) { 966 return this.first().data(dataKey); 967 } 968 969 return this.each(function () { 970 var inputElement = $(this), 971 instance = inputElement.data(dataKey); 972 973 if (typeof options === 'string') { 974 if (instance && typeof instance[options] === 'function') { 975 instance[options](args); 976 } 977 } else { 978 // If instance already exists, destroy it: 979 if (instance && instance.dispose) { 980 instance.dispose(); 981 } 982 instance = new Autocomplete(this, options); 983 inputElement.data(dataKey, instance); 984 } 985 }); 986 }; 987}));
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.