hjg
2024-07-09 30304784e82d4bba24121328da8eb8490aec4f4f
提交 | 用户 | 时间
58d006 1 /*!
A 2  * typeahead.js 0.11.1
3  * https://github.com/twitter/typeahead.js
4  * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT
5  */
6
7 (function(root, factory) {
8     if (typeof define === "function" && define.amd) {
9         define("typeahead.js", [ "jquery" ], function(a0) {
10             return factory(a0);
11         });
12     } else if (typeof exports === "object") {
13         module.exports = factory(require("jquery"));
14     } else {
15         factory(jQuery);
16     }
17 })(this, function($) {
18     var _ = function() {
19         "use strict";
20         return {
21             isMsie: function() {
22                 return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
23             },
24             isBlankString: function(str) {
25                 return !str || /^\s*$/.test(str);
26             },
27             escapeRegExChars: function(str) {
28                 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
29             },
30             isString: function(obj) {
31                 return typeof obj === "string";
32             },
33             isNumber: function(obj) {
34                 return typeof obj === "number";
35             },
36             isArray: $.isArray,
37             isFunction: $.isFunction,
38             isObject: $.isPlainObject,
39             isUndefined: function(obj) {
40                 return typeof obj === "undefined";
41             },
42             isElement: function(obj) {
43                 return !!(obj && obj.nodeType === 1);
44             },
45             isJQuery: function(obj) {
46                 return obj instanceof $;
47             },
48             toStr: function toStr(s) {
49                 return _.isUndefined(s) || s === null ? "" : s + "";
50             },
51             bind: $.proxy,
52             each: function(collection, cb) {
53                 $.each(collection, reverseArgs);
54                 function reverseArgs(index, value) {
55                     return cb(value, index);
56                 }
57             },
58             map: $.map,
59             filter: $.grep,
60             every: function(obj, test) {
61                 var result = true;
62                 if (!obj) {
63                     return result;
64                 }
65                 $.each(obj, function(key, val) {
66                     if (!(result = test.call(null, val, key, obj))) {
67                         return false;
68                     }
69                 });
70                 return !!result;
71             },
72             some: function(obj, test) {
73                 var result = false;
74                 if (!obj) {
75                     return result;
76                 }
77                 $.each(obj, function(key, val) {
78                     if (result = test.call(null, val, key, obj)) {
79                         return false;
80                     }
81                 });
82                 return !!result;
83             },
84             mixin: $.extend,
85             identity: function(x) {
86                 return x;
87             },
88             clone: function(obj) {
89                 return $.extend(true, {}, obj);
90             },
91             getIdGenerator: function() {
92                 var counter = 0;
93                 return function() {
94                     return counter++;
95                 };
96             },
97             templatify: function templatify(obj) {
98                 return $.isFunction(obj) ? obj : template;
99                 function template() {
100                     return String(obj);
101                 }
102             },
103             defer: function(fn) {
104                 setTimeout(fn, 0);
105             },
106             debounce: function(func, wait, immediate) {
107                 var timeout, result;
108                 return function() {
109                     var context = this, args = arguments, later, callNow;
110                     later = function() {
111                         timeout = null;
112                         if (!immediate) {
113                             result = func.apply(context, args);
114                         }
115                     };
116                     callNow = immediate && !timeout;
117                     clearTimeout(timeout);
118                     timeout = setTimeout(later, wait);
119                     if (callNow) {
120                         result = func.apply(context, args);
121                     }
122                     return result;
123                 };
124             },
125             throttle: function(func, wait) {
126                 var context, args, timeout, result, previous, later;
127                 previous = 0;
128                 later = function() {
129                     previous = new Date();
130                     timeout = null;
131                     result = func.apply(context, args);
132                 };
133                 return function() {
134                     var now = new Date(), remaining = wait - (now - previous);
135                     context = this;
136                     args = arguments;
137                     if (remaining <= 0) {
138                         clearTimeout(timeout);
139                         timeout = null;
140                         previous = now;
141                         result = func.apply(context, args);
142                     } else if (!timeout) {
143                         timeout = setTimeout(later, remaining);
144                     }
145                     return result;
146                 };
147             },
148             stringify: function(val) {
149                 return _.isString(val) ? val : JSON.stringify(val);
150             },
151             noop: function() {}
152         };
153     }();
154     var WWW = function() {
155         "use strict";
156         var defaultClassNames = {
157             wrapper: "twitter-typeahead",
158             input: "tt-input",
159             hint: "tt-hint",
160             menu: "tt-menu",
161             dataset: "tt-dataset",
162             suggestion: "tt-suggestion",
163             selectable: "tt-selectable",
164             empty: "tt-empty",
165             open: "tt-open",
166             cursor: "tt-cursor",
167             highlight: "tt-highlight"
168         };
169         return build;
170         function build(o) {
171             var www, classes;
172             classes = _.mixin({}, defaultClassNames, o);
173             www = {
174                 css: buildCss(),
175                 classes: classes,
176                 html: buildHtml(classes),
177                 selectors: buildSelectors(classes)
178             };
179             return {
180                 css: www.css,
181                 html: www.html,
182                 classes: www.classes,
183                 selectors: www.selectors,
184                 mixin: function(o) {
185                     _.mixin(o, www);
186                 }
187             };
188         }
189         function buildHtml(c) {
190             return {
191                 wrapper: '<span class="' + c.wrapper + '"></span>',
192                 menu: '<div class="' + c.menu + '"></div>'
193             };
194         }
195         function buildSelectors(classes) {
196             var selectors = {};
197             _.each(classes, function(v, k) {
198                 selectors[k] = "." + v;
199             });
200             return selectors;
201         }
202         function buildCss() {
203             var css = {
204                 wrapper: {
205                     position: "relative",
206                     display: "inline-block"
207                 },
208                 hint: {
209                     position: "absolute",
210                     top: "0",
211                     left: "0",
212                     borderColor: "transparent",
213                     boxShadow: "none",
214                     opacity: "1"
215                 },
216                 input: {
217                     position: "relative",
218                     verticalAlign: "top",
219                     backgroundColor: "transparent"
220                 },
221                 inputWithNoHint: {
222                     position: "relative",
223                     verticalAlign: "top"
224                 },
225                 menu: {
226                     position: "absolute",
227                     top: "100%",
228                     left: "0",
229                     zIndex: "100",
230                     display: "none"
231                 },
232                 ltr: {
233                     left: "0",
234                     right: "auto"
235                 },
236                 rtl: {
237                     left: "auto",
238                     right: " 0"
239                 }
240             };
241             if (_.isMsie()) {
242                 _.mixin(css.input, {
243                     backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
244                 });
245             }
246             return css;
247         }
248     }();
249     var EventBus = function() {
250         "use strict";
251         var namespace, deprecationMap;
252         namespace = "typeahead:";
253         deprecationMap = {
254             render: "rendered",
255             cursorchange: "cursorchanged",
256             select: "selected",
257             autocomplete: "autocompleted"
258         };
259         function EventBus(o) {
260             if (!o || !o.el) {
261                 $.error("EventBus initialized without el");
262             }
263             this.$el = $(o.el);
264         }
265         _.mixin(EventBus.prototype, {
266             _trigger: function(type, args) {
267                 var $e;
268                 $e = $.Event(namespace + type);
269                 (args = args || []).unshift($e);
270                 this.$el.trigger.apply(this.$el, args);
271                 return $e;
272             },
273             before: function(type) {
274                 var args, $e;
275                 args = [].slice.call(arguments, 1);
276                 $e = this._trigger("before" + type, args);
277                 return $e.isDefaultPrevented();
278             },
279             trigger: function(type) {
280                 var deprecatedType;
281                 this._trigger(type, [].slice.call(arguments, 1));
282                 if (deprecatedType = deprecationMap[type]) {
283                     this._trigger(deprecatedType, [].slice.call(arguments, 1));
284                 }
285             }
286         });
287         return EventBus;
288     }();
289     var EventEmitter = function() {
290         "use strict";
291         var splitter = /\s+/, nextTick = getNextTick();
292         return {
293             onSync: onSync,
294             onAsync: onAsync,
295             off: off,
296             trigger: trigger
297         };
298         function on(method, types, cb, context) {
299             var type;
300             if (!cb) {
301                 return this;
302             }
303             types = types.split(splitter);
304             cb = context ? bindContext(cb, context) : cb;
305             this._callbacks = this._callbacks || {};
306             while (type = types.shift()) {
307                 this._callbacks[type] = this._callbacks[type] || {
308                     sync: [],
309                     async: []
310                 };
311                 this._callbacks[type][method].push(cb);
312             }
313             return this;
314         }
315         function onAsync(types, cb, context) {
316             return on.call(this, "async", types, cb, context);
317         }
318         function onSync(types, cb, context) {
319             return on.call(this, "sync", types, cb, context);
320         }
321         function off(types) {
322             var type;
323             if (!this._callbacks) {
324                 return this;
325             }
326             types = types.split(splitter);
327             while (type = types.shift()) {
328                 delete this._callbacks[type];
329             }
330             return this;
331         }
332         function trigger(types) {
333             var type, callbacks, args, syncFlush, asyncFlush;
334             if (!this._callbacks) {
335                 return this;
336             }
337             types = types.split(splitter);
338             args = [].slice.call(arguments, 1);
339             while ((type = types.shift()) && (callbacks = this._callbacks[type])) {
340                 syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args));
341                 asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args));
342                 syncFlush() && nextTick(asyncFlush);
343             }
344             return this;
345         }
346         function getFlush(callbacks, context, args) {
347             return flush;
348             function flush() {
349                 var cancelled;
350                 for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) {
351                     cancelled = callbacks[i].apply(context, args) === false;
352                 }
353                 return !cancelled;
354             }
355         }
356         function getNextTick() {
357             var nextTickFn;
358             if (window.setImmediate) {
359                 nextTickFn = function nextTickSetImmediate(fn) {
360                     setImmediate(function() {
361                         fn();
362                     });
363                 };
364             } else {
365                 nextTickFn = function nextTickSetTimeout(fn) {
366                     setTimeout(function() {
367                         fn();
368                     }, 0);
369                 };
370             }
371             return nextTickFn;
372         }
373         function bindContext(fn, context) {
374             return fn.bind ? fn.bind(context) : function() {
375                 fn.apply(context, [].slice.call(arguments, 0));
376             };
377         }
378     }();
379     var highlight = function(doc) {
380         "use strict";
381         var defaults = {
382             node: null,
383             pattern: null,
384             tagName: "strong",
385             className: null,
386             wordsOnly: false,
387             caseSensitive: false
388         };
389         return function hightlight(o) {
390             var regex;
391             o = _.mixin({}, defaults, o);
392             if (!o.node || !o.pattern) {
393                 return;
394             }
395             o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
396             regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
397             traverse(o.node, hightlightTextNode);
398             function hightlightTextNode(textNode) {
399                 var match, patternNode, wrapperNode;
400                 if (match = regex.exec(textNode.data)) {
401                     wrapperNode = doc.createElement(o.tagName);
402                     o.className && (wrapperNode.className = o.className);
403                     patternNode = textNode.splitText(match.index);
404                     patternNode.splitText(match[0].length);
405                     wrapperNode.appendChild(patternNode.cloneNode(true));
406                     textNode.parentNode.replaceChild(wrapperNode, patternNode);
407                 }
408                 return !!match;
409             }
410             function traverse(el, hightlightTextNode) {
411                 var childNode, TEXT_NODE_TYPE = 3;
412                 for (var i = 0; i < el.childNodes.length; i++) {
413                     childNode = el.childNodes[i];
414                     if (childNode.nodeType === TEXT_NODE_TYPE) {
415                         i += hightlightTextNode(childNode) ? 1 : 0;
416                     } else {
417                         traverse(childNode, hightlightTextNode);
418                     }
419                 }
420             }
421         };
422         function getRegex(patterns, caseSensitive, wordsOnly) {
423             var escapedPatterns = [], regexStr;
424             for (var i = 0, len = patterns.length; i < len; i++) {
425                 escapedPatterns.push(_.escapeRegExChars(patterns[i]));
426             }
427             regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
428             return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
429         }
430     }(window.document);
431     var Input = function() {
432         "use strict";
433         var specialKeyCodeMap;
434         specialKeyCodeMap = {
435             9: "tab",
436             27: "esc",
437             37: "left",
438             39: "right",
439             13: "enter",
440             38: "up",
441             40: "down"
442         };
443         function Input(o, www) {
444             o = o || {};
445             if (!o.input) {
446                 $.error("input is missing");
447             }
448             www.mixin(this);
449             this.$hint = $(o.hint);
450             this.$input = $(o.input);
451             this.query = this.$input.val();
452             this.queryWhenFocused = this.hasFocus() ? this.query : null;
453             this.$overflowHelper = buildOverflowHelper(this.$input);
454             this._checkLanguageDirection();
455             if (this.$hint.length === 0) {
456                 this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
457             }
458         }
459         Input.normalizeQuery = function(str) {
460             return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
461         };
462         _.mixin(Input.prototype, EventEmitter, {
463             _onBlur: function onBlur() {
464                 this.resetInputValue();
465                 this.trigger("blurred");
466             },
467             _onFocus: function onFocus() {
468                 this.queryWhenFocused = this.query;
469                 this.trigger("focused");
470             },
471             _onKeydown: function onKeydown($e) {
472                 var keyName = specialKeyCodeMap[$e.which || $e.keyCode];
473                 this._managePreventDefault(keyName, $e);
474                 if (keyName && this._shouldTrigger(keyName, $e)) {
475                     this.trigger(keyName + "Keyed", $e);
476                 }
477             },
478             _onInput: function onInput() {
479                 this._setQuery(this.getInputValue());
480                 this.clearHintIfInvalid();
481                 this._checkLanguageDirection();
482             },
483             _managePreventDefault: function managePreventDefault(keyName, $e) {
484                 var preventDefault;
485                 switch (keyName) {
486                   case "up":
487                   case "down":
488                     preventDefault = !withModifier($e);
489                     break;
490
491                   default:
492                     preventDefault = false;
493                 }
494                 preventDefault && $e.preventDefault();
495             },
496             _shouldTrigger: function shouldTrigger(keyName, $e) {
497                 var trigger;
498                 switch (keyName) {
499                   case "tab":
500                     trigger = !withModifier($e);
501                     break;
502
503                   default:
504                     trigger = true;
505                 }
506                 return trigger;
507             },
508             _checkLanguageDirection: function checkLanguageDirection() {
509                 var dir = (this.$input.css("direction") || "ltr").toLowerCase();
510                 if (this.dir !== dir) {
511                     this.dir = dir;
512                     this.$hint.attr("dir", dir);
513                     this.trigger("langDirChanged", dir);
514                 }
515             },
516             _setQuery: function setQuery(val, silent) {
517                 var areEquivalent, hasDifferentWhitespace;
518                 areEquivalent = areQueriesEquivalent(val, this.query);
519                 hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false;
520                 this.query = val;
521                 if (!silent && !areEquivalent) {
522                     this.trigger("queryChanged", this.query);
523                 } else if (!silent && hasDifferentWhitespace) {
524                     this.trigger("whitespaceChanged", this.query);
525                 }
526             },
527             bind: function() {
528                 var that = this, onBlur, onFocus, onKeydown, onInput;
529                 onBlur = _.bind(this._onBlur, this);
530                 onFocus = _.bind(this._onFocus, this);
531                 onKeydown = _.bind(this._onKeydown, this);
532                 onInput = _.bind(this._onInput, this);
533                 this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
534                 if (!_.isMsie() || _.isMsie() > 9) {
535                     this.$input.on("input.tt", onInput);
536                 } else {
537                     this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
538                         if (specialKeyCodeMap[$e.which || $e.keyCode]) {
539                             return;
540                         }
541                         _.defer(_.bind(that._onInput, that, $e));
542                     });
543                 }
544                 return this;
545             },
546             focus: function focus() {
547                 this.$input.focus();
548             },
549             blur: function blur() {
550                 this.$input.blur();
551             },
552             getLangDir: function getLangDir() {
553                 return this.dir;
554             },
555             getQuery: function getQuery() {
556                 return this.query || "";
557             },
558             setQuery: function setQuery(val, silent) {
559                 this.setInputValue(val);
560                 this._setQuery(val, silent);
561             },
562             hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() {
563                 return this.query !== this.queryWhenFocused;
564             },
565             getInputValue: function getInputValue() {
566                 return this.$input.val();
567             },
568             setInputValue: function setInputValue(value) {
569                 this.$input.val(value);
570                 this.clearHintIfInvalid();
571                 this._checkLanguageDirection();
572             },
573             resetInputValue: function resetInputValue() {
574                 this.setInputValue(this.query);
575             },
576             getHint: function getHint() {
577                 return this.$hint.val();
578             },
579             setHint: function setHint(value) {
580                 this.$hint.val(value);
581             },
582             clearHint: function clearHint() {
583                 this.setHint("");
584             },
585             clearHintIfInvalid: function clearHintIfInvalid() {
586                 var val, hint, valIsPrefixOfHint, isValid;
587                 val = this.getInputValue();
588                 hint = this.getHint();
589                 valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
590                 isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
591                 !isValid && this.clearHint();
592             },
593             hasFocus: function hasFocus() {
594                 return this.$input.is(":focus");
595             },
596             hasOverflow: function hasOverflow() {
597                 var constraint = this.$input.width() - 2;
598                 this.$overflowHelper.text(this.getInputValue());
599                 return this.$overflowHelper.width() >= constraint;
600             },
601             isCursorAtEnd: function() {
602                 var valueLength, selectionStart, range;
603                 valueLength = this.$input.val().length;
604                 selectionStart = this.$input[0].selectionStart;
605                 if (_.isNumber(selectionStart)) {
606                     return selectionStart === valueLength;
607                 } else if (document.selection) {
608                     range = document.selection.createRange();
609                     range.moveStart("character", -valueLength);
610                     return valueLength === range.text.length;
611                 }
612                 return true;
613             },
614             destroy: function destroy() {
615                 this.$hint.off(".tt");
616                 this.$input.off(".tt");
617                 this.$overflowHelper.remove();
618                 this.$hint = this.$input = this.$overflowHelper = $("<div>");
619             }
620         });
621         return Input;
622         function buildOverflowHelper($input) {
623             return $('<pre aria-hidden="true"></pre>').css({
624                 position: "absolute",
625                 visibility: "hidden",
626                 whiteSpace: "pre",
627                 fontFamily: $input.css("font-family"),
628                 fontSize: $input.css("font-size"),
629                 fontStyle: $input.css("font-style"),
630                 fontVariant: $input.css("font-variant"),
631                 fontWeight: $input.css("font-weight"),
632                 wordSpacing: $input.css("word-spacing"),
633                 letterSpacing: $input.css("letter-spacing"),
634                 textIndent: $input.css("text-indent"),
635                 textRendering: $input.css("text-rendering"),
636                 textTransform: $input.css("text-transform")
637             }).insertAfter($input);
638         }
639         function areQueriesEquivalent(a, b) {
640             return Input.normalizeQuery(a) === Input.normalizeQuery(b);
641         }
642         function withModifier($e) {
643             return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;
644         }
645     }();
646     var Dataset = function() {
647         "use strict";
648         var keys, nameGenerator;
649         keys = {
650             val: "tt-selectable-display",
651             obj: "tt-selectable-object"
652         };
653         nameGenerator = _.getIdGenerator();
654         function Dataset(o, www) {
655             o = o || {};
656             o.templates = o.templates || {};
657             o.templates.notFound = o.templates.notFound || o.templates.empty;
658             if (!o.source) {
659                 $.error("missing source");
660             }
661             if (!o.node) {
662                 $.error("missing node");
663             }
664             if (o.name && !isValidName(o.name)) {
665                 $.error("invalid dataset name: " + o.name);
666             }
667             www.mixin(this);
668             this.highlight = !!o.highlight;
669             this.name = o.name || nameGenerator();
670             this.limit = o.limit || 5;
671             this.displayFn = getDisplayFn(o.display || o.displayKey);
672             this.templates = getTemplates(o.templates, this.displayFn);
673             this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source;
674             this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async;
675             this._resetLastSuggestion();
676             this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name);
677         }
678         Dataset.extractData = function extractData(el) {
679             var $el = $(el);
680             if ($el.data(keys.obj)) {
681                 return {
682                     val: $el.data(keys.val) || "",
683                     obj: $el.data(keys.obj) || null
684                 };
685             }
686             return null;
687         };
688         _.mixin(Dataset.prototype, EventEmitter, {
689             _overwrite: function overwrite(query, suggestions) {
690                 suggestions = suggestions || [];
691                 if (suggestions.length) {
692                     this._renderSuggestions(query, suggestions);
693                 } else if (this.async && this.templates.pending) {
694                     this._renderPending(query);
695                 } else if (!this.async && this.templates.notFound) {
696                     this._renderNotFound(query);
697                 } else {
698                     this._empty();
699                 }
700                 this.trigger("rendered", this.name, suggestions, false);
701             },
702             _append: function append(query, suggestions) {
703                 suggestions = suggestions || [];
704                 if (suggestions.length && this.$lastSuggestion.length) {
705                     this._appendSuggestions(query, suggestions);
706                 } else if (suggestions.length) {
707                     this._renderSuggestions(query, suggestions);
708                 } else if (!this.$lastSuggestion.length && this.templates.notFound) {
709                     this._renderNotFound(query);
710                 }
711                 this.trigger("rendered", this.name, suggestions, true);
712             },
713             _renderSuggestions: function renderSuggestions(query, suggestions) {
714                 var $fragment;
715                 $fragment = this._getSuggestionsFragment(query, suggestions);
716                 this.$lastSuggestion = $fragment.children().last();
717                 this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions));
718             },
719             _appendSuggestions: function appendSuggestions(query, suggestions) {
720                 var $fragment, $lastSuggestion;
721                 $fragment = this._getSuggestionsFragment(query, suggestions);
722                 $lastSuggestion = $fragment.children().last();
723                 this.$lastSuggestion.after($fragment);
724                 this.$lastSuggestion = $lastSuggestion;
725             },
726             _renderPending: function renderPending(query) {
727                 var template = this.templates.pending;
728                 this._resetLastSuggestion();
729                 template && this.$el.html(template({
730                     query: query,
731                     dataset: this.name
732                 }));
733             },
734             _renderNotFound: function renderNotFound(query) {
735                 var template = this.templates.notFound;
736                 this._resetLastSuggestion();
737                 template && this.$el.html(template({
738                     query: query,
739                     dataset: this.name
740                 }));
741             },
742             _empty: function empty() {
743                 this.$el.empty();
744                 this._resetLastSuggestion();
745             },
746             _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) {
747                 var that = this, fragment;
748                 fragment = document.createDocumentFragment();
749                 _.each(suggestions, function getSuggestionNode(suggestion) {
750                     var $el, context;
751                     context = that._injectQuery(query, suggestion);
752                     $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable);
753                     fragment.appendChild($el[0]);
754                 });
755                 this.highlight && highlight({
756                     className: this.classes.highlight,
757                     node: fragment,
758                     pattern: query
759                 });
760                 return $(fragment);
761             },
762             _getFooter: function getFooter(query, suggestions) {
763                 return this.templates.footer ? this.templates.footer({
764                     query: query,
765                     suggestions: suggestions,
766                     dataset: this.name
767                 }) : null;
768             },
769             _getHeader: function getHeader(query, suggestions) {
770                 return this.templates.header ? this.templates.header({
771                     query: query,
772                     suggestions: suggestions,
773                     dataset: this.name
774                 }) : null;
775             },
776             _resetLastSuggestion: function resetLastSuggestion() {
777                 this.$lastSuggestion = $();
778             },
779             _injectQuery: function injectQuery(query, obj) {
780                 return _.isObject(obj) ? _.mixin({
781                     _query: query
782                 }, obj) : obj;
783             },
784             update: function update(query) {
785                 var that = this, canceled = false, syncCalled = false, rendered = 0;
786                 this.cancel();
787                 this.cancel = function cancel() {
788                     canceled = true;
789                     that.cancel = $.noop;
790                     that.async && that.trigger("asyncCanceled", query);
791                 };
792                 this.source(query, sync, async);
793                 !syncCalled && sync([]);
794                 function sync(suggestions) {
795                     if (syncCalled) {
796                         return;
797                     }
798                     syncCalled = true;
799                     suggestions = (suggestions || []).slice(0, that.limit);
800                     rendered = suggestions.length;
801                     that._overwrite(query, suggestions);
802                     if (rendered < that.limit && that.async) {
803                         that.trigger("asyncRequested", query);
804                     }
805                 }
806                 function async(suggestions) {
807                     suggestions = suggestions || [];
808                     if (!canceled && rendered < that.limit) {
809                         that.cancel = $.noop;
810                         rendered += suggestions.length;
811                         that._append(query, suggestions.slice(0, that.limit - rendered));
812                         that.async && that.trigger("asyncReceived", query);
813                     }
814                 }
815             },
816             cancel: $.noop,
817             clear: function clear() {
818                 this._empty();
819                 this.cancel();
820                 this.trigger("cleared");
821             },
822             isEmpty: function isEmpty() {
823                 return this.$el.is(":empty");
824             },
825             destroy: function destroy() {
826                 this.$el = $("<div>");
827             }
828         });
829         return Dataset;
830         function getDisplayFn(display) {
831             display = display || _.stringify;
832             return _.isFunction(display) ? display : displayFn;
833             function displayFn(obj) {
834                 return obj[display];
835             }
836         }
837         function getTemplates(templates, displayFn) {
838             return {
839                 notFound: templates.notFound && _.templatify(templates.notFound),
840                 pending: templates.pending && _.templatify(templates.pending),
841                 header: templates.header && _.templatify(templates.header),
842                 footer: templates.footer && _.templatify(templates.footer),
843                 suggestion: templates.suggestion || suggestionTemplate
844             };
845             function suggestionTemplate(context) {
846                 return $("<div>").text(displayFn(context));
847             }
848         }
849         function isValidName(str) {
850             return /^[_a-zA-Z0-9-]+$/.test(str);
851         }
852     }();
853     var Menu = function() {
854         "use strict";
855         function Menu(o, www) {
856             var that = this;
857             o = o || {};
858             if (!o.node) {
859                 $.error("node is required");
860             }
861             www.mixin(this);
862             this.$node = $(o.node);
863             this.query = null;
864             this.datasets = _.map(o.datasets, initializeDataset);
865             function initializeDataset(oDataset) {
866                 var node = that.$node.find(oDataset.node).first();
867                 oDataset.node = node.length ? node : $("<div>").appendTo(that.$node);
868                 return new Dataset(oDataset, www);
869             }
870         }
871         _.mixin(Menu.prototype, EventEmitter, {
872             _onSelectableClick: function onSelectableClick($e) {
873                 this.trigger("selectableClicked", $($e.currentTarget));
874             },
875             _onRendered: function onRendered(type, dataset, suggestions, async) {
876                 this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
877                 this.trigger("datasetRendered", dataset, suggestions, async);
878             },
879             _onCleared: function onCleared() {
880                 this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
881                 this.trigger("datasetCleared");
882             },
883             _propagate: function propagate() {
884                 this.trigger.apply(this, arguments);
885             },
886             _allDatasetsEmpty: function allDatasetsEmpty() {
887                 return _.every(this.datasets, isDatasetEmpty);
888                 function isDatasetEmpty(dataset) {
889                     return dataset.isEmpty();
890                 }
891             },
892             _getSelectables: function getSelectables() {
893                 return this.$node.find(this.selectors.selectable);
894             },
895             _removeCursor: function _removeCursor() {
896                 var $selectable = this.getActiveSelectable();
897                 $selectable && $selectable.removeClass(this.classes.cursor);
898             },
899             _ensureVisible: function ensureVisible($el) {
900                 var elTop, elBottom, nodeScrollTop, nodeHeight;
901                 elTop = $el.position().top;
902                 elBottom = elTop + $el.outerHeight(true);
903                 nodeScrollTop = this.$node.scrollTop();
904                 nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10);
905                 if (elTop < 0) {
906                     this.$node.scrollTop(nodeScrollTop + elTop);
907                 } else if (nodeHeight < elBottom) {
908                     this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight));
909                 }
910             },
911             bind: function() {
912                 var that = this, onSelectableClick;
913                 onSelectableClick = _.bind(this._onSelectableClick, this);
914                 this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
915                 _.each(this.datasets, function(dataset) {
916                     dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that);
917                 });
918                 return this;
919             },
920             isOpen: function isOpen() {
921                 return this.$node.hasClass(this.classes.open);
922             },
923             open: function open() {
924                 this.$node.addClass(this.classes.open);
925             },
926             close: function close() {
927                 this.$node.removeClass(this.classes.open);
928                 this._removeCursor();
929             },
930             setLanguageDirection: function setLanguageDirection(dir) {
931                 this.$node.attr("dir", dir);
932             },
933             selectableRelativeToCursor: function selectableRelativeToCursor(delta) {
934                 var $selectables, $oldCursor, oldIndex, newIndex;
935                 $oldCursor = this.getActiveSelectable();
936                 $selectables = this._getSelectables();
937                 oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1;
938                 newIndex = oldIndex + delta;
939                 newIndex = (newIndex + 1) % ($selectables.length + 1) - 1;
940                 newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex;
941                 return newIndex === -1 ? null : $selectables.eq(newIndex);
942             },
943             setCursor: function setCursor($selectable) {
944                 this._removeCursor();
945                 if ($selectable = $selectable && $selectable.first()) {
946                     $selectable.addClass(this.classes.cursor);
947                     this._ensureVisible($selectable);
948                 }
949             },
950             getSelectableData: function getSelectableData($el) {
951                 return $el && $el.length ? Dataset.extractData($el) : null;
952             },
953             getActiveSelectable: function getActiveSelectable() {
954                 var $selectable = this._getSelectables().filter(this.selectors.cursor).first();
955                 return $selectable.length ? $selectable : null;
956             },
957             getTopSelectable: function getTopSelectable() {
958                 var $selectable = this._getSelectables().first();
959                 return $selectable.length ? $selectable : null;
960             },
961             update: function update(query) {
962                 var isValidUpdate = query !== this.query;
963                 if (isValidUpdate) {
964                     this.query = query;
965                     _.each(this.datasets, updateDataset);
966                 }
967                 return isValidUpdate;
968                 function updateDataset(dataset) {
969                     dataset.update(query);
970                 }
971             },
972             empty: function empty() {
973                 _.each(this.datasets, clearDataset);
974                 this.query = null;
975                 this.$node.addClass(this.classes.empty);
976                 function clearDataset(dataset) {
977                     dataset.clear();
978                 }
979             },
980             destroy: function destroy() {
981                 this.$node.off(".tt");
982                 this.$node = $("<div>");
983                 _.each(this.datasets, destroyDataset);
984                 function destroyDataset(dataset) {
985                     dataset.destroy();
986                 }
987             }
988         });
989         return Menu;
990     }();
991     var DefaultMenu = function() {
992         "use strict";
993         var s = Menu.prototype;
994         function DefaultMenu() {
995             Menu.apply(this, [].slice.call(arguments, 0));
996         }
997         _.mixin(DefaultMenu.prototype, Menu.prototype, {
998             open: function open() {
999                 !this._allDatasetsEmpty() && this._show();
1000                 return s.open.apply(this, [].slice.call(arguments, 0));
1001             },
1002             close: function close() {
1003                 this._hide();
1004                 return s.close.apply(this, [].slice.call(arguments, 0));
1005             },
1006             _onRendered: function onRendered() {
1007                 if (this._allDatasetsEmpty()) {
1008                     this._hide();
1009                 } else {
1010                     this.isOpen() && this._show();
1011                 }
1012                 return s._onRendered.apply(this, [].slice.call(arguments, 0));
1013             },
1014             _onCleared: function onCleared() {
1015                 if (this._allDatasetsEmpty()) {
1016                     this._hide();
1017                 } else {
1018                     this.isOpen() && this._show();
1019                 }
1020                 return s._onCleared.apply(this, [].slice.call(arguments, 0));
1021             },
1022             setLanguageDirection: function setLanguageDirection(dir) {
1023                 this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl);
1024                 return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0));
1025             },
1026             _hide: function hide() {
1027                 this.$node.hide();
1028             },
1029             _show: function show() {
1030                 this.$node.css("display", "block");
1031             }
1032         });
1033         return DefaultMenu;
1034     }();
1035     var Typeahead = function() {
1036         "use strict";
1037         function Typeahead(o, www) {
1038             var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged;
1039             o = o || {};
1040             if (!o.input) {
1041                 $.error("missing input");
1042             }
1043             if (!o.menu) {
1044                 $.error("missing menu");
1045             }
1046             if (!o.eventBus) {
1047                 $.error("missing event bus");
1048             }
1049             www.mixin(this);
1050             this.eventBus = o.eventBus;
1051             this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
1052             this.input = o.input;
1053             this.menu = o.menu;
1054             this.enabled = true;
1055             this.active = false;
1056             this.input.hasFocus() && this.activate();
1057             this.dir = this.input.getLangDir();
1058             this._hacks();
1059             this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this);
1060             onFocused = c(this, "activate", "open", "_onFocused");
1061             onBlurred = c(this, "deactivate", "_onBlurred");
1062             onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed");
1063             onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed");
1064             onEscKeyed = c(this, "isActive", "_onEscKeyed");
1065             onUpKeyed = c(this, "isActive", "open", "_onUpKeyed");
1066             onDownKeyed = c(this, "isActive", "open", "_onDownKeyed");
1067             onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed");
1068             onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed");
1069             onQueryChanged = c(this, "_openIfActive", "_onQueryChanged");
1070             onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged");
1071             this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this);
1072         }
1073         _.mixin(Typeahead.prototype, {
1074             _hacks: function hacks() {
1075                 var $input, $menu;
1076                 $input = this.input.$input || $("<div>");
1077                 $menu = this.menu.$node || $("<div>");
1078                 $input.on("blur.tt", function($e) {
1079                     var active, isActive, hasActive;
1080                     active = document.activeElement;
1081                     isActive = $menu.is(active);
1082                     hasActive = $menu.has(active).length > 0;
1083                     if (_.isMsie() && (isActive || hasActive)) {
1084                         $e.preventDefault();
1085                         $e.stopImmediatePropagation();
1086                         _.defer(function() {
1087                             $input.focus();
1088                         });
1089                     }
1090                 });
1091                 $menu.on("mousedown.tt", function($e) {
1092                     $e.preventDefault();
1093                 });
1094             },
1095             _onSelectableClicked: function onSelectableClicked(type, $el) {
1096                 this.select($el);
1097             },
1098             _onDatasetCleared: function onDatasetCleared() {
1099                 this._updateHint();
1100             },
1101             _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
1102                 this._updateHint();
1103                 this.eventBus.trigger("render", suggestions, async, dataset);
1104             },
1105             _onAsyncRequested: function onAsyncRequested(type, dataset, query) {
1106                 this.eventBus.trigger("asyncrequest", query, dataset);
1107             },
1108             _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) {
1109                 this.eventBus.trigger("asynccancel", query, dataset);
1110             },
1111             _onAsyncReceived: function onAsyncReceived(type, dataset, query) {
1112                 this.eventBus.trigger("asyncreceive", query, dataset);
1113             },
1114             _onFocused: function onFocused() {
1115                 this._minLengthMet() && this.menu.update(this.input.getQuery());
1116             },
1117             _onBlurred: function onBlurred() {
1118                 if (this.input.hasQueryChangedSinceLastFocus()) {
1119                     this.eventBus.trigger("change", this.input.getQuery());
1120                 }
1121             },
1122             _onEnterKeyed: function onEnterKeyed(type, $e) {
1123                 var $selectable;
1124                 if ($selectable = this.menu.getActiveSelectable()) {
1125                     this.select($selectable) && $e.preventDefault();
1126                 }
1127             },
1128             _onTabKeyed: function onTabKeyed(type, $e) {
1129                 var $selectable;
1130                 if ($selectable = this.menu.getActiveSelectable()) {
1131                     this.select($selectable) && $e.preventDefault();
1132                 } else if ($selectable = this.menu.getTopSelectable()) {
1133                     this.autocomplete($selectable) && $e.preventDefault();
1134                 }
1135             },
1136             _onEscKeyed: function onEscKeyed() {
1137                 this.close();
1138             },
1139             _onUpKeyed: function onUpKeyed() {
1140                 this.moveCursor(-1);
1141             },
1142             _onDownKeyed: function onDownKeyed() {
1143                 this.moveCursor(+1);
1144             },
1145             _onLeftKeyed: function onLeftKeyed() {
1146                 if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
1147                     this.autocomplete(this.menu.getTopSelectable());
1148                 }
1149             },
1150             _onRightKeyed: function onRightKeyed() {
1151                 if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
1152                     this.autocomplete(this.menu.getTopSelectable());
1153                 }
1154             },
1155             _onQueryChanged: function onQueryChanged(e, query) {
1156                 this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty();
1157             },
1158             _onWhitespaceChanged: function onWhitespaceChanged() {
1159                 this._updateHint();
1160             },
1161             _onLangDirChanged: function onLangDirChanged(e, dir) {
1162                 if (this.dir !== dir) {
1163                     this.dir = dir;
1164                     this.menu.setLanguageDirection(dir);
1165                 }
1166             },
1167             _openIfActive: function openIfActive() {
1168                 this.isActive() && this.open();
1169             },
1170             _minLengthMet: function minLengthMet(query) {
1171                 query = _.isString(query) ? query : this.input.getQuery() || "";
1172                 return query.length >= this.minLength;
1173             },
1174             _updateHint: function updateHint() {
1175                 var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match;
1176                 $selectable = this.menu.getTopSelectable();
1177                 data = this.menu.getSelectableData($selectable);
1178                 val = this.input.getInputValue();
1179                 if (data && !_.isBlankString(val) && !this.input.hasOverflow()) {
1180                     query = Input.normalizeQuery(val);
1181                     escapedQuery = _.escapeRegExChars(query);
1182                     frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
1183                     match = frontMatchRegEx.exec(data.val);
1184                     match && this.input.setHint(val + match[1]);
1185                 } else {
1186                     this.input.clearHint();
1187                 }
1188             },
1189             isEnabled: function isEnabled() {
1190                 return this.enabled;
1191             },
1192             enable: function enable() {
1193                 this.enabled = true;
1194             },
1195             disable: function disable() {
1196                 this.enabled = false;
1197             },
1198             isActive: function isActive() {
1199                 return this.active;
1200             },
1201             activate: function activate() {
1202                 if (this.isActive()) {
1203                     return true;
1204                 } else if (!this.isEnabled() || this.eventBus.before("active")) {
1205                     return false;
1206                 } else {
1207                     this.active = true;
1208                     this.eventBus.trigger("active");
1209                     return true;
1210                 }
1211             },
1212             deactivate: function deactivate() {
1213                 if (!this.isActive()) {
1214                     return true;
1215                 } else if (this.eventBus.before("idle")) {
1216                     return false;
1217                 } else {
1218                     this.active = false;
1219                     this.close();
1220                     this.eventBus.trigger("idle");
1221                     return true;
1222                 }
1223             },
1224             isOpen: function isOpen() {
1225                 return this.menu.isOpen();
1226             },
1227             open: function open() {
1228                 if (!this.isOpen() && !this.eventBus.before("open")) {
1229                     this.menu.open();
1230                     this._updateHint();
1231                     this.eventBus.trigger("open");
1232                 }
1233                 return this.isOpen();
1234             },
1235             close: function close() {
1236                 if (this.isOpen() && !this.eventBus.before("close")) {
1237                     this.menu.close();
1238                     this.input.clearHint();
1239                     this.input.resetInputValue();
1240                     this.eventBus.trigger("close");
1241                 }
1242                 return !this.isOpen();
1243             },
1244             setVal: function setVal(val) {
1245                 this.input.setQuery(_.toStr(val));
1246             },
1247             getVal: function getVal() {
1248                 return this.input.getQuery();
1249             },
1250             select: function select($selectable) {
1251                 var data = this.menu.getSelectableData($selectable);
1252                 if (data && !this.eventBus.before("select", data.obj)) {
1253                     this.input.setQuery(data.val, true);
1254                     this.eventBus.trigger("select", data.obj);
1255                     this.close();
1256                     return true;
1257                 }
1258                 return false;
1259             },
1260             autocomplete: function autocomplete($selectable) {
1261                 var query, data, isValid;
1262                 query = this.input.getQuery();
1263                 data = this.menu.getSelectableData($selectable);
1264                 isValid = data && query !== data.val;
1265                 if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
1266                     this.input.setQuery(data.val);
1267                     this.eventBus.trigger("autocomplete", data.obj);
1268                     return true;
1269                 }
1270                 return false;
1271             },
1272             moveCursor: function moveCursor(delta) {
1273                 var query, $candidate, data, payload, cancelMove;
1274                 query = this.input.getQuery();
1275                 $candidate = this.menu.selectableRelativeToCursor(delta);
1276                 data = this.menu.getSelectableData($candidate);
1277                 payload = data ? data.obj : null;
1278                 cancelMove = this._minLengthMet() && this.menu.update(query);
1279                 if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
1280                     this.menu.setCursor($candidate);
1281                     if (data) {
1282                         this.input.setInputValue(data.val);
1283                     } else {
1284                         this.input.resetInputValue();
1285                         this._updateHint();
1286                     }
1287                     this.eventBus.trigger("cursorchange", payload);
1288                     return true;
1289                 }
1290                 return false;
1291             },
1292             destroy: function destroy() {
1293                 this.input.destroy();
1294                 this.menu.destroy();
1295             }
1296         });
1297         return Typeahead;
1298         function c(ctx) {
1299             var methods = [].slice.call(arguments, 1);
1300             return function() {
1301                 var args = [].slice.call(arguments);
1302                 _.each(methods, function(method) {
1303                     return ctx[method].apply(ctx, args);
1304                 });
1305             };
1306         }
1307     }();
1308     (function() {
1309         "use strict";
1310         var old, keys, methods;
1311         old = $.fn.typeahead;
1312         keys = {
1313             www: "tt-www",
1314             attrs: "tt-attrs",
1315             typeahead: "tt-typeahead"
1316         };
1317         methods = {
1318             initialize: function initialize(o, datasets) {
1319                 var www;
1320                 datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
1321                 o = o || {};
1322                 www = WWW(o.classNames);
1323                 return this.each(attach);
1324                 function attach() {
1325                     var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
1326                     _.each(datasets, function(d) {
1327                         d.highlight = !!o.highlight;
1328                     });
1329                     $input = $(this);
1330                     $wrapper = $(www.html.wrapper);
1331                     $hint = $elOrNull(o.hint);
1332                     $menu = $elOrNull(o.menu);
1333                     defaultHint = o.hint !== false && !$hint;
1334                     defaultMenu = o.menu !== false && !$menu;
1335                     defaultHint && ($hint = buildHintFromInput($input, www));
1336                     defaultMenu && ($menu = $(www.html.menu).css(www.css.menu));
1337                     $hint && $hint.val("");
1338                     $input = prepInput($input, www);
1339                     if (defaultHint || defaultMenu) {
1340                         $wrapper.css(www.css.wrapper);
1341                         $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint);
1342                         $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null);
1343                     }
1344                     MenuConstructor = defaultMenu ? DefaultMenu : Menu;
1345                     eventBus = new EventBus({
1346                         el: $input
1347                     });
1348                     input = new Input({
1349                         hint: $hint,
1350                         input: $input
1351                     }, www);
1352                     menu = new MenuConstructor({
1353                         node: $menu,
1354                         datasets: datasets
1355                     }, www);
1356                     typeahead = new Typeahead({
1357                         input: input,
1358                         menu: menu,
1359                         eventBus: eventBus,
1360                         minLength: o.minLength
1361                     }, www);
1362                     $input.data(keys.www, www);
1363                     $input.data(keys.typeahead, typeahead);
1364                 }
1365             },
1366             isEnabled: function isEnabled() {
1367                 var enabled;
1368                 ttEach(this.first(), function(t) {
1369                     enabled = t.isEnabled();
1370                 });
1371                 return enabled;
1372             },
1373             enable: function enable() {
1374                 ttEach(this, function(t) {
1375                     t.enable();
1376                 });
1377                 return this;
1378             },
1379             disable: function disable() {
1380                 ttEach(this, function(t) {
1381                     t.disable();
1382                 });
1383                 return this;
1384             },
1385             isActive: function isActive() {
1386                 var active;
1387                 ttEach(this.first(), function(t) {
1388                     active = t.isActive();
1389                 });
1390                 return active;
1391             },
1392             activate: function activate() {
1393                 ttEach(this, function(t) {
1394                     t.activate();
1395                 });
1396                 return this;
1397             },
1398             deactivate: function deactivate() {
1399                 ttEach(this, function(t) {
1400                     t.deactivate();
1401                 });
1402                 return this;
1403             },
1404             isOpen: function isOpen() {
1405                 var open;
1406                 ttEach(this.first(), function(t) {
1407                     open = t.isOpen();
1408                 });
1409                 return open;
1410             },
1411             open: function open() {
1412                 ttEach(this, function(t) {
1413                     t.open();
1414                 });
1415                 return this;
1416             },
1417             close: function close() {
1418                 ttEach(this, function(t) {
1419                     t.close();
1420                 });
1421                 return this;
1422             },
1423             select: function select(el) {
1424                 var success = false, $el = $(el);
1425                 ttEach(this.first(), function(t) {
1426                     success = t.select($el);
1427                 });
1428                 return success;
1429             },
1430             autocomplete: function autocomplete(el) {
1431                 var success = false, $el = $(el);
1432                 ttEach(this.first(), function(t) {
1433                     success = t.autocomplete($el);
1434                 });
1435                 return success;
1436             },
1437             moveCursor: function moveCursoe(delta) {
1438                 var success = false;
1439                 ttEach(this.first(), function(t) {
1440                     success = t.moveCursor(delta);
1441                 });
1442                 return success;
1443             },
1444             val: function val(newVal) {
1445                 var query;
1446                 if (!arguments.length) {
1447                     ttEach(this.first(), function(t) {
1448                         query = t.getVal();
1449                     });
1450                     return query;
1451                 } else {
1452                     ttEach(this, function(t) {
1453                         t.setVal(newVal);
1454                     });
1455                     return this;
1456                 }
1457             },
1458             destroy: function destroy() {
1459                 ttEach(this, function(typeahead, $input) {
1460                     revert($input);
1461                     typeahead.destroy();
1462                 });
1463                 return this;
1464             }
1465         };
1466         $.fn.typeahead = function(method) {
1467             if (methods[method]) {
1468                 return methods[method].apply(this, [].slice.call(arguments, 1));
1469             } else {
1470                 return methods.initialize.apply(this, arguments);
1471             }
1472         };
1473         $.fn.typeahead.noConflict = function noConflict() {
1474             $.fn.typeahead = old;
1475             return this;
1476         };
1477         function ttEach($els, fn) {
1478             $els.each(function() {
1479                 var $input = $(this), typeahead;
1480                 (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input);
1481             });
1482         }
1483         function buildHintFromInput($input, www) {
1484             return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
1485                 autocomplete: "off",
1486                 spellcheck: "false",
1487                 tabindex: -1
1488             });
1489         }
1490         function prepInput($input, www) {
1491             $input.data(keys.attrs, {
1492                 dir: $input.attr("dir"),
1493                 autocomplete: $input.attr("autocomplete"),
1494                 spellcheck: $input.attr("spellcheck"),
1495                 style: $input.attr("style")
1496             });
1497             $input.addClass(www.classes.input).attr({
1498                 autocomplete: "off",
1499                 spellcheck: false
1500             });
1501             try {
1502                 !$input.attr("dir") && $input.attr("dir", "auto");
1503             } catch (e) {}
1504             return $input;
1505         }
1506         function getBackgroundStyles($el) {
1507             return {
1508                 backgroundAttachment: $el.css("background-attachment"),
1509                 backgroundClip: $el.css("background-clip"),
1510                 backgroundColor: $el.css("background-color"),
1511                 backgroundImage: $el.css("background-image"),
1512                 backgroundOrigin: $el.css("background-origin"),
1513                 backgroundPosition: $el.css("background-position"),
1514                 backgroundRepeat: $el.css("background-repeat"),
1515                 backgroundSize: $el.css("background-size")
1516             };
1517         }
1518         function revert($input) {
1519             var www, $wrapper;
1520             www = $input.data(keys.www);
1521             $wrapper = $input.parent().filter(www.selectors.wrapper);
1522             _.each($input.data(keys.attrs), function(val, key) {
1523                 _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
1524             });
1525             $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input);
1526             if ($wrapper.length) {
1527                 $input.detach().insertAfter($wrapper);
1528                 $wrapper.remove();
1529             }
1530         }
1531         function $elOrNull(obj) {
1532             var isValid, $el;
1533             isValid = _.isJQuery(obj) || _.isElement(obj);
1534             $el = isValid ? $(obj).first() : [];
1535             return $el.length ? $el : null;
1536         }
1537     })();
1538 });