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