1/*!
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
812                        // HACK: because we don't have a synchronous way of
813                        // retrieving results, we use the async function every
814                        // time we update the drop-down; however, the typeahead
815                        // does some internal book-keeping which means that we
816                        // only get the additional items in the drop-down when
817                        // the next set of results is fetched, instead of all
818                        // of them (it appears to implicitly track which
819                        // results have already been shown in the drop-down); by
820                        // forcing an overwrite, we see all of the new results
821                        // every time we fetch a set of suggestions
822                        //that._append(query, suggestions.slice(0, that.limit - rendered));
823                        that._overwrite(query, suggestions);
824
825                        that.async && that.trigger("asyncReceived", query);
826                    }
827                }
828            },
829            cancel: $.noop,
830            clear: function clear() {
831                this._empty();
832                this.cancel();
833                this.trigger("cleared");
834            },
835            isEmpty: function isEmpty() {
836                return this.$el.is(":empty");
837            },
838            destroy: function destroy() {
839                this.$el = $("<div>");
840            }
841        });
842        return Dataset;
843        function getDisplayFn(display) {
844            display = display || _.stringify;
845            return _.isFunction(display) ? display : displayFn;
846            function displayFn(obj) {
847                return obj[display];
848            }
849        }
850        function getTemplates(templates, displayFn) {
851            return {
852                notFound: templates.notFound && _.templatify(templates.notFound),
853                pending: templates.pending && _.templatify(templates.pending),
854                header: templates.header && _.templatify(templates.header),
855                footer: templates.footer && _.templatify(templates.footer),
856                suggestion: templates.suggestion || suggestionTemplate
857            };
858            function suggestionTemplate(context) {
859                return $("<div>").text(displayFn(context));
860            }
861        }
862        function isValidName(str) {
863            return /^[_a-zA-Z0-9-]+$/.test(str);
864        }
865    }();
866    var Menu = function() {
867        "use strict";
868        function Menu(o, www) {
869            var that = this;
870            o = o || {};
871            if (!o.node) {
872                $.error("node is required");
873            }
874            www.mixin(this);
875            this.$node = $(o.node);
876            this.query = null;
877            this.datasets = _.map(o.datasets, initializeDataset);
878            function initializeDataset(oDataset) {
879                var node = that.$node.find(oDataset.node).first();
880                oDataset.node = node.length ? node : $("<div>").appendTo(that.$node);
881                return new Dataset(oDataset, www);
882            }
883        }
884        _.mixin(Menu.prototype, EventEmitter, {
885            _onSelectableClick: function onSelectableClick($e) {
886                this.trigger("selectableClicked", $($e.currentTarget));
887            },
888            _onRendered: function onRendered(type, dataset, suggestions, async) {
889                this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
890                this.trigger("datasetRendered", dataset, suggestions, async);
891            },
892            _onCleared: function onCleared() {
893                this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
894                this.trigger("datasetCleared");
895            },
896            _propagate: function propagate() {
897                this.trigger.apply(this, arguments);
898            },
899            _allDatasetsEmpty: function allDatasetsEmpty() {
900                return _.every(this.datasets, isDatasetEmpty);
901                function isDatasetEmpty(dataset) {
902                    return dataset.isEmpty();
903                }
904            },
905            _getSelectables: function getSelectables() {
906                return this.$node.find(this.selectors.selectable);
907            },
908            _removeCursor: function _removeCursor() {
909                var $selectable = this.getActiveSelectable();
910                $selectable && $selectable.removeClass(this.classes.cursor);
911            },
912            _ensureVisible: function ensureVisible($el) {
913                var elTop, elBottom, nodeScrollTop, nodeHeight;
914                elTop = $el.position().top;
915                elBottom = elTop + $el.outerHeight(true);
916                nodeScrollTop = this.$node.scrollTop();
917                nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10);
918                if (elTop < 0) {
919                    this.$node.scrollTop(nodeScrollTop + elTop);
920                } else if (nodeHeight < elBottom) {
921                    this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight));
922                }
923            },
924            bind: function() {
925                var that = this, onSelectableClick;
926                onSelectableClick = _.bind(this._onSelectableClick, this);
927                this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
928                _.each(this.datasets, function(dataset) {
929                    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);
930                });
931                return this;
932            },
933            isOpen: function isOpen() {
934                return this.$node.hasClass(this.classes.open);
935            },
936            open: function open() {
937                this.$node.addClass(this.classes.open);
938            },
939            close: function close() {
940                this.$node.removeClass(this.classes.open);
941                this._removeCursor();
942            },
943            setLanguageDirection: function setLanguageDirection(dir) {
944                this.$node.attr("dir", dir);
945            },
946            selectableRelativeToCursor: function selectableRelativeToCursor(delta) {
947                var $selectables, $oldCursor, oldIndex, newIndex;
948                $oldCursor = this.getActiveSelectable();
949                $selectables = this._getSelectables();
950                oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1;
951                newIndex = oldIndex + delta;
952                newIndex = (newIndex + 1) % ($selectables.length + 1) - 1;
953                newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex;
954                return newIndex === -1 ? null : $selectables.eq(newIndex);
955            },
956            setCursor: function setCursor($selectable) {
957                this._removeCursor();
958                if ($selectable = $selectable && $selectable.first()) {
959                    $selectable.addClass(this.classes.cursor);
960                    this._ensureVisible($selectable);
961                }
962            },
963            getSelectableData: function getSelectableData($el) {
964                return $el && $el.length ? Dataset.extractData($el) : null;
965            },
966            getActiveSelectable: function getActiveSelectable() {
967                var $selectable = this._getSelectables().filter(this.selectors.cursor).first();
968                return $selectable.length ? $selectable : null;
969            },
970            getTopSelectable: function getTopSelectable() {
971                var $selectable = this._getSelectables().first();
972                return $selectable.length ? $selectable : null;
973            },
974            update: function update(query) {
975                var isValidUpdate = query !== this.query;
976                if (isValidUpdate) {
977                    this.query = query;
978                    _.each(this.datasets, updateDataset);
979                }
980                return isValidUpdate;
981                function updateDataset(dataset) {
982                    dataset.update(query);
983                }
984            },
985            empty: function empty() {
986                _.each(this.datasets, clearDataset);
987                this.query = null;
988                this.$node.addClass(this.classes.empty);
989                function clearDataset(dataset) {
990                    dataset.clear();
991                }
992            },
993            destroy: function destroy() {
994                this.$node.off(".tt");
995                this.$node = $("<div>");
996                _.each(this.datasets, destroyDataset);
997                function destroyDataset(dataset) {
998                    dataset.destroy();
999                }
1000            }
1001        });
1002        return Menu;
1003    }();
1004    var DefaultMenu = function() {
1005        "use strict";
1006        var s = Menu.prototype;
1007        function DefaultMenu() {
1008            Menu.apply(this, [].slice.call(arguments, 0));
1009        }
1010        _.mixin(DefaultMenu.prototype, Menu.prototype, {
1011            open: function open() {
1012                !this._allDatasetsEmpty() && this._show();
1013                return s.open.apply(this, [].slice.call(arguments, 0));
1014            },
1015            close: function close() {
1016                this._hide();
1017                return s.close.apply(this, [].slice.call(arguments, 0));
1018            },
1019            _onRendered: function onRendered() {
1020                if (this._allDatasetsEmpty()) {
1021                    this._hide();
1022                } else {
1023                    this.isOpen() && this._show();
1024                }
1025                return s._onRendered.apply(this, [].slice.call(arguments, 0));
1026            },
1027            _onCleared: function onCleared() {
1028                if (this._allDatasetsEmpty()) {
1029                    this._hide();
1030                } else {
1031                    this.isOpen() && this._show();
1032                }
1033                return s._onCleared.apply(this, [].slice.call(arguments, 0));
1034            },
1035            setLanguageDirection: function setLanguageDirection(dir) {
1036                this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl);
1037                return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0));
1038            },
1039            _hide: function hide() {
1040                this.$node.hide();
1041            },
1042            _show: function show() {
1043                this.$node.css("display", "block");
1044            }
1045        });
1046        return DefaultMenu;
1047    }();
1048    var Typeahead = function() {
1049        "use strict";
1050        function Typeahead(o, www) {
1051            var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged;
1052            o = o || {};
1053            if (!o.input) {
1054                $.error("missing input");
1055            }
1056            if (!o.menu) {
1057                $.error("missing menu");
1058            }
1059            if (!o.eventBus) {
1060                $.error("missing event bus");
1061            }
1062            www.mixin(this);
1063            this.eventBus = o.eventBus;
1064            this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
1065            this.input = o.input;
1066            this.menu = o.menu;
1067            this.enabled = true;
1068            this.active = false;
1069            this.input.hasFocus() && this.activate();
1070            this.dir = this.input.getLangDir();
1071            this._hacks();
1072            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);
1073            onFocused = c(this, "activate", "open", "_onFocused");
1074            onBlurred = c(this, "deactivate", "_onBlurred");
1075            onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed");
1076            onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed");
1077            onEscKeyed = c(this, "isActive", "_onEscKeyed");
1078            onUpKeyed = c(this, "isActive", "open", "_onUpKeyed");
1079            onDownKeyed = c(this, "isActive", "open", "_onDownKeyed");
1080            onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed");
1081            onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed");
1082            onQueryChanged = c(this, "_openIfActive", "_onQueryChanged");
1083            onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged");
1084            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);
1085        }
1086        _.mixin(Typeahead.prototype, {
1087            _hacks: function hacks() {
1088                var $input, $menu;
1089                $input = this.input.$input || $("<div>");
1090                $menu = this.menu.$node || $("<div>");
1091                $input.on("blur.tt", function($e) {
1092                    var active, isActive, hasActive;
1093                    active = document.activeElement;
1094                    isActive = $menu.is(active);
1095                    hasActive = $menu.has(active).length > 0;
1096                    if (_.isMsie() && (isActive || hasActive)) {
1097                        $e.preventDefault();
1098                        $e.stopImmediatePropagation();
1099                        _.defer(function() {
1100                            $input.focus();
1101                        });
1102                    }
1103                });
1104                $menu.on("mousedown.tt", function($e) {
1105                    $e.preventDefault();
1106                });
1107            },
1108            _onSelectableClicked: function onSelectableClicked(type, $el) {
1109                this.select($el);
1110            },
1111            _onDatasetCleared: function onDatasetCleared() {
1112                this._updateHint();
1113            },
1114            _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
1115                this._updateHint();
1116                this.eventBus.trigger("render", suggestions, async, dataset);
1117            },
1118            _onAsyncRequested: function onAsyncRequested(type, dataset, query) {
1119                this.eventBus.trigger("asyncrequest", query, dataset);
1120            },
1121            _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) {
1122                this.eventBus.trigger("asynccancel", query, dataset);
1123            },
1124            _onAsyncReceived: function onAsyncReceived(type, dataset, query) {
1125                this.eventBus.trigger("asyncreceive", query, dataset);
1126            },
1127            _onFocused: function onFocused() {
1128                this._minLengthMet() && this.menu.update(this.input.getQuery());
1129            },
1130            _onBlurred: function onBlurred() {
1131                if (this.input.hasQueryChangedSinceLastFocus()) {
1132                    this.eventBus.trigger("change", this.input.getQuery());
1133                }
1134            },
1135            _onEnterKeyed: function onEnterKeyed(type, $e) {
1136                var $selectable;
1137                if ($selectable = this.menu.getActiveSelectable()) {
1138                    this.select($selectable) && $e.preventDefault();
1139                }
1140            },
1141            _onTabKeyed: function onTabKeyed(type, $e) {
1142                var $selectable;
1143                if ($selectable = this.menu.getActiveSelectable()) {
1144                    this.select($selectable) && $e.preventDefault();
1145                } else if ($selectable = this.menu.getTopSelectable()) {
1146                    this.autocomplete($selectable) && $e.preventDefault();
1147                }
1148            },
1149            _onEscKeyed: function onEscKeyed() {
1150                this.close();
1151            },
1152            _onUpKeyed: function onUpKeyed() {
1153                this.moveCursor(-1);
1154            },
1155            _onDownKeyed: function onDownKeyed() {
1156                this.moveCursor(+1);
1157            },
1158            _onLeftKeyed: function onLeftKeyed() {
1159                if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
1160                    this.autocomplete(this.menu.getTopSelectable());
1161                }
1162            },
1163            _onRightKeyed: function onRightKeyed() {
1164                if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
1165                    this.autocomplete(this.menu.getTopSelectable());
1166                }
1167            },
1168            _onQueryChanged: function onQueryChanged(e, query) {
1169                this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty();
1170            },
1171            _onWhitespaceChanged: function onWhitespaceChanged() {
1172                this._updateHint();
1173            },
1174            _onLangDirChanged: function onLangDirChanged(e, dir) {
1175                if (this.dir !== dir) {
1176                    this.dir = dir;
1177                    this.menu.setLanguageDirection(dir);
1178                }
1179            },
1180            _openIfActive: function openIfActive() {
1181                this.isActive() && this.open();
1182            },
1183            _minLengthMet: function minLengthMet(query) {
1184                query = _.isString(query) ? query : this.input.getQuery() || "";
1185                return query.length >= this.minLength;
1186            },
1187            _updateHint: function updateHint() {
1188                var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match;
1189                $selectable = this.menu.getTopSelectable();
1190                data = this.menu.getSelectableData($selectable);
1191                val = this.input.getInputValue();
1192                if (data && !_.isBlankString(val) && !this.input.hasOverflow()) {
1193                    query = Input.normalizeQuery(val);
1194                    escapedQuery = _.escapeRegExChars(query);
1195                    frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
1196                    match = frontMatchRegEx.exec(data.val);
1197                    match && this.input.setHint(val + match[1]);
1198                } else {
1199                    this.input.clearHint();
1200                }
1201            },
1202            isEnabled: function isEnabled() {
1203                return this.enabled;
1204            },
1205            enable: function enable() {
1206                this.enabled = true;
1207            },
1208            disable: function disable() {
1209                this.enabled = false;
1210            },
1211            isActive: function isActive() {
1212                return this.active;
1213            },
1214            activate: function activate() {
1215                if (this.isActive()) {
1216                    return true;
1217                } else if (!this.isEnabled() || this.eventBus.before("active")) {
1218                    return false;
1219                } else {
1220                    this.active = true;
1221                    this.eventBus.trigger("active");
1222                    return true;
1223                }
1224            },
1225            deactivate: function deactivate() {
1226                if (!this.isActive()) {
1227                    return true;
1228                } else if (this.eventBus.before("idle")) {
1229                    return false;
1230                } else {
1231                    this.active = false;
1232                    this.close();
1233                    this.eventBus.trigger("idle");
1234                    return true;
1235                }
1236            },
1237            isOpen: function isOpen() {
1238                return this.menu.isOpen();
1239            },
1240            open: function open() {
1241                if (!this.isOpen() && !this.eventBus.before("open")) {
1242                    this.menu.open();
1243                    this._updateHint();
1244                    this.eventBus.trigger("open");
1245                }
1246                return this.isOpen();
1247            },
1248            close: function close() {
1249                if (this.isOpen() && !this.eventBus.before("close")) {
1250                    this.menu.close();
1251                    this.input.clearHint();
1252                    this.input.resetInputValue();
1253                    this.eventBus.trigger("close");
1254                }
1255                return !this.isOpen();
1256            },
1257            setVal: function setVal(val) {
1258                this.input.setQuery(_.toStr(val));
1259            },
1260            getVal: function getVal() {
1261                return this.input.getQuery();
1262            },
1263            select: function select($selectable) {
1264                var data = this.menu.getSelectableData($selectable);
1265                if (data && !this.eventBus.before("select", data.obj)) {
1266                    this.input.setQuery(data.val, true);
1267                    this.eventBus.trigger("select", data.obj);
1268                    this.close();
1269                    return true;
1270                }
1271                return false;
1272            },
1273            autocomplete: function autocomplete($selectable) {
1274                var query, data, isValid;
1275                query = this.input.getQuery();
1276                data = this.menu.getSelectableData($selectable);
1277                isValid = data && query !== data.val;
1278                if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
1279                    this.input.setQuery(data.val);
1280                    this.eventBus.trigger("autocomplete", data.obj);
1281                    return true;
1282                }
1283                return false;
1284            },
1285            moveCursor: function moveCursor(delta) {
1286                var query, $candidate, data, payload, cancelMove;
1287                query = this.input.getQuery();
1288                $candidate = this.menu.selectableRelativeToCursor(delta);
1289                data = this.menu.getSelectableData($candidate);
1290                payload = data ? data.obj : null;
1291                cancelMove = this._minLengthMet() && this.menu.update(query);
1292                if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
1293                    this.menu.setCursor($candidate);
1294                    if (data) {
1295                        this.input.setInputValue(data.val);
1296                    } else {
1297                        this.input.resetInputValue();
1298                        this._updateHint();
1299                    }
1300                    this.eventBus.trigger("cursorchange", payload);
1301                    return true;
1302                }
1303                return false;
1304            },
1305            destroy: function destroy() {
1306                this.input.destroy();
1307                this.menu.destroy();
1308            }
1309        });
1310        return Typeahead;
1311        function c(ctx) {
1312            var methods = [].slice.call(arguments, 1);
1313            return function() {
1314                var args = [].slice.call(arguments);
1315                _.each(methods, function(method) {
1316                    return ctx[method].apply(ctx, args);
1317                });
1318            };
1319        }
1320    }();
1321    (function() {
1322        "use strict";
1323        var old, keys, methods;
1324        old = $.fn.typeahead;
1325        keys = {
1326            www: "tt-www",
1327            attrs: "tt-attrs",
1328            typeahead: "tt-typeahead"
1329        };
1330        methods = {
1331            initialize: function initialize(o, datasets) {
1332                var www;
1333                datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
1334                o = o || {};
1335                www = WWW(o.classNames);
1336                return this.each(attach);
1337                function attach() {
1338                    var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
1339                    _.each(datasets, function(d) {
1340                        d.highlight = !!o.highlight;
1341                    });
1342                    $input = $(this);
1343                    $wrapper = $(www.html.wrapper);
1344                    $hint = $elOrNull(o.hint);
1345                    $menu = $elOrNull(o.menu);
1346                    defaultHint = o.hint !== false && !$hint;
1347                    defaultMenu = o.menu !== false && !$menu;
1348                    defaultHint && ($hint = buildHintFromInput($input, www));
1349                    defaultMenu && ($menu = $(www.html.menu).css(www.css.menu));
1350                    $hint && $hint.val("");
1351                    $input = prepInput($input, www);
1352                    if (defaultHint || defaultMenu) {
1353                        $wrapper.css(www.css.wrapper);
1354                        $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint);
1355                        $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null);
1356                    }
1357                    MenuConstructor = defaultMenu ? DefaultMenu : Menu;
1358                    eventBus = new EventBus({
1359                        el: $input
1360                    });
1361                    input = new Input({
1362                        hint: $hint,
1363                        input: $input
1364                    }, www);
1365                    menu = new MenuConstructor({
1366                        node: $menu,
1367                        datasets: datasets
1368                    }, www);
1369                    typeahead = new Typeahead({
1370                        input: input,
1371                        menu: menu,
1372                        eventBus: eventBus,
1373                        minLength: o.minLength
1374                    }, www);
1375                    $input.data(keys.www, www);
1376                    $input.data(keys.typeahead, typeahead);
1377                }
1378            },
1379            isEnabled: function isEnabled() {
1380                var enabled;
1381                ttEach(this.first(), function(t) {
1382                    enabled = t.isEnabled();
1383                });
1384                return enabled;
1385            },
1386            enable: function enable() {
1387                ttEach(this, function(t) {
1388                    t.enable();
1389                });
1390                return this;
1391            },
1392            disable: function disable() {
1393                ttEach(this, function(t) {
1394                    t.disable();
1395                });
1396                return this;
1397            },
1398            isActive: function isActive() {
1399                var active;
1400                ttEach(this.first(), function(t) {
1401                    active = t.isActive();
1402                });
1403                return active;
1404            },
1405            activate: function activate() {
1406                ttEach(this, function(t) {
1407                    t.activate();
1408                });
1409                return this;
1410            },
1411            deactivate: function deactivate() {
1412                ttEach(this, function(t) {
1413                    t.deactivate();
1414                });
1415                return this;
1416            },
1417            isOpen: function isOpen() {
1418                var open;
1419                ttEach(this.first(), function(t) {
1420                    open = t.isOpen();
1421                });
1422                return open;
1423            },
1424            open: function open() {
1425                ttEach(this, function(t) {
1426                    t.open();
1427                });
1428                return this;
1429            },
1430            close: function close() {
1431                ttEach(this, function(t) {
1432                    t.close();
1433                });
1434                return this;
1435            },
1436            select: function select(el) {
1437                var success = false, $el = $(el);
1438                ttEach(this.first(), function(t) {
1439                    success = t.select($el);
1440                });
1441                return success;
1442            },
1443            autocomplete: function autocomplete(el) {
1444                var success = false, $el = $(el);
1445                ttEach(this.first(), function(t) {
1446                    success = t.autocomplete($el);
1447                });
1448                return success;
1449            },
1450            moveCursor: function moveCursoe(delta) {
1451                var success = false;
1452                ttEach(this.first(), function(t) {
1453                    success = t.moveCursor(delta);
1454                });
1455                return success;
1456            },
1457            val: function val(newVal) {
1458                var query;
1459                if (!arguments.length) {
1460                    ttEach(this.first(), function(t) {
1461                        query = t.getVal();
1462                    });
1463                    return query;
1464                } else {
1465                    ttEach(this, function(t) {
1466                        t.setVal(newVal);
1467                    });
1468                    return this;
1469                }
1470            },
1471            destroy: function destroy() {
1472                ttEach(this, function(typeahead, $input) {
1473                    revert($input);
1474                    typeahead.destroy();
1475                });
1476                return this;
1477            }
1478        };
1479        $.fn.typeahead = function(method) {
1480            if (methods[method]) {
1481                return methods[method].apply(this, [].slice.call(arguments, 1));
1482            } else {
1483                return methods.initialize.apply(this, arguments);
1484            }
1485        };
1486        $.fn.typeahead.noConflict = function noConflict() {
1487            $.fn.typeahead = old;
1488            return this;
1489        };
1490        function ttEach($els, fn) {
1491            $els.each(function() {
1492                var $input = $(this), typeahead;
1493                (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input);
1494            });
1495        }
1496        function buildHintFromInput($input, www) {
1497            return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
1498                autocomplete: "off",
1499                spellcheck: "false",
1500                tabindex: -1
1501            });
1502        }
1503        function prepInput($input, www) {
1504            $input.data(keys.attrs, {
1505                dir: $input.attr("dir"),
1506                autocomplete: $input.attr("autocomplete"),
1507                spellcheck: $input.attr("spellcheck"),
1508                style: $input.attr("style")
1509            });
1510            $input.addClass(www.classes.input).attr({
1511                autocomplete: "off",
1512                spellcheck: false
1513            });
1514            try {
1515                !$input.attr("dir") && $input.attr("dir", "auto");
1516            } catch (e) {}
1517            return $input;
1518        }
1519        function getBackgroundStyles($el) {
1520            return {
1521                backgroundAttachment: $el.css("background-attachment"),
1522                backgroundClip: $el.css("background-clip"),
1523                backgroundColor: $el.css("background-color"),
1524                backgroundImage: $el.css("background-image"),
1525                backgroundOrigin: $el.css("background-origin"),
1526                backgroundPosition: $el.css("background-position"),
1527                backgroundRepeat: $el.css("background-repeat"),
1528                backgroundSize: $el.css("background-size")
1529            };
1530        }
1531        function revert($input) {
1532            var www, $wrapper;
1533            www = $input.data(keys.www);
1534            $wrapper = $input.parent().filter(www.selectors.wrapper);
1535            _.each($input.data(keys.attrs), function(val, key) {
1536                _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
1537            });
1538            $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input);
1539            if ($wrapper.length) {
1540                $input.detach().insertAfter($wrapper);
1541                $wrapper.remove();
1542            }
1543        }
1544        function $elOrNull(obj) {
1545            var isValid, $el;
1546            isValid = _.isJQuery(obj) || _.isElement(obj);
1547            $el = isValid ? $(obj).first() : [];
1548            return $el.length ? $el : null;
1549        }
1550    })();
1551});