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