vendor: 26,008 bytes, lines 1-941
1/*! 2 * typeahead.js 0.11.1 3 * https://github.com/twitter/typeahead.js 4 * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT 5 */ 6 7(function (root, factory) { 8 if (typeof define === "function" && define.amd) { 9 define("bloodhound", ["jquery"], function (a0) { 10 return root["Bloodhound"] = factory(a0); 11 }); 12 } else if (typeof exports === "object") { 13 module.exports = factory(require("jquery")); 14 } else { 15 root["Bloodhound"] = factory(jQuery); 16 } 17})(this, function ($) { 18 var _ = function () { 19 "use strict"; 20 return { 21 isMsie: function () { 22 return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; 23 }, 24 isBlankString: function (str) { 25 return !str || /^\s*$/.test(str); 26 }, 27 escapeRegExChars: function (str) { 28 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 29 }, 30 isString: function (obj) { 31 return typeof obj === "string"; 32 }, 33 isNumber: function (obj) { 34 return typeof obj === "number"; 35 }, 36 isArray: $.isArray, 37 isFunction: $.isFunction, 38 isObject: $.isPlainObject, 39 isUndefined: function (obj) { 40 return typeof obj === "undefined"; 41 }, 42 isElement: function (obj) { 43 return !!(obj && obj.nodeType === 1); 44 }, 45 isJQuery: function (obj) { 46 return obj instanceof $; 47 }, 48 toStr: function toStr(s) { 49 return _.isUndefined(s) || s === null ? "" : s + ""; 50 }, 51 bind: $.proxy, 52 each: function (collection, cb) { 53 $.each(collection, reverseArgs); 54 55 function reverseArgs(index, value) { 56 return cb(value, index); 57 } 58 }, 59 map: $.map, 60 filter: $.grep, 61 every: function (obj, test) { 62 var result = true; 63 if (!obj) { 64 return result; 65 } 66 $.each(obj, function (key, val) { 67 if (!(result = test.call(null, val, key, obj))) { 68 return false; 69 } 70 }); 71 return !!result; 72 }, 73 some: function (obj, test) { 74 var result = false; 75 if (!obj) { 76 return result; 77 } 78 $.each(obj, function (key, val) { 79 if (result = test.call(null, val, key, obj)) { 80 return false; 81 } 82 }); 83 return !!result; 84 }, 85 mixin: $.extend, 86 identity: function (x) { 87 return x; 88 }, 89 clone: function (obj) { 90 return $.extend(true, {}, obj); 91 }, 92 getIdGenerator: function () { 93 var counter = 0; 94 return function () { 95 return counter++; 96 }; 97 }, 98 templatify: function templatify(obj) { 99 return $.isFunction(obj) ? obj : template; 100 101 function template() { 102 return String(obj); 103 } 104 }, 105 defer: function (fn) { 106 setTimeout(fn, 0); 107 }, 108 debounce: function (func, wait, immediate) { 109 var timeout, result; 110 return function () { 111 var context = this, args = arguments, later, callNow; 112 later = function () { 113 timeout = null; 114 if (!immediate) { 115 result = func.apply(context, args); 116 } 117 }; 118 callNow = immediate && !timeout; 119 clearTimeout(timeout); 120 timeout = setTimeout(later, wait); 121 if (callNow) { 122 result = func.apply(context, args); 123 } 124 return result; 125 }; 126 }, 127 throttle: function (func, wait) { 128 var context, args, timeout, result, previous, later; 129 previous = 0; 130 later = function () { 131 previous = new Date(); 132 timeout = null; 133 result = func.apply(context, args); 134 }; 135 return function () { 136 var now = new Date(), remaining = wait - (now - previous); 137 context = this; 138 args = arguments; 139 if (remaining <= 0) { 140 clearTimeout(timeout); 141 timeout = null; 142 previous = now; 143 result = func.apply(context, args); 144 } else if (!timeout) { 145 timeout = setTimeout(later, remaining); 146 } 147 return result; 148 }; 149 }, 150 stringify: function (val) { 151 return _.isString(val) ? val : JSON.stringify(val); 152 }, 153 noop: function () { 154 } 155 }; 156 }(); 157 var VERSION = "0.11.1"; 158 var tokenizers = function () { 159 "use strict"; 160 return { 161 nonword: nonword, 162 whitespace: whitespace, 163 obj: { 164 nonword: getObjTokenizer(nonword), 165 whitespace: getObjTokenizer(whitespace) 166 } 167 }; 168 169 function whitespace(str) { 170 str = _.toStr(str); 171 return str ? str.split(/\s+/) : []; 172 } 173 174 function nonword(str) { 175 str = _.toStr(str); 176 return str ? str.split(/\W+/) : []; 177 } 178 179 function getObjTokenizer(tokenizer) { 180 return function setKey(keys) { 181 keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0); 182 return function tokenize(o) { 183 var tokens = []; 184 _.each(keys, function (k) { 185 tokens = tokens.concat(tokenizer(_.toStr(o[k]))); 186 }); 187 return tokens; 188 }; 189 }; 190 } 191 }(); 192 var LruCache = function () { 193 "use strict"; 194 195 function LruCache(maxSize) { 196 this.maxSize = _.isNumber(maxSize) ? maxSize : 100; 197 this.reset(); 198 if (this.maxSize <= 0) { 199 this.set = this.get = $.noop; 200 } 201 } 202 203 _.mixin(LruCache.prototype, { 204 set: function set(key, val) { 205 var tailItem = this.list.tail, node; 206 if (this.size >= this.maxSize) { 207 this.list.remove(tailItem); 208 delete this.hash[tailItem.key]; 209 this.size--; 210 } 211 if (node = this.hash[key]) { 212 node.val = val; 213 this.list.moveToFront(node); 214 } else { 215 node = new Node(key, val); 216 this.list.add(node); 217 this.hash[key] = node; 218 this.size++; 219 } 220 }, 221 get: function get(key) { 222 var node = this.hash[key]; 223 if (node) { 224 this.list.moveToFront(node); 225 return node.val; 226 } 227 }, 228 reset: function reset() { 229 this.size = 0; 230 this.hash = {}; 231 this.list = new List(); 232 } 233 }); 234 235 function List() { 236 this.head = this.tail = null; 237 } 238 239 _.mixin(List.prototype, { 240 add: function add(node) { 241 if (this.head) { 242 node.next = this.head; 243 this.head.prev = node; 244 } 245 this.head = node; 246 this.tail = this.tail || node; 247 }, 248 remove: function remove(node) { 249 node.prev ? node.prev.next = node.next : this.head = node.next; 250 node.next ? node.next.prev = node.prev : this.tail = node.prev; 251 }, 252 moveToFront: function (node) { 253 this.remove(node); 254 this.add(node); 255 } 256 }); 257 258 function Node(key, val) { 259 this.key = key; 260 this.val = val; 261 this.prev = this.next = null; 262 } 263 264 return LruCache; 265 }(); 266 var PersistentStorage = function () { 267 "use strict"; 268 var LOCAL_STORAGE; 269 try { 270 LOCAL_STORAGE = window.localStorage; 271 LOCAL_STORAGE.setItem("~~~", "!"); 272 LOCAL_STORAGE.removeItem("~~~"); 273 } catch (err) { 274 LOCAL_STORAGE = null; 275 } 276 277 function PersistentStorage(namespace, override) { 278 this.prefix = ["__", namespace, "__"].join(""); 279 this.ttlKey = "__ttl__"; 280 this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix)); 281 this.ls = override || LOCAL_STORAGE; 282 !this.ls && this._noop(); 283 } 284 285 _.mixin(PersistentStorage.prototype, { 286 _prefix: function (key) { 287 return this.prefix + key; 288 }, 289 _ttlKey: function (key) { 290 return this._prefix(key) + this.ttlKey; 291 }, 292 _noop: function () { 293 this.get = this.set = this.remove = this.clear = this.isExpired = _.noop; 294 }, 295 _safeSet: function (key, val) { 296 try { 297 this.ls.setItem(key, val); 298 } catch (err) { 299 if (err.name === "QuotaExceededError") { 300 this.clear(); 301 this._noop(); 302 } 303 } 304 }, 305 get: function (key) { 306 if (this.isExpired(key)) { 307 this.remove(key); 308 } 309 return decode(this.ls.getItem(this._prefix(key))); 310 }, 311 set: function (key, val, ttl) { 312 if (_.isNumber(ttl)) { 313 this._safeSet(this._ttlKey(key), encode(now() + ttl)); 314 } else { 315 this.ls.removeItem(this._ttlKey(key)); 316 } 317 return this._safeSet(this._prefix(key), encode(val)); 318 }, 319 remove: function (key) { 320 this.ls.removeItem(this._ttlKey(key)); 321 this.ls.removeItem(this._prefix(key)); 322 return this; 323 }, 324 clear: function () { 325 var i, keys = gatherMatchingKeys(this.keyMatcher); 326 for (i = keys.length; i--;) { 327 this.remove(keys[i]); 328 } 329 return this; 330 }, 331 isExpired: function (key) { 332 var ttl = decode(this.ls.getItem(this._ttlKey(key))); 333 return _.isNumber(ttl) && now() > ttl ? true : false; 334 } 335 }); 336 return PersistentStorage; 337 338 function now() { 339 return new Date().getTime(); 340 } 341 342 function encode(val) { 343 return JSON.stringify(_.isUndefined(val) ? null : val); 344 } 345 346 function decode(val) { 347 return $.parseJSON(val); 348 } 349 350 function gatherMatchingKeys(keyMatcher) { 351 var i, key, keys = [], len = LOCAL_STORAGE.length; 352 for (i = 0; i < len; i++) { 353 if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) { 354 keys.push(key.replace(keyMatcher, "")); 355 } 356 } 357 return keys; 358 } 359 }(); 360 var Transport = function () { 361 "use strict"; 362 var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10); 363 364 function Transport(o) { 365 o = o || {}; 366 this.cancelled = false; 367 this.lastReq = null; 368 this._send = o.transport; 369 this._get = o.limiter ? o.limiter(this._get) : this._get; 370 this._cache = o.cache === false ? new LruCache(0) : sharedCache; 371 } 372 373 Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { 374 maxPendingRequests = num; 375 }; 376 Transport.resetCache = function resetCache() { 377 sharedCache.reset(); 378 }; 379 _.mixin(Transport.prototype, { 380 _fingerprint: function fingerprint(o) { 381 o = o || {}; 382 return o.url + o.type + $.param(o.data || {}); 383 }, 384 _get: function (o, cb) { 385 var that = this, fingerprint, jqXhr; 386 fingerprint = this._fingerprint(o); 387 if (this.cancelled || fingerprint !== this.lastReq) { 388 return; 389 } 390 if (jqXhr = pendingRequests[fingerprint]) { 391 jqXhr.done(done).fail(fail); 392 } else if (pendingRequestsCount < maxPendingRequests) { 393 pendingRequestsCount++; 394 pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always); 395 } else { 396 this.onDeckRequestArgs = [].slice.call(arguments, 0); 397 } 398 399 function done(resp) { 400 cb(null, resp); 401 that._cache.set(fingerprint, resp); 402 } 403 404 function fail() { 405 cb(true); 406 } 407 408 function always() { 409 pendingRequestsCount--; 410 delete pendingRequests[fingerprint]; 411 if (that.onDeckRequestArgs) { 412 that._get.apply(that, that.onDeckRequestArgs); 413 that.onDeckRequestArgs = null; 414 } 415 } 416 }, 417 get: function (o, cb) { 418 var resp, fingerprint; 419 cb = cb || $.noop; 420 o = _.isString(o) ? { 421 url: o 422 } : o || {}; 423 fingerprint = this._fingerprint(o); 424 this.cancelled = false; 425 this.lastReq = fingerprint; 426 if (resp = this._cache.get(fingerprint)) { 427 cb(null, resp); 428 } else { 429 this._get(o, cb); 430 } 431 }, 432 cancel: function () { 433 this.cancelled = true; 434 } 435 }); 436 return Transport; 437 }(); 438 var SearchIndex = window.SearchIndex = function () { 439 "use strict"; 440 var CHILDREN = "c", IDS = "i"; 441 442 function SearchIndex(o) { 443 o = o || {}; 444 if (!o.datumTokenizer || !o.queryTokenizer) { 445 $.error("datumTokenizer and queryTokenizer are both required"); 446 } 447 this.identify = o.identify || _.stringify; 448 this.datumTokenizer = o.datumTokenizer; 449 this.queryTokenizer = o.queryTokenizer; 450 this.reset(); 451 } 452 453 _.mixin(SearchIndex.prototype, { 454 bootstrap: function bootstrap(o) { 455 this.datums = o.datums; 456 this.trie = o.trie; 457 }, 458 add: function (data) { 459 var that = this; 460 data = _.isArray(data) ? data : [data]; 461 _.each(data, function (datum) { 462 var id, tokens; 463 that.datums[id = that.identify(datum)] = datum; 464 tokens = normalizeTokens(that.datumTokenizer(datum)); 465 _.each(tokens, function (token) { 466 var node, chars, ch; 467 node = that.trie; 468 chars = token.split(""); 469 while (ch = chars.shift()) { 470 node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode()); 471 node[IDS].push(id); 472 } 473 }); 474 }); 475 }, 476 get: function get(ids) { 477 var that = this; 478 return _.map(ids, function (id) { 479 return that.datums[id]; 480 }); 481 }, 482 search: function search(query) { 483 var that = this, tokens, matches; 484 tokens = normalizeTokens(this.queryTokenizer(query)); 485 _.each(tokens, function (token) { 486 var node, chars, ch, ids; 487 if (matches && matches.length === 0) { 488 return false; 489 } 490 node = that.trie; 491 chars = token.split(""); 492 while (node && (ch = chars.shift())) { 493 node = node[CHILDREN][ch]; 494 } 495 if (node && chars.length === 0) { 496 ids = node[IDS].slice(0); 497 matches = matches ? getIntersection(matches, ids) : ids; 498 } else { 499 matches = []; 500 return false; 501 } 502 }); 503 return matches ? _.map(unique(matches), function (id) { 504 return that.datums[id]; 505 }) : []; 506 }, 507 all: function all() { 508 var values = []; 509 for (var key in this.datums) { 510 values.push(this.datums[key]); 511 } 512 return values; 513 }, 514 reset: function reset() { 515 this.datums = {}; 516 this.trie = newNode(); 517 }, 518 serialize: function serialize() { 519 return { 520 datums: this.datums, 521 trie: this.trie 522 }; 523 } 524 }); 525 return SearchIndex; 526 527 function normalizeTokens(tokens) { 528 tokens = _.filter(tokens, function (token) { 529 return !!token; 530 }); 531 tokens = _.map(tokens, function (token) { 532 return token.toLowerCase(); 533 }); 534 return tokens; 535 } 536 537 function newNode() { 538 var node = {}; 539 node[IDS] = []; 540 node[CHILDREN] = {}; 541 return node; 542 } 543 544 function unique(array) { 545 var seen = {}, uniques = []; 546 for (var i = 0, len = array.length; i < len; i++) { 547 if (!seen[array[i]]) { 548 seen[array[i]] = true; 549 uniques.push(array[i]); 550 } 551 } 552 return uniques; 553 } 554 555 function getIntersection(arrayA, arrayB) { 556 var ai = 0, bi = 0, intersection = []; 557 arrayA = arrayA.sort(); 558 arrayB = arrayB.sort(); 559 var lenArrayA = arrayA.length, lenArrayB = arrayB.length; 560 while (ai < lenArrayA && bi < lenArrayB) { 561 if (arrayA[ai] < arrayB[bi]) { 562 ai++; 563 } else if (arrayA[ai] > arrayB[bi]) { 564 bi++; 565 } else { 566 intersection.push(arrayA[ai]); 567 ai++; 568 bi++; 569 } 570 } 571 return intersection; 572 } 573 }(); 574 var Prefetch = function () { 575 "use strict"; 576 var keys; 577 keys = { 578 data: "data", 579 protocol: "protocol", 580 thumbprint: "thumbprint" 581 }; 582 583 function Prefetch(o) { 584 this.url = o.url; 585 this.ttl = o.ttl; 586 this.cache = o.cache; 587 this.prepare = o.prepare; 588 this.transform = o.transform; 589 this.transport = o.transport; 590 this.thumbprint = o.thumbprint; 591 this.storage = new PersistentStorage(o.cacheKey); 592 } 593 594 _.mixin(Prefetch.prototype, { 595 _settings: function settings() { 596 return { 597 url: this.url, 598 type: "GET", 599 dataType: "json" 600 }; 601 }, 602 store: function store(data) { 603 if (!this.cache) { 604 return; 605 } 606 this.storage.set(keys.data, data, this.ttl); 607 this.storage.set(keys.protocol, location.protocol, this.ttl); 608 this.storage.set(keys.thumbprint, this.thumbprint, this.ttl); 609 }, 610 fromCache: function fromCache() { 611 var stored = {}, isExpired; 612 if (!this.cache) { 613 return null; 614 } 615 stored.data = this.storage.get(keys.data); 616 stored.protocol = this.storage.get(keys.protocol); 617 stored.thumbprint = this.storage.get(keys.thumbprint); 618 isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol; 619 return stored.data && !isExpired ? stored.data : null; 620 }, 621 fromNetwork: function (cb) { 622 var that = this, settings; 623 if (!cb) { 624 return; 625 } 626 settings = this.prepare(this._settings()); 627 this.transport(settings).fail(onError).done(onResponse); 628 629 function onError() { 630 cb(true); 631 } 632 633 function onResponse(resp) { 634 cb(null, that.transform(resp)); 635 } 636 }, 637 clear: function clear() { 638 this.storage.clear(); 639 return this; 640 } 641 }); 642 return Prefetch; 643 }(); 644 var Remote = function () { 645 "use strict"; 646 647 function Remote(o) { 648 this.url = o.url; 649 this.prepare = o.prepare; 650 this.transform = o.transform; 651 this.transport = new Transport({ 652 cache: o.cache, 653 limiter: o.limiter, 654 transport: o.transport 655 }); 656 } 657 658 _.mixin(Remote.prototype, { 659 _settings: function settings() { 660 return { 661 url: this.url, 662 type: "GET", 663 dataType: "json" 664 }; 665 }, 666 get: function get(query, cb) { 667 var that = this, settings; 668 if (!cb) { 669 return; 670 } 671 query = query || ""; 672 settings = this.prepare(query, this._settings()); 673 return this.transport.get(settings, onResponse); 674 675 function onResponse(err, resp) { 676 err ? cb([]) : cb(that.transform(resp)); 677 } 678 }, 679 cancelLastRequest: function cancelLastRequest() { 680 this.transport.cancel(); 681 } 682 }); 683 return Remote; 684 }(); 685 var oParser = function () { 686 "use strict"; 687 return function parse(o) { 688 var defaults, sorter; 689 defaults = { 690 initialize: true, 691 identify: _.stringify, 692 datumTokenizer: null, 693 queryTokenizer: null, 694 sufficient: 5, 695 sorter: null, 696 local: [], 697 prefetch: null, 698 remote: null 699 }; 700 o = _.mixin(defaults, o || {}); 701 !o.datumTokenizer && $.error("datumTokenizer is required"); 702 !o.queryTokenizer && $.error("queryTokenizer is required"); 703 sorter = o.sorter; 704 o.sorter = sorter ? function (x) { 705 return x.sort(sorter); 706 } : _.identity; 707 o.local = _.isFunction(o.local) ? o.local() : o.local; 708 o.prefetch = parsePrefetch(o.prefetch); 709 o.remote = parseRemote(o.remote); 710 return o; 711 }; 712 713 function parsePrefetch(o) { 714 var defaults; 715 if (!o) { 716 return null; 717 } 718 defaults = { 719 url: null, 720 ttl: 24 * 60 * 60 * 1e3, 721 cache: true, 722 cacheKey: null, 723 thumbprint: "", 724 prepare: _.identity, 725 transform: _.identity, 726 transport: null 727 }; 728 o = _.isString(o) ? { 729 url: o 730 } : o; 731 o = _.mixin(defaults, o); 732 !o.url && $.error("prefetch requires url to be set"); 733 o.transform = o.filter || o.transform; 734 o.cacheKey = o.cacheKey || o.url; 735 o.thumbprint = VERSION + o.thumbprint; 736 o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; 737 return o; 738 } 739 740 function parseRemote(o) { 741 var defaults; 742 if (!o) { 743 return; 744 } 745 defaults = { 746 url: null, 747 cache: true, 748 prepare: null, 749 replace: null, 750 wildcard: null, 751 limiter: null, 752 rateLimitBy: "debounce", 753 rateLimitWait: 300, 754 transform: _.identity, 755 transport: null 756 }; 757 o = _.isString(o) ? { 758 url: o 759 } : o; 760 o = _.mixin(defaults, o); 761 !o.url && $.error("remote requires url to be set"); 762 o.transform = o.filter || o.transform; 763 o.prepare = toRemotePrepare(o); 764 o.limiter = toLimiter(o); 765 o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; 766 delete o.replace; 767 delete o.wildcard; 768 delete o.rateLimitBy; 769 delete o.rateLimitWait; 770 return o; 771 } 772 773 function toRemotePrepare(o) { 774 var prepare, replace, wildcard; 775 prepare = o.prepare; 776 replace = o.replace; 777 wildcard = o.wildcard; 778 if (prepare) { 779 return prepare; 780 } 781 if (replace) { 782 prepare = prepareByReplace; 783 } else if (o.wildcard) { 784 prepare = prepareByWildcard; 785 } else { 786 prepare = idenityPrepare; 787 } 788 return prepare; 789 790 function prepareByReplace(query, settings) { 791 settings.url = replace(settings.url, query); 792 return settings; 793 } 794 795 function prepareByWildcard(query, settings) { 796 settings.url = settings.url.replace(wildcard, encodeURIComponent(query)); 797 return settings; 798 } 799 800 function idenityPrepare(query, settings) { 801 return settings; 802 } 803 } 804 805 function toLimiter(o) { 806 var limiter, method, wait; 807 limiter = o.limiter; 808 method = o.rateLimitBy; 809 wait = o.rateLimitWait; 810 if (!limiter) { 811 limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait); 812 } 813 return limiter; 814 815 function debounce(wait) { 816 return function debounce(fn) { 817 return _.debounce(fn, wait); 818 }; 819 } 820 821 function throttle(wait) { 822 return function throttle(fn) { 823 return _.throttle(fn, wait); 824 }; 825 } 826 } 827 828 function callbackToDeferred(fn) { 829 return function wrapper(o) { 830 var deferred = $.Deferred(); 831 fn(o, onSuccess, onError); 832 return deferred; 833 834 function onSuccess(resp) { 835 _.defer(function () { 836 deferred.resolve(resp); 837 }); 838 } 839 840 function onError(err) { 841 _.defer(function () { 842 deferred.reject(err); 843 }); 844 } 845 }; 846 } 847 }(); 848 var Bloodhound = function () { 849 "use strict"; 850 var old; 851 old = window && window.Bloodhound; 852 853 function Bloodhound(o) { 854 o = oParser(o); 855 this.sorter = o.sorter; 856 this.identify = o.identify; 857 this.sufficient = o.sufficient; 858 this.local = o.local; 859 this.remote = o.remote ? new Remote(o.remote) : null; 860 this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null; 861 this.index = new SearchIndex({ 862 identify: this.identify, 863 datumTokenizer: o.datumTokenizer, 864 queryTokenizer: o.queryTokenizer 865 }); 866 o.initialize !== false && this.initialize(); 867 } 868 869 Bloodhound.noConflict = function noConflict() { 870 window && (window.Bloodhound = old); 871 return Bloodhound; 872 }; 873 Bloodhound.tokenizers = tokenizers; 874 _.mixin(Bloodhound.prototype, { 875 __ttAdapter: function ttAdapter() { 876 var that = this; 877 return this.remote ? withAsync : withoutAsync; 878 879 function withAsync(query, sync, async) { 880 return that.search(query, sync, async); 881 } 882 883 function withoutAsync(query, sync) { 884 return that.search(query, sync); 885 } 886 }, 887 _loadPrefetch: function loadPrefetch() { 888 var that = this, deferred, serialized; 889 deferred = $.Deferred(); 890 if (!this.prefetch) { 891 deferred.resolve(); 892 } else if (serialized = this.prefetch.fromCache()) { 893 this.index.bootstrap(serialized); 894 deferred.resolve(); 895 } else { 896 this.prefetch.fromNetwork(done); 897 } 898 return deferred.promise(); 899 900 function done(err, data) { 901 if (err) { 902 return deferred.reject(); 903 } 904 that.add(data); 905 that.prefetch.store(that.index.serialize()); 906 deferred.resolve(); 907 } 908 }, 909 _initialize: function initialize() { 910 var that = this, deferred; 911 this.clear(); 912 (this.initPromise = this._loadPrefetch()).done(addLocalToIndex); 913 return this.initPromise; 914 915 function addLocalToIndex() { 916 that.add(that.local); 917 } 918 }, 919 initialize: function initialize(force) { 920 return !this.initPromise || force ? this._initialize() : this.initPromise; 921 }, 922 add: function add(data) { 923 this.index.add(data); 924 return this; 925 }, 926 get: function get(ids) { 927 ids = _.isArray(ids) ? ids : [].slice.call(arguments); 928 return this.index.get(ids); 929 }, 930 search: function search(query, sync, async) { 931 var that = this, local; 932 local = this.sorter(this.index.search(query)); 933 sync(this.remote ? local.slice() : local); 934 if (this.remote && local.length < this.sufficient) { 935 this.remote.get(query, processRemote); 936 } else if (this.remote) { 937 this.remote.cancelLastRequest(); 938 } 939 return this; 940 941 function processRemote(remote) {
vendor: 52,863 bytes, lines 942-2563
942 var nonDuplicates = []; 943 _.each(remote, function (r) { 944 !_.some(local, function (l) { 945 return that.identify(r) === that.identify(l); 946 }) && nonDuplicates.push(r); 947 }); 948 async && async(nonDuplicates); 949 } 950 }, 951 all: function all() { 952 return this.index.all(); 953 }, 954 clear: function clear() { 955 this.index.reset(); 956 return this; 957 }, 958 clearPrefetchCache: function clearPrefetchCache() { 959 this.prefetch && this.prefetch.clear(); 960 return this; 961 }, 962 clearRemoteCache: function clearRemoteCache() { 963 Transport.resetCache(); 964 return this; 965 }, 966 ttAdapter: function ttAdapter() { 967 return this.__ttAdapter(); 968 } 969 }); 970 return Bloodhound; 971 }(); 972 return Bloodhound; 973}); 974 975(function (root, factory) { 976 if (typeof define === "function" && define.amd) { 977 define("typeahead.js", ["jquery"], function (a0) { 978 return factory(a0); 979 }); 980 } else if (typeof exports === "object") { 981 module.exports = factory(require("jquery")); 982 } else { 983 factory(jQuery); 984 } 985})(this, function ($) { 986 var _ = function () { 987 "use strict"; 988 return { 989 isMsie: function () { 990 return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; 991 }, 992 isBlankString: function (str) { 993 return !str || /^\s*$/.test(str); 994 }, 995 escapeRegExChars: function (str) { 996 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 997 }, 998 isString: function (obj) { 999 return typeof obj === "string"; 1000 }, 1001 isNumber: function (obj) { 1002 return typeof obj === "number"; 1003 }, 1004 isArray: $.isArray, 1005 isFunction: $.isFunction, 1006 isObject: $.isPlainObject, 1007 isUndefined: function (obj) { 1008 return typeof obj === "undefined"; 1009 }, 1010 isElement: function (obj) { 1011 return !!(obj && obj.nodeType === 1); 1012 }, 1013 isJQuery: function (obj) { 1014 return obj instanceof $; 1015 }, 1016 toStr: function toStr(s) { 1017 return _.isUndefined(s) || s === null ? "" : s + ""; 1018 }, 1019 bind: $.proxy, 1020 each: function (collection, cb) { 1021 $.each(collection, reverseArgs); 1022 1023 function reverseArgs(index, value) { 1024 return cb(value, index); 1025 } 1026 }, 1027 map: $.map, 1028 filter: $.grep, 1029 every: function (obj, test) { 1030 var result = true; 1031 if (!obj) { 1032 return result; 1033 } 1034 $.each(obj, function (key, val) { 1035 if (!(result = test.call(null, val, key, obj))) { 1036 return false; 1037 } 1038 }); 1039 return !!result; 1040 }, 1041 some: function (obj, test) { 1042 var result = false; 1043 if (!obj) { 1044 return result; 1045 } 1046 $.each(obj, function (key, val) { 1047 if (result = test.call(null, val, key, obj)) { 1048 return false; 1049 } 1050 }); 1051 return !!result; 1052 }, 1053 mixin: $.extend, 1054 identity: function (x) { 1055 return x; 1056 }, 1057 clone: function (obj) { 1058 return $.extend(true, {}, obj); 1059 }, 1060 getIdGenerator: function () { 1061 var counter = 0; 1062 return function () { 1063 return counter++; 1064 }; 1065 }, 1066 templatify: function templatify(obj) { 1067 return $.isFunction(obj) ? obj : template; 1068 1069 function template() { 1070 return String(obj); 1071 } 1072 }, 1073 defer: function (fn) { 1074 setTimeout(fn, 0); 1075 }, 1076 debounce: function (func, wait, immediate) { 1077 var timeout, result; 1078 return function () { 1079 var context = this, args = arguments, later, callNow; 1080 later = function () { 1081 timeout = null; 1082 if (!immediate) { 1083 result = func.apply(context, args); 1084 } 1085 }; 1086 callNow = immediate && !timeout; 1087 clearTimeout(timeout); 1088 timeout = setTimeout(later, wait); 1089 if (callNow) { 1090 result = func.apply(context, args); 1091 } 1092 return result; 1093 }; 1094 }, 1095 throttle: function (func, wait) { 1096 var context, args, timeout, result, previous, later; 1097 previous = 0; 1098 later = function () { 1099 previous = new Date(); 1100 timeout = null; 1101 result = func.apply(context, args); 1102 }; 1103 return function () { 1104 var now = new Date(), remaining = wait - (now - previous); 1105 context = this; 1106 args = arguments; 1107 if (remaining <= 0) { 1108 clearTimeout(timeout); 1109 timeout = null; 1110 previous = now; 1111 result = func.apply(context, args); 1112 } else if (!timeout) { 1113 timeout = setTimeout(later, remaining); 1114 } 1115 return result; 1116 }; 1117 }, 1118 stringify: function (val) { 1119 return _.isString(val) ? val : JSON.stringify(val); 1120 }, 1121 noop: function () { 1122 } 1123 }; 1124 }(); 1125 var WWW = function () { 1126 "use strict"; 1127 var defaultClassNames = { 1128 wrapper: "twitter-typeahead", 1129 input: "tt-input", 1130 hint: "tt-hint", 1131 menu: "tt-menu", 1132 dataset: "tt-dataset", 1133 suggestion: "tt-suggestion", 1134 selectable: "tt-selectable", 1135 empty: "tt-empty", 1136 open: "tt-open", 1137 cursor: "tt-cursor", 1138 highlight: "tt-highlight" 1139 }; 1140 return build; 1141 1142 function build(o) { 1143 var www, classes; 1144 classes = _.mixin({}, defaultClassNames, o); 1145 www = { 1146 css: buildCss(), 1147 classes: classes, 1148 html: buildHtml(classes), 1149 selectors: buildSelectors(classes) 1150 }; 1151 return { 1152 css: www.css, 1153 html: www.html, 1154 classes: www.classes, 1155 selectors: www.selectors, 1156 mixin: function (o) { 1157 _.mixin(o, www); 1158 } 1159 }; 1160 } 1161 1162 function buildHtml(c) { 1163 return { 1164 wrapper: '<span class="' + c.wrapper + '"></span>', 1165 menu: '<div class="' + c.menu + '"></div>' 1166 }; 1167 } 1168 1169 function buildSelectors(classes) { 1170 var selectors = {}; 1171 _.each(classes, function (v, k) { 1172 selectors[k] = "." + v; 1173 }); 1174 return selectors; 1175 } 1176 1177 function buildCss() { 1178 var css = { 1179 wrapper: { 1180 position: "relative", 1181 display: "inline-block" 1182 }, 1183 hint: { 1184 position: "absolute", 1185 top: "0", 1186 left: "0", 1187 borderColor: "transparent", 1188 boxShadow: "none", 1189 opacity: "1" 1190 }, 1191 input: { 1192 position: "relative", 1193 verticalAlign: "top", 1194 backgroundColor: "transparent" 1195 }, 1196 inputWithNoHint: { 1197 position: "relative", 1198 verticalAlign: "top" 1199 }, 1200 menu: { 1201 position: "absolute", 1202 top: "100%", 1203 left: "0", 1204 zIndex: "100", 1205 display: "none" 1206 }, 1207 ltr: { 1208 left: "0", 1209 right: "auto" 1210 }, 1211 rtl: { 1212 left: "auto", 1213 right: " 0" 1214 } 1215 }; 1216 if (_.isMsie()) { 1217 _.mixin(css.input, { 1218 backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" 1219 }); 1220 } 1221 return css; 1222 } 1223 }(); 1224 var EventBus = function () { 1225 "use strict"; 1226 var namespace, deprecationMap; 1227 namespace = "typeahead:"; 1228 deprecationMap = { 1229 render: "rendered", 1230 cursorchange: "cursorchanged", 1231 select: "selected", 1232 autocomplete: "autocompleted" 1233 }; 1234 1235 function EventBus(o) { 1236 if (!o || !o.el) { 1237 $.error("EventBus initialized without el"); 1238 } 1239 this.$el = $(o.el); 1240 } 1241 1242 _.mixin(EventBus.prototype, { 1243 _trigger: function (type, args) { 1244 var $e; 1245 $e = $.Event(namespace + type); 1246 (args = args || []).unshift($e); 1247 this.$el.trigger.apply(this.$el, args); 1248 return $e; 1249 }, 1250 before: function (type) { 1251 var args, $e; 1252 args = [].slice.call(arguments, 1); 1253 $e = this._trigger("before" + type, args); 1254 return $e.isDefaultPrevented(); 1255 }, 1256 trigger: function (type) { 1257 var deprecatedType; 1258 this._trigger(type, [].slice.call(arguments, 1)); 1259 if (deprecatedType = deprecationMap[type]) { 1260 this._trigger(deprecatedType, [].slice.call(arguments, 1)); 1261 } 1262 } 1263 }); 1264 return EventBus; 1265 }(); 1266 var EventEmitter = function () { 1267 "use strict"; 1268 var splitter = /\s+/, nextTick = getNextTick(); 1269 return { 1270 onSync: onSync, 1271 onAsync: onAsync, 1272 off: off, 1273 trigger: trigger 1274 }; 1275 1276 function on(method, types, cb, context) { 1277 var type; 1278 if (!cb) { 1279 return this; 1280 } 1281 types = types.split(splitter); 1282 cb = context ? bindContext(cb, context) : cb; 1283 this._callbacks = this._callbacks || {}; 1284 while (type = types.shift()) { 1285 this._callbacks[type] = this._callbacks[type] || { 1286 sync: [], 1287 async: [] 1288 }; 1289 this._callbacks[type][method].push(cb); 1290 } 1291 return this; 1292 } 1293 1294 function onAsync(types, cb, context) { 1295 return on.call(this, "async", types, cb, context); 1296 } 1297 1298 function onSync(types, cb, context) { 1299 return on.call(this, "sync", types, cb, context); 1300 } 1301 1302 function off(types) { 1303 var type; 1304 if (!this._callbacks) { 1305 return this; 1306 } 1307 types = types.split(splitter); 1308 while (type = types.shift()) { 1309 delete this._callbacks[type]; 1310 } 1311 return this; 1312 } 1313 1314 function trigger(types) { 1315 var type, callbacks, args, syncFlush, asyncFlush; 1316 if (!this._callbacks) { 1317 return this; 1318 } 1319 types = types.split(splitter); 1320 args = [].slice.call(arguments, 1); 1321 while ((type = types.shift()) && (callbacks = this._callbacks[type])) { 1322 syncFlush = getFlush(callbacks.sync, this, [type].concat(args)); 1323 asyncFlush = getFlush(callbacks.async, this, [type].concat(args)); 1324 syncFlush() && nextTick(asyncFlush); 1325 } 1326 return this; 1327 } 1328 1329 function getFlush(callbacks, context, args) { 1330 return flush; 1331 1332 function flush() { 1333 var cancelled; 1334 for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { 1335 cancelled = callbacks[i].apply(context, args) === false; 1336 } 1337 return !cancelled; 1338 } 1339 } 1340 1341 function getNextTick() { 1342 var nextTickFn; 1343 if (window.setImmediate) { 1344 nextTickFn = function nextTickSetImmediate(fn) { 1345 setImmediate(function () { 1346 fn(); 1347 }); 1348 }; 1349 } else { 1350 nextTickFn = function nextTickSetTimeout(fn) { 1351 setTimeout(function () { 1352 fn(); 1353 }, 0); 1354 }; 1355 } 1356 return nextTickFn; 1357 } 1358 1359 function bindContext(fn, context) { 1360 return fn.bind ? fn.bind(context) : function () { 1361 fn.apply(context, [].slice.call(arguments, 0)); 1362 }; 1363 } 1364 }(); 1365 var highlight = function (doc) { 1366 "use strict"; 1367 var defaults = { 1368 node: null, 1369 pattern: null, 1370 tagName: "strong", 1371 className: null, 1372 wordsOnly: false, 1373 caseSensitive: false 1374 }; 1375 return function hightlight(o) { 1376 var regex; 1377 o = _.mixin({}, defaults, o); 1378 if (!o.node || !o.pattern) { 1379 return; 1380 } 1381 o.pattern = _.isArray(o.pattern) ? o.pattern : [o.pattern]; 1382 regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); 1383 traverse(o.node, hightlightTextNode); 1384 1385 function hightlightTextNode(textNode) { 1386 var match, patternNode, wrapperNode; 1387 if (match = regex.exec(textNode.data)) { 1388 wrapperNode = doc.createElement(o.tagName); 1389 o.className && (wrapperNode.className = o.className); 1390 patternNode = textNode.splitText(match.index); 1391 patternNode.splitText(match[0].length); 1392 wrapperNode.appendChild(patternNode.cloneNode(true)); 1393 textNode.parentNode.replaceChild(wrapperNode, patternNode); 1394 } 1395 return !!match; 1396 } 1397 1398 function traverse(el, hightlightTextNode) { 1399 var childNode, TEXT_NODE_TYPE = 3; 1400 for (var i = 0; i < el.childNodes.length; i++) { 1401 childNode = el.childNodes[i]; 1402 if (childNode.nodeType === TEXT_NODE_TYPE) { 1403 i += hightlightTextNode(childNode) ? 1 : 0; 1404 } else { 1405 traverse(childNode, hightlightTextNode); 1406 } 1407 } 1408 } 1409 }; 1410 1411 function getRegex(patterns, caseSensitive, wordsOnly) { 1412 var escapedPatterns = [], regexStr; 1413 for (var i = 0, len = patterns.length; i < len; i++) { 1414 escapedPatterns.push(_.escapeRegExChars(patterns[i])); 1415 } 1416 regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; 1417 return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); 1418 } 1419 }(window.document); 1420 var Input = function () { 1421 "use strict"; 1422 var specialKeyCodeMap; 1423 specialKeyCodeMap = { 1424 9: "tab", 1425 27: "esc", 1426 37: "left", 1427 39: "right", 1428 13: "enter", 1429 38: "up", 1430 40: "down" 1431 }; 1432 1433 function Input(o, www) { 1434 o = o || {}; 1435 if (!o.input) { 1436 $.error("input is missing"); 1437 } 1438 www.mixin(this); 1439 this.$hint = $(o.hint); 1440 this.$input = $(o.input); 1441 this.query = this.$input.val(); 1442 this.queryWhenFocused = this.hasFocus() ? this.query : null; 1443 this.$overflowHelper = buildOverflowHelper(this.$input); 1444 this._checkLanguageDirection(); 1445 if (this.$hint.length === 0) { 1446 this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; 1447 } 1448 } 1449 1450 Input.normalizeQuery = function (str) { 1451 return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); 1452 }; 1453 _.mixin(Input.prototype, EventEmitter, { 1454 _onBlur: function onBlur() { 1455 this.resetInputValue(); 1456 this.trigger("blurred"); 1457 }, 1458 _onFocus: function onFocus() { 1459 this.queryWhenFocused = this.query; 1460 this.trigger("focused"); 1461 }, 1462 _onKeydown: function onKeydown($e) { 1463 var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; 1464 this._managePreventDefault(keyName, $e); 1465 if (keyName && this._shouldTrigger(keyName, $e)) { 1466 this.trigger(keyName + "Keyed", $e); 1467 } 1468 }, 1469 _onInput: function onInput() { 1470 this._setQuery(this.getInputValue()); 1471 this.clearHintIfInvalid(); 1472 this._checkLanguageDirection(); 1473 }, 1474 _managePreventDefault: function managePreventDefault(keyName, $e) { 1475 var preventDefault; 1476 switch (keyName) { 1477 case "up": 1478 case "down": 1479 preventDefault = !withModifier($e); 1480 break; 1481 1482 default: 1483 preventDefault = false; 1484 } 1485 preventDefault && $e.preventDefault(); 1486 }, 1487 _shouldTrigger: function shouldTrigger(keyName, $e) { 1488 var trigger; 1489 switch (keyName) { 1490 case "tab": 1491 trigger = !withModifier($e); 1492 break; 1493 1494 default: 1495 trigger = true; 1496 } 1497 return trigger; 1498 }, 1499 _checkLanguageDirection: function checkLanguageDirection() { 1500 var dir = (this.$input.css("direction") || "ltr").toLowerCase(); 1501 if (this.dir !== dir) { 1502 this.dir = dir; 1503 this.$hint.attr("dir", dir); 1504 this.trigger("langDirChanged", dir); 1505 } 1506 }, 1507 _setQuery: function setQuery(val, silent) { 1508 var areEquivalent, hasDifferentWhitespace; 1509 areEquivalent = areQueriesEquivalent(val, this.query); 1510 hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; 1511 this.query = val; 1512 if (!silent && !areEquivalent) { 1513 this.trigger("queryChanged", this.query); 1514 } else if (!silent && hasDifferentWhitespace) { 1515 this.trigger("whitespaceChanged", this.query); 1516 } 1517 }, 1518 bind: function () { 1519 var that = this, onBlur, onFocus, onKeydown, onInput; 1520 onBlur = _.bind(this._onBlur, this); 1521 onFocus = _.bind(this._onFocus, this); 1522 onKeydown = _.bind(this._onKeydown, this); 1523 onInput = _.bind(this._onInput, this); 1524 this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); 1525 if (!_.isMsie() || _.isMsie() > 9) { 1526 this.$input.on("input.tt", onInput); 1527 } else { 1528 this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function ($e) { 1529 if (specialKeyCodeMap[$e.which || $e.keyCode]) { 1530 return; 1531 } 1532 _.defer(_.bind(that._onInput, that, $e)); 1533 }); 1534 } 1535 return this; 1536 }, 1537 focus: function focus() { 1538 this.$input.focus(); 1539 }, 1540 blur: function blur() { 1541 this.$input.blur(); 1542 }, 1543 getLangDir: function getLangDir() { 1544 return this.dir; 1545 }, 1546 getQuery: function getQuery() { 1547 return this.query || ""; 1548 }, 1549 setQuery: function setQuery(val, silent) { 1550 this.setInputValue(val); 1551 this._setQuery(val, silent); 1552 }, 1553 hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { 1554 return this.query !== this.queryWhenFocused; 1555 }, 1556 getInputValue: function getInputValue() { 1557 return this.$input.val(); 1558 }, 1559 setInputValue: function setInputValue(value) { 1560 this.$input.val(value); 1561 this.clearHintIfInvalid(); 1562 this._checkLanguageDirection(); 1563 }, 1564 resetInputValue: function resetInputValue() { 1565 this.setInputValue(this.query); 1566 }, 1567 getHint: function getHint() { 1568 return this.$hint.val(); 1569 }, 1570 setHint: function setHint(value) { 1571 this.$hint.val(value); 1572 }, 1573 clearHint: function clearHint() { 1574 this.setHint(""); 1575 }, 1576 clearHintIfInvalid: function clearHintIfInvalid() { 1577 var val, hint, valIsPrefixOfHint, isValid; 1578 val = this.getInputValue(); 1579 hint = this.getHint(); 1580 valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; 1581 isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); 1582 !isValid && this.clearHint(); 1583 }, 1584 hasFocus: function hasFocus() { 1585 return this.$input.is(":focus"); 1586 }, 1587 hasOverflow: function hasOverflow() { 1588 var constraint = this.$input.width() - 2; 1589 this.$overflowHelper.text(this.getInputValue()); 1590 return this.$overflowHelper.width() >= constraint; 1591 }, 1592 isCursorAtEnd: function () { 1593 var valueLength, selectionStart, range; 1594 valueLength = this.$input.val().length; 1595 selectionStart = this.$input[0].selectionStart; 1596 if (_.isNumber(selectionStart)) { 1597 return selectionStart === valueLength; 1598 } else if (document.selection) { 1599 range = document.selection.createRange(); 1600 range.moveStart("character", -valueLength); 1601 return valueLength === range.text.length; 1602 } 1603 return true; 1604 }, 1605 destroy: function destroy() { 1606 this.$hint.off(".tt"); 1607 this.$input.off(".tt"); 1608 this.$overflowHelper.remove(); 1609 this.$hint = this.$input = this.$overflowHelper = $("<div>"); 1610 } 1611 }); 1612 return Input; 1613 1614 function buildOverflowHelper($input) { 1615 return $('<pre aria-hidden="true"></pre>').css({ 1616 position: "absolute", 1617 visibility: "hidden", 1618 whiteSpace: "pre", 1619 fontFamily: $input.css("font-family"), 1620 fontSize: $input.css("font-size"), 1621 fontStyle: $input.css("font-style"), 1622 fontVariant: $input.css("font-variant"), 1623 fontWeight: $input.css("font-weight"), 1624 wordSpacing: $input.css("word-spacing"), 1625 letterSpacing: $input.css("letter-spacing"), 1626 textIndent: $input.css("text-indent"), 1627 textRendering: $input.css("text-rendering"), 1628 textTransform: $input.css("text-transform") 1629 }).insertAfter($input); 1630 } 1631 1632 function areQueriesEquivalent(a, b) { 1633 return Input.normalizeQuery(a) === Input.normalizeQuery(b); 1634 } 1635 1636 function withModifier($e) { 1637 return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; 1638 } 1639 }(); 1640 var Dataset = function () { 1641 "use strict"; 1642 var keys, nameGenerator; 1643 keys = { 1644 val: "tt-selectable-display", 1645 obj: "tt-selectable-object" 1646 }; 1647 nameGenerator = _.getIdGenerator(); 1648 1649 function Dataset(o, www) { 1650 o = o || {}; 1651 o.templates = o.templates || {}; 1652 o.templates.notFound = o.templates.notFound || o.templates.empty; 1653 if (!o.source) { 1654 $.error("missing source"); 1655 } 1656 if (!o.node) { 1657 $.error("missing node"); 1658 } 1659 if (o.name && !isValidName(o.name)) { 1660 $.error("invalid dataset name: " + o.name); 1661 } 1662 www.mixin(this); 1663 this.highlight = !!o.highlight; 1664 this.name = o.name || nameGenerator(); 1665 this.limit = o.limit || 5; 1666 this.displayFn = getDisplayFn(o.display || o.displayKey); 1667 this.templates = getTemplates(o.templates, this.displayFn); 1668 this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; 1669 this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; 1670 this._resetLastSuggestion(); 1671 this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); 1672 } 1673 1674 Dataset.extractData = function extractData(el) { 1675 var $el = $(el); 1676 if ($el.data(keys.obj)) { 1677 return { 1678 val: $el.data(keys.val) || "", 1679 obj: $el.data(keys.obj) || null 1680 }; 1681 } 1682 return null; 1683 }; 1684 _.mixin(Dataset.prototype, EventEmitter, { 1685 _overwrite: function overwrite(query, suggestions) { 1686 suggestions = suggestions || []; 1687 if (suggestions.length) { 1688 this._renderSuggestions(query, suggestions); 1689 } else if (this.async && this.templates.pending) { 1690 this._renderPending(query); 1691 } else if (!this.async && this.templates.notFound) { 1692 this._renderNotFound(query); 1693 } else { 1694 this._empty(); 1695 } 1696 this.trigger("rendered", this.name, suggestions, false); 1697 }, 1698 _append: function append(query, suggestions) { 1699 suggestions = suggestions || []; 1700 if (suggestions.length && this.$lastSuggestion.length) { 1701 this._appendSuggestions(query, suggestions); 1702 } else if (suggestions.length) { 1703 this._renderSuggestions(query, suggestions); 1704 } else if (!this.$lastSuggestion.length && this.templates.notFound) { 1705 this._renderNotFound(query); 1706 } 1707 this.trigger("rendered", this.name, suggestions, true); 1708 }, 1709 _renderSuggestions: function renderSuggestions(query, suggestions) { 1710 var $fragment; 1711 $fragment = this._getSuggestionsFragment(query, suggestions); 1712 this.$lastSuggestion = $fragment.children().last(); 1713 this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); 1714 }, 1715 _appendSuggestions: function appendSuggestions(query, suggestions) { 1716 var $fragment, $lastSuggestion; 1717 $fragment = this._getSuggestionsFragment(query, suggestions); 1718 $lastSuggestion = $fragment.children().last(); 1719 this.$lastSuggestion.after($fragment); 1720 this.$lastSuggestion = $lastSuggestion; 1721 }, 1722 _renderPending: function renderPending(query) { 1723 var template = this.templates.pending; 1724 this._resetLastSuggestion(); 1725 template && this.$el.html(template({ 1726 query: query, 1727 dataset: this.name 1728 })); 1729 }, 1730 _renderNotFound: function renderNotFound(query) { 1731 var template = this.templates.notFound; 1732 this._resetLastSuggestion(); 1733 template && this.$el.html(template({ 1734 query: query, 1735 dataset: this.name 1736 })); 1737 }, 1738 _empty: function empty() { 1739 this.$el.empty(); 1740 this._resetLastSuggestion(); 1741 }, 1742 _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { 1743 var that = this, fragment; 1744 fragment = document.createDocumentFragment(); 1745 _.each(suggestions, function getSuggestionNode(suggestion) { 1746 var $el, context; 1747 context = that._injectQuery(query, suggestion); 1748 $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); 1749 fragment.appendChild($el[0]); 1750 }); 1751 this.highlight && highlight({ 1752 className: this.classes.highlight, 1753 node: fragment, 1754 pattern: query 1755 }); 1756 return $(fragment); 1757 }, 1758 _getFooter: function getFooter(query, suggestions) { 1759 return this.templates.footer ? this.templates.footer({ 1760 query: query, 1761 suggestions: suggestions, 1762 dataset: this.name 1763 }) : null; 1764 }, 1765 _getHeader: function getHeader(query, suggestions) { 1766 return this.templates.header ? this.templates.header({ 1767 query: query, 1768 suggestions: suggestions, 1769 dataset: this.name 1770 }) : null; 1771 }, 1772 _resetLastSuggestion: function resetLastSuggestion() { 1773 this.$lastSuggestion = $(); 1774 }, 1775 _injectQuery: function injectQuery(query, obj) { 1776 return _.isObject(obj) ? _.mixin({ 1777 _query: query 1778 }, obj) : obj; 1779 }, 1780 update: function update(query) { 1781 var that = this, canceled = false, syncCalled = false, rendered = 0; 1782 this.cancel(); 1783 this.cancel = function cancel() { 1784 canceled = true; 1785 that.cancel = $.noop; 1786 that.async && that.trigger("asyncCanceled", query); 1787 }; 1788 this.source(query, sync, async); 1789 !syncCalled && sync([]); 1790 1791 function sync(suggestions) { 1792 if (syncCalled) { 1793 return; 1794 } 1795 syncCalled = true; 1796 suggestions = (suggestions || []).slice(0, that.limit); 1797 rendered = suggestions.length; 1798 that._overwrite(query, suggestions); 1799 if (rendered < that.limit && that.async) { 1800 that.trigger("asyncRequested", query); 1801 } 1802 } 1803 1804 function async(suggestions) { 1805 suggestions = suggestions || []; 1806 if (!canceled && rendered < that.limit) { 1807 that.cancel = $.noop; 1808 rendered += suggestions.length; 1809 that._append(query, suggestions.slice(0, that.limit - rendered)); 1810 that.async && that.trigger("asyncReceived", query); 1811 } 1812 } 1813 }, 1814 cancel: $.noop, 1815 clear: function clear() { 1816 this._empty(); 1817 this.cancel(); 1818 this.trigger("cleared"); 1819 }, 1820 isEmpty: function isEmpty() { 1821 return this.$el.is(":empty"); 1822 }, 1823 destroy: function destroy() { 1824 this.$el = $("<div>"); 1825 } 1826 }); 1827 return Dataset; 1828 1829 function getDisplayFn(display) { 1830 display = display || _.stringify; 1831 return _.isFunction(display) ? display : displayFn; 1832 1833 function displayFn(obj) { 1834 return obj[display]; 1835 } 1836 } 1837 1838 function getTemplates(templates, displayFn) { 1839 return { 1840 notFound: templates.notFound && _.templatify(templates.notFound), 1841 pending: templates.pending && _.templatify(templates.pending), 1842 header: templates.header && _.templatify(templates.header), 1843 footer: templates.footer && _.templatify(templates.footer), 1844 suggestion: templates.suggestion || suggestionTemplate 1845 }; 1846 1847 function suggestionTemplate(context) { 1848 return $("<div>").text(displayFn(context)); 1849 } 1850 } 1851 1852 function isValidName(str) { 1853 return /^[_a-zA-Z0-9-]+$/.test(str); 1854 } 1855 }(); 1856 var Menu = function () { 1857 "use strict"; 1858 1859 function Menu(o, www) { 1860 var that = this; 1861 o = o || {}; 1862 if (!o.node) { 1863 $.error("node is required"); 1864 } 1865 www.mixin(this); 1866 this.$node = $(o.node); 1867 this.query = null; 1868 this.datasets = _.map(o.datasets, initializeDataset); 1869 1870 function initializeDataset(oDataset) { 1871 var node = that.$node.find(oDataset.node).first(); 1872 oDataset.node = node.length ? node : $("<div>").appendTo(that.$node); 1873 return new Dataset(oDataset, www); 1874 } 1875 } 1876 1877 _.mixin(Menu.prototype, EventEmitter, { 1878 _onSelectableClick: function onSelectableClick($e) { 1879 this.trigger("selectableClicked", $($e.currentTarget)); 1880 }, 1881 _onRendered: function onRendered(type, dataset, suggestions, async) { 1882 this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); 1883 this.trigger("datasetRendered", dataset, suggestions, async); 1884 }, 1885 _onCleared: function onCleared() { 1886 this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); 1887 this.trigger("datasetCleared"); 1888 }, 1889 _propagate: function propagate() { 1890 this.trigger.apply(this, arguments); 1891 }, 1892 _allDatasetsEmpty: function allDatasetsEmpty() { 1893 return _.every(this.datasets, isDatasetEmpty); 1894 1895 function isDatasetEmpty(dataset) { 1896 return dataset.isEmpty(); 1897 } 1898 }, 1899 _getSelectables: function getSelectables() { 1900 return this.$node.find(this.selectors.selectable); 1901 }, 1902 _removeCursor: function _removeCursor() { 1903 var $selectable = this.getActiveSelectable(); 1904 $selectable && $selectable.removeClass(this.classes.cursor); 1905 }, 1906 _ensureVisible: function ensureVisible($el) { 1907 var elTop, elBottom, nodeScrollTop, nodeHeight; 1908 elTop = $el.position().top; 1909 elBottom = elTop + $el.outerHeight(true); 1910 nodeScrollTop = this.$node.scrollTop(); 1911 nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); 1912 if (elTop < 0) { 1913 this.$node.scrollTop(nodeScrollTop + elTop); 1914 } else if (nodeHeight < elBottom) { 1915 this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); 1916 } 1917 }, 1918 bind: function () { 1919 var that = this, onSelectableClick; 1920 onSelectableClick = _.bind(this._onSelectableClick, this); 1921 this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); 1922 _.each(this.datasets, function (dataset) { 1923 dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); 1924 }); 1925 return this; 1926 }, 1927 isOpen: function isOpen() { 1928 return this.$node.hasClass(this.classes.open); 1929 }, 1930 open: function open() { 1931 this.$node.addClass(this.classes.open); 1932 }, 1933 close: function close() { 1934 this.$node.removeClass(this.classes.open); 1935 this._removeCursor(); 1936 }, 1937 setLanguageDirection: function setLanguageDirection(dir) { 1938 this.$node.attr("dir", dir); 1939 }, 1940 selectableRelativeToCursor: function selectableRelativeToCursor(delta) { 1941 var $selectables, $oldCursor, oldIndex, newIndex; 1942 $oldCursor = this.getActiveSelectable(); 1943 $selectables = this._getSelectables(); 1944 oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; 1945 newIndex = oldIndex + delta; 1946 newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; 1947 newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; 1948 return newIndex === -1 ? null : $selectables.eq(newIndex); 1949 }, 1950 setCursor: function setCursor($selectable) { 1951 this._removeCursor(); 1952 if ($selectable = $selectable && $selectable.first()) { 1953 $selectable.addClass(this.classes.cursor); 1954 this._ensureVisible($selectable); 1955 } 1956 }, 1957 getSelectableData: function getSelectableData($el) { 1958 return $el && $el.length ? Dataset.extractData($el) : null; 1959 }, 1960 getActiveSelectable: function getActiveSelectable() { 1961 var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); 1962 return $selectable.length ? $selectable : null; 1963 }, 1964 getTopSelectable: function getTopSelectable() { 1965 var $selectable = this._getSelectables().first(); 1966 return $selectable.length ? $selectable : null; 1967 }, 1968 update: function update(query) { 1969 var isValidUpdate = query !== this.query; 1970 if (isValidUpdate) { 1971 this.query = query; 1972 _.each(this.datasets, updateDataset); 1973 } 1974 return isValidUpdate; 1975 1976 function updateDataset(dataset) { 1977 dataset.update(query); 1978 } 1979 }, 1980 empty: function empty() { 1981 _.each(this.datasets, clearDataset); 1982 this.query = null; 1983 this.$node.addClass(this.classes.empty); 1984 1985 function clearDataset(dataset) { 1986 dataset.clear(); 1987 } 1988 }, 1989 destroy: function destroy() { 1990 this.$node.off(".tt"); 1991 this.$node = $("<div>"); 1992 _.each(this.datasets, destroyDataset); 1993 1994 function destroyDataset(dataset) { 1995 dataset.destroy(); 1996 } 1997 } 1998 }); 1999 return Menu; 2000 }(); 2001 var DefaultMenu = function () { 2002 "use strict"; 2003 var s = Menu.prototype; 2004 2005 function DefaultMenu() { 2006 Menu.apply(this, [].slice.call(arguments, 0)); 2007 } 2008 2009 _.mixin(DefaultMenu.prototype, Menu.prototype, { 2010 open: function open() { 2011 !this._allDatasetsEmpty() && this._show(); 2012 return s.open.apply(this, [].slice.call(arguments, 0)); 2013 }, 2014 close: function close() { 2015 this._hide(); 2016 return s.close.apply(this, [].slice.call(arguments, 0)); 2017 }, 2018 _onRendered: function onRendered() { 2019 if (this._allDatasetsEmpty()) { 2020 this._hide(); 2021 } else { 2022 this.isOpen() && this._show(); 2023 } 2024 return s._onRendered.apply(this, [].slice.call(arguments, 0)); 2025 }, 2026 _onCleared: function onCleared() { 2027 if (this._allDatasetsEmpty()) { 2028 this._hide(); 2029 } else { 2030 this.isOpen() && this._show(); 2031 } 2032 return s._onCleared.apply(this, [].slice.call(arguments, 0)); 2033 }, 2034 setLanguageDirection: function setLanguageDirection(dir) { 2035 this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); 2036 return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); 2037 }, 2038 _hide: function hide() { 2039 this.$node.hide(); 2040 }, 2041 _show: function show() { 2042 this.$node.css("display", "block"); 2043 } 2044 }); 2045 return DefaultMenu; 2046 }(); 2047 var Typeahead = function () { 2048 "use strict"; 2049 2050 function Typeahead(o, www) { 2051 var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, 2052 onQueryChanged, onWhitespaceChanged; 2053 o = o || {}; 2054 if (!o.input) { 2055 $.error("missing input"); 2056 } 2057 if (!o.menu) { 2058 $.error("missing menu"); 2059 } 2060 if (!o.eventBus) { 2061 $.error("missing event bus"); 2062 } 2063 www.mixin(this); 2064 this.eventBus = o.eventBus; 2065 this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; 2066 this.input = o.input; 2067 this.menu = o.menu; 2068 this.enabled = true; 2069 this.active = false; 2070 this.input.hasFocus() && this.activate(); 2071 this.dir = this.input.getLangDir(); 2072 this._hacks(); 2073 this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); 2074 onFocused = c(this, "activate", "open", "_onFocused"); 2075 onBlurred = c(this, "deactivate", "_onBlurred"); 2076 onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); 2077 onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); 2078 onEscKeyed = c(this, "isActive", "_onEscKeyed"); 2079 onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); 2080 onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); 2081 onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); 2082 onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); 2083 onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); 2084 onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); 2085 this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); 2086 } 2087 2088 _.mixin(Typeahead.prototype, { 2089 _hacks: function hacks() { 2090 var $input, $menu; 2091 $input = this.input.$input || $("<div>"); 2092 $menu = this.menu.$node || $("<div>"); 2093 $input.on("blur.tt", function ($e) { 2094 var active, isActive, hasActive; 2095 active = document.activeElement; 2096 isActive = $menu.is(active); 2097 hasActive = $menu.has(active).length > 0; 2098 if (_.isMsie() && (isActive || hasActive)) { 2099 $e.preventDefault(); 2100 $e.stopImmediatePropagation(); 2101 _.defer(function () { 2102 $input.focus(); 2103 }); 2104 } 2105 }); 2106 $menu.on("mousedown.tt", function ($e) { 2107 $e.preventDefault(); 2108 }); 2109 }, 2110 _onSelectableClicked: function onSelectableClicked(type, $el) { 2111 this.select($el); 2112 }, 2113 _onDatasetCleared: function onDatasetCleared() { 2114 this._updateHint(); 2115 }, 2116 _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) { 2117 this._updateHint(); 2118 this.eventBus.trigger("render", suggestions, async, dataset); 2119 }, 2120 _onAsyncRequested: function onAsyncRequested(type, dataset, query) { 2121 this.eventBus.trigger("asyncrequest", query, dataset); 2122 }, 2123 _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { 2124 this.eventBus.trigger("asynccancel", query, dataset); 2125 }, 2126 _onAsyncReceived: function onAsyncReceived(type, dataset, query) { 2127 this.eventBus.trigger("asyncreceive", query, dataset); 2128 }, 2129 _onFocused: function onFocused() { 2130 this._minLengthMet() && this.menu.update(this.input.getQuery()); 2131 }, 2132 _onBlurred: function onBlurred() { 2133 if (this.input.hasQueryChangedSinceLastFocus()) { 2134 this.eventBus.trigger("change", this.input.getQuery()); 2135 } 2136 }, 2137 _onEnterKeyed: function onEnterKeyed(type, $e) { 2138 var $selectable; 2139 if ($selectable = this.menu.getActiveSelectable()) { 2140 this.select($selectable) && $e.preventDefault(); 2141 } 2142 }, 2143 _onTabKeyed: function onTabKeyed(type, $e) { 2144 var $selectable; 2145 if ($selectable = this.menu.getActiveSelectable()) { 2146 this.select($selectable) && $e.preventDefault(); 2147 } else if ($selectable = this.menu.getTopSelectable()) { 2148 this.autocomplete($selectable) && $e.preventDefault(); 2149 } 2150 }, 2151 _onEscKeyed: function onEscKeyed() { 2152 this.close(); 2153 }, 2154 _onUpKeyed: function onUpKeyed() { 2155 this.moveCursor(-1); 2156 }, 2157 _onDownKeyed: function onDownKeyed() { 2158 this.moveCursor(+1); 2159 }, 2160 _onLeftKeyed: function onLeftKeyed() { 2161 if (this.dir === "rtl" && this.input.isCursorAtEnd()) { 2162 this.autocomplete(this.menu.getTopSelectable()); 2163 } 2164 }, 2165 _onRightKeyed: function onRightKeyed() { 2166 if (this.dir === "ltr" && this.input.isCursorAtEnd()) { 2167 this.autocomplete(this.menu.getTopSelectable()); 2168 } 2169 }, 2170 _onQueryChanged: function onQueryChanged(e, query) { 2171 this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); 2172 }, 2173 _onWhitespaceChanged: function onWhitespaceChanged() { 2174 this._updateHint(); 2175 }, 2176 _onLangDirChanged: function onLangDirChanged(e, dir) { 2177 if (this.dir !== dir) { 2178 this.dir = dir; 2179 this.menu.setLanguageDirection(dir); 2180 } 2181 }, 2182 _openIfActive: function openIfActive() { 2183 this.isActive() && this.open(); 2184 }, 2185 _minLengthMet: function minLengthMet(query) { 2186 query = _.isString(query) ? query : this.input.getQuery() || ""; 2187 return query.length >= this.minLength; 2188 }, 2189 _updateHint: function updateHint() { 2190 var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; 2191 $selectable = this.menu.getTopSelectable(); 2192 data = this.menu.getSelectableData($selectable); 2193 val = this.input.getInputValue(); 2194 if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { 2195 query = Input.normalizeQuery(val); 2196 escapedQuery = _.escapeRegExChars(query); 2197 frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); 2198 match = frontMatchRegEx.exec(data.val); 2199 match && this.input.setHint(val + match[1]); 2200 } else { 2201 this.input.clearHint(); 2202 } 2203 }, 2204 isEnabled: function isEnabled() { 2205 return this.enabled; 2206 }, 2207 enable: function enable() { 2208 this.enabled = true; 2209 }, 2210 disable: function disable() { 2211 this.enabled = false; 2212 }, 2213 isActive: function isActive() { 2214 return this.active; 2215 }, 2216 activate: function activate() { 2217 if (this.isActive()) { 2218 return true; 2219 } else if (!this.isEnabled() || this.eventBus.before("active")) { 2220 return false; 2221 } else { 2222 this.active = true; 2223 this.eventBus.trigger("active"); 2224 return true; 2225 } 2226 }, 2227 deactivate: function deactivate() { 2228 if (!this.isActive()) { 2229 return true; 2230 } else if (this.eventBus.before("idle")) { 2231 return false; 2232 } else { 2233 this.active = false; 2234 this.close(); 2235 this.eventBus.trigger("idle"); 2236 return true; 2237 } 2238 }, 2239 isOpen: function isOpen() { 2240 return this.menu.isOpen(); 2241 }, 2242 open: function open() { 2243 if (!this.isOpen() && !this.eventBus.before("open")) { 2244 this.menu.open(); 2245 this._updateHint(); 2246 this.eventBus.trigger("open"); 2247 } 2248 return this.isOpen(); 2249 }, 2250 close: function close() { 2251 if (this.isOpen() && !this.eventBus.before("close")) { 2252 this.menu.close(); 2253 this.input.clearHint(); 2254 this.input.resetInputValue(); 2255 this.eventBus.trigger("close"); 2256 } 2257 return !this.isOpen(); 2258 }, 2259 setVal: function setVal(val) { 2260 this.input.setQuery(_.toStr(val)); 2261 }, 2262 getVal: function getVal() { 2263 return this.input.getQuery(); 2264 }, 2265 select: function select($selectable) { 2266 var data = this.menu.getSelectableData($selectable); 2267 if (data && !this.eventBus.before("select", data.obj)) { 2268 this.input.setQuery(data.val, true); 2269 this.eventBus.trigger("select", data.obj); 2270 this.close(); 2271 return true; 2272 } 2273 return false; 2274 }, 2275 autocomplete: function autocomplete($selectable) { 2276 var query, data, isValid; 2277 query = this.input.getQuery(); 2278 data = this.menu.getSelectableData($selectable); 2279 isValid = data && query !== data.val; 2280 if (isValid && !this.eventBus.before("autocomplete", data.obj)) { 2281 this.input.setQuery(data.val); 2282 this.eventBus.trigger("autocomplete", data.obj); 2283 return true; 2284 } 2285 return false; 2286 }, 2287 moveCursor: function moveCursor(delta) { 2288 var query, $candidate, data, payload, cancelMove; 2289 query = this.input.getQuery(); 2290 $candidate = this.menu.selectableRelativeToCursor(delta); 2291 data = this.menu.getSelectableData($candidate); 2292 payload = data ? data.obj : null; 2293 cancelMove = this._minLengthMet() && this.menu.update(query); 2294 if (!cancelMove && !this.eventBus.before("cursorchange", payload)) { 2295 this.menu.setCursor($candidate); 2296 if (data) { 2297 this.input.setInputValue(data.val); 2298 } else { 2299 this.input.resetInputValue(); 2300 this._updateHint(); 2301 } 2302 this.eventBus.trigger("cursorchange", payload); 2303 return true; 2304 } 2305 return false; 2306 }, 2307 destroy: function destroy() { 2308 this.input.destroy(); 2309 this.menu.destroy(); 2310 } 2311 }); 2312 return Typeahead; 2313 2314 function c(ctx) { 2315 var methods = [].slice.call(arguments, 1); 2316 return function () { 2317 var args = [].slice.call(arguments); 2318 _.each(methods, function (method) { 2319 return ctx[method].apply(ctx, args); 2320 }); 2321 }; 2322 } 2323 }(); 2324 (function () { 2325 "use strict"; 2326 var old, keys, methods; 2327 old = $.fn.typeahead; 2328 keys = { 2329 www: "tt-www", 2330 attrs: "tt-attrs", 2331 typeahead: "tt-typeahead" 2332 }; 2333 methods = { 2334 initialize: function initialize(o, datasets) { 2335 var www; 2336 datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); 2337 o = o || {}; 2338 www = WWW(o.classNames); 2339 return this.each(attach); 2340 2341 function attach() { 2342 var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, 2343 MenuConstructor; 2344 _.each(datasets, function (d) { 2345 d.highlight = !!o.highlight; 2346 }); 2347 $input = $(this); 2348 $wrapper = $(www.html.wrapper); 2349 $hint = $elOrNull(o.hint); 2350 $menu = $elOrNull(o.menu); 2351 defaultHint = o.hint !== false && !$hint; 2352 defaultMenu = o.menu !== false && !$menu; 2353 defaultHint && ($hint = buildHintFromInput($input, www)); 2354 defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); 2355 $hint && $hint.val(""); 2356 $input = prepInput($input, www); 2357 if (defaultHint || defaultMenu) { 2358 $wrapper.css(www.css.wrapper); 2359 $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); 2360 $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); 2361 } 2362 MenuConstructor = defaultMenu ? DefaultMenu : Menu; 2363 eventBus = new EventBus({ 2364 el: $input 2365 }); 2366 input = new Input({ 2367 hint: $hint, 2368 input: $input 2369 }, www); 2370 menu = new MenuConstructor({ 2371 node: $menu, 2372 datasets: datasets 2373 }, www); 2374 typeahead = new Typeahead({ 2375 input: input, 2376 menu: menu, 2377 eventBus: eventBus, 2378 minLength: o.minLength 2379 }, www); 2380 $input.data(keys.www, www); 2381 $input.data(keys.typeahead, typeahead); 2382 } 2383 }, 2384 isEnabled: function isEnabled() { 2385 var enabled; 2386 ttEach(this.first(), function (t) { 2387 enabled = t.isEnabled(); 2388 }); 2389 return enabled; 2390 }, 2391 enable: function enable() { 2392 ttEach(this, function (t) { 2393 t.enable(); 2394 }); 2395 return this; 2396 }, 2397 disable: function disable() { 2398 ttEach(this, function (t) { 2399 t.disable(); 2400 }); 2401 return this; 2402 }, 2403 isActive: function isActive() { 2404 var active; 2405 ttEach(this.first(), function (t) { 2406 active = t.isActive(); 2407 }); 2408 return active; 2409 }, 2410 activate: function activate() { 2411 ttEach(this, function (t) { 2412 t.activate(); 2413 }); 2414 return this; 2415 }, 2416 deactivate: function deactivate() { 2417 ttEach(this, function (t) { 2418 t.deactivate(); 2419 }); 2420 return this; 2421 }, 2422 isOpen: function isOpen() { 2423 var open; 2424 ttEach(this.first(), function (t) { 2425 open = t.isOpen(); 2426 }); 2427 return open; 2428 }, 2429 open: function open() { 2430 ttEach(this, function (t) { 2431 t.open(); 2432 }); 2433 return this; 2434 }, 2435 close: function close() { 2436 ttEach(this, function (t) { 2437 t.close(); 2438 }); 2439 return this; 2440 }, 2441 select: function select(el) { 2442 var success = false, $el = $(el); 2443 ttEach(this.first(), function (t) { 2444 success = t.select($el); 2445 }); 2446 return success; 2447 }, 2448 autocomplete: function autocomplete(el) { 2449 var success = false, $el = $(el); 2450 ttEach(this.first(), function (t) { 2451 success = t.autocomplete($el); 2452 }); 2453 return success; 2454 }, 2455 moveCursor: function moveCursoe(delta) { 2456 var success = false; 2457 ttEach(this.first(), function (t) { 2458 success = t.moveCursor(delta); 2459 }); 2460 return success; 2461 }, 2462 val: function val(newVal) { 2463 var query; 2464 if (!arguments.length) { 2465 ttEach(this.first(), function (t) { 2466 query = t.getVal(); 2467 }); 2468 return query; 2469 } else { 2470 ttEach(this, function (t) { 2471 t.setVal(newVal); 2472 }); 2473 return this; 2474 } 2475 }, 2476 destroy: function destroy() { 2477 ttEach(this, function (typeahead, $input) { 2478 revert($input); 2479 typeahead.destroy(); 2480 }); 2481 return this; 2482 } 2483 }; 2484 $.fn.typeahead = function (method) { 2485 if (methods[method]) { 2486 return methods[method].apply(this, [].slice.call(arguments, 1)); 2487 } else { 2488 return methods.initialize.apply(this, arguments); 2489 } 2490 }; 2491 $.fn.typeahead.noConflict = function noConflict() { 2492 $.fn.typeahead = old; 2493 return this; 2494 }; 2495 2496 function ttEach($els, fn) { 2497 $els.each(function () { 2498 var $input = $(this), typeahead; 2499 (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); 2500 }); 2501 } 2502 2503 function buildHintFromInput($input, www) { 2504 return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({ 2505 autocomplete: "off", 2506 spellcheck: "false", 2507 tabindex: -1 2508 }); 2509 } 2510 2511 function prepInput($input, www) { 2512 $input.data(keys.attrs, { 2513 dir: $input.attr("dir"), 2514 autocomplete: $input.attr("autocomplete"), 2515 spellcheck: $input.attr("spellcheck"), 2516 style: $input.attr("style") 2517 }); 2518 $input.addClass(www.classes.input).attr({ 2519 autocomplete: "off", 2520 spellcheck: false 2521 }); 2522 try { 2523 !$input.attr("dir") && $input.attr("dir", "auto"); 2524 } catch (e) { 2525 } 2526 return $input; 2527 } 2528 2529 function getBackgroundStyles($el) { 2530 return { 2531 backgroundAttachment: $el.css("background-attachment"), 2532 backgroundClip: $el.css("background-clip"), 2533 backgroundColor: $el.css("background-color"), 2534 backgroundImage: $el.css("background-image"), 2535 backgroundOrigin: $el.css("background-origin"), 2536 backgroundPosition: $el.css("background-position"), 2537 backgroundRepeat: $el.css("background-repeat"), 2538 backgroundSize: $el.css("background-size") 2539 }; 2540 } 2541 2542 function revert($input) { 2543 var www, $wrapper; 2544 www = $input.data(keys.www); 2545 $wrapper = $input.parent().filter(www.selectors.wrapper); 2546 _.each($input.data(keys.attrs), function (val, key) { 2547 _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); 2548 }); 2549 $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); 2550 if ($wrapper.length) { 2551 $input.detach().insertAfter($wrapper); 2552 $wrapper.remove(); 2553 } 2554 } 2555 2556 function $elOrNull(obj) { 2557 var isValid, $el; 2558 isValid = _.isJQuery(obj) || _.isElement(obj); 2559 $el = isValid ? $(obj).first() : []; 2560 return $el.length ? $el : null; 2561 } 2562 })(); 2563});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.