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