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