vendor: 1,639 bytes, lines 1-52
1/*! 2 * typeahead.js 0.10.2 3 * https://github.com/twitter/typeahead.js 4 * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT 5 */ 6 7(function($) { 8 var _ = { 9 isMsie: function() { 10 return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; 11 }, 12 isBlankString: function(str) { 13 return !str || /^\s*$/.test(str); 14 }, 15 escapeRegExChars: function(str) { 16 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 17 }, 18 isString: function(obj) { 19 return typeof obj === "string"; 20 }, 21 isNumber: function(obj) { 22 return typeof obj === "number"; 23 }, 24 isArray: $.isArray, 25 isFunction: $.isFunction, 26 isObject: $.isPlainObject, 27 isUndefined: function(obj) { 28 return typeof obj === "undefined"; 29 }, 30 bind: $.proxy, 31 each: function(collection, cb) { 32 $.each(collection, reverseArgs); 33 function reverseArgs(index, value) { 34 return cb(value, index); 35 } 36 }, 37 map: $.map, 38 filter: $.grep, 39 every: function(obj, test) { 40 var result = true; 41 if (!obj) { 42 return result; 43 } 44 $.each(obj, function(key, val) { 45 if (!(result = test.call(null, val, key, obj))) { 46 return false; 47 } 48 }); 49 return !!result; 50 }, 51 some: function(obj, test) { 52 var result = false;
53 if (!obj) { 54 return result; 55 } 56 $.each(obj, function(key, val) { 57 if (result = test.call(null, val, key, obj)) { 58 return false; 59 } 60 }); 61 return !!result; 62 }, 63 mixin: $.extend, 64 getUniqueId: function() { 65 var counter = 0; 66 return function() { 67 return counter++; 68 }; 69 }(), 70 templatify: function templatify(obj) { 71 return $.isFunction(obj) ? obj : template; 72 function template() { 73 return String(obj); 74 } 75 }, 76 defer: function(fn) { 77 setTimeout(fn, 0); 78 }, 79 debounce: function(func, wait, immediate) { 80 var timeout, result; 81 return function() { 82 var context = this, args = arguments, later, callNow; 83 later = function() { 84 timeout = null; 85 if (!immediate) { 86 result = func.apply(context, args); 87 } 88 }; 89 callNow = immediate && !timeout; 90 clearTimeout(timeout); 91 timeout = setTimeout(later, wait); 92 if (callNow) { 93 result = func.apply(context, args); 94 } 95 return result; 96 }; 97 }, 98 throttle: function(func, wait) { 99 var context, args, timeout, result, previous, later; 100 previous = 0; 101 later = function() { 102 previous = new Date(); 103 timeout = null; 104 result = func.apply(context, args); 105 }; 106 return function() { 107 var now = new Date(), remaining = wait - (now - previous); 108 context = this; 109 args = arguments; 110 if (remaining <= 0) { 111 clearTimeout(timeout); 112 timeout = null; 113 previous = now; 114 result = func.apply(context, args); 115 } else if (!timeout) { 116 timeout = setTimeout(later, remaining); 117 } 118 return result; 119 }; 120 }, 121 noop: function() {} 122 }; 123 var VERSION = "0.10.2"; 124 var tokenizers = function(root) { 125 return { 126 nonword: nonword, 127 whitespace: whitespace, 128 obj: { 129 nonword: getObjTokenizer(nonword), 130 whitespace: getObjTokenizer(whitespace) 131 } 132 }; 133 function whitespace(s) { 134 return s.split(/\s+/); 135 } 136 function nonword(s) { 137 return s.split(/\W+/); 138 } 139 function getObjTokenizer(tokenizer) { 140 return function setKey(key) { 141 return function tokenize(o) { 142 return tokenizer(o[key]); 143 }; 144 }; 145 } 146 }(); 147 var LruCache = function() { 148 function LruCache(maxSize) { 149 this.maxSize = maxSize || 100; 150 this.size = 0; 151 this.hash = {}; 152 this.list = new List(); 153 } 154 _.mixin(LruCache.prototype, { 155 set: function set(key, val) { 156 var tailItem = this.list.tail, node; 157 if (this.size >= this.maxSize) { 158 this.list.remove(tailItem); 159 delete this.hash[tailItem.key]; 160 } 161 if (node = this.hash[key]) { 162 node.val = val; 163 this.list.moveToFront(node); 164 } else { 165 node = new Node(key, val); 166 this.list.add(node); 167 this.hash[key] = node; 168 this.size++; 169 } 170 }, 171 get: function get(key) { 172 var node = this.hash[key]; 173 if (node) { 174 this.list.moveToFront(node); 175 return node.val; 176 } 177 } 178 }); 179 function List() { 180 this.head = this.tail = null; 181 } 182 _.mixin(List.prototype, { 183 add: function add(node) { 184 if (this.head) { 185 node.next = this.head; 186 this.head.prev = node; 187 } 188 this.head = node; 189 this.tail = this.tail || node; 190 }, 191 remove: function remove(node) { 192 node.prev ? node.prev.next = node.next : this.head = node.next; 193 node.next ? node.next.prev = node.prev : this.tail = node.prev; 194 }, 195 moveToFront: function(node) { 196 this.remove(node); 197 this.add(node); 198 } 199 }); 200 function Node(key, val) { 201 this.key = key; 202 this.val = val; 203 this.prev = this.next = null; 204 } 205 return LruCache; 206 }(); 207 var PersistentStorage = function() { 208 var ls, methods; 209 try { 210 ls = window.localStorage; 211 ls.setItem("~~~", "!"); 212 ls.removeItem("~~~"); 213 } catch (err) { 214 ls = null; 215 } 216 function PersistentStorage(namespace) { 217 this.prefix = [ "__", namespace, "__" ].join(""); 218 this.ttlKey = "__ttl__"; 219 this.keyMatcher = new RegExp("^" + this.prefix); 220 } 221 if (ls && window.JSON) { 222 methods = { 223 _prefix: function(key) { 224 return this.prefix + key; 225 },
226 _ttlKey: function(key) { 227 return this._prefix(key) + this.ttlKey; 228 }, 229 get: function(key) { 230 if (this.isExpired(key)) { 231 this.remove(key); 232 } 233 return decode(ls.getItem(this._prefix(key))); 234 }, 235 set: function(key, val, ttl) { 236 if (_.isNumber(ttl)) { 237 ls.setItem(this._ttlKey(key), encode(now() + ttl)); 238 } else { 239 ls.removeItem(this._ttlKey(key)); 240 } 241 return ls.setItem(this._prefix(key), encode(val)); 242 }, 243 remove: function(key) { 244 ls.removeItem(this._ttlKey(key)); 245 ls.removeItem(this._prefix(key)); 246 return this; 247 }, 248 clear: function() { 249 var i, key, keys = [], len = ls.length; 250 for (i = 0; i < len; i++) { 251 if ((key = ls.key(i)).match(this.keyMatcher)) { 252 keys.push(key.replace(this.keyMatcher, "")); 253 } 254 } 255 for (i = keys.length; i--; ) { 256 this.remove(keys[i]); 257 } 258 return this; 259 }, 260 isExpired: function(key) { 261 var ttl = decode(ls.getItem(this._ttlKey(key))); 262 return _.isNumber(ttl) && now() > ttl ? true : false; 263 } 264 }; 265 } else { 266 methods = { 267 get: _.noop, 268 set: _.noop, 269 remove: _.noop, 270 clear: _.noop, 271 isExpired: _.noop 272 }; 273 } 274 _.mixin(PersistentStorage.prototype, methods); 275 return PersistentStorage; 276 function now() { 277 return new Date().getTime(); 278 } 279 function encode(val) { 280 return JSON.stringify(_.isUndefined(val) ? null : val); 281 } 282 function decode(val) { 283 return JSON.parse(val); 284 } 285 }(); 286 var Transport = function() { 287 var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10); 288 function Transport(o) { 289 o = o || {}; 290 this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax; 291 this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get; 292 } 293 Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { 294 maxPendingRequests = num; 295 }; 296 Transport.resetCache = function clearCache() { 297 requestCache = new LruCache(10); 298 }; 299 _.mixin(Transport.prototype, { 300 _get: function(url, o, cb) { 301 var that = this, jqXhr; 302 if (jqXhr = pendingRequests[url]) { 303 jqXhr.done(done).fail(fail); 304 } else if (pendingRequestsCount < maxPendingRequests) { 305 pendingRequestsCount++; 306 pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always); 307 } else { 308 this.onDeckRequestArgs = [].slice.call(arguments, 0); 309 } 310 function done(resp) { 311 cb && cb(null, resp); 312 requestCache.set(url, resp); 313 } 314 function fail() { 315 cb && cb(true); 316 } 317 function always() { 318 pendingRequestsCount--; 319 delete pendingRequests[url]; 320 if (that.onDeckRequestArgs) { 321 that._get.apply(that, that.onDeckRequestArgs); 322 that.onDeckRequestArgs = null; 323 } 324 } 325 }, 326 get: function(url, o, cb) { 327 var resp; 328 if (_.isFunction(o)) { 329 cb = o; 330 o = {}; 331 } 332 if (resp = requestCache.get(url)) { 333 _.defer(function() { 334 cb && cb(null, resp); 335 }); 336 } else { 337 this._get(url, o, cb); 338 } 339 return !!resp; 340 } 341 }); 342 return Transport; 343 function callbackToDeferred(fn) { 344 return function customSendWrapper(url, o) { 345 var deferred = $.Deferred(); 346 fn(url, o, onSuccess, onError); 347 return deferred; 348 function onSuccess(resp) { 349 _.defer(function() { 350 deferred.resolve(resp); 351 }); 352 } 353 function onError(err) { 354 _.defer(function() { 355 deferred.reject(err); 356 }); 357 } 358 }; 359 } 360 }(); 361 var SearchIndex = function() { 362 function SearchIndex(o) { 363 o = o || {}; 364 if (!o.datumTokenizer || !o.queryTokenizer) { 365 $.error("datumTokenizer and queryTokenizer are both required"); 366 } 367 this.datumTokenizer = o.datumTokenizer; 368 this.queryTokenizer = o.queryTokenizer; 369 this.reset(); 370 } 371 _.mixin(SearchIndex.prototype, { 372 bootstrap: function bootstrap(o) { 373 this.datums = o.datums; 374 this.trie = o.trie; 375 }, 376 add: function(data) { 377 var that = this; 378 data = _.isArray(data) ? data : [ data ]; 379 _.each(data, function(datum) { 380 var id, tokens; 381 id = that.datums.push(datum) - 1; 382 tokens = normalizeTokens(that.datumTokenizer(datum)); 383 _.each(tokens, function(token) { 384 var node, chars, ch; 385 node = that.trie; 386 chars = token.split(""); 387 while (ch = chars.shift()) { 388 node = node.children[ch] || (node.children[ch] = newNode()); 389 node.ids.push(id); 390 } 391 }); 392 }); 393 }, 394 get: function get(query) { 395 var that = this, tokens, matches; 396 tokens = normalizeTokens(this.queryTokenizer(query)); 397 _.each(tokens, function(token) { 398 var node, chars, ch, ids; 399 if (matches && matches.length === 0) { 400 return false;
401 } 402 node = that.trie; 403 chars = token.split(""); 404 while (node && (ch = chars.shift())) { 405 node = node.children[ch]; 406 } 407 if (node && chars.length === 0) { 408 ids = node.ids.slice(0); 409 matches = matches ? getIntersection(matches, ids) : ids; 410 } else { 411 matches = []; 412 return false; 413 } 414 }); 415 return matches ? _.map(unique(matches), function(id) { 416 return that.datums[id]; 417 }) : []; 418 }, 419 reset: function reset() { 420 this.datums = []; 421 this.trie = newNode(); 422 }, 423 serialize: function serialize() { 424 return { 425 datums: this.datums, 426 trie: this.trie 427 }; 428 } 429 }); 430 return SearchIndex; 431 function normalizeTokens(tokens) { 432 tokens = _.filter(tokens, function(token) { 433 return !!token; 434 }); 435 tokens = _.map(tokens, function(token) { 436 return token.toLowerCase(); 437 }); 438 return tokens; 439 } 440 function newNode() { 441 return { 442 ids: [], 443 children: {} 444 }; 445 } 446 function unique(array) { 447 var seen = {}, uniques = []; 448 for (var i = 0; i < array.length; i++) { 449 if (!seen[array[i]]) { 450 seen[array[i]] = true; 451 uniques.push(array[i]); 452 } 453 } 454 return uniques; 455 } 456 function getIntersection(arrayA, arrayB) { 457 var ai = 0, bi = 0, intersection = []; 458 arrayA = arrayA.sort(compare); 459 arrayB = arrayB.sort(compare); 460 while (ai < arrayA.length && bi < arrayB.length) { 461 if (arrayA[ai] < arrayB[bi]) { 462 ai++; 463 } else if (arrayA[ai] > arrayB[bi]) { 464 bi++; 465 } else { 466 intersection.push(arrayA[ai]); 467 ai++; 468 bi++; 469 } 470 } 471 return intersection; 472 function compare(a, b) { 473 return a - b; 474 } 475 } 476 }(); 477 var oParser = function() { 478 return { 479 local: getLocal, 480 prefetch: getPrefetch, 481 remote: getRemote 482 }; 483 function getLocal(o) { 484 return o.local || null; 485 } 486 function getPrefetch(o) { 487 var prefetch, defaults; 488 defaults = { 489 url: null, 490 thumbprint: "", 491 ttl: 24 * 60 * 60 * 1e3, 492 filter: null, 493 ajax: {} 494 }; 495 if (prefetch = o.prefetch || null) { 496 prefetch = _.isString(prefetch) ? { 497 url: prefetch 498 } : prefetch; 499 prefetch = _.mixin(defaults, prefetch); 500 prefetch.thumbprint = VERSION + prefetch.thumbprint; 501 prefetch.ajax.type = prefetch.ajax.type || "GET"; 502 prefetch.ajax.dataType = prefetch.ajax.dataType || "json"; 503 !prefetch.url && $.error("prefetch requires url to be set"); 504 } 505 return prefetch; 506 } 507 function getRemote(o) { 508 var remote, defaults; 509 defaults = { 510 url: null, 511 wildcard: "%QUERY", 512 replace: null, 513 rateLimitBy: "debounce", 514 rateLimitWait: 200, 515 send: null, 516 filter: null, 517 ajax: {} 518 }; 519 if (remote = o.remote || null) { 520 remote = _.isString(remote) ? { 521 url: remote 522 } : remote; 523 remote = _.mixin(defaults, remote); 524 remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait); 525 remote.ajax.type = remote.ajax.type || "GET"; 526 remote.ajax.dataType = remote.ajax.dataType || "json"; 527 delete remote.rateLimitBy; 528 delete remote.rateLimitWait; 529 !remote.url && $.error("remote requires url to be set"); 530 } 531 return remote; 532 function byDebounce(wait) { 533 return function(fn) { 534 return _.debounce(fn, wait); 535 }; 536 } 537 function byThrottle(wait) { 538 return function(fn) { 539 return _.throttle(fn, wait); 540 }; 541 } 542 } 543 }(); 544 (function(root) { 545 var old, keys; 546 old = root.Bloodhound; 547 keys = { 548 data: "data", 549 protocol: "protocol", 550 thumbprint: "thumbprint" 551 }; 552 root.Bloodhound = Bloodhound; 553 function Bloodhound(o) { 554 if (!o || !o.local && !o.prefetch && !o.remote) { 555 $.error("one of local, prefetch, or remote is required"); 556 } 557 this.limit = o.limit || 5; 558 this.sorter = getSorter(o.sorter); 559 this.dupDetector = o.dupDetector || ignoreDuplicates; 560 this.local = oParser.local(o); 561 this.prefetch = oParser.prefetch(o); 562 this.remote = oParser.remote(o); 563 this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null; 564 this.index = new SearchIndex({ 565 datumTokenizer: o.datumTokenizer, 566 queryTokenizer: o.queryTokenizer 567 }); 568 this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null; 569 } 570 Bloodhound.noConflict = function noConflict() { 571 root.Bloodhound = old; 572 return Bloodhound; 573 }; 574 Bloodhound.tokenizers = tokenizers; 575 _.mixin(Bloodhound.prototype, { 576 _loadPrefetch: function loadPrefetch(o) { 577 var that = this, serialized, deferred; 578 if (serialized = this._readFromStorage(o.thumbprint)) { 579 this.index.bootstrap(serialized); 580 deferred = $.Deferred().resolve(); 581 } else { 582 deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse); 583 } 584 return deferred; 585 function handlePrefetchResponse(resp) { 586 that.clear(); 587 that.add(o.filter ? o.filter(resp) : resp); 588 that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); 589 } 590 }, 591 _getFromRemote: function getFromRemote(query, cb) { 592 var that = this, url, uriEncodedQuery; 593 query = query || ""; 594 uriEncodedQuery = encodeURIComponent(query); 595 url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery); 596 return this.transport.get(url, this.remote.ajax, handleRemoteResponse); 597 function handleRemoteResponse(err, resp) { 598 err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp); 599 } 600 }, 601 _saveToStorage: function saveToStorage(data, thumbprint, ttl) { 602 if (this.storage) { 603 this.storage.set(keys.data, data, ttl); 604 this.storage.set(keys.protocol, location.protocol, ttl); 605 this.storage.set(keys.thumbprint, thumbprint, ttl); 606 } 607 }, 608 _readFromStorage: function readFromStorage(thumbprint) { 609 var stored = {}, isExpired; 610 if (this.storage) { 611 stored.data = this.storage.get(keys.data); 612 stored.protocol = this.storage.get(keys.protocol); 613 stored.thumbprint = this.storage.get(keys.thumbprint); 614 } 615 isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol; 616 return stored.data && !isExpired ? stored.data : null; 617 }, 618 _initialize: function initialize() { 619 var that = this, local = this.local, deferred; 620 deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve(); 621 local && deferred.done(addLocalToIndex); 622 this.transport = this.remote ? new Transport(this.remote) : null; 623 return this.initPromise = deferred.promise(); 624 function addLocalToIndex() { 625 that.add(_.isFunction(local) ? local() : local); 626 } 627 }, 628 initialize: function initialize(force) { 629 return !this.initPromise || force ? this._initialize() : this.initPromise; 630 }, 631 add: function add(data) { 632 this.index.add(data); 633 }, 634 get: function get(query, cb) { 635 var that = this, matches = [], cacheHit = false;
636 matches = this.index.get(query); 637 matches = this.sorter(matches).slice(0, this.limit); 638 if (matches.length < this.limit && this.transport) { 639 cacheHit = this._getFromRemote(query, returnRemoteMatches); 640 } 641 if (!cacheHit) { 642 (matches.length > 0 || !this.transport) && cb && cb(matches); 643 } 644 function returnRemoteMatches(remoteMatches) { 645 var matchesWithBackfill = matches.slice(0); 646 _.each(remoteMatches, function(remoteMatch) { 647 var isDuplicate; 648 isDuplicate = _.some(matchesWithBackfill, function(match) { 649 return that.dupDetector(remoteMatch, match); 650 }); 651 !isDuplicate && matchesWithBackfill.push(remoteMatch); 652 return matchesWithBackfill.length < that.limit; 653 }); 654 cb && cb(that.sorter(matchesWithBackfill)); 655 } 656 }, 657 clear: function clear() { 658 this.index.reset(); 659 }, 660 clearPrefetchCache: function clearPrefetchCache() { 661 this.storage && this.storage.clear(); 662 }, 663 clearRemoteCache: function clearRemoteCache() { 664 this.transport && Transport.resetCache(); 665 },
666 ttAdapter: function ttAdapter() { 667 return _.bind(this.get, this); 668 } 669 }); 670 return Bloodhound; 671 function getSorter(sortFn) { 672 return _.isFunction(sortFn) ? sort : noSort; 673 function sort(array) { 674 return array.sort(sortFn); 675 } 676 function noSort(array) { 677 return array; 678 } 679 } 680 function ignoreDuplicates() { 681 return false; 682 } 683 })(this); 684 var html = { 685 wrapper: '<span class="twitter-typeahead"></span>', 686 dropdown: '<span class="tt-dropdown-menu"></span>', 687 dataset: '<div class="tt-dataset-%CLASS%"></div>', 688 suggestions: '<span class="tt-suggestions"></span>', 689 suggestion: '<div class="tt-suggestion"></div>' 690 }; 691 var css = { 692 wrapper: { 693 position: "relative", 694 display: "inline-block" 695 }, 696 hint: { 697 position: "absolute", 698 top: "0", 699 left: "0", 700 borderColor: "transparent", 701 boxShadow: "none" 702 }, 703 input: { 704 position: "relative", 705 verticalAlign: "top", 706 backgroundColor: "transparent" 707 }, 708 inputWithNoHint: { 709 position: "relative", 710 verticalAlign: "top" 711 }, 712 dropdown: { 713 position: "absolute", 714 top: "100%", 715 left: "0", 716 zIndex: "100", 717 display: "none" 718 }, 719 suggestions: { 720 display: "block" 721 }, 722 suggestion: { 723 whiteSpace: "nowrap", 724 cursor: "pointer" 725 }, 726 suggestionChild: { 727 whiteSpace: "normal" 728 }, 729 ltr: { 730 left: "0", 731 right: "auto" 732 }, 733 rtl: { 734 left: "auto", 735 right: " 0" 736 } 737 }; 738 if (_.isMsie()) { 739 _.mixin(css.input, { 740 backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" 741 }); 742 } 743 if (_.isMsie() && _.isMsie() <= 7) { 744 _.mixin(css.input, { 745 marginTop: "-1px" 746 }); 747 } 748 var EventBus = function() { 749 var namespace = "typeahead:"; 750 function EventBus(o) { 751 if (!o || !o.el) { 752 $.error("EventBus initialized without el"); 753 } 754 this.$el = $(o.el); 755 } 756 _.mixin(EventBus.prototype, { 757 trigger: function(type) { 758 var args = [].slice.call(arguments, 1); 759 this.$el.trigger(namespace + type, args); 760 } 761 }); 762 return EventBus; 763 }(); 764 var EventEmitter = function() { 765 var splitter = /\s+/, nextTick = getNextTick(); 766 return { 767 onSync: onSync, 768 onAsync: onAsync, 769 off: off, 770 trigger: trigger 771 }; 772 function on(method, types, cb, context) { 773 var type; 774 if (!cb) { 775 return this; 776 } 777 types = types.split(splitter); 778 cb = context ? bindContext(cb, context) : cb; 779 this._callbacks = this._callbacks || {}; 780 while (type = types.shift()) { 781 this._callbacks[type] = this._callbacks[type] || { 782 sync: [], 783 async: [] 784 }; 785 this._callbacks[type][method].push(cb); 786 } 787 return this; 788 } 789 function onAsync(types, cb, context) { 790 return on.call(this, "async", types, cb, context); 791 } 792 function onSync(types, cb, context) { 793 return on.call(this, "sync", types, cb, context); 794 } 795 function off(types) { 796 var type; 797 if (!this._callbacks) { 798 return this; 799 } 800 types = types.split(splitter); 801 while (type = types.shift()) { 802 delete this._callbacks[type]; 803 } 804 return this; 805 } 806 function trigger(types) { 807 var type, callbacks, args, syncFlush, asyncFlush; 808 if (!this._callbacks) { 809 return this; 810 } 811 types = types.split(splitter); 812 args = [].slice.call(arguments, 1); 813 while ((type = types.shift()) && (callbacks = this._callbacks[type])) { 814 syncFlush = getFlush(callbacks.sync, this, [ type ].
vendor: 4,669 bytes, lines 814-932
814concat(args)); 815 asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); 816 syncFlush() && nextTick(asyncFlush); 817 } 818 return this; 819 } 820 function getFlush(callbacks, context, args) { 821 return flush; 822 function flush() { 823 var cancelled; 824 for (var i = 0; !cancelled && i < callbacks.length; i += 1) { 825 cancelled = callbacks[i].apply(context, args) === false; 826 } 827 return !cancelled; 828 } 829 } 830 function getNextTick() { 831 var nextTickFn; 832 if (window.setImmediate) { 833 nextTickFn = function nextTickSetImmediate(fn) { 834 setImmediate(function() { 835 fn(); 836 }); 837 }; 838 } else { 839 nextTickFn = function nextTickSetTimeout(fn) { 840 setTimeout(function() { 841 fn(); 842 }, 0); 843 }; 844 } 845 return nextTickFn; 846 } 847 function bindContext(fn, context) { 848 return fn.bind ? fn.bind(context) : function() { 849 fn.apply(context, [].slice.call(arguments, 0)); 850 }; 851 } 852 }(); 853 var highlight = function(doc) { 854 var defaults = { 855 node: null, 856 pattern: null, 857 tagName: "strong", 858 className: null, 859 wordsOnly: false, 860 caseSensitive: false 861 }; 862 return function hightlight(o) { 863 var regex; 864 o = _.mixin({}, defaults, o); 865 if (!o.node || !o.pattern) { 866 return; 867 } 868 o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; 869 regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); 870 traverse(o.node, hightlightTextNode); 871 function hightlightTextNode(textNode) { 872 var match, patternNode; 873 if (match = regex.exec(textNode.data)) { 874 wrapperNode = doc.createElement(o.tagName); 875 o.className && (wrapperNode.className = o.className); 876 patternNode = textNode.splitText(match.index); 877 patternNode.splitText(match[0].length); 878 wrapperNode.appendChild(patternNode.cloneNode(true)); 879 textNode.parentNode.replaceChild(wrapperNode, patternNode); 880 } 881 return !!match; 882 } 883 function traverse(el, hightlightTextNode) { 884 var childNode, TEXT_NODE_TYPE = 3; 885 for (var i = 0; i < el.childNodes.length; i++) { 886 childNode = el.childNodes[i]; 887 if (childNode.nodeType === TEXT_NODE_TYPE) { 888 i += hightlightTextNode(childNode) ? 1 : 0; 889 } else { 890 traverse(childNode, hightlightTextNode); 891 } 892 } 893 } 894 }; 895 function getRegex(patterns, caseSensitive, wordsOnly) { 896 var escapedPatterns = [], regexStr; 897 for (var i = 0; i < patterns.length; i++) { 898 escapedPatterns.push(_.escapeRegExChars(patterns[i])); 899 } 900 regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; 901 return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); 902 } 903 }(window.document); 904 var Input = function() { 905 var specialKeyCodeMap; 906 specialKeyCodeMap = { 907 9: "tab", 908 27: "esc", 909 37: "left", 910 39: "right", 911 13: "enter", 912 38: "up", 913 40: "down" 914 }; 915 function Input(o) { 916 var that = this, onBlur, onFocus, onKeydown, onInput; 917 o = o || {}; 918 if (!o.input) { 919 $.error("input is missing"); 920 } 921 onBlur = _.bind(this._onBlur, this); 922 onFocus = _.bind(this._onFocus, this); 923 onKeydown = _.bind(this._onKeydown, this); 924 onInput = _.bind(this._onInput, this); 925 this.$hint = $(o.hint); 926 this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); 927 if (this.$hint.length === 0) { 928 this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; 929 } 930 if (!_.isMsie()) { 931 this.$input.on("input.tt", onInput); 932 } else {
vendor: 1,932 bytes, lines 933-979
933 this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { 934 if (specialKeyCodeMap[$e.which || $e.keyCode]) { 935 return; 936 } 937 _.defer(_.bind(that._onInput, that, $e)); 938 }); 939 } 940 this.query = this.$input.val(); 941 this.$overflowHelper = buildOverflowHelper(this.$input); 942 } 943 Input.normalizeQuery = function(str) { 944 return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); 945 }; 946 _.mixin(Input.prototype, EventEmitter, { 947 _onBlur: function onBlur() { 948 this.resetInputValue(); 949 this.trigger("blurred"); 950 }, 951 _onFocus: function onFocus() { 952 this.trigger("focused"); 953 }, 954 _onKeydown: function onKeydown($e) { 955 var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; 956 this._managePreventDefault(keyName, $e); 957 if (keyName && this._shouldTrigger(keyName, $e)) { 958 this.trigger(keyName + "Keyed", $e); 959 } 960 }, 961 _onInput: function onInput() { 962 this._checkInputValue(); 963 }, 964 _managePreventDefault: function managePreventDefault(keyName, $e) { 965 var preventDefault, hintValue, inputValue; 966 switch (keyName) { 967 case "tab": 968 hintValue = this.getHint(); 969 inputValue = this.getInputValue(); 970 preventDefault = hintValue && hintValue !== inputValue && !withModifier($e); 971 break; 972 973 case "up": 974 case "down": 975 preventDefault = !withModifier($e); 976 break; 977 978 default: 979 preventDefault = false;
980 } 981 preventDefault && $e.preventDefault(); 982 }, 983 _shouldTrigger: function shouldTrigger(keyName, $e) { 984 var trigger; 985 switch (keyName) { 986 case "tab": 987 trigger = !withModifier($e); 988 break; 989 990 default: 991 trigger = true; 992 } 993 return trigger; 994 }, 995 _checkInputValue: function checkInputValue() { 996 var inputValue, areEquivalent, hasDifferentWhitespace; 997 inputValue = this.getInputValue(); 998 areEquivalent = areQueriesEquivalent(inputValue, this.query); 999 hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false; 1000 if (!areEquivalent) { 1001 this.trigger("queryChanged", this.query = inputValue); 1002 } else if (hasDifferentWhitespace) { 1003 this.trigger("whitespaceChanged", this.query); 1004 } 1005 }, 1006 focus: function focus() { 1007 this.$input.focus(); 1008 }, 1009 blur: function blur() { 1010 this.$input.blur(); 1011 }, 1012 getQuery: function getQuery() { 1013 return this.query; 1014 }, 1015 setQuery: function setQuery(query) { 1016 this.query = query; 1017 }, 1018 getInputValue: function getInputValue() { 1019 return this.$input.val(); 1020 }, 1021 setInputValue: function setInputValue(value, silent) { 1022 this.$input.val(value); 1023 silent ? this.clearHint() : this._checkInputValue(); 1024 }, 1025 resetInputValue: function resetInputValue() { 1026 this.setInputValue(this.query, true); 1027 }, 1028 getHint: function getHint() { 1029 return this.$hint.val(); 1030 }, 1031 setHint: function setHint(value) { 1032 this.$hint.val(value); 1033 }, 1034 clearHint: function clearHint() { 1035 this.setHint(""); 1036 }, 1037 clearHintIfInvalid: function clearHintIfInvalid() { 1038 var val, hint, valIsPrefixOfHint, isValid; 1039 val = this.getInputValue(); 1040 hint = this.getHint(); 1041 valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; 1042 isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); 1043 !isValid && this.clearHint(); 1044 }, 1045 getLanguageDirection: function getLanguageDirection() { 1046 return (this.$input.css("direction") || "ltr").toLowerCase(); 1047 }, 1048 hasOverflow: function hasOverflow() { 1049 var constraint = this.$input.width() - 2; 1050 this.$overflowHelper.text(this.getInputValue()); 1051 return this.$overflowHelper.width() >= constraint; 1052 }, 1053 isCursorAtEnd: function() { 1054 var valueLength, selectionStart, range; 1055 valueLength = this.$input.val().length; 1056 selectionStart = this.$input[0].selectionStart; 1057 if (_.isNumber(selectionStart)) { 1058 return selectionStart === valueLength; 1059 } else if (document.selection) { 1060 range = document.selection.createRange(); 1061 range.moveStart("character", -valueLength); 1062 return valueLength === range.text.length; 1063 } 1064 return true; 1065 }, 1066 destroy: function destroy() { 1067 this.$hint.off(".tt"); 1068 this.$input.off(".tt"); 1069 this.$hint = this.$input = this.$overflowHelper = null; 1070 } 1071 }); 1072 return Input; 1073 function buildOverflowHelper($input) { 1074 return $('<pre aria-hidden="true"></pre>').css({ 1075 position: "absolute", 1076 visibility: "hidden", 1077 whiteSpace: "pre", 1078 fontFamily: $input.css("font-family"), 1079 fontSize: $input.css("font-size"), 1080 fontStyle: $input.css("font-style"), 1081 fontVariant: $input.css("font-variant"), 1082 fontWeight: $input.css("font-weight"), 1083 wordSpacing: $input.css("word-spacing"), 1084 letterSpacing: $input.css("letter-spacing"), 1085 textIndent: $input.css("text-indent"), 1086 textRendering: $input.css("text-rendering"), 1087 textTransform: $input.css("text-transform") 1088 }).insertAfter($input); 1089 } 1090 function areQueriesEquivalent(a, b) { 1091 return Input.normalizeQuery(a) === Input.normalizeQuery(b); 1092 } 1093 function withModifier($e) { 1094 return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; 1095 } 1096 }(); 1097 var Dataset = function() { 1098 var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum"; 1099 function Dataset(o) { 1100 o = o || {}; 1101 o.templates = o.templates || {}; 1102 if (!o.source) { 1103 $.error("missing source"); 1104 } 1105 if (o.name && !isValidName(o.name)) { 1106 $.error("invalid dataset name: " + o.name); 1107 } 1108 this.query = null; 1109 this.highlight = !!o.highlight; 1110 this.name = o.name || _.getUniqueId(); 1111 this.source = o.source; 1112 this.displayFn = getDisplayFn(o.display || o.displayKey); 1113 this.templates = getTemplates(o.templates, this.displayFn); 1114 this.$el = $(html.dataset.replace("%CLASS%", this.name)); 1115 } 1116 Dataset.extractDatasetName = function extractDatasetName(el) { 1117 return $(el).data(datasetKey); 1118 }; 1119 Dataset.extractValue = function extractDatum(el) { 1120 return $(el).data(valueKey); 1121 }; 1122 Dataset.extractDatum = function extractDatum(el) { 1123 return $(el).data(datumKey); 1124 }; 1125 _.mixin(Dataset.prototype, EventEmitter, { 1126 _render: function render(query, suggestions) { 1127 if (!this.$el) { 1128 return; 1129 } 1130 var that = this, hasSuggestions; 1131 this.$el.empty(); 1132 hasSuggestions = suggestions && suggestions.length; 1133 if (!hasSuggestions && this.templates.empty) { 1134 this.$el.html(getEmptyHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); 1135 } else if (hasSuggestions) { 1136 this.$el.html(getSuggestionsHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); 1137 } 1138 this.trigger("rendered"); 1139 function getEmptyHtml() { 1140 return that.templates.empty({ 1141 query: query, 1142 isEmpty: true 1143 }); 1144 } 1145 function getSuggestionsHtml() { 1146 var $suggestions, nodes; 1147 $suggestions = $(html.suggestions).css(css.suggestions); 1148 nodes = _.map(suggestions, getSuggestionNode); 1149 $suggestions.append.apply($suggestions, nodes); 1150 that.highlight && highlight({ 1151 node: $suggestions[0], 1152 pattern: query 1153 }); 1154 return $suggestions; 1155 function getSuggestionNode(suggestion) { 1156 var $el; 1157 $el = $(html.suggestion).append(that.templates.suggestion(suggestion)).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion); 1158 $el.children().each(function() { 1159 $(this).css(css.suggestionChild); 1160 }); 1161 return $el; 1162 } 1163 } 1164 function getHeaderHtml() { 1165 return that.templates.header({ 1166 query: query, 1167 isEmpty: !hasSuggestions 1168 }); 1169 } 1170 function getFooterHtml() { 1171 return that.templates.footer({ 1172 query: query, 1173 isEmpty: !hasSuggestions 1174 }); 1175 } 1176 }, 1177 getRoot: function getRoot() { 1178 return this.$el; 1179 }, 1180 update: function update(query) { 1181 var that = this; 1182 this.query = query; 1183 this.canceled = false;
1184 this.source(query, render); 1185 function render(suggestions) { 1186 if (!that.canceled && query === that.query) { 1187 that._render(query, suggestions); 1188 } 1189 } 1190 }, 1191 cancel: function cancel() { 1192 this.canceled = true; 1193 }, 1194 clear: function clear() { 1195 this.cancel(); 1196 this.$el.empty(); 1197 this.trigger("rendered"); 1198 }, 1199 isEmpty: function isEmpty() { 1200 return this.$el.is(":empty"); 1201 }, 1202 destroy: function destroy() { 1203 this.$el = null; 1204 } 1205 }); 1206 return Dataset; 1207 function getDisplayFn(display) { 1208 display = display || "value"; 1209 return _.isFunction(display) ? display : displayFn; 1210 function displayFn(obj) { 1211 return obj[display]; 1212 } 1213 } 1214 function getTemplates(templates, displayFn) { 1215 return { 1216 empty: templates.empty && _.templatify(templates.empty), 1217 header: templates.header && _.templatify(templates.header), 1218 footer: templates.footer && _.templatify(templates.footer), 1219 suggestion: templates.suggestion || suggestionTemplate 1220 }; 1221 function suggestionTemplate(context) { 1222 return "<p>" + displayFn(context) + "</p>"; 1223 } 1224 } 1225 function isValidName(str) { 1226 return /^[_a-zA-Z0-9-]+$/.test(str); 1227 } 1228 }(); 1229 var Dropdown = function() { 1230 function Dropdown(o) { 1231 var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave; 1232 o = o || {}; 1233 if (!o.menu) { 1234 $.error("menu is required"); 1235 } 1236 this.isOpen = false; 1237 this.isEmpty = true; 1238 this.datasets = _.map(o.datasets, initializeDataset); 1239 onSuggestionClick = _.bind(this._onSuggestionClick, this); 1240 onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this); 1241 onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this); 1242 this.$menu = $(o.menu).on("click.tt", ".tt-suggestion", onSuggestionClick).on("mouseenter.tt", ".tt-suggestion", onSuggestionMouseEnter).on("mouseleave.tt", ".tt-suggestion", onSuggestionMouseLeave); 1243 _.each(this.datasets, function(dataset) { 1244 that.$menu.append(dataset.getRoot()); 1245 dataset.onSync("rendered", that._onRendered, that); 1246 }); 1247 } 1248 _.mixin(Dropdown.prototype, EventEmitter, { 1249 _onSuggestionClick: function onSuggestionClick($e) { 1250 this.trigger("suggestionClicked", $($e.currentTarget)); 1251 }, 1252 _onSuggestionMouseEnter: function onSuggestionMouseEnter($e) { 1253 this._removeCursor(); 1254 this._setCursor($($e.currentTarget), true); 1255 }, 1256 _onSuggestionMouseLeave: function onSuggestionMouseLeave() { 1257 this._removeCursor(); 1258 }, 1259 _onRendered: function onRendered() { 1260 this.isEmpty = _.every(this.datasets, isDatasetEmpty); 1261 this.isEmpty ? this._hide() : this.isOpen && this._show(); 1262 this.trigger("datasetRendered"); 1263 function isDatasetEmpty(dataset) { 1264 return dataset.isEmpty(); 1265 } 1266 }, 1267 _hide: function() { 1268 this.$menu.hide(); 1269 }, 1270 _show: function() { 1271 this.$menu.css("display", "block"); 1272 }, 1273 _getSuggestions: function getSuggestions() { 1274 return this.$menu.find(".tt-suggestion"); 1275 }, 1276 _getCursor: function getCursor() { 1277 return this.$menu.find(".tt-cursor").first(); 1278 }, 1279 _setCursor: function setCursor($el, silent) { 1280 $el.first().addClass("tt-cursor"); 1281 !silent && this.trigger("cursorMoved"); 1282 },
vendor: 4,684 bytes, lines 1283-1396
1283 _removeCursor: function removeCursor() { 1284 this._getCursor().removeClass("tt-cursor"); 1285 }, 1286 _moveCursor: function moveCursor(increment) { 1287 var $suggestions, $oldCursor, newCursorIndex, $newCursor; 1288 if (!this.isOpen) { 1289 return; 1290 } 1291 $oldCursor = this._getCursor(); 1292 $suggestions = this._getSuggestions(); 1293 this._removeCursor(); 1294 newCursorIndex = $suggestions.index($oldCursor) + increment; 1295 newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1; 1296 if (newCursorIndex === -1) { 1297 this.trigger("cursorRemoved"); 1298 return; 1299 } else if (newCursorIndex < -1) { 1300 newCursorIndex = $suggestions.length - 1; 1301 } 1302 this._setCursor($newCursor = $suggestions.eq(newCursorIndex)); 1303 this._ensureVisible($newCursor); 1304 }, 1305 _ensureVisible: function ensureVisible($el) { 1306 var elTop, elBottom, menuScrollTop, menuHeight; 1307 elTop = $el.position().top; 1308 elBottom = elTop + $el.outerHeight(true); 1309 menuScrollTop = this.$menu.scrollTop(); 1310 menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10); 1311 if (elTop < 0) { 1312 this.$menu.scrollTop(menuScrollTop + elTop); 1313 } else if (menuHeight < elBottom) { 1314 this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); 1315 } 1316 }, 1317 close: function close() { 1318 if (this.isOpen) { 1319 this.isOpen = false; 1320 this._removeCursor(); 1321 this._hide(); 1322 this.trigger("closed"); 1323 } 1324 }, 1325 open: function open() { 1326 if (!this.isOpen) { 1327 this.isOpen = true; 1328 !this.isEmpty && this._show(); 1329 this.trigger("opened"); 1330 } 1331 }, 1332 setLanguageDirection: function setLanguageDirection(dir) { 1333 this.$menu.css(dir === "ltr" ? css.ltr : css.rtl); 1334 }, 1335 moveCursorUp: function moveCursorUp() { 1336 this._moveCursor(-1); 1337 }, 1338 moveCursorDown: function moveCursorDown() { 1339 this._moveCursor(+1); 1340 }, 1341 getDatumForSuggestion: function getDatumForSuggestion($el) { 1342 var datum = null; 1343 if ($el.length) { 1344 datum = { 1345 raw: Dataset.extractDatum($el), 1346 value: Dataset.extractValue($el), 1347 datasetName: Dataset.extractDatasetName($el) 1348 }; 1349 } 1350 return datum; 1351 }, 1352 getDatumForCursor: function getDatumForCursor() { 1353 return this.getDatumForSuggestion(this._getCursor().first()); 1354 }, 1355 getDatumForTopSuggestion: function getDatumForTopSuggestion() { 1356 return this.getDatumForSuggestion(this._getSuggestions().first()); 1357 }, 1358 update: function update(query) { 1359 _.each(this.datasets, updateDataset); 1360 function updateDataset(dataset) { 1361 dataset.update(query); 1362 } 1363 }, 1364 empty: function empty() { 1365 _.each(this.datasets, clearDataset); 1366 this.isEmpty = true; 1367 function clearDataset(dataset) { 1368 dataset.clear(); 1369 } 1370 }, 1371 isVisible: function isVisible() { 1372 return this.isOpen && !this.isEmpty; 1373 }, 1374 destroy: function destroy() { 1375 this.$menu.off(".tt"); 1376 this.$menu = null; 1377 _.each(this.datasets, destroyDataset); 1378 function destroyDataset(dataset) { 1379 dataset.destroy(); 1380 } 1381 } 1382 }); 1383 return Dropdown; 1384 function initializeDataset(oDataset) { 1385 return new Dataset(oDataset); 1386 } 1387 }(); 1388 var Typeahead = function() { 1389 var attrsKey = "ttAttrs"; 1390 function Typeahead(o) { 1391 var $menu, $input, $hint; 1392 o = o || {}; 1393 if (!o.input) { 1394 $.error("missing input"); 1395 } 1396 this.isActivated = false;
1397 this.autoselect = !!o.autoselect; 1398 this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; 1399 this.$node = buildDomStructure(o.input, o.withHint); 1400 $menu = this.$node.find(".tt-dropdown-menu"); 1401 $input = this.$node.find(".tt-input"); 1402 $hint = this.$node.find(".tt-hint"); 1403 $input.on("blur.tt", function($e) { 1404 var active, isActive, hasActive; 1405 active = document.activeElement; 1406 isActive = $menu.is(active); 1407 hasActive = $menu.has(active).length > 0; 1408 if (_.isMsie() && (isActive || hasActive)) { 1409 $e.preventDefault(); 1410 $e.stopImmediatePropagation(); 1411 _.defer(function() { 1412 $input.focus(); 1413 }); 1414 } 1415 }); 1416 $menu.on("mousedown.tt", function($e) { 1417 $e.preventDefault(); 1418 }); 1419 this.eventBus = o.eventBus || new EventBus({ 1420 el: $input 1421 }); 1422 this.dropdown = new Dropdown({ 1423 menu: $menu, 1424 datasets: o.datasets 1425 }).onSync("suggestionClicked", this._onSuggestionClicked, this).onSync("cursorMoved", this._onCursorMoved, this).onSync("cursorRemoved", this._onCursorRemoved, this).onSync("opened", this._onOpened, this).onSync("closed", this._onClosed, this).onAsync("datasetRendered", this._onDatasetRendered, this); 1426 this.input = new Input({ 1427 input: $input, 1428 hint: $hint 1429 }).onSync("focused", this._onFocused, this).onSync("blurred", this._onBlurred, this).onSync("enterKeyed", this._onEnterKeyed, this).onSync("tabKeyed", this._onTabKeyed, this).onSync("escKeyed", this._onEscKeyed, this).onSync("upKeyed", this._onUpKeyed, this).onSync("downKeyed", this._onDownKeyed, this).onSync("leftKeyed", this._onLeftKeyed, this).onSync("rightKeyed", this._onRightKeyed, this).onSync("queryChanged", this._onQueryChanged, this).onSync("whitespaceChanged", this._onWhitespaceChanged, this); 1430 this._setLanguageDirection(); 1431 } 1432 _.mixin(Typeahead.prototype, { 1433 _onSuggestionClicked: function onSuggestionClicked(type, $el) { 1434 var datum; 1435 if (datum = this.dropdown.getDatumForSuggestion($el)) { 1436 this._select(datum); 1437 } 1438 }, 1439 _onCursorMoved: function onCursorMoved() { 1440 var datum = this.dropdown.getDatumForCursor(); 1441 this.input.setInputValue(datum.value, true); 1442 this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName); 1443 }, 1444 _onCursorRemoved: function onCursorRemoved() { 1445 this.input.resetInputValue(); 1446 this._updateHint(); 1447 }, 1448 _onDatasetRendered: function onDatasetRendered() { 1449 this._updateHint(); 1450 }, 1451 _onOpened: function onOpened() { 1452 this._updateHint(); 1453 this.eventBus.trigger("opened"); 1454 }, 1455 _onClosed: function onClosed() { 1456 this.input.clearHint(); 1457 this.eventBus.trigger("closed"); 1458 }, 1459 _onFocused: function onFocused() { 1460 this.isActivated = true; 1461 this.dropdown.open(); 1462 }, 1463 _onBlurred: function onBlurred() { 1464 this.isActivated = false; 1465 this.dropdown.empty(); 1466 this.dropdown.close(); 1467 }, 1468 _onEnterKeyed: function onEnterKeyed(type, $e) { 1469 var cursorDatum, topSuggestionDatum; 1470 cursorDatum = this.dropdown.getDatumForCursor(); 1471 topSuggestionDatum = this.dropdown.getDatumForTopSuggestion(); 1472 if (cursorDatum) { 1473 this._select(cursorDatum); 1474 $e.preventDefault(); 1475 } else if (this.autoselect && topSuggestionDatum) { 1476 this._select(topSuggestionDatum); 1477 $e.preventDefault(); 1478 } 1479 }, 1480 _onTabKeyed: function onTabKeyed(type, $e) { 1481 var datum;
1482 if (datum = this.dropdown.getDatumForCursor()) { 1483 this._select(datum); 1484 $e.preventDefault(); 1485 } else { 1486 this._autocomplete(true); 1487 } 1488 }, 1489 _onEscKeyed: function onEscKeyed() { 1490 this.dropdown.close(); 1491 this.input.resetInputValue(); 1492 }, 1493 _onUpKeyed: function onUpKeyed() { 1494 var query = this.input.getQuery(); 1495 this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp(); 1496 this.dropdown.open(); 1497 }, 1498 _onDownKeyed: function onDownKeyed() { 1499 var query = this.input.getQuery(); 1500 this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown(); 1501 this.dropdown.open(); 1502 }, 1503 _onLeftKeyed: function onLeftKeyed() { 1504 this.dir === "rtl" && this._autocomplete(); 1505 }, 1506 _onRightKeyed: function onRightKeyed() { 1507 this.dir === "ltr" && this._autocomplete(); 1508 }, 1509 _onQueryChanged: function onQueryChanged(e, query) { 1510 this.input.clearHintIfInvalid(); 1511 query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty(); 1512 this.dropdown.open(); 1513 this._setLanguageDirection(); 1514 }, 1515 _onWhitespaceChanged: function onWhitespaceChanged() { 1516 this._updateHint(); 1517 this.dropdown.open(); 1518 }, 1519 _setLanguageDirection: function setLanguageDirection() { 1520 var dir; 1521 if (this.dir !== (dir = this.input.getLanguageDirection())) { 1522 this.dir = dir; 1523 this.$node.css("direction", dir); 1524 this.dropdown.setLanguageDirection(dir); 1525 } 1526 }, 1527 _updateHint: function updateHint() { 1528 var datum, val, query, escapedQuery, frontMatchRegEx, match; 1529 datum = this.dropdown.getDatumForTopSuggestion(); 1530 if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) { 1531 val = this.input.getInputValue(); 1532 query = Input.normalizeQuery(val); 1533 escapedQuery = _.escapeRegExChars(query); 1534 frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); 1535 match = frontMatchRegEx.exec(datum.value); 1536 match ? this.input.setHint(val + match[1]) : this.input.clearHint(); 1537 } else { 1538 this.input.clearHint(); 1539 } 1540 }, 1541 _autocomplete: function autocomplete(laxCursor) { 1542 var hint, query, isCursorAtEnd, datum; 1543 hint = this.input.getHint(); 1544 query = this.input.getQuery(); 1545 isCursorAtEnd = laxCursor || this.input.isCursorAtEnd(); 1546 if (hint && query !== hint && isCursorAtEnd) { 1547 datum = this.dropdown.getDatumForTopSuggestion(); 1548 datum && this.input.setInputValue(datum.value); 1549 this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName); 1550 } 1551 }, 1552 _select: function select(datum) { 1553 this.input.setQuery(datum.value); 1554 this.input.setInputValue(datum.value, true); 1555 this._setLanguageDirection(); 1556 this.eventBus.trigger("selected", datum.raw, datum.datasetName); 1557 this.dropdown.close(); 1558 _.defer(_.bind(this.dropdown.empty, this.dropdown)); 1559 }, 1560 open: function open() { 1561 this.dropdown.open(); 1562 }, 1563 close: function close() { 1564 this.dropdown.close(); 1565 }, 1566 setVal: function setVal(val) { 1567 if (this.isActivated) { 1568 this.input.setInputValue(val); 1569 } else { 1570 this.input.setQuery(val); 1571 this.input.setInputValue(val, true); 1572 } 1573 this._setLanguageDirection(); 1574 }, 1575 getVal: function getVal() { 1576 return this.input.getQuery(); 1577 }, 1578 destroy: function destroy() { 1579 this.input.destroy(); 1580 this.dropdown.destroy(); 1581 destroyDomStructure(this.$node); 1582 this.$node = null; 1583 } 1584 }); 1585 return Typeahead; 1586 function buildDomStructure(input, withHint) { 1587 var $input, $wrapper, $dropdown, $hint; 1588 $input = $(input); 1589 $wrapper = $(html.wrapper).css(css.wrapper); 1590 $dropdown = $(html.dropdown).css(css.dropdown); 1591 $hint = $input.clone().css(css.hint).css(getBackgroun
1591dStyles($input)); 1592 $hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder").prop("disabled", true).attr({ 1593 autocomplete: "off", 1594 spellcheck: "false" 1595 }); 1596 $input.data(attrsKey, { 1597 dir: $input.attr("dir"), 1598 autocomplete: $input.attr("autocomplete"), 1599 spellcheck: $input.attr("spellcheck"), 1600 style: $input.attr("style") 1601 }); 1602 $input.addClass("tt-input").attr({ 1603 autocomplete: "off", 1604 spellcheck: false 1605 }).css(withHint ? css.input : css.inputWithNoHint); 1606 try { 1607 !$input.attr("dir") && $input.attr("dir", "auto"); 1608 } catch (e) {} 1609 return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown); 1610 } 1611 function getBackgroundStyles($el) { 1612 return { 1613 backgroundAttachment: $el.css("background-attachment"), 1614 backgroundClip: $el.css("background-clip"), 1615 backgroundColor: $el.css("background-color"), 1616 backgroundImage: $el.css("background-image"), 1617 backgroundOrigin: $el.css("background-origin"), 1618 backgroundPosition: $el.css("background-position"), 1619 backgroundRepeat: $el.css("background-repeat"), 1620 backgroundSize: $el.css("background-size") 1621 }; 1622 } 1623 function destroyDomStructure($node) { 1624 var $input = $node.find(".tt-input"); 1625 _.each($input.data(attrsKey), function(val, key) { 1626 _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); 1627 }); 1628 $input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node); 1629 $node.remove(); 1630 } 1631 }(); 1632 (function() { 1633 var old, typeaheadKey, methods; 1634 old = $.fn.typeahead; 1635 typeaheadKey = "ttTypeahead"; 1636 methods = { 1637 initialize: function initialize(o, datasets) { 1638 datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); 1639 o = o || {}; 1640 return this.each(attach); 1641 function attach() { 1642 var $input = $(this), eventBus, typeahead; 1643 _.each(datasets, function(d) { 1644 d.highlight = !!o.highlight; 1645 }); 1646 typeahead = new Typeahead({ 1647 input: $input, 1648 eventBus: eventBus = new EventBus({ 1649 el: $input 1650 }), 1651 withHint: _.isUndefined(o.hint) ? true : !!o.hint, 1652 minLength: o.minLength, 1653 autoselect: o.autoselect, 1654 datasets: datasets 1655 }); 1656 $input.data(typeaheadKey, typeahead); 1657 } 1658 }, 1659 open: function open() { 1660 return this.each(openTypeahead); 1661 function openTypeahead() { 1662 var $input = $(this), typeahead; 1663 if (typeahead = $input.data(typeaheadKey)) { 1664 typeahead.open(); 1665 } 1666 } 1667 }, 1668 close: function close() { 1669 return this.each(closeTypeahead); 1670 function closeTypeahead() { 1671 var $input = $(this), typeahead; 1672 if (typeahead = $input.data(typeaheadKey)) { 1673 typeahead.close(); 1674 } 1675 } 1676 }, 1677 val: function val(newVal) { 1678 return !arguments.length ? getVal(this.first()) : this.each(setVal); 1679 function setVal() { 1680 var $input = $(this), typeahead; 1681 if (typeahead = $input.data(typeaheadKey)) { 1682 typeahead.setVal(newVal); 1683 } 1684 } 1685 function getVal($input) { 1686 var typeahead, query; 1687 if (typeahead = $input.data(typeaheadKey)) { 1688 query = typeahead.getVal(); 1689 } 1690 return query; 1691 } 1692 }, 1693 destroy: function destroy() { 1694 return this.each(unattach); 1695 function unattach() { 1696 var $input = $(this), typeahead; 1697 if (typeahead = $input.data(typeaheadKey)) { 1698 typeahead.destroy(); 1699 $input.removeData(typeaheadKey); 1700 } 1701 } 1702 } 1703 }; 1704 $.fn.typeahead = function(method) { 1705 if (methods[method]) { 1706 return methods[method].apply(this, [].slice.call(arguments, 1)); 1707 } else { 1708 return methods.initialize.apply(this, arguments); 1709 } 1710 }; 1711 $.fn.typeahead.noConflict = function noConflict() { 1712 $.fn.typeahead = old; 1713 return this; 1714 }; 1715 })(); 1716})(window.jQuery);
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.