JAVASCRIPT   63

typeahead bundle

Guest on 7th May 2022 02:16:11 AM

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

Raw Paste


Login or Register to edit or fork this paste. It's free.