├── .gitignore ├── content └── styles.css ├── devbridge-autocomplete.jquery.json ├── dist ├── jquery.autocomplete.js ├── jquery.autocomplete.min.js └── license.txt ├── gruntfile.js ├── index.htm ├── license.txt ├── package-lock.json ├── package.json ├── readme.md ├── scripts ├── countries.js └── demo.js ├── spec ├── autocompleteBehavior.js ├── lib │ ├── jasmine-1.3.1 │ │ ├── MIT.LICENSE │ │ ├── jasmine-html.js │ │ ├── jasmine.css │ │ └── jasmine.js │ └── jasmine-2.0.1 │ │ ├── boot.js │ │ ├── console.js │ │ ├── jasmine-html.js │ │ ├── jasmine.css │ │ ├── jasmine.js │ │ └── jasmine_favicon.png └── runner.html ├── src └── jquery.autocomplete.js └── typings ├── jquery.autocomplete-tests.ts └── jquery.autocomplete.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /content/styles.css: -------------------------------------------------------------------------------- 1 | body { font-family: sans-serif; font-size: 14px; line-height: 1.6em; margin: 0; padding: 0; } 2 | .container { width: 800px; margin: 0 auto; } 3 | 4 | .autocomplete-suggestions { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; border: 1px solid #999; background: #FFF; cursor: default; overflow: auto; -webkit-box-shadow: 1px 4px 3px rgba(50, 50, 50, 0.64); -moz-box-shadow: 1px 4px 3px rgba(50, 50, 50, 0.64); box-shadow: 1px 4px 3px rgba(50, 50, 50, 0.64); } 5 | .autocomplete-suggestion { padding: 2px 5px; white-space: nowrap; overflow: hidden; } 6 | .autocomplete-no-suggestion { padding: 2px 5px;} 7 | .autocomplete-selected { background: #F0F0F0; } 8 | .autocomplete-suggestions strong { font-weight: bold; color: #000; } 9 | .autocomplete-group { padding: 2px 5px; font-weight: bold; font-size: 16px; color: #000; display: block; border-bottom: 1px solid #000; } 10 | 11 | input { font-size: 28px; padding: 10px; border: 1px solid #CCC; display: block; margin: 20px 0; } 12 | -------------------------------------------------------------------------------- /devbridge-autocomplete.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devbridge-autocomplete", 3 | "title": "DevBridge Autocomplete", 4 | "description": "Autocomplete provides suggestions while you type into the text field. ", 5 | "keywords": [ 6 | "ajax", 7 | "autocomplete" 8 | ], 9 | "version": "1.4.11", 10 | "author": { 11 | "name": "Tomas Kirda", 12 | "url": "https://github.com/tkirda" 13 | }, 14 | "licenses": [ 15 | { 16 | "type": "MIT", 17 | "url": "https://github.com/devbridge/jQuery-Autocomplete/blob/master/license.txt" 18 | } 19 | ], 20 | "bugs": "https://github.com/devbridge/jQuery-Autocomplete/issues?state=open", 21 | "homepage": "https://github.com/devbridge/jQuery-Autocomplete", 22 | "docs": "https://github.com/devbridge/jQuery-Autocomplete", 23 | "demo": "http://www.devbridge.com/sourcery/components/jquery-autocomplete/", 24 | "download": "https://github.com/devbridge/jQuery-Autocomplete/tree/master/dist", 25 | "dependencies": { 26 | "jquery": ">=1.7" 27 | } 28 | } -------------------------------------------------------------------------------- /dist/jquery.autocomplete.js: -------------------------------------------------------------------------------- 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) : 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'), '$1<\/strong>') 147 | .replace(/&/g, '&') 148 | .replace(//g, '>') 150 | .replace(/"/g, '"') 151 | .replace(/<(\/?strong)>/g, '<$1>'); 152 | }; 153 | 154 | function _formatGroup(suggestion, category) { 155 | return '
' + category + '
'; 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 = $('
') 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 += '
' + formatResult(suggestion, value, i) + '
'; 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 supported 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 chainable 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 | })); 1014 | -------------------------------------------------------------------------------- /dist/jquery.autocomplete.min.js: -------------------------------------------------------------------------------- 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 | !function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof exports&&"function"==typeof require?t(require("jquery")):t(jQuery)}(function(t){"use strict";var e={escapeRegExChars:function(t){return t.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&")},createNode:function(t){var e=document.createElement("div");return e.className=t,e.style.position="absolute",e.style.display="none",e}},s=27,i=9,n=13,o=38,a=39,u=40,l=t.noop;function r(e,s){this.element=e,this.el=t(e),this.suggestions=[],this.badQueries=[],this.selectedIndex=-1,this.currentValue=this.element.value,this.timeoutId=null,this.cachedResponse={},this.onChangeTimeout=null,this.onChange=null,this.isLocal=!1,this.suggestionsContainer=null,this.noSuggestionsContainer=null,this.options=t.extend(!0,{},r.defaults,s),this.classes={selected:"autocomplete-selected",suggestion:"autocomplete-suggestion"},this.hint=null,this.hintValue="",this.selection=null,this.initialize(),this.setOptions(s)}r.utils=e,t.Autocomplete=r,r.defaults={ajaxSettings:{},autoSelectFirst:!1,appendTo:"body",serviceUrl:null,lookup:null,onSelect:null,onHint:null,width:"auto",minChars:1,maxHeight:300,deferRequestBy:0,params:{},formatResult:function(t,s){if(!s)return t.value;var i="("+e.escapeRegExChars(s)+")";return t.value.replace(new RegExp(i,"gi"),"$1").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/<(\/?strong)>/g,"<$1>")},formatGroup:function(t,e){return'
'+e+"
"},delimiter:null,zIndex:9999,type:"GET",noCache:!1,onSearchStart:l,onSearchComplete:l,onSearchError:l,preserveInput:!1,containerClass:"autocomplete-suggestions",tabDisabled:!1,dataType:"text",currentRequest:null,triggerSelectOnValidInput:!0,preventBadQueries:!0,lookupFilter:function(t,e,s){return-1!==t.value.toLowerCase().indexOf(s)},paramName:"query",transformResult:function(e){return"string"==typeof e?t.parseJSON(e):e},showNoSuggestionNotice:!1,noSuggestionNotice:"No results",orientation:"bottom",forceFixPosition:!1},r.prototype={initialize:function(){var e,s=this,i="."+s.classes.suggestion,n=s.classes.selected,o=s.options;s.element.setAttribute("autocomplete","off"),s.noSuggestionsContainer=t('
').html(this.options.noSuggestionNotice).get(0),s.suggestionsContainer=r.utils.createNode(o.containerClass),(e=t(s.suggestionsContainer)).appendTo(o.appendTo||"body"),"auto"!==o.width&&e.css("width",o.width),e.on("mouseover.autocomplete",i,function(){s.activate(t(this).data("index"))}),e.on("mouseout.autocomplete",function(){s.selectedIndex=-1,e.children("."+n).removeClass(n)}),e.on("click.autocomplete",i,function(){s.select(t(this).data("index"))}),e.on("click.autocomplete",function(){clearTimeout(s.blurTimeoutId)}),s.fixPositionCapture=function(){s.visible&&s.fixPosition()},t(window).on("resize.autocomplete",s.fixPositionCapture),s.el.on("keydown.autocomplete",function(t){s.onKeyPress(t)}),s.el.on("keyup.autocomplete",function(t){s.onKeyUp(t)}),s.el.on("blur.autocomplete",function(){s.onBlur()}),s.el.on("focus.autocomplete",function(){s.onFocus()}),s.el.on("change.autocomplete",function(t){s.onKeyUp(t)}),s.el.on("input.autocomplete",function(t){s.onKeyUp(t)})},onFocus:function(){this.disabled||(this.fixPosition(),this.el.val().length>=this.options.minChars&&this.onValueChange())},onBlur:function(){var e=this,s=e.options,i=e.el.val(),n=e.getQuery(i);e.blurTimeoutId=setTimeout(function(){e.hide(),e.selection&&e.currentValue!==n&&(s.onInvalidateSelection||t.noop).call(e.element)},200)},abortAjax:function(){this.currentRequest&&(this.currentRequest.abort(),this.currentRequest=null)},setOptions:function(e){var s=t.extend({},this.options,e);this.isLocal=Array.isArray(s.lookup),this.isLocal&&(s.lookup=this.verifySuggestionsFormat(s.lookup)),s.orientation=this.validateOrientation(s.orientation,"bottom"),t(this.suggestionsContainer).css({"max-height":s.maxHeight+"px",width:s.width+"px","z-index":s.zIndex}),this.options=s},clearCache:function(){this.cachedResponse={},this.badQueries=[]},clear:function(){this.clearCache(),this.currentValue="",this.suggestions=[]},disable:function(){this.disabled=!0,clearTimeout(this.onChangeTimeout),this.abortAjax()},enable:function(){this.disabled=!1},fixPosition:function(){var e=t(this.suggestionsContainer),s=e.parent().get(0);if(s===document.body||this.options.forceFixPosition){var i=this.options.orientation,n=e.outerHeight(),o=this.el.outerHeight(),a=this.el.offset(),u={top:a.top,left:a.left};if("auto"===i){var l=t(window).height(),r=t(window).scrollTop(),h=-r+a.top-n,c=r+l-(a.top+o+n);i=Math.max(h,c)===h?"top":"bottom"}if(u.top+="top"===i?-n:o,s!==document.body){var g,d=e.css("opacity");this.visible||e.css("opacity",0).show(),g=e.offsetParent().offset(),u.top-=g.top,u.top+=s.scrollTop,u.left-=g.left,this.visible||e.css("opacity",d).hide()}"auto"===this.options.width&&(u.width=this.el.outerWidth()+"px"),e.css(u)}},isCursorAtEnd:function(){var t,e=this.el.val().length,s=this.element.selectionStart;return"number"==typeof s?s===e:!document.selection||((t=document.selection.createRange()).moveStart("character",-e),e===t.text.length)},onKeyPress:function(t){if(this.disabled||this.visible||t.which!==u||!this.currentValue){if(!this.disabled&&this.visible){switch(t.which){case s:this.el.val(this.currentValue),this.hide();break;case a:if(this.hint&&this.options.onHint&&this.isCursorAtEnd()){this.selectHint();break}return;case i:if(this.hint&&this.options.onHint)return void this.selectHint();if(-1===this.selectedIndex)return void this.hide();if(this.select(this.selectedIndex),!1===this.options.tabDisabled)return;break;case n:if(-1===this.selectedIndex)return void this.hide();this.select(this.selectedIndex);break;case o:this.moveUp();break;case u:this.moveDown();break;default:return}t.stopImmediatePropagation(),t.preventDefault()}}else this.suggest()},onKeyUp:function(t){var e=this;if(!e.disabled){switch(t.which){case o:case u:return}clearTimeout(e.onChangeTimeout),e.currentValue!==e.el.val()&&(e.findBestHint(),e.options.deferRequestBy>0?e.onChangeTimeout=setTimeout(function(){e.onValueChange()},e.options.deferRequestBy):e.onValueChange())}},onValueChange:function(){if(this.ignoreValueChange)this.ignoreValueChange=!1;else{var e=this.options,s=this.el.val(),i=this.getQuery(s);this.selection&&this.currentValue!==i&&(this.selection=null,(e.onInvalidateSelection||t.noop).call(this.element)),clearTimeout(this.onChangeTimeout),this.currentValue=s,this.selectedIndex=-1,e.triggerSelectOnValidInput&&this.isExactMatch(i)?this.select(0):i.lengtha&&(s.suggestions=s.suggestions.slice(0,a)),s},getSuggestions:function(e){var s,i,n,o,a=this,u=a.options,l=u.serviceUrl;u.params[u.paramName]=e,!1!==u.onSearchStart.call(a.element,u.params)&&(i=u.ignoreParams?null:u.params,t.isFunction(u.lookup)?u.lookup(e,function(t){a.suggestions=t.suggestions,a.suggest(),u.onSearchComplete.call(a.element,e,t.suggestions)}):(a.isLocal?s=a.getSuggestionsLocal(e):(t.isFunction(l)&&(l=l.call(a.element,e)),n=l+"?"+t.param(i||{}),s=a.cachedResponse[n]),s&&Array.isArray(s.suggestions)?(a.suggestions=s.suggestions,a.suggest(),u.onSearchComplete.call(a.element,e,s.suggestions)):a.isBadQuery(e)?u.onSearchComplete.call(a.element,e,[]):(a.abortAjax(),o={url:l,data:i,type:u.type,dataType:u.dataType},t.extend(o,u.ajaxSettings),a.currentRequest=t.ajax(o).done(function(t){var s;a.currentRequest=null,s=u.transformResult(t,e),a.processResponse(s,e,n),u.onSearchComplete.call(a.element,e,s.suggestions)}).fail(function(t,s,i){u.onSearchError.call(a.element,e,t,s,i)}))))},isBadQuery:function(t){if(!this.options.preventBadQueries)return!1;for(var e=this.badQueries,s=e.length;s--;)if(0===t.indexOf(e[s]))return!0;return!1},hide:function(){var e=t(this.suggestionsContainer);t.isFunction(this.options.onHide)&&this.visible&&this.options.onHide.call(this.element,e),this.visible=!1,this.selectedIndex=-1,clearTimeout(this.onChangeTimeout),t(this.suggestionsContainer).hide(),this.onHint(null)},suggest:function(){if(this.suggestions.length){var e,s=this.options,i=s.groupBy,n=s.formatResult,o=this.getQuery(this.currentValue),a=this.classes.suggestion,u=this.classes.selected,l=t(this.suggestionsContainer),r=t(this.noSuggestionsContainer),h=s.beforeRender,c="";s.triggerSelectOnValidInput&&this.isExactMatch(o)?this.select(0):(t.each(this.suggestions,function(t,u){i&&(c+=function(t,n){var o=t.data[i];return e===o?"":(e=o,s.formatGroup(t,e))}(u,0)),c+='
'+n(u,o,t)+"
"}),this.adjustContainerWidth(),r.detach(),l.html(c),t.isFunction(h)&&h.call(this.element,l,this.suggestions),this.fixPosition(),l.show(),s.autoSelectFirst&&(this.selectedIndex=0,l.scrollTop(0),l.children("."+a).first().addClass(u)),this.visible=!0,this.findBestHint())}else this.options.showNoSuggestionNotice?this.noSuggestions():this.hide()},noSuggestions:function(){var e=this.options.beforeRender,s=t(this.suggestionsContainer),i=t(this.noSuggestionsContainer);this.adjustContainerWidth(),i.detach(),s.empty(),s.append(i),t.isFunction(e)&&e.call(this.element,s,this.suggestions),this.fixPosition(),s.show(),this.visible=!0},adjustContainerWidth:function(){var e,s=this.options,i=t(this.suggestionsContainer);"auto"===s.width?(e=this.el.outerWidth(),i.css("width",e>0?e:300)):"flex"===s.width&&i.css("width","")},findBestHint:function(){var e=this.el.val().toLowerCase(),s=null;e&&(t.each(this.suggestions,function(t,i){var n=0===i.value.toLowerCase().indexOf(e);return n&&(s=i),!n}),this.onHint(s))},onHint:function(e){var s=this.options.onHint,i="";e&&(i=this.currentValue+e.value.substr(this.currentValue.length)),this.hintValue!==i&&(this.hintValue=i,this.hint=e,t.isFunction(s)&&s.call(this.element,i))},verifySuggestionsFormat:function(e){return e.length&&"string"==typeof e[0]?t.map(e,function(t){return{value:t,data:null}}):e},validateOrientation:function(e,s){return e=t.trim(e||"").toLowerCase(),-1===t.inArray(e,["auto","bottom","top"])&&(e=s),e},processResponse:function(t,e,s){var i=this.options;t.suggestions=this.verifySuggestionsFormat(t.suggestions),i.noCache||(this.cachedResponse[s]=t,i.preventBadQueries&&!t.suggestions.length&&this.badQueries.push(e)),e===this.getQuery(this.currentValue)&&(this.suggestions=t.suggestions,this.suggest())},activate:function(e){var s,i=this.classes.selected,n=t(this.suggestionsContainer),o=n.find("."+this.classes.suggestion);return n.find("."+i).removeClass(i),this.selectedIndex=e,-1!==this.selectedIndex&&o.length>this.selectedIndex?(s=o.get(this.selectedIndex),t(s).addClass(i),s):null},selectHint:function(){var e=t.inArray(this.hint,this.suggestions);this.select(e)},select:function(t){this.hide(),this.onSelect(t)},moveUp:function(){if(-1!==this.selectedIndex)return 0===this.selectedIndex?(t(this.suggestionsContainer).children("."+this.classes.suggestion).first().removeClass(this.classes.selected),this.selectedIndex=-1,this.ignoreValueChange=!1,this.el.val(this.currentValue),void this.findBestHint()):void this.adjustScroll(this.selectedIndex-1)},moveDown:function(){this.selectedIndex!==this.suggestions.length-1&&this.adjustScroll(this.selectedIndex+1)},adjustScroll:function(e){var s=this.activate(e);if(s){var i,n,o,a=t(s).outerHeight();i=s.offsetTop,o=(n=t(this.suggestionsContainer).scrollTop())+this.options.maxHeight-a,io&&t(this.suggestionsContainer).scrollTop(i-this.options.maxHeight+a),this.options.preserveInput||(this.ignoreValueChange=!0,this.el.val(this.getValue(this.suggestions[e].value))),this.onHint(null)}},onSelect:function(e){var s=this.options.onSelect,i=this.suggestions[e];this.currentValue=this.getValue(i.value),this.currentValue===this.el.val()||this.options.preserveInput||this.el.val(this.currentValue),this.onHint(null),this.suggestions=[],this.selection=i,t.isFunction(s)&&s.call(this.element,i)},getValue:function(t){var e,s,i=this.options.delimiter;return i?1===(s=(e=this.currentValue).split(i)).length?t:e.substr(0,e.length-s[s.length-1].length)+t:t},dispose:function(){this.el.off(".autocomplete").removeData("autocomplete"),t(window).off("resize.autocomplete",this.fixPositionCapture),t(this.suggestionsContainer).remove()}},t.fn.devbridgeAutocomplete=function(e,s){return arguments.length?this.each(function(){var i=t(this),n=i.data("autocomplete");"string"==typeof e?n&&"function"==typeof n[e]&&n[e](s):(n&&n.dispose&&n.dispose(),n=new r(this,e),i.data("autocomplete",n))}):this.first().data("autocomplete")},t.fn.autocomplete||(t.fn.autocomplete=t.fn.devbridgeAutocomplete)}); 9 | -------------------------------------------------------------------------------- /dist/license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 DevBridge and other contributors 2 | http://www.devbridge.com/projects/autocomplete/jquery/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | var pkg = grunt.file.readJSON('package.json'); 4 | 5 | var banner = [ 6 | '/**', 7 | '* Ajax Autocomplete for jQuery, version ' + pkg.version, 8 | '* (c) 2017 Tomas Kirda', 9 | '*', 10 | '* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.', 11 | '* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete', 12 | '*/'].join('\n') + '\n'; 13 | 14 | // Project configuration. 15 | grunt.initConfig({ 16 | pkg: pkg, 17 | uglify: { 18 | options: { 19 | banner: banner 20 | }, 21 | build: { 22 | src: 'src/jquery.autocomplete.js', 23 | dest: 'dist/jquery.autocomplete.min.js' 24 | } 25 | } 26 | }); 27 | 28 | // Load the plugin that provides the "uglify" task. 29 | grunt.loadNpmTasks('grunt-contrib-uglify'); 30 | 31 | // Default task(s). 32 | grunt.registerTask('default', ['uglify']); 33 | 34 | grunt.task.registerTask('build', 'Create release', function() { 35 | var version = pkg.version, 36 | src = grunt.file.read('src/jquery.autocomplete.js').replace('%version%', version), 37 | filePath = 'dist/jquery.autocomplete.js'; 38 | 39 | // Update not minimized release version: 40 | console.log('Updating: ' + filePath); 41 | grunt.file.write(filePath, src); 42 | 43 | // Minify latest version: 44 | grunt.task.run('uglify'); 45 | 46 | // Update plugin version: 47 | filePath = 'devbridge-autocomplete.jquery.json'; 48 | src = grunt.file.readJSON(filePath); 49 | 50 | if (src.version !== version){ 51 | src.version = version; 52 | console.log('Updating: ' + filePath); 53 | grunt.file.write(filePath, JSON.stringify(src, null, 4)); 54 | } else { 55 | console.log('No updates for: ' + filePath); 56 | } 57 | }); 58 | }; -------------------------------------------------------------------------------- /index.htm: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DevBridge Autocomplete Demo 5 | 6 | 7 | 8 |
9 |

Ajax Autocomplete Demo

10 | 11 |

Ajax Lookup

12 |

Type country name in english:

13 |
14 | 15 | 16 |
17 |
18 | 19 |

Local Lookup and Grouping

20 |

Type NHL or NBA team name:

21 |
22 | 23 |
24 |
25 | 26 |

Custom Lookup Container

27 |

Type country name in english:

28 |
29 | 30 |
31 |
32 |
33 | 34 |
35 |

Dynamic Width

36 |

Type country name in english:

37 |
38 | 39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 DevBridge and other contributors 2 | http://www.devbridge.com/projects/autocomplete/jquery/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devbridge-autocomplete", 3 | "version": "1.4.11", 4 | "description": "Autocomplete provides suggestions while you type into the text field.", 5 | "homepage": "https://github.com/devbridge/jQuery-Autocomplete", 6 | "author": "Tomas Kirda (https://twitter.com/tkirda)", 7 | "main": "dist/jquery.autocomplete.js", 8 | "types": "./typings/jquery.autocomplete.d.ts", 9 | "license": "MIT", 10 | "keywords": [ 11 | "jquery-plugin" 12 | ], 13 | "scripts": { 14 | "build": "grunt build", 15 | "format": "prettier --write ./src/**", 16 | "lint": "eslint ./src" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/devbridge/jQuery-Autocomplete.git" 21 | }, 22 | "peerDependencies": { 23 | "jquery": ">=1.9" 24 | }, 25 | "devDependencies": { 26 | "@types/jquery": "^3.5.22", 27 | "eslint": "^8.51.0", 28 | "grunt": "^1.6.1", 29 | "grunt-contrib-uglify": "^5.2.2", 30 | "prettier": "^3.0.3", 31 | "typescript": "^5.2.2" 32 | }, 33 | "prettier": { 34 | "printWidth": 100, 35 | "singleQuote": true, 36 | "trailingComma": "es5", 37 | "tabWidth": 4 38 | }, 39 | "eslintConfig": { 40 | "env": { 41 | "browser": true 42 | }, 43 | "extends": [ 44 | "eslint:recommended" 45 | ], 46 | "globals": { 47 | "define": "readonly", 48 | "jQuery": "readonly", 49 | "module": "readonly", 50 | "require": "readonly" 51 | } 52 | }, 53 | "files": [ 54 | "dist/", 55 | "typings/jquery-autocomplete/*.d.ts", 56 | "readme.md" 57 | ] 58 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Ajax Autocomplete for jQuery 2 | 3 | Ajax Autocomplete for jQuery allows you to easily create 4 | autocomplete/autosuggest boxes for text input fields. 5 | 6 | It has no dependencies other than jQuery. 7 | 8 | The standard jquery.autocomplete.js file is around 13KB when minified. 9 | 10 | ## API 11 | The following sets up autocomplete for input fields where `options` is an object literal that defines the settings to use for the autocomplete plugin. All available option settings are shown in the tables below. 12 | ```js 13 | $(selector).autocomplete(options); 14 | ``` 15 | ### General settings (local and Ajax) 16 | | Setting | Default | Description | 17 | | :--- | :--- | :--- | 18 | | `noCache` | `false` | Boolean value indicating whether to cache suggestion results | 19 | | `delimiter` | optional | String or RegExp, that splits input value and takes last part to as query for suggestions. Useful when for example you need to fill list of comma separated values. | 20 | | `minChars` | `1` | Minimum number of characters required to trigger autosuggest | 21 | | `triggerSelectOnValidInput` | `true` | Boolean value indicating if `select` should be triggered if it matches suggestion | 22 | | `preventBadQueries` | `true` | Boolean value indicating if it should prevent future Ajax requests for queries with the same root if no results were returned. E.g. if `Jam` returns no suggestions, it will not fire for any future query that starts with `Jam` | 23 | | `autoSelectFirst` | `false` | If set to `true`, first item will be selected when showing suggestions | 24 | | `beforeRender` | optional | `function (container, suggestions) {}` called before displaying the suggestions. You may manipulate suggestions DOM before it is displayed | 25 | | `formatResult` | optional | `function (suggestion, currentValue) {}` custom function to format suggestion entry inside suggestions container | 26 | | `formatGroup` | optional | `function (suggestion, category) {}` custom function to format group header | 27 | | `groupBy` | optional | property name of the suggestion `data` object, by which results should be grouped | 28 | | `maxHeight` | `300` | Maximum height of the suggestions container in pixels | 29 | | `width` | `auto` | Suggestions container width in pixels, e.g.: 300, `flex` for max suggestion size and `auto` takes input field width | 30 | | `zIndex` | `9999` | 'z-index' for suggestions container | 31 | | `appendTo` | optional | Container where suggestions will be appended. Default value `document.body`. Can be jQuery object, selector or HTML element. Make sure to set `position: absolute` or `position: relative` for that element | 32 | | `forceFixPosition` | `false` | Suggestions are automatically positioned when their container is appended to body (look at `appendTo` option), in other cases suggestions are rendered but no positioning is applied. Set this option to force auto positioning in other cases | 33 | | `orientation` | `bottom` | Vertical orientation of the displayed suggestions, available values are `auto`, `top`, `bottom`. If set to `auto`, the suggestions will be orientated it the way that place them closer to middle of the view port | 34 | | `preserveInput` | `false` | If `true`, input value stays the same when navigating over suggestions | 35 | | `showNoSuggestionNotice` | `false` | When no matching results, display a notification label | 36 | | `noSuggestionNotice` | `No results` | Text or htmlString or Element or jQuery object for no matching results label | 37 | | `onInvalidateSelection` | optional | `function () {}` called when input is altered after selection has been made. `this` is bound to input element | 38 | | `tabDisabled` | `false` | Set to true to leave the cursor in the input field after the user tabs to select a suggestion | 39 | 40 | 41 | ### Event function settings (local and Ajax) 42 | | Event setting | Function description | 43 | | :--- | :--- | 44 | | `onSearchStart` | `function (params) {}` called before Ajax request. `this` is bound to input element | 45 | | `onHint` | `function (hint) {}` used to change input value to first suggestion automatically. `this` is bound to input element | 46 | | `onSearchComplete` | `function (query, suggestions) {}` called after Ajax response is processed. `this` is bound to input element. `suggestions` is an array containing the results | 47 | | `transformResult` | `function(response, originalQuery) {}` called after the result of the query is ready. Converts the result into response.suggestions format | 48 | | `onSelect` | `function (suggestion) {}` Callback function invoked when user selects suggestion from the list. `this` inside callback refers to input HtmlElement.| 49 | | `onSearchError` | `function (query, jqXHR, textStatus, errorThrown) {}` called if Ajax request fails. `this` is bound to input element | 50 | | `onHide` | `function (container) {}` called before container will be hidden | 51 | 52 | 53 | ### Local only settings 54 | | Setting | Default | Description | 55 | | :--- | :--- | :--- | 56 | | `lookupLimit` | `no limit` | Number of maximum results to display for local lookup | 57 | | `lookup` | n/a | Callback function or lookup array for the suggestions. It may be array of strings or `suggestion` object literals | 58 | | `suggestion` | n/a | Not a settings, but in the context of above row, a suggestion is an object literal with the following format: `{ value: 'string', data: any }` | 59 | | `lookupFilter` | n/a | `function (suggestion, query, queryLowerCase) {}` filter function for local lookups. By default it does partial string match (case insensitive) | 60 | 61 | ### Ajax only settings 62 | | Setting | Default | Description | 63 | | :--- | :--- | :--- | 64 | | `serviceUrl` | n/a | Server side URL or callback function that returns serviceUrl string | 65 | | `type` | `GET` | Ajax request type to get suggestions | 66 | | `dataType` | `text` | type of data returned from server. Either `text`, `json` or `jsonp`, which will cause the autocomplete to use jsonp. You may return a json object in your callback when using jsonp | 67 | | `paramName` | `query` | The name of the request parameter that contains the query | 68 | | `params` | optional | Additional parameters to pass with the request | 69 | | `deferRequestBy` | `0` | Number of miliseconds to defer Ajax request | 70 | | `ajaxSettings` | optional | Any additional [Ajax Settings](http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings) that configure the jQuery Ajax request | 71 | 72 | ## Default Options 73 | 74 | Default options for all instances can be accessed via `$.Autocomplete.defaults`. 75 | 76 | ## Instance Methods 77 | 78 | Autocomplete instance has following methods: 79 | 80 | * `setOptions(options)`: you may update any option at any time. Options are listed above. 81 | * `clear`: clears suggestion cache and current suggestions. 82 | * `clearCache`: clears suggestion cache. 83 | * `disable`: deactivate autocomplete. 84 | * `enable`: activates autocomplete if it was deactivated before. 85 | * `hide`: hides suggestions. 86 | * `dispose`: destroys autocomplete instance. All events are detached and suggestion containers removed. 87 | 88 | There are two ways that you can invoke Autocomplete method. One is calling autocomplete on jQuery object and passing method name as string literal. 89 | If method has arguments, arguments are passed as consecutive parameters: 90 | 91 | ```javascript 92 | $('#autocomplete').autocomplete('disable'); 93 | $('#autocomplete').autocomplete('setOptions', options); 94 | ``` 95 | 96 | Or you can get Autocomplete instance by calling autcomplete on jQuery object without any parameters and then invoke desired method. 97 | 98 | ```javascript 99 | $('#autocomplete').autocomplete().disable(); 100 | $('#autocomplete').autocomplete().setOptions(options); 101 | ``` 102 | 103 | ## Usage 104 | 105 | Html: 106 | 107 | ```html 108 | 109 | ``` 110 | 111 | Ajax lookup: 112 | 113 | ```javascript 114 | $('#autocomplete').autocomplete({ 115 | serviceUrl: '/autocomplete/countries', 116 | onSelect: function (suggestion) { 117 | alert('You selected: ' + suggestion.value + ', ' + suggestion.data); 118 | } 119 | }); 120 | ``` 121 | 122 | Local lookup (no Ajax): 123 | 124 | ```javascript 125 | var countries = [ 126 | { value: 'Andorra', data: 'AD' }, 127 | // ... 128 | { value: 'Zimbabwe', data: 'ZZ' } 129 | ]; 130 | 131 | $('#autocomplete').autocomplete({ 132 | lookup: countries, 133 | onSelect: function (suggestion) { 134 | alert('You selected: ' + suggestion.value + ', ' + suggestion.data); 135 | } 136 | }); 137 | ``` 138 | 139 | Custom lookup function: 140 | ```javascript 141 | 142 | $('#autocomplete').autocomplete({ 143 | lookup: function (query, done) { 144 | // Do Ajax call or lookup locally, when done, 145 | // call the callback and pass your results: 146 | var result = { 147 | suggestions: [ 148 | { "value": "United Arab Emirates", "data": "AE" }, 149 | { "value": "United Kingdom", "data": "UK" }, 150 | { "value": "United States", "data": "US" } 151 | ] 152 | }; 153 | 154 | done(result); 155 | }, 156 | onSelect: function (suggestion) { 157 | alert('You selected: ' + suggestion.value + ', ' + suggestion.data); 158 | } 159 | }); 160 | ``` 161 | 162 | ## Styling 163 | 164 | Generated HTML markup for suggestions is displayed below. You may style it any way you'd like. 165 | 166 | ```html 167 |
168 |
NHL
169 |
...
170 |
...
171 |
...
172 |
173 | ``` 174 | 175 | Style sample: 176 | 177 | ```css 178 | .autocomplete-suggestions { border: 1px solid #999; background: #FFF; overflow: auto; } 179 | .autocomplete-suggestion { padding: 2px 5px; white-space: nowrap; overflow: hidden; } 180 | .autocomplete-selected { background: #F0F0F0; } 181 | .autocomplete-suggestions strong { font-weight: normal; color: #3399FF; } 182 | .autocomplete-group { padding: 2px 5px; } 183 | .autocomplete-group strong { display: block; border-bottom: 1px solid #000; } 184 | ``` 185 | 186 | 187 | ## Response Format 188 | 189 | Response from the server must be JSON formatted following JavaScript object: 190 | 191 | ```javascript 192 | { 193 | // Query is not required as of version 1.2.5 194 | "query": "Unit", 195 | "suggestions": [ 196 | { "value": "United Arab Emirates", "data": "AE" }, 197 | { "value": "United Kingdom", "data": "UK" }, 198 | { "value": "United States", "data": "US" } 199 | ] 200 | } 201 | ``` 202 | 203 | Data can be any value or object. Data object is passed to formatResults function 204 | and onSelect callback. Alternatively, if there is no data you can 205 | supply just a string array for suggestions: 206 | 207 | ```json 208 | { 209 | "query": "Unit", 210 | "suggestions": ["United Arab Emirates", "United Kingdom", "United States"] 211 | } 212 | ``` 213 | 214 | ## Non standard query/results 215 | 216 | If your Ajax service expects the query in a different format, and returns data in a different format than the standard response, 217 | you can supply the "paramName" and "transformResult" options: 218 | 219 | ```javascript 220 | $('#autocomplete').autocomplete({ 221 | paramName: 'searchString', 222 | transformResult: function(response) { 223 | return { 224 | suggestions: $.map(response.myData, function(dataItem) { 225 | return { value: dataItem.valueField, data: dataItem.dataField }; 226 | }) 227 | }; 228 | } 229 | }) 230 | ``` 231 | 232 | ## Grouping Results 233 | 234 | Specify `groupBy` option of you data property if you wish results to be displayed in groups. For example, set `groupBy: 'category'` if your suggestion data format is: 235 | 236 | ```javascript 237 | [ 238 | { value: 'Chicago Blackhawks', data: { category: 'NHL' } }, 239 | { value: 'Chicago Bulls', data: { category: 'NBA' } } 240 | ] 241 | ``` 242 | 243 | Results will be formatted into two groups **NHL** and **NBA**. 244 | 245 | ## Known Issues 246 | 247 | If you use it with jQuery UI library it also has plugin named `autocomplete`. In this case you can use plugin alias `devbridgeAutocomplete`: 248 | 249 | ```javascript 250 | $('.autocomplete').devbridgeAutocomplete({ ... }); 251 | ``` 252 | 253 | It seems that for mobile Safari click events are only triggered if the CSS of the object being tapped has the cursor set to pointer: 254 | 255 | .autocomplete-suggestion { 256 | cursor: pointer; 257 | } 258 | 259 | See issue #542 260 | 261 | ## License 262 | 263 | Ajax Autocomplete for jQuery is freely distributable under the 264 | terms of an MIT-style [license](https://github.com/devbridge/jQuery-Autocomplete/blob/master/dist/license.txt). 265 | 266 | Copyright notice and permission notice shall be included in all 267 | copies or substantial portions of the Software. 268 | 269 | ## Authors 270 | 271 | Tomas Kirda / [@tkirda](https://twitter.com/tkirda) 272 | -------------------------------------------------------------------------------- /scripts/countries.js: -------------------------------------------------------------------------------- 1 | var countries = { 2 | "AD": "Andorra", 3 | "A2": "Andorra Test", 4 | "AE": "United Arab Emirates", 5 | "AF": "Afghanistan", 6 | "AG": "Antigua and Barbuda", 7 | "AI": "Anguilla", 8 | "AL": "Albania", 9 | "AM": "Armenia", 10 | "AN": "Netherlands Antilles", 11 | "AO": "Angola", 12 | "AQ": "Antarctica", 13 | "AR": "Argentina", 14 | "AS": "American Samoa", 15 | "AT": "Austria", 16 | "AU": "Australia", 17 | "AW": "Aruba", 18 | "AX": "\u00c5land Islands", 19 | "AZ": "Azerbaijan", 20 | "BA": "Bosnia and Herzegovina", 21 | "BB": "Barbados", 22 | "BD": "Bangladesh", 23 | "BE": "Belgium", 24 | "BF": "Burkina Faso", 25 | "BG": "Bulgaria", 26 | "BH": "Bahrain", 27 | "BI": "Burundi", 28 | "BJ": "Benin", 29 | "BL": "Saint Barth\u00e9lemy", 30 | "BM": "Bermuda", 31 | "BN": "Brunei", 32 | "BO": "Bolivia", 33 | "BQ": "British Antarctic Territory", 34 | "BR": "Brazil", 35 | "BS": "Bahamas", 36 | "BT": "Bhutan", 37 | "BV": "Bouvet Island", 38 | "BW": "Botswana", 39 | "BY": "Belarus", 40 | "BZ": "Belize", 41 | "CA": "Canada", 42 | "CC": "Cocos [Keeling] Islands", 43 | "CD": "Congo - Kinshasa", 44 | "CF": "Central African Republic", 45 | "CG": "Congo - Brazzaville", 46 | "CH": "Switzerland", 47 | "CI": "C\u00f4te d\u2019Ivoire", 48 | "CK": "Cook Islands", 49 | "CL": "Chile", 50 | "CM": "Cameroon", 51 | "CN": "China", 52 | "CO": "Colombia", 53 | "CR": "Costa Rica", 54 | "CS": "Serbia and Montenegro", 55 | "CT": "Canton and Enderbury Islands", 56 | "CU": "Cuba", 57 | "CV": "Cape Verde", 58 | "CX": "Christmas Island", 59 | "CY": "Cyprus", 60 | "CZ": "Czech Republic", 61 | "DD": "East Germany", 62 | "DE": "Germany", 63 | "DJ": "Djibouti", 64 | "DK": "Denmark", 65 | "DM": "Dominica", 66 | "DO": "Dominican Republic", 67 | "DZ": "Algeria", 68 | "EC": "Ecuador", 69 | "EE": "Estonia", 70 | "EG": "Egypt", 71 | "EH": "Western Sahara", 72 | "ER": "Eritrea", 73 | "ES": "Spain", 74 | "ET": "Ethiopia", 75 | "FI": "Finland", 76 | "FJ": "Fiji", 77 | "FK": "Falkland Islands", 78 | "FM": "Micronesia", 79 | "FO": "Faroe Islands", 80 | "FQ": "French Southern and Antarctic Territories", 81 | "FR": "France", 82 | "FX": "Metropolitan France", 83 | "GA": "Gabon", 84 | "GB": "United Kingdom", 85 | "GD": "Grenada", 86 | "GE": "Georgia", 87 | "GF": "French Guiana", 88 | "GG": "Guernsey", 89 | "GH": "Ghana", 90 | "GI": "Gibraltar", 91 | "GL": "Greenland", 92 | "GM": "Gambia", 93 | "GN": "Guinea", 94 | "GP": "Guadeloupe", 95 | "GQ": "Equatorial Guinea", 96 | "GR": "Greece", 97 | "GS": "South Georgia and the South Sandwich Islands", 98 | "GT": "Guatemala", 99 | "GU": "Guam", 100 | "GW": "Guinea-Bissau", 101 | "GY": "Guyana", 102 | "HK": "Hong Kong SAR China", 103 | "HM": "Heard Island and McDonald Islands", 104 | "HN": "Honduras", 105 | "HR": "Croatia", 106 | "HT": "Haiti", 107 | "HU": "Hungary", 108 | "ID": "Indonesia", 109 | "IE": "Ireland", 110 | "IL": "Israel", 111 | "IM": "Isle of Man", 112 | "IN": "India", 113 | "IO": "British Indian Ocean Territory", 114 | "IQ": "Iraq", 115 | "IR": "Iran", 116 | "IS": "Iceland", 117 | "IT": "Italy", 118 | "JE": "Jersey", 119 | "JM": "Jamaica", 120 | "JO": "Jordan", 121 | "JP": "Japan", 122 | "JT": "Johnston Island", 123 | "KE": "Kenya", 124 | "KG": "Kyrgyzstan", 125 | "KH": "Cambodia", 126 | "KI": "Kiribati", 127 | "KM": "Comoros", 128 | "KN": "Saint Kitts and Nevis", 129 | "KP": "North Korea", 130 | "KR": "South Korea", 131 | "KW": "Kuwait", 132 | "KY": "Cayman Islands", 133 | "KZ": "Kazakhstan", 134 | "LA": "Laos", 135 | "LB": "Lebanon", 136 | "LC": "Saint Lucia", 137 | "LI": "Liechtenstein", 138 | "LK": "Sri Lanka", 139 | "LR": "Liberia", 140 | "LS": "Lesotho", 141 | "LT": "Lithuania", 142 | "LU": "Luxembourg", 143 | "LV": "Latvia", 144 | "LY": "Libya", 145 | "MA": "Morocco", 146 | "MC": "Monaco", 147 | "MD": "Moldova", 148 | "ME": "Montenegro", 149 | "MF": "Saint Martin", 150 | "MG": "Madagascar", 151 | "MH": "Marshall Islands", 152 | "MI": "Midway Islands", 153 | "MK": "Macedonia", 154 | "ML": "Mali", 155 | "MM": "Myanmar [Burma]", 156 | "MN": "Mongolia", 157 | "MO": "Macau SAR China", 158 | "MP": "Northern Mariana Islands", 159 | "MQ": "Martinique", 160 | "MR": "Mauritania", 161 | "MS": "Montserrat", 162 | "MT": "Malta", 163 | "MU": "Mauritius", 164 | "MV": "Maldives", 165 | "MW": "Malawi", 166 | "MX": "Mexico", 167 | "MY": "Malaysia", 168 | "MZ": "Mozambique", 169 | "NA": "Namibia", 170 | "NC": "New Caledonia", 171 | "NE": "Niger", 172 | "NF": "Norfolk Island", 173 | "NG": "Nigeria", 174 | "NI": "Nicaragua", 175 | "NL": "Netherlands", 176 | "NO": "Norway", 177 | "NP": "Nepal", 178 | "NQ": "Dronning Maud Land", 179 | "NR": "Nauru", 180 | "NT": "Neutral Zone", 181 | "NU": "Niue", 182 | "NZ": "New Zealand", 183 | "OM": "Oman", 184 | "PA": "Panama", 185 | "PC": "Pacific Islands Trust Territory", 186 | "PE": "Peru", 187 | "PF": "French Polynesia", 188 | "PG": "Papua New Guinea", 189 | "PH": "Philippines", 190 | "PK": "Pakistan", 191 | "PL": "Poland", 192 | "PM": "Saint Pierre and Miquelon", 193 | "PN": "Pitcairn Islands", 194 | "PR": "Puerto Rico", 195 | "PS": "Palestinian Territories", 196 | "PT": "Portugal", 197 | "PU": "U.S. Miscellaneous Pacific Islands", 198 | "PW": "Palau", 199 | "PY": "Paraguay", 200 | "PZ": "Panama Canal Zone", 201 | "QA": "Qatar", 202 | "RE": "R\u00e9union", 203 | "RO": "Romania", 204 | "RS": "Serbia", 205 | "RU": "Russia", 206 | "RW": "Rwanda", 207 | "SA": "Saudi Arabia", 208 | "SB": "Solomon Islands", 209 | "SC": "Seychelles", 210 | "SD": "Sudan", 211 | "SE": "Sweden", 212 | "SG": "Singapore", 213 | "SH": "Saint Helena", 214 | "SI": "Slovenia", 215 | "SJ": "Svalbard and Jan Mayen", 216 | "SK": "Slovakia", 217 | "SL": "Sierra Leone", 218 | "SM": "San Marino", 219 | "SN": "Senegal", 220 | "SO": "Somalia", 221 | "SR": "Suriname", 222 | "ST": "S\u00e3o Tom\u00e9 and Pr\u00edncipe", 223 | "SU": "Union of Soviet Socialist Republics", 224 | "SV": "El Salvador", 225 | "SY": "Syria", 226 | "SZ": "Swaziland", 227 | "TC": "Turks and Caicos Islands", 228 | "TD": "Chad", 229 | "TF": "French Southern Territories", 230 | "TG": "Togo", 231 | "TH": "Thailand", 232 | "TJ": "Tajikistan", 233 | "TK": "Tokelau", 234 | "TL": "Timor-Leste", 235 | "TM": "Turkmenistan", 236 | "TN": "Tunisia", 237 | "TO": "Tonga", 238 | "TR": "Turkey", 239 | "TT": "Trinidad and Tobago", 240 | "TV": "Tuvalu", 241 | "TW": "Taiwan", 242 | "TZ": "Tanzania", 243 | "UA": "Ukraine", 244 | "UG": "Uganda", 245 | "UM": "U.S. Minor Outlying Islands", 246 | "US": "United States", 247 | "UY": "Uruguay", 248 | "UZ": "Uzbekistan", 249 | "VA": "Vatican City", 250 | "VC": "Saint Vincent and the Grenadines", 251 | "VD": "North Vietnam", 252 | "VE": "Venezuela", 253 | "VG": "British Virgin Islands", 254 | "VI": "U.S. Virgin Islands", 255 | "VN": "Vietnam", 256 | "VU": "Vanuatu", 257 | "WF": "Wallis and Futuna", 258 | "WK": "Wake Island", 259 | "WS": "Samoa", 260 | "YD": "People's Democratic Republic of Yemen", 261 | "YE": "Yemen", 262 | "YT": "Mayotte", 263 | "ZA": "South Africa", 264 | "ZM": "Zambia", 265 | "ZW": "Zimbabwe", 266 | "ZZ": "Unknown or Invalid Region" 267 | } -------------------------------------------------------------------------------- /scripts/demo.js: -------------------------------------------------------------------------------- 1 | /*jslint browser: true, white: true, plusplus: true */ 2 | /*global $, countries */ 3 | 4 | $(function () { 5 | 'use strict'; 6 | 7 | var countriesArray = $.map(countries, function (value, key) { return { value: value, data: key }; }); 8 | 9 | // Setup jQuery ajax mock: 10 | $.mockjax({ 11 | url: '*', 12 | responseTime: 2000, 13 | response: function (settings) { 14 | var query = settings.data.query, 15 | queryLowerCase = query.toLowerCase(), 16 | re = new RegExp('\\b' + $.Autocomplete.utils.escapeRegExChars(queryLowerCase), 'gi'), 17 | suggestions = $.grep(countriesArray, function (country) { 18 | // return country.value.toLowerCase().indexOf(queryLowerCase) === 0; 19 | return re.test(country.value); 20 | }), 21 | response = { 22 | query: query, 23 | suggestions: suggestions 24 | }; 25 | 26 | this.responseText = JSON.stringify(response); 27 | } 28 | }); 29 | 30 | // Initialize ajax autocomplete: 31 | $('#autocomplete-ajax').autocomplete({ 32 | // serviceUrl: '/autosuggest/service/url', 33 | lookup: countriesArray, 34 | lookupFilter: function(suggestion, originalQuery, queryLowerCase) { 35 | var re = new RegExp('\\b' + $.Autocomplete.utils.escapeRegExChars(queryLowerCase), 'gi'); 36 | return re.test(suggestion.value); 37 | }, 38 | onSelect: function(suggestion) { 39 | $('#selction-ajax').html('You selected: ' + suggestion.value + ', ' + suggestion.data); 40 | }, 41 | onHint: function (hint) { 42 | $('#autocomplete-ajax-x').val(hint); 43 | }, 44 | onInvalidateSelection: function() { 45 | $('#selction-ajax').html('You selected: none'); 46 | } 47 | }); 48 | 49 | var nhlTeams = ['Anaheim Ducks', 'Atlanta Thrashers', 'Boston Bruins', 'Buffalo Sabres', 'Calgary Flames', 'Carolina Hurricanes', 'Chicago Blackhawks', 'Colorado Avalanche', 'Columbus Blue Jackets', 'Dallas Stars', 'Detroit Red Wings', 'Edmonton OIlers', 'Florida Panthers', 'Los Angeles Kings', 'Minnesota Wild', 'Montreal Canadiens', 'Nashville Predators', 'New Jersey Devils', 'New Rork Islanders', 'New York Rangers', 'Ottawa Senators', 'Philadelphia Flyers', 'Phoenix Coyotes', 'Pittsburgh Penguins', 'Saint Louis Blues', 'San Jose Sharks', 'Tampa Bay Lightning', 'Toronto Maple Leafs', 'Vancouver Canucks', 'Washington Capitals']; 50 | var nbaTeams = ['Atlanta Hawks', 'Boston Celtics', 'Charlotte Bobcats', 'Chicago Bulls', 'Cleveland Cavaliers', 'Dallas Mavericks', 'Denver Nuggets', 'Detroit Pistons', 'Golden State Warriors', 'Houston Rockets', 'Indiana Pacers', 'LA Clippers', 'LA Lakers', 'Memphis Grizzlies', 'Miami Heat', 'Milwaukee Bucks', 'Minnesota Timberwolves', 'New Jersey Nets', 'New Orleans Hornets', 'New York Knicks', 'Oklahoma City Thunder', 'Orlando Magic', 'Philadelphia Sixers', 'Phoenix Suns', 'Portland Trail Blazers', 'Sacramento Kings', 'San Antonio Spurs', 'Toronto Raptors', 'Utah Jazz', 'Washington Wizards']; 51 | var nhl = $.map(nhlTeams, function (team) { return { value: team, data: { category: 'NHL' }}; }); 52 | var nba = $.map(nbaTeams, function (team) { return { value: team, data: { category: 'NBA' } }; }); 53 | var teams = nhl.concat(nba); 54 | 55 | // Initialize autocomplete with local lookup: 56 | $('#autocomplete').devbridgeAutocomplete({ 57 | lookup: teams, 58 | minChars: 1, 59 | onSelect: function (suggestion) { 60 | $('#selection').html('You selected: ' + suggestion.value + ', ' + suggestion.data.category); 61 | }, 62 | showNoSuggestionNotice: true, 63 | noSuggestionNotice: 'Sorry, no matching results', 64 | groupBy: 'category' 65 | }); 66 | 67 | // Initialize autocomplete with custom appendTo: 68 | $('#autocomplete-custom-append').autocomplete({ 69 | lookup: countriesArray, 70 | appendTo: '#suggestions-container' 71 | }); 72 | 73 | // Initialize autocomplete with custom appendTo: 74 | $('#autocomplete-dynamic').autocomplete({ 75 | lookup: countriesArray 76 | }); 77 | }); -------------------------------------------------------------------------------- /spec/autocompleteBehavior.js: -------------------------------------------------------------------------------- 1 | /*jslint vars: true*/ 2 | /*global describe, it, expect, waits, waitsFor, runs, afterEach, spyOn, $, beforeEach*/ 3 | 4 | describe('Autocomplete Async', function () { 5 | 'use strict'; 6 | 7 | var input = document.createElement('input'), 8 | startQuery, 9 | ajaxExecuted = false, 10 | autocomplete = new $.Autocomplete(input, { 11 | serviceUrl: '/test', 12 | onSearchStart: function (params) { 13 | startQuery = params.query; 14 | } 15 | }); 16 | 17 | beforeEach(function (done) { 18 | $.mockjax({ 19 | url: '/test', 20 | responseTime: 50, 21 | response: function (settings) { 22 | ajaxExecuted = true; 23 | var query = settings.data.query, 24 | response = { 25 | query: query, 26 | suggestions: [] 27 | }; 28 | this.responseText = JSON.stringify(response); 29 | done(); 30 | } 31 | }); 32 | 33 | input.value = 'A'; 34 | autocomplete.onValueChange(); 35 | }); 36 | 37 | it('Should execute onSearchStart', function () { 38 | expect(ajaxExecuted).toBe(true); 39 | expect(startQuery).toBe('A'); 40 | }); 41 | }); 42 | 43 | describe('Autocomplete Async', function () { 44 | 'use strict'; 45 | 46 | var input = document.createElement('input'), 47 | completeQuery, 48 | mockupSuggestion = { value: 'A', data: 'A' }, 49 | resultSuggestions, 50 | ajaxExecuted = false, 51 | url = '/test-completed'; 52 | 53 | beforeEach(function (done) { 54 | var autocomplete = new $.Autocomplete(input, { 55 | serviceUrl: url, 56 | onSearchComplete: function (query, suggestions) { 57 | completeQuery = query; 58 | resultSuggestions = suggestions; 59 | done(); 60 | } 61 | }); 62 | 63 | $.mockjax({ 64 | url: url, 65 | responseTime: 50, 66 | response: function (settings) { 67 | ajaxExecuted = true; 68 | var query = settings.data.query, 69 | response = { 70 | query: query, 71 | suggestions: [mockupSuggestion] 72 | }; 73 | this.responseText = JSON.stringify(response); 74 | } 75 | }); 76 | 77 | input.value = 'A'; 78 | autocomplete.onValueChange(); 79 | }); 80 | 81 | it('Should execute onSearchComplete', function () { 82 | expect(ajaxExecuted).toBe(true); 83 | expect(completeQuery).toBe('A'); 84 | expect(resultSuggestions[0].value).toBe('A'); 85 | expect(resultSuggestions[0].data).toBe('A'); 86 | }); 87 | }); 88 | 89 | describe('Autocomplete Async', function () { 90 | 'use strict'; 91 | var errorMessage = false; 92 | 93 | beforeEach(function (done) { 94 | var input = document.createElement('input'), 95 | url = '/test-error', 96 | autocomplete = new $.Autocomplete(input, { 97 | serviceUrl: url, 98 | onSearchError: function (q, jqXHR, textStatus, errorThrown) { 99 | errorMessage = jqXHR.responseText; 100 | done(); 101 | } 102 | }); 103 | 104 | $.mockjax({ 105 | url: url, 106 | responseTime: 50, 107 | status: 500, 108 | response: function (settings) { 109 | this.responseText = "An error occurred"; 110 | } 111 | }); 112 | 113 | input.value = 'A'; 114 | autocomplete.onValueChange(); 115 | }); 116 | 117 | it('Should execute onSearchError', function () { 118 | expect(errorMessage).toBe("An error occurred"); 119 | }); 120 | }); 121 | 122 | describe('Asyn', function () { 123 | 'use strict'; 124 | 125 | var instance; 126 | 127 | beforeEach(function (done) { 128 | var input = document.createElement('input'), 129 | ajaxExecuted = false, 130 | url = '/test-transform', 131 | autocomplete = new $.Autocomplete(input, { 132 | serviceUrl: url, 133 | transformResult: function (result, query) { 134 | 135 | // call done after we return; 136 | setTimeout(done, 0); 137 | 138 | return { 139 | query: query, 140 | suggestions: $.map(result.split(','), function (item) { 141 | return { value: item, data: null }; 142 | }) 143 | }; 144 | } 145 | }); 146 | 147 | $.mockjax({ 148 | url: url, 149 | responseTime: 50, 150 | response: function () { 151 | ajaxExecuted = true; 152 | this.responseText = 'Andora,Angola,Argentina'; 153 | } 154 | }); 155 | 156 | instance = autocomplete; 157 | 158 | input.value = 'A'; 159 | autocomplete.onValueChange(); 160 | }); 161 | 162 | 163 | it('Should transform results', function () { 164 | expect(instance.suggestions.length).toBe(3); 165 | expect(instance.suggestions[0].value).toBe('Andora'); 166 | }); 167 | }); 168 | 169 | describe('Autocomplete Async', function () { 170 | 'use strict'; 171 | 172 | var instance; 173 | 174 | beforeEach(function (done) { 175 | var input = document.createElement('input'), 176 | ajaxExecuted = false, 177 | url = '/test-original-query', 178 | autocomplete = new $.Autocomplete(input, { 179 | serviceUrl: url 180 | }); 181 | 182 | $.mockjax({ 183 | url: url, 184 | responseTime: 50, 185 | response: function () { 186 | ajaxExecuted = true; 187 | var response = { 188 | query: null, 189 | suggestions: ['Aa', 'Bb', 'Cc'] 190 | }; 191 | this.responseText = JSON.stringify(response); 192 | setTimeout(done, 0); 193 | } 194 | }); 195 | 196 | input.value = 'A'; 197 | instance = autocomplete; 198 | autocomplete.onValueChange(); 199 | }); 200 | 201 | it('Should not require orginal query value from the server', function () { 202 | expect(instance.suggestions.length).toBe(3); 203 | expect(instance.suggestions[0].value).toBe('Aa'); 204 | }); 205 | 206 | }); 207 | 208 | describe('Autocomplete Async', function () { 209 | 'use strict'; 210 | 211 | var paramValue; 212 | 213 | beforeEach(function (done) { 214 | var input = document.createElement('input'), 215 | paramName = 'custom', 216 | autocomplete = new $.Autocomplete(input, { 217 | serviceUrl: '/test-query', 218 | paramName: paramName 219 | }); 220 | 221 | $.mockjax({ 222 | url: '/test-query', 223 | responseTime: 5, 224 | response: function (settings) { 225 | paramValue = settings.data[paramName]; 226 | var response = { 227 | query: paramValue, 228 | suggestions: [] 229 | }; 230 | this.responseText = JSON.stringify(response); 231 | done(); 232 | } 233 | }); 234 | 235 | input.value = 'Jam'; 236 | autocomplete.onValueChange(); 237 | }); 238 | 239 | 240 | it('Should use custom query parameter name', function () { 241 | expect(paramValue).toBe('Jam'); 242 | }); 243 | }); 244 | 245 | describe('Autocomplete Async', function () { 246 | 'use strict'; 247 | 248 | var dynamicUrl, 249 | data; 250 | 251 | beforeEach(function (done) { 252 | var input = $(document.createElement('input')); 253 | 254 | input.autocomplete({ 255 | ignoreParams: true, 256 | serviceUrl: function (query) { 257 | return '/dynamic-url/' + encodeURIComponent(query).replace(/%20/g, "+"); 258 | } 259 | }); 260 | 261 | $.mockjax({ 262 | url: '/dynamic-url/*', 263 | responseTime: 5, 264 | response: function (settings) { 265 | dynamicUrl = settings.url; 266 | data = settings.data; 267 | var response = { 268 | suggestions: [] 269 | }; 270 | this.responseText = JSON.stringify(response); 271 | done(); 272 | } 273 | }); 274 | 275 | input.val('Hello World'); 276 | input.autocomplete().onValueChange(); 277 | }); 278 | 279 | it('Should construct serviceUrl via callback function.', function () { 280 | expect(dynamicUrl).toBe('/dynamic-url/Hello+World'); 281 | expect(data).toBeFalsy(); 282 | }); 283 | }); 284 | 285 | describe('Autocomplete Async', function () { 286 | 'use strict'; 287 | 288 | var instance, 289 | cacheKey; 290 | 291 | 292 | beforeEach(function (done) { 293 | var input = $(''), 294 | ajaxExecuted = false, 295 | data = { a: 1, query: 'Jam' }, 296 | serviceUrl = '/autocomplete/cached/url'; 297 | 298 | cacheKey = serviceUrl + '?' + $.param(data); 299 | 300 | input.autocomplete({ 301 | serviceUrl: serviceUrl, 302 | params: data 303 | }); 304 | 305 | $.mockjax({ 306 | url: serviceUrl, 307 | responseTime: 5, 308 | response: function (settings) { 309 | ajaxExecuted = true; 310 | var query = settings.data.query, 311 | response = { 312 | suggestions: [{ value: 'Jamaica' }, { value: 'Jamaica' }] 313 | }; 314 | this.responseText = JSON.stringify(response); 315 | setTimeout(done, 10); 316 | } 317 | }); 318 | 319 | input.val('Jam'); 320 | instance = input.autocomplete(); 321 | instance.onValueChange(); 322 | }); 323 | 324 | it('Should use serviceUrl and params as cacheKey', function () { 325 | expect(instance.cachedResponse[cacheKey]).toBeTruthy(); 326 | }); 327 | }); 328 | 329 | describe('Autocomplete Async', function () { 330 | 'use strict'; 331 | 332 | var ajaxCount = 0; 333 | 334 | beforeEach(function (done) { 335 | var input = $(''), 336 | instance, 337 | serviceUrl = '/autocomplete/prevent/ajax'; 338 | 339 | input.autocomplete({ 340 | serviceUrl: serviceUrl 341 | }); 342 | 343 | $.mockjax({ 344 | url: serviceUrl, 345 | responseTime: 1, 346 | response: function (settings) { 347 | ajaxCount += 1; 348 | var response = { suggestions: [] }; 349 | this.responseText = JSON.stringify(response); 350 | if (ajaxCount === 2) { 351 | done(); 352 | } 353 | } 354 | }); 355 | 356 | setTimeout(function () { 357 | input.val('Jam'); 358 | instance = input.autocomplete(); 359 | instance.onValueChange(); 360 | }, 10); 361 | 362 | setTimeout(function () { 363 | input.val('Jama'); 364 | instance.onValueChange(); 365 | }, 20); 366 | 367 | setTimeout(function () { 368 | // Change setting and continue: 369 | instance.setOptions({ preventBadQueries: false }); 370 | input.val('Jamai'); 371 | instance.onValueChange(); 372 | }, 30); 373 | }); 374 | 375 | it('Should prevent Ajax requests if previous query with matching root failed.', function () { 376 | // Ajax call should have been made: 377 | expect(ajaxCount).toBe(2); 378 | }); 379 | }); 380 | 381 | describe('Autocomplete', function () { 382 | 'use strict'; 383 | 384 | afterEach(function () { 385 | $('.autocomplete-suggestions').hide(); 386 | }); 387 | 388 | it('Should initialize autocomplete options', function () { 389 | var input = document.createElement('input'), 390 | options = { serviceUrl: '/autocomplete/service/url' }, 391 | autocomplete = new $.Autocomplete(input, options); 392 | 393 | expect(autocomplete.options.serviceUrl).toEqual(options.serviceUrl); 394 | expect(autocomplete.suggestionsContainer).not.toBeNull(); 395 | }); 396 | 397 | it('Should set autocomplete attribute to "off"', function () { 398 | var input = document.createElement('input'), 399 | autocomplete = new $.Autocomplete(input, {}); 400 | 401 | expect(autocomplete).not.toBeNull(); 402 | expect(input.getAttribute('autocomplete')).toEqual('off'); 403 | }); 404 | 405 | it('Should get current value', function () { 406 | var input = document.createElement('input'), 407 | autocomplete = new $.Autocomplete(input, { 408 | lookup: [{ value: 'Jamaica', data: 'B' }] 409 | }); 410 | 411 | input.value = 'Jam'; 412 | autocomplete.onValueChange(); 413 | 414 | expect(autocomplete.visible).toBe(true); 415 | expect(autocomplete.currentValue).toEqual('Jam'); 416 | }); 417 | 418 | it('Should call formatResult three times', function () { 419 | var input = document.createElement('input'), 420 | counter = 0, 421 | suggestion, 422 | currentValue, 423 | autocomplete = new $.Autocomplete(input, { 424 | lookup: ['Jamaica', 'Jamaica', 'Jamaica'], 425 | formatResult: function (s, v) { 426 | suggestion = s; 427 | currentValue = v; 428 | counter += 1; 429 | } 430 | }); 431 | 432 | input.value = 'Jam'; 433 | autocomplete.onValueChange(); 434 | 435 | expect(suggestion.value).toBe('Jamaica'); 436 | expect(suggestion.data).toBe(null); 437 | expect(currentValue).toEqual('Jam'); 438 | expect(counter).toEqual(3); 439 | }); 440 | 441 | it('Verify onSelect callback', function () { 442 | var input = document.createElement('input'), 443 | context, 444 | value, 445 | data, 446 | autocomplete = $(input).autocomplete({ 447 | lookup: [{ value: 'A', data: 'B' }], 448 | triggerSelectOnValidInput: false, 449 | onSelect: function (suggestion) { 450 | context = this; 451 | value = suggestion.value; 452 | data = suggestion.data; 453 | } 454 | }).autocomplete(); 455 | 456 | input.value = 'A'; 457 | autocomplete.onValueChange(); 458 | autocomplete.select(0); 459 | 460 | expect(context).toEqual(input); 461 | expect(value).toEqual('A'); 462 | expect(data).toEqual('B'); 463 | }); 464 | 465 | it('Should convert suggestions format', function () { 466 | var input = document.createElement('input'), 467 | autocomplete = new $.Autocomplete(input, { 468 | lookup: ['A', 'B'] 469 | }); 470 | 471 | expect(autocomplete.options.lookup[0].value).toBe('A'); 472 | expect(autocomplete.options.lookup[1].value).toBe('B'); 473 | }); 474 | 475 | it('Should should not preventDefault when tabDisabled is set to false', function () { 476 | var input = document.createElement('input'), 477 | autocomplete = new $.Autocomplete(input, { 478 | lookup: [{ value: 'Jamaica', data: 'B' }], 479 | tabDisabled: false, 480 | autoSelectFirst: true 481 | }); 482 | input.value = 'Jam'; 483 | autocomplete.onValueChange(); 484 | 485 | var event = $.Event('keydown'); 486 | event.which = 9; // the tab keycode 487 | spyOn(event, 'stopImmediatePropagation'); 488 | spyOn(event, 'preventDefault'); 489 | spyOn(autocomplete, 'suggest'); 490 | 491 | expect(autocomplete.visible).toBe(true); 492 | expect(autocomplete.disabled).toBe(undefined); 493 | expect(autocomplete.selectedIndex).not.toBe(-1); 494 | 495 | $(input).trigger(event); 496 | 497 | expect(event.stopImmediatePropagation).not.toHaveBeenCalled(); 498 | expect(event.preventDefault).not.toHaveBeenCalled(); 499 | expect(autocomplete.suggest).not.toHaveBeenCalled(); 500 | }); 501 | 502 | it('Should should preventDefault when tabDisabled is set to true', function () { 503 | var input = document.createElement('input'), 504 | autocomplete = new $.Autocomplete(input, { 505 | lookup: [{ value: 'Jamaica', data: 'B' }], 506 | tabDisabled: true, 507 | autoSelectFirst: true 508 | }); 509 | input.value = 'Jam'; 510 | autocomplete.onValueChange(); 511 | 512 | var event = $.Event('keydown'); 513 | event.which = 9; // the tab keycode 514 | spyOn(autocomplete, 'suggest'); 515 | 516 | expect(autocomplete.visible).toBe(true); 517 | expect(autocomplete.disabled).toBe(undefined); 518 | expect(autocomplete.selectedIndex).not.toBe(-1); 519 | 520 | $(input).trigger(event); 521 | 522 | expect(autocomplete.suggest).not.toHaveBeenCalled(); 523 | }); 524 | 525 | it('Should not autoselect first item by default', function () { 526 | var input = document.createElement('input'), 527 | autocomplete = new $.Autocomplete(input, { 528 | lookup: ['Jamaica', 'Jamaica', 'Jamaica'] 529 | }); 530 | 531 | input.value = 'Jam'; 532 | autocomplete.onValueChange(); 533 | 534 | expect(autocomplete.selectedIndex).toBe(-1); 535 | }); 536 | 537 | it('Should autoselect first item autoSelectFirst set to true', function () { 538 | var input = document.createElement('input'), 539 | autocomplete = new $.Autocomplete(input, { 540 | lookup: ['Jamaica', 'Jamaica', 'Jamaica'], 541 | autoSelectFirst: true 542 | }); 543 | 544 | input.value = 'Jam'; 545 | autocomplete.onValueChange(); 546 | 547 | expect(autocomplete.selectedIndex).toBe(0); 548 | }); 549 | 550 | it('Should destroy autocomplete instance', function () { 551 | var input = $(document.createElement('input')), 552 | div = $(document.createElement('div')); 553 | 554 | input.autocomplete({ 555 | serviceUrl: '/test-dispose', 556 | appendTo: div 557 | }); 558 | 559 | expect(input.data('autocomplete')).toBeDefined(); 560 | expect(div.children().length).toBeGreaterThan(0); 561 | 562 | input.autocomplete('dispose'); 563 | 564 | expect(input.data('autocomplete')).toBeUndefined(); 565 | expect(div.children().length).toBe(0); 566 | }); 567 | 568 | it('Should return Autocomplete instance if called without arguments', function () { 569 | var input = $(document.createElement('input')); 570 | 571 | input.autocomplete({ 572 | serviceUrl: '/test-dispose' 573 | }); 574 | 575 | var instance = input.autocomplete(); 576 | 577 | expect(instance instanceof $.Autocomplete).toBe(true); 578 | }); 579 | 580 | 581 | it('Should set width to be greater than zero', function () { 582 | var input = $(document.createElement('input')), 583 | instance, 584 | width; 585 | 586 | input.autocomplete({ 587 | lookup: [{ value: 'Jamaica', data: 'B' }] 588 | }); 589 | 590 | input.val('Jam'); 591 | input.width(100); 592 | 593 | instance = input.autocomplete(); 594 | instance.onValueChange(); 595 | 596 | width = $(instance.suggestionsContainer).width(); 597 | 598 | expect(width).toBeGreaterThan(0); 599 | }); 600 | 601 | it('Should call beforeRender and pass container jQuery object', function () { 602 | var element = document.createElement('input'), 603 | input = $(element), 604 | instance, 605 | elementCount, 606 | context; 607 | 608 | input.autocomplete({ 609 | lookup: [{ value: 'Jamaica', data: 'B' }], 610 | beforeRender: function (container) { 611 | context = this; 612 | elementCount = container.length; 613 | } 614 | }); 615 | 616 | input.val('Jam'); 617 | instance = input.autocomplete(); 618 | instance.onValueChange(); 619 | 620 | expect(context).toBe(element); 621 | expect(elementCount).toBe(1); 622 | }); 623 | 624 | it('Should trigger select when input value matches suggestion', function () { 625 | var input = $(''), 626 | instance, 627 | suggestionData = false; 628 | 629 | input.autocomplete({ 630 | lookup: [{ value: 'Jamaica', data: 'J' }], 631 | triggerSelectOnValidInput: true, 632 | onSelect: function (suggestion) { 633 | suggestionData = suggestion.data; 634 | } 635 | }); 636 | 637 | input.val('Jamaica'); 638 | instance = input.autocomplete(); 639 | instance.onValueChange(); 640 | 641 | expect(suggestionData).toBe('J'); 642 | }); 643 | 644 | it('Should NOT trigger select when input value matches suggestion', function () { 645 | var input = $(''), 646 | instance, 647 | suggestionData = null; 648 | 649 | input.autocomplete({ 650 | lookup: [{ value: 'Jamaica', data: 'J' }], 651 | triggerSelectOnValidInput: false, 652 | onSelect: function (suggestion) { 653 | suggestionData = suggestion.data; 654 | } 655 | }); 656 | 657 | input.val('Jamaica'); 658 | instance = input.autocomplete(); 659 | instance.onValueChange(); 660 | 661 | expect(suggestionData).toBeNull(); 662 | }); 663 | 664 | it('Should limit results for local request', function () { 665 | var input = $(''), 666 | instance, 667 | limit = 3; 668 | 669 | input.autocomplete({ 670 | lookup: [{ value: 'Jamaica' }, { value: 'Jamaica' }, { value: 'Jamaica' }, { value: 'Jamaica' }, { value: 'Jamaica' }] 671 | }); 672 | 673 | input.val('Jam'); 674 | instance = input.autocomplete(); 675 | instance.onValueChange(); 676 | 677 | // Expect all items to be displayed: 678 | expect(instance.suggestions.length).toBe(5); 679 | 680 | // Set lookup result limit and verify: 681 | instance.setOptions({ lookupLimit: limit }); 682 | instance.onValueChange(); 683 | 684 | expect(instance.suggestions.length).toBe(limit); 685 | }); 686 | 687 | it('Should display no suggestion notice when no matching results', function () { 688 | var input = document.createElement('input'), 689 | options = { 690 | lookup: [{ value: 'Colombia', data: 'Spain' }], 691 | showNoSuggestionNotice: true, 692 | noSuggestionNotice: 'Sorry, no matching results' 693 | }, 694 | autocomplete = new $.Autocomplete(input, options), 695 | suggestionsContainer = $(autocomplete.suggestionsContainer); 696 | 697 | input.value = 'Jamaica'; 698 | autocomplete.onValueChange(); 699 | 700 | expect(autocomplete.visible).toBe(true); 701 | expect(autocomplete.selectedIndex).toBe(-1); 702 | expect(suggestionsContainer.find('.autocomplete-no-suggestion').length).toBe(1); 703 | expect(suggestionsContainer.find('.autocomplete-no-suggestion').text()).toBe('Sorry, no matching results'); 704 | }); 705 | 706 | it('Should call onHide and pass container jQuery object', function () { 707 | var element = document.createElement('input'), 708 | input = $(element), 709 | instance, 710 | elementCount, 711 | context; 712 | 713 | input.autocomplete({ 714 | lookup: [{ value: 'Jamaica', data: 'B' }], 715 | onHide: function (container) { 716 | context = this; 717 | elementCount = container.length; 718 | } 719 | }); 720 | 721 | input.val('Jam'); 722 | instance = input.autocomplete(); 723 | instance.onValueChange(); 724 | 725 | input.val('Colombia'); 726 | instance.onValueChange(); 727 | 728 | expect(context).toBe(element); 729 | expect(elementCount).toBe(1); 730 | }); 731 | 732 | }); 733 | 734 | describe('When options.preserveInput is true', function () { 735 | 'use strict'; 736 | 737 | var input = $(''), 738 | instance, 739 | suggestionData = null; 740 | 741 | beforeEach(function () { 742 | input.autocomplete({ 743 | lookup: [{ value: 'Jamaica', data: 'J' }, { value: 'Jamaica2', data: 'J' }, { value: 'Jamaica3', data: 'J' }], 744 | preserveInput: true, 745 | onSelect: function (suggestion) { 746 | suggestionData = suggestion.data; 747 | } 748 | }); 749 | 750 | input.val('J'); 751 | instance = input.autocomplete(); 752 | }); 753 | 754 | afterEach(function () { 755 | instance.dispose(); 756 | }); 757 | 758 | it('Should NOT change input value when item is selected', function () { 759 | instance.onValueChange(); 760 | instance.select(0); 761 | 762 | expect(input.val()).toEqual('J'); 763 | }); 764 | 765 | it('Should NOT change input value when move down', function () { 766 | instance.onValueChange(); 767 | instance.moveDown(); 768 | 769 | expect(input.val()).toEqual('J'); 770 | }); 771 | 772 | it('Should NOT change input value when move up', function () { 773 | instance.onValueChange(); 774 | instance.moveUp(); 775 | 776 | expect(input.val()).toEqual('J'); 777 | }); 778 | }); 779 | -------------------------------------------------------------------------------- /spec/lib/jasmine-1.3.1/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /spec/lib/jasmine-1.3.1/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporterHelpers = {}; 2 | 3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 4 | var el = document.createElement(type); 5 | 6 | for (var i = 2; i < arguments.length; i++) { 7 | var child = arguments[i]; 8 | 9 | if (typeof child === 'string') { 10 | el.appendChild(document.createTextNode(child)); 11 | } else { 12 | if (child) { 13 | el.appendChild(child); 14 | } 15 | } 16 | } 17 | 18 | for (var attr in attrs) { 19 | if (attr == "className") { 20 | el[attr] = attrs[attr]; 21 | } else { 22 | el.setAttribute(attr, attrs[attr]); 23 | } 24 | } 25 | 26 | return el; 27 | }; 28 | 29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 30 | var results = child.results(); 31 | var status = results.passed() ? 'passed' : 'failed'; 32 | if (results.skipped) { 33 | status = 'skipped'; 34 | } 35 | 36 | return status; 37 | }; 38 | 39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 40 | var parentDiv = this.dom.summary; 41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 42 | var parent = child[parentSuite]; 43 | 44 | if (parent) { 45 | if (typeof this.views.suites[parent.id] == 'undefined') { 46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 47 | } 48 | parentDiv = this.views.suites[parent.id].element; 49 | } 50 | 51 | parentDiv.appendChild(childElement); 52 | }; 53 | 54 | 55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 56 | for(var fn in jasmine.HtmlReporterHelpers) { 57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 58 | } 59 | }; 60 | 61 | jasmine.HtmlReporter = function(_doc) { 62 | var self = this; 63 | var doc = _doc || window.document; 64 | 65 | var reporterView; 66 | 67 | var dom = {}; 68 | 69 | // Jasmine Reporter Public Interface 70 | self.logRunningSpecs = false; 71 | 72 | self.reportRunnerStarting = function(runner) { 73 | var specs = runner.specs() || []; 74 | 75 | if (specs.length == 0) { 76 | return; 77 | } 78 | 79 | createReporterDom(runner.env.versionString()); 80 | doc.body.appendChild(dom.reporter); 81 | setExceptionHandling(); 82 | 83 | reporterView = new jasmine.HtmlReporter.ReporterView(dom); 84 | reporterView.addSpecs(specs, self.specFilter); 85 | }; 86 | 87 | self.reportRunnerResults = function(runner) { 88 | reporterView && reporterView.complete(); 89 | }; 90 | 91 | self.reportSuiteResults = function(suite) { 92 | reporterView.suiteComplete(suite); 93 | }; 94 | 95 | self.reportSpecStarting = function(spec) { 96 | if (self.logRunningSpecs) { 97 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 98 | } 99 | }; 100 | 101 | self.reportSpecResults = function(spec) { 102 | reporterView.specComplete(spec); 103 | }; 104 | 105 | self.log = function() { 106 | var console = jasmine.getGlobal().console; 107 | if (console && console.log) { 108 | if (console.log.apply) { 109 | console.log.apply(console, arguments); 110 | } else { 111 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 112 | } 113 | } 114 | }; 115 | 116 | self.specFilter = function(spec) { 117 | if (!focusedSpecName()) { 118 | return true; 119 | } 120 | 121 | return spec.getFullName().indexOf(focusedSpecName()) === 0; 122 | }; 123 | 124 | return self; 125 | 126 | function focusedSpecName() { 127 | var specName; 128 | 129 | (function memoizeFocusedSpec() { 130 | if (specName) { 131 | return; 132 | } 133 | 134 | var paramMap = []; 135 | var params = jasmine.HtmlReporter.parameters(doc); 136 | 137 | for (var i = 0; i < params.length; i++) { 138 | var p = params[i].split('='); 139 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 140 | } 141 | 142 | specName = paramMap.spec; 143 | })(); 144 | 145 | return specName; 146 | } 147 | 148 | function createReporterDom(version) { 149 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, 150 | dom.banner = self.createDom('div', { className: 'banner' }, 151 | self.createDom('span', { className: 'title' }, "Jasmine "), 152 | self.createDom('span', { className: 'version' }, version)), 153 | 154 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), 155 | dom.alert = self.createDom('div', {className: 'alert'}, 156 | self.createDom('span', { className: 'exceptions' }, 157 | self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), 158 | self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), 159 | dom.results = self.createDom('div', {className: 'results'}, 160 | dom.summary = self.createDom('div', { className: 'summary' }), 161 | dom.details = self.createDom('div', { id: 'details' })) 162 | ); 163 | } 164 | 165 | function noTryCatch() { 166 | return window.location.search.match(/catch=false/); 167 | } 168 | 169 | function searchWithCatch() { 170 | var params = jasmine.HtmlReporter.parameters(window.document); 171 | var removed = false; 172 | var i = 0; 173 | 174 | while (!removed && i < params.length) { 175 | if (params[i].match(/catch=/)) { 176 | params.splice(i, 1); 177 | removed = true; 178 | } 179 | i++; 180 | } 181 | if (jasmine.CATCH_EXCEPTIONS) { 182 | params.push("catch=false"); 183 | } 184 | 185 | return params.join("&"); 186 | } 187 | 188 | function setExceptionHandling() { 189 | var chxCatch = document.getElementById('no_try_catch'); 190 | 191 | if (noTryCatch()) { 192 | chxCatch.setAttribute('checked', true); 193 | jasmine.CATCH_EXCEPTIONS = false; 194 | } 195 | chxCatch.onclick = function() { 196 | window.location.search = searchWithCatch(); 197 | }; 198 | } 199 | }; 200 | jasmine.HtmlReporter.parameters = function(doc) { 201 | var paramStr = doc.location.search.substring(1); 202 | var params = []; 203 | 204 | if (paramStr.length > 0) { 205 | params = paramStr.split('&'); 206 | } 207 | return params; 208 | } 209 | jasmine.HtmlReporter.sectionLink = function(sectionName) { 210 | var link = '?'; 211 | var params = []; 212 | 213 | if (sectionName) { 214 | params.push('spec=' + encodeURIComponent(sectionName)); 215 | } 216 | if (!jasmine.CATCH_EXCEPTIONS) { 217 | params.push("catch=false"); 218 | } 219 | if (params.length > 0) { 220 | link += params.join("&"); 221 | } 222 | 223 | return link; 224 | }; 225 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); 226 | jasmine.HtmlReporter.ReporterView = function(dom) { 227 | this.startedAt = new Date(); 228 | this.runningSpecCount = 0; 229 | this.completeSpecCount = 0; 230 | this.passedCount = 0; 231 | this.failedCount = 0; 232 | this.skippedCount = 0; 233 | 234 | this.createResultsMenu = function() { 235 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, 236 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), 237 | ' | ', 238 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); 239 | 240 | this.summaryMenuItem.onclick = function() { 241 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); 242 | }; 243 | 244 | this.detailsMenuItem.onclick = function() { 245 | showDetails(); 246 | }; 247 | }; 248 | 249 | this.addSpecs = function(specs, specFilter) { 250 | this.totalSpecCount = specs.length; 251 | 252 | this.views = { 253 | specs: {}, 254 | suites: {} 255 | }; 256 | 257 | for (var i = 0; i < specs.length; i++) { 258 | var spec = specs[i]; 259 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); 260 | if (specFilter(spec)) { 261 | this.runningSpecCount++; 262 | } 263 | } 264 | }; 265 | 266 | this.specComplete = function(spec) { 267 | this.completeSpecCount++; 268 | 269 | if (isUndefined(this.views.specs[spec.id])) { 270 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); 271 | } 272 | 273 | var specView = this.views.specs[spec.id]; 274 | 275 | switch (specView.status()) { 276 | case 'passed': 277 | this.passedCount++; 278 | break; 279 | 280 | case 'failed': 281 | this.failedCount++; 282 | break; 283 | 284 | case 'skipped': 285 | this.skippedCount++; 286 | break; 287 | } 288 | 289 | specView.refresh(); 290 | this.refresh(); 291 | }; 292 | 293 | this.suiteComplete = function(suite) { 294 | var suiteView = this.views.suites[suite.id]; 295 | if (isUndefined(suiteView)) { 296 | return; 297 | } 298 | suiteView.refresh(); 299 | }; 300 | 301 | this.refresh = function() { 302 | 303 | if (isUndefined(this.resultsMenu)) { 304 | this.createResultsMenu(); 305 | } 306 | 307 | // currently running UI 308 | if (isUndefined(this.runningAlert)) { 309 | this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); 310 | dom.alert.appendChild(this.runningAlert); 311 | } 312 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); 313 | 314 | // skipped specs UI 315 | if (isUndefined(this.skippedAlert)) { 316 | this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); 317 | } 318 | 319 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 320 | 321 | if (this.skippedCount === 1 && isDefined(dom.alert)) { 322 | dom.alert.appendChild(this.skippedAlert); 323 | } 324 | 325 | // passing specs UI 326 | if (isUndefined(this.passedAlert)) { 327 | this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); 328 | } 329 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); 330 | 331 | // failing specs UI 332 | if (isUndefined(this.failedAlert)) { 333 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); 334 | } 335 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); 336 | 337 | if (this.failedCount === 1 && isDefined(dom.alert)) { 338 | dom.alert.appendChild(this.failedAlert); 339 | dom.alert.appendChild(this.resultsMenu); 340 | } 341 | 342 | // summary info 343 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); 344 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; 345 | }; 346 | 347 | this.complete = function() { 348 | dom.alert.removeChild(this.runningAlert); 349 | 350 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 351 | 352 | if (this.failedCount === 0) { 353 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); 354 | } else { 355 | showDetails(); 356 | } 357 | 358 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); 359 | }; 360 | 361 | return this; 362 | 363 | function showDetails() { 364 | if (dom.reporter.className.search(/showDetails/) === -1) { 365 | dom.reporter.className += " showDetails"; 366 | } 367 | } 368 | 369 | function isUndefined(obj) { 370 | return typeof obj === 'undefined'; 371 | } 372 | 373 | function isDefined(obj) { 374 | return !isUndefined(obj); 375 | } 376 | 377 | function specPluralizedFor(count) { 378 | var str = count + " spec"; 379 | if (count > 1) { 380 | str += "s" 381 | } 382 | return str; 383 | } 384 | 385 | }; 386 | 387 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); 388 | 389 | 390 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) { 391 | this.spec = spec; 392 | this.dom = dom; 393 | this.views = views; 394 | 395 | this.symbol = this.createDom('li', { className: 'pending' }); 396 | this.dom.symbolSummary.appendChild(this.symbol); 397 | 398 | this.summary = this.createDom('div', { className: 'specSummary' }, 399 | this.createDom('a', { 400 | className: 'description', 401 | href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), 402 | title: this.spec.getFullName() 403 | }, this.spec.description) 404 | ); 405 | 406 | this.detail = this.createDom('div', { className: 'specDetail' }, 407 | this.createDom('a', { 408 | className: 'description', 409 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 410 | title: this.spec.getFullName() 411 | }, this.spec.getFullName()) 412 | ); 413 | }; 414 | 415 | jasmine.HtmlReporter.SpecView.prototype.status = function() { 416 | return this.getSpecStatus(this.spec); 417 | }; 418 | 419 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() { 420 | this.symbol.className = this.status(); 421 | 422 | switch (this.status()) { 423 | case 'skipped': 424 | break; 425 | 426 | case 'passed': 427 | this.appendSummaryToSuiteDiv(); 428 | break; 429 | 430 | case 'failed': 431 | this.appendSummaryToSuiteDiv(); 432 | this.appendFailureDetail(); 433 | break; 434 | } 435 | }; 436 | 437 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { 438 | this.summary.className += ' ' + this.status(); 439 | this.appendToSummary(this.spec, this.summary); 440 | }; 441 | 442 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { 443 | this.detail.className += ' ' + this.status(); 444 | 445 | var resultItems = this.spec.results().getItems(); 446 | var messagesDiv = this.createDom('div', { className: 'messages' }); 447 | 448 | for (var i = 0; i < resultItems.length; i++) { 449 | var result = resultItems[i]; 450 | 451 | if (result.type == 'log') { 452 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 453 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 454 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 455 | 456 | if (result.trace.stack) { 457 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 458 | } 459 | } 460 | } 461 | 462 | if (messagesDiv.childNodes.length > 0) { 463 | this.detail.appendChild(messagesDiv); 464 | this.dom.details.appendChild(this.detail); 465 | } 466 | }; 467 | 468 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { 469 | this.suite = suite; 470 | this.dom = dom; 471 | this.views = views; 472 | 473 | this.element = this.createDom('div', { className: 'suite' }, 474 | this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) 475 | ); 476 | 477 | this.appendToSummary(this.suite, this.element); 478 | }; 479 | 480 | jasmine.HtmlReporter.SuiteView.prototype.status = function() { 481 | return this.getSpecStatus(this.suite); 482 | }; 483 | 484 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { 485 | this.element.className += " " + this.status(); 486 | }; 487 | 488 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); 489 | 490 | /* @deprecated Use jasmine.HtmlReporter instead 491 | */ 492 | jasmine.TrivialReporter = function(doc) { 493 | this.document = doc || document; 494 | this.suiteDivs = {}; 495 | this.logRunningSpecs = false; 496 | }; 497 | 498 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 499 | var el = document.createElement(type); 500 | 501 | for (var i = 2; i < arguments.length; i++) { 502 | var child = arguments[i]; 503 | 504 | if (typeof child === 'string') { 505 | el.appendChild(document.createTextNode(child)); 506 | } else { 507 | if (child) { el.appendChild(child); } 508 | } 509 | } 510 | 511 | for (var attr in attrs) { 512 | if (attr == "className") { 513 | el[attr] = attrs[attr]; 514 | } else { 515 | el.setAttribute(attr, attrs[attr]); 516 | } 517 | } 518 | 519 | return el; 520 | }; 521 | 522 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 523 | var showPassed, showSkipped; 524 | 525 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, 526 | this.createDom('div', { className: 'banner' }, 527 | this.createDom('div', { className: 'logo' }, 528 | this.createDom('span', { className: 'title' }, "Jasmine"), 529 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 530 | this.createDom('div', { className: 'options' }, 531 | "Show ", 532 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 533 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 534 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 535 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 536 | ) 537 | ), 538 | 539 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 540 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 541 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 542 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 543 | ); 544 | 545 | this.document.body.appendChild(this.outerDiv); 546 | 547 | var suites = runner.suites(); 548 | for (var i = 0; i < suites.length; i++) { 549 | var suite = suites[i]; 550 | var suiteDiv = this.createDom('div', { className: 'suite' }, 551 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 552 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 553 | this.suiteDivs[suite.id] = suiteDiv; 554 | var parentDiv = this.outerDiv; 555 | if (suite.parentSuite) { 556 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 557 | } 558 | parentDiv.appendChild(suiteDiv); 559 | } 560 | 561 | this.startedAt = new Date(); 562 | 563 | var self = this; 564 | showPassed.onclick = function(evt) { 565 | if (showPassed.checked) { 566 | self.outerDiv.className += ' show-passed'; 567 | } else { 568 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 569 | } 570 | }; 571 | 572 | showSkipped.onclick = function(evt) { 573 | if (showSkipped.checked) { 574 | self.outerDiv.className += ' show-skipped'; 575 | } else { 576 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 577 | } 578 | }; 579 | }; 580 | 581 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 582 | var results = runner.results(); 583 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 584 | this.runnerDiv.setAttribute("class", className); 585 | //do it twice for IE 586 | this.runnerDiv.setAttribute("className", className); 587 | var specs = runner.specs(); 588 | var specCount = 0; 589 | for (var i = 0; i < specs.length; i++) { 590 | if (this.specFilter(specs[i])) { 591 | specCount++; 592 | } 593 | } 594 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 595 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 596 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 597 | 598 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 599 | }; 600 | 601 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 602 | var results = suite.results(); 603 | var status = results.passed() ? 'passed' : 'failed'; 604 | if (results.totalCount === 0) { // todo: change this to check results.skipped 605 | status = 'skipped'; 606 | } 607 | this.suiteDivs[suite.id].className += " " + status; 608 | }; 609 | 610 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 611 | if (this.logRunningSpecs) { 612 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 613 | } 614 | }; 615 | 616 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 617 | var results = spec.results(); 618 | var status = results.passed() ? 'passed' : 'failed'; 619 | if (results.skipped) { 620 | status = 'skipped'; 621 | } 622 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 623 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 624 | this.createDom('a', { 625 | className: 'description', 626 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 627 | title: spec.getFullName() 628 | }, spec.description)); 629 | 630 | 631 | var resultItems = results.getItems(); 632 | var messagesDiv = this.createDom('div', { className: 'messages' }); 633 | for (var i = 0; i < resultItems.length; i++) { 634 | var result = resultItems[i]; 635 | 636 | if (result.type == 'log') { 637 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 638 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 639 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 640 | 641 | if (result.trace.stack) { 642 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 643 | } 644 | } 645 | } 646 | 647 | if (messagesDiv.childNodes.length > 0) { 648 | specDiv.appendChild(messagesDiv); 649 | } 650 | 651 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 652 | }; 653 | 654 | jasmine.TrivialReporter.prototype.log = function() { 655 | var console = jasmine.getGlobal().console; 656 | if (console && console.log) { 657 | if (console.log.apply) { 658 | console.log.apply(console, arguments); 659 | } else { 660 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 661 | } 662 | } 663 | }; 664 | 665 | jasmine.TrivialReporter.prototype.getLocation = function() { 666 | return this.document.location; 667 | }; 668 | 669 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 670 | var paramMap = {}; 671 | var params = this.getLocation().search.substring(1).split('&'); 672 | for (var i = 0; i < params.length; i++) { 673 | var p = params[i].split('='); 674 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 675 | } 676 | 677 | if (!paramMap.spec) { 678 | return true; 679 | } 680 | return spec.getFullName().indexOf(paramMap.spec) === 0; 681 | }; 682 | -------------------------------------------------------------------------------- /spec/lib/jasmine-1.3.1/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 24 | #HTMLReporter .runningAlert { background-color: #666666; } 25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 28 | #HTMLReporter .passingAlert { background-color: #a6b779; } 29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 30 | #HTMLReporter .failingAlert { background-color: #cf867e; } 31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 32 | #HTMLReporter .results { margin-top: 14px; } 33 | #HTMLReporter #details { display: none; } 34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | #HTMLReporter.showDetails .summary { display: none; } 39 | #HTMLReporter.showDetails #details { display: block; } 40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | #HTMLReporter .summary { margin-top: 14px; } 42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 45 | #HTMLReporter .description + .suite { margin-top: 0; } 46 | #HTMLReporter .suite { margin-top: 14px; } 47 | #HTMLReporter .suite a { color: #333333; } 48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 51 | #HTMLReporter .resultMessage span.result { display: block; } 52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 53 | 54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 61 | #TrivialReporter .runner.running { background-color: yellow; } 62 | #TrivialReporter .options { text-align: right; font-size: .8em; } 63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 64 | #TrivialReporter .suite .suite { margin: 5px; } 65 | #TrivialReporter .suite.passed { background-color: #dfd; } 66 | #TrivialReporter .suite.failed { background-color: #fdd; } 67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 71 | #TrivialReporter .spec.skipped { background-color: #bbb; } 72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 73 | #TrivialReporter .passed { background-color: #cfc; display: none; } 74 | #TrivialReporter .failed { background-color: #fbb; } 75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 77 | #TrivialReporter .resultMessage .mismatch { color: black; } 78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 83 | -------------------------------------------------------------------------------- /spec/lib/jasmine-2.0.1/boot.js: -------------------------------------------------------------------------------- 1 | /** 2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. 3 | 4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. 5 | 6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. 7 | 8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem 9 | */ 10 | 11 | (function() { 12 | 13 | /** 14 | * ## Require & Instantiate 15 | * 16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. 17 | */ 18 | window.jasmine = jasmineRequire.core(jasmineRequire); 19 | 20 | /** 21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. 22 | */ 23 | jasmineRequire.html(jasmine); 24 | 25 | /** 26 | * Create the Jasmine environment. This is used to run all specs in a project. 27 | */ 28 | var env = jasmine.getEnv(); 29 | 30 | /** 31 | * ## The Global Interface 32 | * 33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. 34 | */ 35 | var jasmineInterface = { 36 | describe: function(description, specDefinitions) { 37 | return env.describe(description, specDefinitions); 38 | }, 39 | 40 | xdescribe: function(description, specDefinitions) { 41 | return env.xdescribe(description, specDefinitions); 42 | }, 43 | 44 | it: function(desc, func) { 45 | return env.it(desc, func); 46 | }, 47 | 48 | xit: function(desc, func) { 49 | return env.xit(desc, func); 50 | }, 51 | 52 | beforeEach: function(beforeEachFunction) { 53 | return env.beforeEach(beforeEachFunction); 54 | }, 55 | 56 | afterEach: function(afterEachFunction) { 57 | return env.afterEach(afterEachFunction); 58 | }, 59 | 60 | expect: function(actual) { 61 | return env.expect(actual); 62 | }, 63 | 64 | pending: function() { 65 | return env.pending(); 66 | }, 67 | 68 | spyOn: function(obj, methodName) { 69 | return env.spyOn(obj, methodName); 70 | }, 71 | 72 | jsApiReporter: new jasmine.JsApiReporter({ 73 | timer: new jasmine.Timer() 74 | }) 75 | }; 76 | 77 | /** 78 | * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. 79 | */ 80 | if (typeof window == "undefined" && typeof exports == "object") { 81 | extend(exports, jasmineInterface); 82 | } else { 83 | extend(window, jasmineInterface); 84 | } 85 | 86 | /** 87 | * Expose the interface for adding custom equality testers. 88 | */ 89 | jasmine.addCustomEqualityTester = function(tester) { 90 | env.addCustomEqualityTester(tester); 91 | }; 92 | 93 | /** 94 | * Expose the interface for adding custom expectation matchers 95 | */ 96 | jasmine.addMatchers = function(matchers) { 97 | return env.addMatchers(matchers); 98 | }; 99 | 100 | /** 101 | * Expose the mock interface for the JavaScript timeout functions 102 | */ 103 | jasmine.clock = function() { 104 | return env.clock; 105 | }; 106 | 107 | /** 108 | * ## Runner Parameters 109 | * 110 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. 111 | */ 112 | 113 | var queryString = new jasmine.QueryString({ 114 | getWindowLocation: function() { return window.location; } 115 | }); 116 | 117 | var catchingExceptions = queryString.getParam("catch"); 118 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); 119 | 120 | /** 121 | * ## Reporters 122 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). 123 | */ 124 | var htmlReporter = new jasmine.HtmlReporter({ 125 | env: env, 126 | onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); }, 127 | getContainer: function() { return document.body; }, 128 | createElement: function() { return document.createElement.apply(document, arguments); }, 129 | createTextNode: function() { return document.createTextNode.apply(document, arguments); }, 130 | timer: new jasmine.Timer() 131 | }); 132 | 133 | /** 134 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. 135 | */ 136 | env.addReporter(jasmineInterface.jsApiReporter); 137 | env.addReporter(htmlReporter); 138 | 139 | /** 140 | * Filter which specs will be run by matching the start of the full name against the `spec` query param. 141 | */ 142 | var specFilter = new jasmine.HtmlSpecFilter({ 143 | filterString: function() { return queryString.getParam("spec"); } 144 | }); 145 | 146 | env.specFilter = function(spec) { 147 | return specFilter.matches(spec.getFullName()); 148 | }; 149 | 150 | /** 151 | * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. 152 | */ 153 | window.setTimeout = window.setTimeout; 154 | window.setInterval = window.setInterval; 155 | window.clearTimeout = window.clearTimeout; 156 | window.clearInterval = window.clearInterval; 157 | 158 | /** 159 | * ## Execution 160 | * 161 | * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. 162 | */ 163 | var currentWindowOnload = window.onload; 164 | 165 | window.onload = function() { 166 | if (currentWindowOnload) { 167 | currentWindowOnload(); 168 | } 169 | htmlReporter.initialize(); 170 | env.execute(); 171 | }; 172 | 173 | /** 174 | * Helper function for readability above. 175 | */ 176 | function extend(destination, source) { 177 | for (var property in source) destination[property] = source[property]; 178 | return destination; 179 | } 180 | 181 | }()); 182 | -------------------------------------------------------------------------------- /spec/lib/jasmine-2.0.1/console.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2014 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | function getJasmineRequireObj() { 24 | if (typeof module !== 'undefined' && module.exports) { 25 | return exports; 26 | } else { 27 | window.jasmineRequire = window.jasmineRequire || {}; 28 | return window.jasmineRequire; 29 | } 30 | } 31 | 32 | getJasmineRequireObj().console = function(jRequire, j$) { 33 | j$.ConsoleReporter = jRequire.ConsoleReporter(); 34 | }; 35 | 36 | getJasmineRequireObj().ConsoleReporter = function() { 37 | 38 | var noopTimer = { 39 | start: function(){}, 40 | elapsed: function(){ return 0; } 41 | }; 42 | 43 | function ConsoleReporter(options) { 44 | var print = options.print, 45 | showColors = options.showColors || false, 46 | onComplete = options.onComplete || function() {}, 47 | timer = options.timer || noopTimer, 48 | specCount, 49 | failureCount, 50 | failedSpecs = [], 51 | pendingCount, 52 | ansi = { 53 | green: '\x1B[32m', 54 | red: '\x1B[31m', 55 | yellow: '\x1B[33m', 56 | none: '\x1B[0m' 57 | }; 58 | 59 | this.jasmineStarted = function() { 60 | specCount = 0; 61 | failureCount = 0; 62 | pendingCount = 0; 63 | print('Started'); 64 | printNewline(); 65 | timer.start(); 66 | }; 67 | 68 | this.jasmineDone = function() { 69 | printNewline(); 70 | for (var i = 0; i < failedSpecs.length; i++) { 71 | specFailureDetails(failedSpecs[i]); 72 | } 73 | 74 | if(specCount > 0) { 75 | printNewline(); 76 | 77 | var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + 78 | failureCount + ' ' + plural('failure', failureCount); 79 | 80 | if (pendingCount) { 81 | specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); 82 | } 83 | 84 | print(specCounts); 85 | } else { 86 | print('No specs found'); 87 | } 88 | 89 | printNewline(); 90 | var seconds = timer.elapsed() / 1000; 91 | print('Finished in ' + seconds + ' ' + plural('second', seconds)); 92 | 93 | printNewline(); 94 | 95 | onComplete(failureCount === 0); 96 | }; 97 | 98 | this.specDone = function(result) { 99 | specCount++; 100 | 101 | if (result.status == 'pending') { 102 | pendingCount++; 103 | print(colored('yellow', '*')); 104 | return; 105 | } 106 | 107 | if (result.status == 'passed') { 108 | print(colored('green', '.')); 109 | return; 110 | } 111 | 112 | if (result.status == 'failed') { 113 | failureCount++; 114 | failedSpecs.push(result); 115 | print(colored('red', 'F')); 116 | } 117 | }; 118 | 119 | return this; 120 | 121 | function printNewline() { 122 | print('\n'); 123 | } 124 | 125 | function colored(color, str) { 126 | return showColors ? (ansi[color] + str + ansi.none) : str; 127 | } 128 | 129 | function plural(str, count) { 130 | return count == 1 ? str : str + 's'; 131 | } 132 | 133 | function repeat(thing, times) { 134 | var arr = []; 135 | for (var i = 0; i < times; i++) { 136 | arr.push(thing); 137 | } 138 | return arr; 139 | } 140 | 141 | function indent(str, spaces) { 142 | var lines = (str || '').split('\n'); 143 | var newArr = []; 144 | for (var i = 0; i < lines.length; i++) { 145 | newArr.push(repeat(' ', spaces).join('') + lines[i]); 146 | } 147 | return newArr.join('\n'); 148 | } 149 | 150 | function specFailureDetails(result) { 151 | printNewline(); 152 | print(result.fullName); 153 | 154 | for (var i = 0; i < result.failedExpectations.length; i++) { 155 | var failedExpectation = result.failedExpectations[i]; 156 | printNewline(); 157 | print(indent(failedExpectation.stack, 2)); 158 | } 159 | 160 | printNewline(); 161 | } 162 | } 163 | 164 | return ConsoleReporter; 165 | }; 166 | -------------------------------------------------------------------------------- /spec/lib/jasmine-2.0.1/jasmine-html.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2014 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | jasmineRequire.html = function(j$) { 24 | j$.ResultsNode = jasmineRequire.ResultsNode(); 25 | j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); 26 | j$.QueryString = jasmineRequire.QueryString(); 27 | j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); 28 | }; 29 | 30 | jasmineRequire.HtmlReporter = function(j$) { 31 | 32 | var noopTimer = { 33 | start: function() {}, 34 | elapsed: function() { return 0; } 35 | }; 36 | 37 | function HtmlReporter(options) { 38 | var env = options.env || {}, 39 | getContainer = options.getContainer, 40 | createElement = options.createElement, 41 | createTextNode = options.createTextNode, 42 | onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, 43 | timer = options.timer || noopTimer, 44 | results = [], 45 | specsExecuted = 0, 46 | failureCount = 0, 47 | pendingSpecCount = 0, 48 | htmlReporterMain, 49 | symbols; 50 | 51 | this.initialize = function() { 52 | clearPrior(); 53 | htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'}, 54 | createDom('div', {className: 'banner'}, 55 | createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}), 56 | createDom('span', {className: 'version'}, j$.version) 57 | ), 58 | createDom('ul', {className: 'symbol-summary'}), 59 | createDom('div', {className: 'alert'}), 60 | createDom('div', {className: 'results'}, 61 | createDom('div', {className: 'failures'}) 62 | ) 63 | ); 64 | getContainer().appendChild(htmlReporterMain); 65 | 66 | symbols = find('.symbol-summary'); 67 | }; 68 | 69 | var totalSpecsDefined; 70 | this.jasmineStarted = function(options) { 71 | totalSpecsDefined = options.totalSpecsDefined || 0; 72 | timer.start(); 73 | }; 74 | 75 | var summary = createDom('div', {className: 'summary'}); 76 | 77 | var topResults = new j$.ResultsNode({}, '', null), 78 | currentParent = topResults; 79 | 80 | this.suiteStarted = function(result) { 81 | currentParent.addChild(result, 'suite'); 82 | currentParent = currentParent.last(); 83 | }; 84 | 85 | this.suiteDone = function(result) { 86 | if (currentParent == topResults) { 87 | return; 88 | } 89 | 90 | currentParent = currentParent.parent; 91 | }; 92 | 93 | this.specStarted = function(result) { 94 | currentParent.addChild(result, 'spec'); 95 | }; 96 | 97 | var failures = []; 98 | this.specDone = function(result) { 99 | if(noExpectations(result) && console && console.error) { 100 | console.error('Spec \'' + result.fullName + '\' has no expectations.'); 101 | } 102 | 103 | if (result.status != 'disabled') { 104 | specsExecuted++; 105 | } 106 | 107 | symbols.appendChild(createDom('li', { 108 | className: noExpectations(result) ? 'empty' : result.status, 109 | id: 'spec_' + result.id, 110 | title: result.fullName 111 | } 112 | )); 113 | 114 | if (result.status == 'failed') { 115 | failureCount++; 116 | 117 | var failure = 118 | createDom('div', {className: 'spec-detail failed'}, 119 | createDom('div', {className: 'description'}, 120 | createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName) 121 | ), 122 | createDom('div', {className: 'messages'}) 123 | ); 124 | var messages = failure.childNodes[1]; 125 | 126 | for (var i = 0; i < result.failedExpectations.length; i++) { 127 | var expectation = result.failedExpectations[i]; 128 | messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message)); 129 | messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack)); 130 | } 131 | 132 | failures.push(failure); 133 | } 134 | 135 | if (result.status == 'pending') { 136 | pendingSpecCount++; 137 | } 138 | }; 139 | 140 | this.jasmineDone = function() { 141 | var banner = find('.banner'); 142 | banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's')); 143 | 144 | var alert = find('.alert'); 145 | 146 | alert.appendChild(createDom('span', { className: 'exceptions' }, 147 | createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'), 148 | createDom('input', { 149 | className: 'raise', 150 | id: 'raise-exceptions', 151 | type: 'checkbox' 152 | }) 153 | )); 154 | var checkbox = find('#raise-exceptions'); 155 | 156 | checkbox.checked = !env.catchingExceptions(); 157 | checkbox.onclick = onRaiseExceptionsClick; 158 | 159 | if (specsExecuted < totalSpecsDefined) { 160 | var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; 161 | alert.appendChild( 162 | createDom('span', {className: 'bar skipped'}, 163 | createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage) 164 | ) 165 | ); 166 | } 167 | var statusBarMessage = ''; 168 | var statusBarClassName = 'bar '; 169 | 170 | if (totalSpecsDefined > 0) { 171 | statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); 172 | if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } 173 | statusBarClassName += (failureCount > 0) ? 'failed' : 'passed'; 174 | } else { 175 | statusBarClassName += 'skipped'; 176 | statusBarMessage += 'No specs found'; 177 | } 178 | 179 | alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); 180 | 181 | var results = find('.results'); 182 | results.appendChild(summary); 183 | 184 | summaryList(topResults, summary); 185 | 186 | function summaryList(resultsTree, domParent) { 187 | var specListNode; 188 | for (var i = 0; i < resultsTree.children.length; i++) { 189 | var resultNode = resultsTree.children[i]; 190 | if (resultNode.type == 'suite') { 191 | var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id}, 192 | createDom('li', {className: 'suite-detail'}, 193 | createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) 194 | ) 195 | ); 196 | 197 | summaryList(resultNode, suiteListNode); 198 | domParent.appendChild(suiteListNode); 199 | } 200 | if (resultNode.type == 'spec') { 201 | if (domParent.getAttribute('class') != 'specs') { 202 | specListNode = createDom('ul', {className: 'specs'}); 203 | domParent.appendChild(specListNode); 204 | } 205 | var specDescription = resultNode.result.description; 206 | if(noExpectations(resultNode.result)) { 207 | specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; 208 | } 209 | specListNode.appendChild( 210 | createDom('li', { 211 | className: resultNode.result.status, 212 | id: 'spec-' + resultNode.result.id 213 | }, 214 | createDom('a', {href: specHref(resultNode.result)}, specDescription) 215 | ) 216 | ); 217 | } 218 | } 219 | } 220 | 221 | if (failures.length) { 222 | alert.appendChild( 223 | createDom('span', {className: 'menu bar spec-list'}, 224 | createDom('span', {}, 'Spec List | '), 225 | createDom('a', {className: 'failures-menu', href: '#'}, 'Failures'))); 226 | alert.appendChild( 227 | createDom('span', {className: 'menu bar failure-list'}, 228 | createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'), 229 | createDom('span', {}, ' | Failures '))); 230 | 231 | find('.failures-menu').onclick = function() { 232 | setMenuModeTo('failure-list'); 233 | }; 234 | find('.spec-list-menu').onclick = function() { 235 | setMenuModeTo('spec-list'); 236 | }; 237 | 238 | setMenuModeTo('failure-list'); 239 | 240 | var failureNode = find('.failures'); 241 | for (var i = 0; i < failures.length; i++) { 242 | failureNode.appendChild(failures[i]); 243 | } 244 | } 245 | }; 246 | 247 | return this; 248 | 249 | function find(selector) { 250 | return getContainer().querySelector('.jasmine_html-reporter ' + selector); 251 | } 252 | 253 | function clearPrior() { 254 | // return the reporter 255 | var oldReporter = find(''); 256 | 257 | if(oldReporter) { 258 | getContainer().removeChild(oldReporter); 259 | } 260 | } 261 | 262 | function createDom(type, attrs, childrenVarArgs) { 263 | var el = createElement(type); 264 | 265 | for (var i = 2; i < arguments.length; i++) { 266 | var child = arguments[i]; 267 | 268 | if (typeof child === 'string') { 269 | el.appendChild(createTextNode(child)); 270 | } else { 271 | if (child) { 272 | el.appendChild(child); 273 | } 274 | } 275 | } 276 | 277 | for (var attr in attrs) { 278 | if (attr == 'className') { 279 | el[attr] = attrs[attr]; 280 | } else { 281 | el.setAttribute(attr, attrs[attr]); 282 | } 283 | } 284 | 285 | return el; 286 | } 287 | 288 | function pluralize(singular, count) { 289 | var word = (count == 1 ? singular : singular + 's'); 290 | 291 | return '' + count + ' ' + word; 292 | } 293 | 294 | function specHref(result) { 295 | return '?spec=' + encodeURIComponent(result.fullName); 296 | } 297 | 298 | function setMenuModeTo(mode) { 299 | htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode); 300 | } 301 | 302 | function noExpectations(result) { 303 | return (result.failedExpectations.length + result.passedExpectations.length) === 0 && 304 | result.status === 'passed'; 305 | } 306 | } 307 | 308 | return HtmlReporter; 309 | }; 310 | 311 | jasmineRequire.HtmlSpecFilter = function() { 312 | function HtmlSpecFilter(options) { 313 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); 314 | var filterPattern = new RegExp(filterString); 315 | 316 | this.matches = function(specName) { 317 | return filterPattern.test(specName); 318 | }; 319 | } 320 | 321 | return HtmlSpecFilter; 322 | }; 323 | 324 | jasmineRequire.ResultsNode = function() { 325 | function ResultsNode(result, type, parent) { 326 | this.result = result; 327 | this.type = type; 328 | this.parent = parent; 329 | 330 | this.children = []; 331 | 332 | this.addChild = function(result, type) { 333 | this.children.push(new ResultsNode(result, type, this)); 334 | }; 335 | 336 | this.last = function() { 337 | return this.children[this.children.length - 1]; 338 | }; 339 | } 340 | 341 | return ResultsNode; 342 | }; 343 | 344 | jasmineRequire.QueryString = function() { 345 | function QueryString(options) { 346 | 347 | this.setParam = function(key, value) { 348 | var paramMap = queryStringToParamMap(); 349 | paramMap[key] = value; 350 | options.getWindowLocation().search = toQueryString(paramMap); 351 | }; 352 | 353 | this.getParam = function(key) { 354 | return queryStringToParamMap()[key]; 355 | }; 356 | 357 | return this; 358 | 359 | function toQueryString(paramMap) { 360 | var qStrPairs = []; 361 | for (var prop in paramMap) { 362 | qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])); 363 | } 364 | return '?' + qStrPairs.join('&'); 365 | } 366 | 367 | function queryStringToParamMap() { 368 | var paramStr = options.getWindowLocation().search.substring(1), 369 | params = [], 370 | paramMap = {}; 371 | 372 | if (paramStr.length > 0) { 373 | params = paramStr.split('&'); 374 | for (var i = 0; i < params.length; i++) { 375 | var p = params[i].split('='); 376 | var value = decodeURIComponent(p[1]); 377 | if (value === 'true' || value === 'false') { 378 | value = JSON.parse(value); 379 | } 380 | paramMap[decodeURIComponent(p[0])] = value; 381 | } 382 | } 383 | 384 | return paramMap; 385 | } 386 | 387 | } 388 | 389 | return QueryString; 390 | }; 391 | -------------------------------------------------------------------------------- /spec/lib/jasmine-2.0.1/jasmine.css: -------------------------------------------------------------------------------- 1 | body { overflow-y: scroll; } 2 | 3 | .jasmine_html-reporter { background-color: #eeeeee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | .jasmine_html-reporter a { text-decoration: none; } 5 | .jasmine_html-reporter a:hover { text-decoration: underline; } 6 | .jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; } 7 | .jasmine_html-reporter .banner, .jasmine_html-reporter .symbol-summary, .jasmine_html-reporter .summary, .jasmine_html-reporter .result-message, .jasmine_html-reporter .spec .description, .jasmine_html-reporter .spec-detail .description, .jasmine_html-reporter .alert .bar, .jasmine_html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; } 8 | .jasmine_html-reporter .banner { position: relative; } 9 | .jasmine_html-reporter .banner .title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -webkit-background-size: 100%; -moz-background-size: 100%; -o-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; } 10 | .jasmine_html-reporter .banner .version { margin-left: 14px; position: relative; top: 6px; } 11 | .jasmine_html-reporter .banner .duration { position: absolute; right: 14px; top: 6px; } 12 | .jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; } 13 | .jasmine_html-reporter .version { color: #aaaaaa; } 14 | .jasmine_html-reporter .banner { margin-top: 14px; } 15 | .jasmine_html-reporter .duration { color: #aaaaaa; float: right; } 16 | .jasmine_html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; } 17 | .jasmine_html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; } 18 | .jasmine_html-reporter .symbol-summary li.passed { font-size: 14px; } 19 | .jasmine_html-reporter .symbol-summary li.passed:before { color: #007069; content: "\02022"; } 20 | .jasmine_html-reporter .symbol-summary li.failed { line-height: 9px; } 21 | .jasmine_html-reporter .symbol-summary li.failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; } 22 | .jasmine_html-reporter .symbol-summary li.disabled { font-size: 14px; } 23 | .jasmine_html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; } 24 | .jasmine_html-reporter .symbol-summary li.pending { line-height: 17px; } 25 | .jasmine_html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; } 26 | .jasmine_html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 27 | .jasmine_html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 28 | .jasmine_html-reporter .bar.failed { background-color: #ca3a11; } 29 | .jasmine_html-reporter .bar.passed { background-color: #007069; } 30 | .jasmine_html-reporter .bar.skipped { background-color: #bababa; } 31 | .jasmine_html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; } 32 | .jasmine_html-reporter .bar.menu a { color: #333333; } 33 | .jasmine_html-reporter .bar a { color: white; } 34 | .jasmine_html-reporter.spec-list .bar.menu.failure-list, .jasmine_html-reporter.spec-list .results .failures { display: none; } 35 | .jasmine_html-reporter.failure-list .bar.menu.spec-list, .jasmine_html-reporter.failure-list .summary { display: none; } 36 | .jasmine_html-reporter .running-alert { background-color: #666666; } 37 | .jasmine_html-reporter .results { margin-top: 14px; } 38 | .jasmine_html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 39 | .jasmine_html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 40 | .jasmine_html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 41 | .jasmine_html-reporter.showDetails .summary { display: none; } 42 | .jasmine_html-reporter.showDetails #details { display: block; } 43 | .jasmine_html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 44 | .jasmine_html-reporter .summary { margin-top: 14px; } 45 | .jasmine_html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; } 46 | .jasmine_html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; } 47 | .jasmine_html-reporter .summary li.passed a { color: #007069; } 48 | .jasmine_html-reporter .summary li.failed a { color: #ca3a11; } 49 | .jasmine_html-reporter .summary li.empty a { color: #ba9d37; } 50 | .jasmine_html-reporter .summary li.pending a { color: #ba9d37; } 51 | .jasmine_html-reporter .description + .suite { margin-top: 0; } 52 | .jasmine_html-reporter .suite { margin-top: 14px; } 53 | .jasmine_html-reporter .suite a { color: #333333; } 54 | .jasmine_html-reporter .failures .spec-detail { margin-bottom: 28px; } 55 | .jasmine_html-reporter .failures .spec-detail .description { background-color: #ca3a11; } 56 | .jasmine_html-reporter .failures .spec-detail .description a { color: white; } 57 | .jasmine_html-reporter .result-message { padding-top: 14px; color: #333333; white-space: pre; } 58 | .jasmine_html-reporter .result-message span.result { display: block; } 59 | .jasmine_html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 60 | -------------------------------------------------------------------------------- /spec/lib/jasmine-2.0.1/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devbridge/jQuery-Autocomplete/f97172ff73a6d55e376f225470cba2d4a91d1ba7/spec/lib/jasmine-2.0.1/jasmine_favicon.png -------------------------------------------------------------------------------- /spec/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Autocomplete Spec 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /typings/jquery.autocomplete-tests.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // ---------------------------------------------------------------------------------------- 4 | // --------------------------------- WEBSITE EXAMPLE -------------------------------------- 5 | // --------------- https://devbridge.github.io/jQuery-Autocomplete/ ----------------------- 6 | // ---------------------------------------------------------------------------------------- 7 | 8 | var input = $('#autocomplete'); 9 | var options = {}; 10 | 11 | input.autocomplete('disable'); 12 | input.autocomplete('setOptions', options); 13 | 14 | input.autocomplete().disable(); 15 | input.autocomplete().setOptions(options); 16 | 17 | // Ajax lookup: 18 | input.autocomplete({ 19 | serviceUrl: '/autocomplete/countries', 20 | onSelect: function (suggestion) { 21 | alert('You selected: ' + suggestion.value + ', ' + suggestion.data); 22 | }, 23 | }); 24 | 25 | // Local lookup (no ajax): 26 | var countries = [ 27 | { value: 'Andorra', data: 'AD' }, 28 | // ... 29 | { value: 'Zimbabwe', data: 'ZZ' }, 30 | ]; 31 | 32 | input.autocomplete({ 33 | lookup: countries, 34 | onSelect: function (suggestion) { 35 | alert('You selected: ' + suggestion.value + ', ' + suggestion.data); 36 | }, 37 | }); 38 | 39 | // Non standard query/results 40 | input.autocomplete({ 41 | paramName: 'searchString', 42 | transformResult: function (response: any, originalQuery: string): AutocompleteResponse { 43 | return { 44 | suggestions: $.map(response.myData, function (dataItem) { 45 | return { value: dataItem.valueField, data: dataItem.dataField }; 46 | }), 47 | }; 48 | }, 49 | }); 50 | 51 | // ---------------------------------------------------------------------------------------- 52 | // ----------------------------- TEST AUTOCOMPLETE STATIC --------------------------------- 53 | // ---------------------------------------------------------------------------------------- 54 | 55 | $.Autocomplete.defaults; 56 | 57 | // ---------------------------------------------------------------------------------------- 58 | // ------------------------------ TEST INSTANCE METHODS ----------------------------------- 59 | // ---------------------------------------------------------------------------------------- 60 | 61 | input.autocomplete().setOptions(options); 62 | input.autocomplete().clear(); 63 | input.autocomplete().clearCache(); 64 | input.autocomplete().disable(); 65 | input.autocomplete().enable(); 66 | input.autocomplete().hide(); 67 | input.autocomplete().dispose(); 68 | 69 | // ---------------------------------------------------------------------------------------- 70 | // ------------------------------ TEST DEFAULT OPTIONS ------------------------------------ 71 | // ---------------------------------------------------------------------------------------- 72 | 73 | input.autocomplete({ 74 | //----------------o AJAX SETTINGS 75 | 76 | serviceUrl: '/autocomplete/countries', 77 | type: 'GET', 78 | dataType: 'text', 79 | paramName: 'query', 80 | params: {}, 81 | deferRequestBy: 0, 82 | ajaxSettings: {}, 83 | 84 | //----------------o CONFIG SETTINGS 85 | 86 | noCache: false, 87 | delimiter: '-', 88 | onSearchStart(query: string) { 89 | console.log('query: ', query); 90 | }, 91 | onSearchComplete(query: string, suggestions: AutocompleteSuggestion[]) { 92 | console.log('query: ', query); 93 | console.log('suggestions: ', suggestions); 94 | }, 95 | onSearchError(query: string, jqXHR: JQueryXHR, textStatus: string, errorThrown: any) { 96 | console.log('query: ', query); 97 | console.log('jqXHR: ', jqXHR); 98 | console.log('textStatus: ', textStatus); 99 | console.log('errorThrown: ', errorThrown); 100 | }, 101 | transformResult(response: any, originalQuery: string): AutocompleteResponse { 102 | return { 103 | suggestions: [ 104 | { value: 'Andorra', data: 'AD' }, 105 | // ... 106 | { value: 'Zimbabwe', data: 'ZZ' }, 107 | ], 108 | }; 109 | }, 110 | onSelect(suggestion: AutocompleteSuggestion) { 111 | console.log('suggestions: ', suggestion); 112 | }, 113 | minChars: 1, 114 | lookupLimit: 1, 115 | lookup: [ 116 | { value: 'Andorra', data: 'AD' }, 117 | // ... 118 | { value: 'Zimbabwe', data: 'ZZ' }, 119 | ], 120 | lookupFilter(suggestion: AutocompleteSuggestion, query: string, queryLowercase: string): any { 121 | return query !== 'query'; 122 | }, 123 | triggerSelectOnValidInput: true, 124 | preventBadQueries: true, 125 | autoSelectFirst: false, 126 | onHide(container: any) { 127 | console.log('container: ', container); 128 | }, 129 | 130 | //----------------o PRESENTATION SETTINGS 131 | 132 | beforeRender(container: any) { 133 | console.log('container: ', container); 134 | }, 135 | formatResult(suggestion: AutocompleteSuggestion, currentValue: string): string { 136 | return currentValue; 137 | }, 138 | groupBy: 'category', 139 | maxHeight: 300, 140 | width: 'auto', 141 | zIndex: 9999, 142 | appendTo: document.body, 143 | forceFixPosition: false, 144 | orientation: 'bottom', 145 | preserveInput: false, 146 | showNoSuggestionNotice: false, 147 | noSuggestionNotice: 'No results', 148 | onInvalidateSelection() { 149 | console.log('onInvalidateSelection'); 150 | }, 151 | tabDisabled: false, 152 | }); 153 | -------------------------------------------------------------------------------- /typings/jquery.autocomplete.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for jQuery-Autocomplete 1.4.11 2 | // Project: https://github.com/devbridge/jQuery-Autocomplete 3 | // Definitions by: John Gouigouix 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | /// 7 | 8 | interface AutocompleteSuggestion { 9 | value: string; 10 | data: any; 11 | } 12 | 13 | interface AutocompleteResponse { 14 | suggestions: AutocompleteSuggestion[]; 15 | } 16 | 17 | interface JQueryAutocompleteOptions { 18 | //----------------o AJAX SETTINGS 19 | 20 | /** 21 | * Server side URL or callback function that returns serviceUrl string. Optional if local lookup data is provided. 22 | */ 23 | serviceUrl?: string; 24 | 25 | /** 26 | * Ajax request type to get suggestions. 27 | * @default "GET" 28 | */ 29 | type?: string; 30 | 31 | /** 32 | * type of data returned from server. Either text, json or jsonp, which will cause the autocomplete to use jsonp. 33 | * You may return a json object in your callback when using jsonp. 34 | * @default "text" 35 | */ 36 | dataType?: 'text' | 'json' | 'jsonp'; 37 | 38 | /** 39 | * The name of the request parameter that contains the query. 40 | * @default "query" 41 | */ 42 | paramName?: string; 43 | 44 | /** 45 | * Additional parameters to pass with the request, optional. 46 | */ 47 | params?: Object; 48 | 49 | /** 50 | * Number of miliseconds to defer ajax request. 51 | * @default 0 52 | */ 53 | deferRequestBy?: number; 54 | 55 | /** 56 | * Any additional Ajax Settings that configure the jQuery Ajax request. 57 | */ 58 | ajaxSettings?: JQueryAjaxSettings; 59 | 60 | //----------------o CONFIG SETTINGS 61 | 62 | /** 63 | * Boolean value indicating whether to cache suggestion results. 64 | * @default false 65 | */ 66 | noCache?: boolean; 67 | 68 | /** 69 | * That splits input value and takes last part to as query for suggestions. 70 | * Useful when for example you need to fill list of coma separated values. 71 | */ 72 | delimiter?: string | RegExp; 73 | 74 | /** 75 | * Called before ajax request. this is bound to input element. 76 | * @param query 77 | */ 78 | onSearchStart?(query: string): void; 79 | 80 | /** 81 | * Called after ajax response is processed. this is bound to input element. 82 | * Suggestions is an array containing the results. 83 | * @param query 84 | * @param suggestions 85 | */ 86 | onSearchComplete?(query: string, suggestions: AutocompleteSuggestion[]): void; 87 | 88 | /** 89 | * Called if ajax request fails. this is bound to input element. 90 | * @param query 91 | * @param jqXHR 92 | * @param textStatus 93 | * @param errorThrown 94 | */ 95 | onSearchError?(query: string, jqXHR: JQueryXHR, textStatus: string, errorThrown: any): void; 96 | 97 | /** 98 | * Called after the result of the query is ready. Converts the result into response.suggestions format. 99 | * @param response 100 | * @param originalQuery 101 | */ 102 | transformResult?(response: any, originalQuery: string): AutocompleteResponse; 103 | 104 | /** 105 | * Callback function invoked when user selects suggestion from the list. 106 | * This inside callback refers to input HtmlElement. 107 | * @param suggestion 108 | */ 109 | onSelect?(suggestion: AutocompleteSuggestion): void; 110 | 111 | /** 112 | * Minimum number of characters required to trigger autosuggest. 113 | * @default 1 114 | */ 115 | minChars?: number; 116 | 117 | /** 118 | * Number of maximum results to display for local lookup. 119 | * @default no limit 120 | */ 121 | lookupLimit?: number; 122 | 123 | /** 124 | * Callback function or lookup array for the suggestions. It may be array of strings or suggestion object literals. 125 | * -> suggestion: An object literal with the following format: { value: 'string', data: any }. 126 | */ 127 | lookup?: 128 | | { (query: string, done: { (results: AutocompleteResponse): void }): void } 129 | | string[] 130 | | AutocompleteSuggestion[]; 131 | 132 | /** 133 | * Filter function for local lookups. By default it does partial string match (case insensitive). 134 | * @param suggestion 135 | * @param query 136 | * @param queryLowercase 137 | */ 138 | lookupFilter?(suggestion: AutocompleteSuggestion, query: string, queryLowercase: string): any; 139 | 140 | /** 141 | * Boolean value indicating if select should be triggered if it matches suggestion. 142 | * @default true 143 | */ 144 | triggerSelectOnValidInput?: boolean; 145 | 146 | /** 147 | * Boolean value indicating if it shoud prevent future ajax requests for queries with the same root if no results were returned. 148 | * E.g. if Jam returns no suggestions, it will not fire for any future query that starts with Jam. 149 | * @default true 150 | */ 151 | preventBadQueries?: boolean; 152 | 153 | /** 154 | * If set to true, first item will be selected when showing suggestions. 155 | * @default false 156 | */ 157 | autoSelectFirst?: boolean; 158 | 159 | /** 160 | * Called before container will be hidden 161 | * @param container 162 | */ 163 | onHide?(container: any): void; 164 | 165 | //----------------o PRESENTATION SETTINGS 166 | 167 | /** 168 | * Called before displaying the suggestions. You may manipulate suggestions DOM before it is displayed. 169 | * @param container 170 | */ 171 | beforeRender?(container: any): void; 172 | 173 | /** 174 | * Custom function to format suggestion entry inside suggestions container, optional. 175 | * @param suggestion 176 | * @param currentValue 177 | */ 178 | formatResult?(suggestion: AutocompleteSuggestion, currentValue: string): string; 179 | 180 | /** 181 | * Property name of the suggestion data object, by which results should be grouped. 182 | */ 183 | groupBy?: string; 184 | 185 | /** 186 | * Maximum height of the suggestions container in pixels. 187 | * @default 300 188 | */ 189 | maxHeight?: number; 190 | 191 | /** 192 | * Suggestions container width in pixels, e.g.: 300. takes input field width. 193 | * @default "auto" 194 | */ 195 | width?: string | number; 196 | 197 | /** 198 | * 'z-index' for suggestions container. 199 | * @default 9999 200 | */ 201 | zIndex?: number; 202 | 203 | /** 204 | * Container where suggestions will be appended. Can be jQuery object, selector or html element. 205 | * Make sure to set position: absolute or position: relative for that element. 206 | * @default document.body 207 | */ 208 | appendTo?: any; 209 | 210 | /** 211 | * Suggestions are automatically positioned when their container is appended to body (look at appendTo option), 212 | * in other cases suggestions are rendered but no positioning is applied. 213 | * Set this option to force auto positioning in other cases. 214 | * @default false 215 | */ 216 | forceFixPosition?: boolean; 217 | 218 | /** 219 | * Vertical orientation of the displayed suggestions, available values are auto, top, bottom. 220 | * If set to auto, the suggestions will be orientated it the way that place them closer to middle of the view port. 221 | * @default "bottom" 222 | */ 223 | orientation?: 'bottom' | 'auto' | 'top'; 224 | 225 | /** 226 | * If true, input value stays the same when navigating over suggestions. 227 | * @default false 228 | */ 229 | preserveInput?: boolean; 230 | 231 | /** 232 | * When no matching results, display a notification label. 233 | * @default false 234 | */ 235 | showNoSuggestionNotice?: boolean; 236 | 237 | /** 238 | * Text or htmlString or Element or jQuery object for no matching results label. 239 | * @default "No results" 240 | */ 241 | noSuggestionNotice?: string | Element | JQuery; 242 | 243 | /** 244 | * Called when input is altered after selection has been made. this is bound to input element. 245 | */ 246 | onInvalidateSelection?(): void; 247 | 248 | /** 249 | * Set to true to leave the cursor in the input field after the user tabs to select a suggestion. 250 | * @default false 251 | */ 252 | tabDisabled?: boolean; 253 | } 254 | 255 | interface AutocompleteStatic { 256 | /** 257 | * Default options for all instances. 258 | */ 259 | defaults: JQueryAutocompleteOptions; 260 | } 261 | 262 | interface AutocompleteInstance { 263 | /** 264 | * you may update any option at any time. Options are listed above. 265 | * @param options 266 | */ 267 | setOptions(options: JQueryAutocompleteOptions): void; 268 | 269 | /** 270 | * clears suggestion cache and current suggestions suggestions. 271 | */ 272 | clear(): void; 273 | 274 | /** 275 | * clears suggestion cache. 276 | */ 277 | clearCache(): void; 278 | 279 | /** 280 | * deactivate autocomplete. 281 | */ 282 | disable(): void; 283 | 284 | /** 285 | * activates autocomplete if it was deactivated before. 286 | */ 287 | enable(): void; 288 | 289 | /** 290 | * hides suggestions. 291 | */ 292 | hide(): void; 293 | 294 | /** 295 | * destroys autocomplete instance. All events are detached and suggestion containers removed. 296 | */ 297 | dispose(): void; 298 | } 299 | 300 | interface JQueryStatic { 301 | Autocomplete: AutocompleteStatic; 302 | } 303 | 304 | interface JQuery { 305 | /** 306 | * Create Autocomplete component 307 | */ 308 | autocomplete(options?: JQueryAutocompleteOptions): AutocompleteInstance; 309 | 310 | /** 311 | * Trigger non-specialized signature method 312 | * @param methodName 313 | * @param arg 314 | */ 315 | autocomplete(methodName: string, ...arg: any[]): any; 316 | 317 | /** 318 | * You may update any option at any time. Options are listed above. 319 | * @param methodName The name of the method 320 | * @param options 321 | */ 322 | autocomplete( 323 | methodName: 'setOptions', 324 | options: JQueryAutocompleteOptions, 325 | ): AutocompleteInstance; 326 | 327 | /** 328 | * Clears suggestion cache and current suggestions suggestions. 329 | * @param methodName The name of the method 330 | */ 331 | autocomplete(methodName: 'clear'): AutocompleteInstance; 332 | 333 | /** 334 | * Clears suggestion cache. 335 | * @param methodName The name of the method 336 | */ 337 | autocomplete(methodName: 'clearCache'): AutocompleteInstance; 338 | 339 | /** 340 | * Deactivate autocomplete. 341 | * @param methodName The name of the method 342 | */ 343 | autocomplete(methodName: 'disable'): AutocompleteInstance; 344 | 345 | /** 346 | * Activates autocomplete if it was deactivated before. 347 | * @param methodName The name of the method 348 | */ 349 | autocomplete(methodName: 'enable'): AutocompleteInstance; 350 | 351 | /** 352 | * Hides suggestions. 353 | * @param methodName The name of the method 354 | */ 355 | autocomplete(methodName: 'hide'): AutocompleteInstance; 356 | 357 | /** 358 | * Destroys autocomplete instance. All events are detached and suggestion containers removed. 359 | * @param methodName The name of the method 360 | */ 361 | autocomplete(methodName: 'dispose'): AutocompleteInstance; 362 | 363 | /** 364 | * Create Autocomplete component via plugin alias 365 | */ 366 | devbridgeAutocomplete(options?: JQueryAutocompleteOptions): AutocompleteInstance; 367 | 368 | /** 369 | * Trigger non-specialized signature method 370 | * @param methodName 371 | * @param arg 372 | */ 373 | devbridgeAutocomplete(methodName: string, ...arg: any[]): any; 374 | 375 | /** 376 | * You may update any option at any time. Options are listed above. 377 | * @param methodName The name of the method 378 | * @param options 379 | */ 380 | devbridgeAutocomplete( 381 | methodName: 'setOptions', 382 | options: JQueryAutocompleteOptions, 383 | ): AutocompleteInstance; 384 | 385 | /** 386 | * Clears suggestion cache and current suggestions suggestions. 387 | * @param methodName The name of the method 388 | */ 389 | devbridgeAutocomplete(methodName: 'clear'): AutocompleteInstance; 390 | 391 | /** 392 | * Clears suggestion cache. 393 | * @param methodName The name of the method 394 | */ 395 | devbridgeAutocomplete(methodName: 'clearCache'): AutocompleteInstance; 396 | 397 | /** 398 | * Deactivate autocomplete. 399 | * @param methodName The name of the method 400 | */ 401 | devbridgeAutocomplete(methodName: 'disable'): AutocompleteInstance; 402 | 403 | /** 404 | * Activates autocomplete if it was deactivated before. 405 | * @param methodName The name of the method 406 | */ 407 | devbridgeAutocomplete(methodName: 'enable'): AutocompleteInstance; 408 | 409 | /** 410 | * Hides suggestions. 411 | * @param methodName The name of the method 412 | */ 413 | devbridgeAutocomplete(methodName: 'hide'): AutocompleteInstance; 414 | 415 | /** 416 | * Destroys autocomplete instance. All events are detached and suggestion containers removed. 417 | * @param methodName The name of the method 418 | */ 419 | devbridgeAutocomplete(methodName: 'dispose'): AutocompleteInstance; 420 | } 421 | --------------------------------------------------------------------------------