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