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