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