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