You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lo...@apache.org on 2015/07/15 13:54:40 UTC

svn commit: r1691180 [1/2] - in /myfaces/tobago/branches/tobago-3.0.x: tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/overview/ tobago-theme/tobago-theme-standard/src/main/java/org/apache/myfaces/tobago/renderki...

Author: lofwyr
Date: Wed Jul 15 11:54:40 2015
New Revision: 1691180

URL: http://svn.apache.org/r1691180
Log:
TOBAGO-1479: Implement tc:suggest for Bootstrap

Added:
    myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/typeahead/
    myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/typeahead/0.11.1/
    myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/typeahead/0.11.1/typeahead.js
    myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/typeahead/0.11.1/typeahead.min.js
Modified:
    myfaces/tobago/branches/tobago-3.0.x/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/overview/OverviewController.java
    myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/java/org/apache/myfaces/tobago/renderkit/html/standard/standard/tag/SuggestRenderer.java
    myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/META-INF/tobago-config.xml
    myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/script/tobago-suggest.js

Modified: myfaces/tobago/branches/tobago-3.0.x/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/overview/OverviewController.java
URL: http://svn.apache.org/viewvc/myfaces/tobago/branches/tobago-3.0.x/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/overview/OverviewController.java?rev=1691180&r1=1691179&r2=1691180&view=diff
==============================================================================
--- myfaces/tobago/branches/tobago-3.0.x/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/overview/OverviewController.java (original)
+++ myfaces/tobago/branches/tobago-3.0.x/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/overview/OverviewController.java Wed Jul 15 11:54:40 2015
@@ -369,7 +369,10 @@ public class OverviewController implemen
     }
 
   public List<String> getInputSuggestItems(final UIInput component) {
-    final String prefix = (String) component.getSubmittedValue();
+    String prefix = (String) component.getSubmittedValue();
+    if (prefix == null) {
+      prefix = "";
+    }
     LOG.info("Creating items for prefix: '" + prefix + "'");
     final List<String> result = new ArrayList<String>();
     for (final String name : LocaleList.COUNTRY_LANGUAGE) {

Modified: myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/java/org/apache/myfaces/tobago/renderkit/html/standard/standard/tag/SuggestRenderer.java
URL: http://svn.apache.org/viewvc/myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/java/org/apache/myfaces/tobago/renderkit/html/standard/standard/tag/SuggestRenderer.java?rev=1691180&r1=1691179&r2=1691180&view=diff
==============================================================================
--- myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/java/org/apache/myfaces/tobago/renderkit/html/standard/standard/tag/SuggestRenderer.java (original)
+++ myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/java/org/apache/myfaces/tobago/renderkit/html/standard/standard/tag/SuggestRenderer.java Wed Jul 15 11:54:40 2015
@@ -69,9 +69,7 @@ public class SuggestRenderer extends Inp
     writer.writeAttribute(DataAttributes.SUGGEST_TOTAL_COUNT, totalCount);
 
     writer.startElement(HtmlElements.OL, null);
-    writer.writeClassAttribute("tobago-menuBar");
     writer.startElement(HtmlElements.LI, null);
-    writer.writeClassAttribute("tobago-menu tobago-menu-markup-top");
     writer.startElement(HtmlElements.A, null);
     writer.writeAttribute(HtmlAttributes.HREF, "#", false);
     writer.writeAttribute(HtmlAttributes.TABINDEX, -1);

Modified: myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/META-INF/tobago-config.xml
URL: http://svn.apache.org/viewvc/myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/META-INF/tobago-config.xml?rev=1691180&r1=1691179&r2=1691180&view=diff
==============================================================================
--- myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/META-INF/tobago-config.xml (original)
+++ myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/META-INF/tobago-config.xml Wed Jul 15 11:54:40 2015
@@ -397,6 +397,7 @@
         <script name="script/contrib/jquery-ui-timepicker-addon-1.4.5.js"/>
         <script name="script/tobago.js"/>
         <script name="bootstrap/3.3.5/js/bootstrap.js"/>
+        <script name="typeahead/0.11.1/typeahead.js"/>
         <style name="style/contrib/ui-lightness/jquery-ui-1.10.4.custom.css"/>
         <style name="style/contrib/jquery-ui-timepicker-addon-1.4.5.css"/>
         <style name="style/tobago.css"/>
@@ -422,6 +423,7 @@
         <script name="script/tobago-tree.js"/>
         <script name="script/tobago-utils.js"/>
         <script name="bootstrap/3.3.5/js/bootstrap.js"/>
+        <script name="typeahead/0.11.1/typeahead.js"/>
         <style name="style/contrib/ui-lightness/jquery-ui-1.10.4.custom.css"/>
         <style name="style/contrib/jquery-ui-timepicker-addon-1.4.5.css"/>
         <style name="style/tobago.css"/>

Modified: myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/script/tobago-suggest.js
URL: http://svn.apache.org/viewvc/myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/script/tobago-suggest.js?rev=1691180&r1=1691179&r2=1691180&view=diff
==============================================================================
--- myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/script/tobago-suggest.js (original)
+++ myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/script/tobago-suggest.js Wed Jul 15 11:54:40 2015
@@ -17,244 +17,58 @@
 
 Tobago.Suggest = {};
 
-Tobago.Suggest.init = function (elements) {
-
-  var suggests = Tobago.Utils.selectWithJQuery(elements, ".tobago-suggest");
-  suggests.each(function () {
-    jQuery(this).suggest();
-  });
-};
-
-// call before Tobago.Menu.init
-Tobago.registerListener(Tobago.Suggest.init, Tobago.Phase.DOCUMENT_READY, Tobago.Phase.Order.NORMAL);
-Tobago.registerListener(Tobago.Suggest.init, Tobago.Phase.AFTER_UPDATE, Tobago.Phase.Order.NORMAL);
-
-/*
-XXX is this an alternative for initialization?
-
-Tobago.registerListener({
-  name: "suggest",
-  before: ["menu"],
-//    after: ["c", "d"],
-  phases: [Tobago.Phase.DOCUMENT_READY, Tobago.Phase.AFTER_UPDATE],
-  call: function(elements) {
-    var suggests = Tobago.Utils.selectWithJQuery(elements, ".tobago-suggest");
-    suggests.each(function () {
-      jQuery(this).suggest();
-    });
-  }
-  or
-  selector: ".tobago-suggest"
-  call: function() {
-    jQuery(this).suggest();
-  }
-});
-*/
-
-/**
- * Input suggest.
- */
-(function ($) {
-
-  $.widget("tobago.suggest", {
-
-    options: {
-    },
-
-    subMenu: undefined,
-    input: undefined,
-    scheduled: undefined,
-    delay: undefined,
-    minChars: undefined,
-    maxItems: undefined,
-    update: undefined,
-    totalCount: undefined,
-
-    _create: function () {
-
-      this.delay = this.element.data("tobago-suggest-delay");
-      this.minChars = this.element.data("tobago-suggest-min-chars");
-      this.maxItems = this.element.data("tobago-suggest-max-items");
-      this.update = this.element.data("tobago-suggest-update");
-      this.totalCount = this.element.data("tobago-suggest-total-count");
-
-      // creating the menu stuff
-      var menuBar = this.element.find(".tobago-menuBar");
-      menuBar.find("a").data("tobago-ignore-focus", true); // XXX can be removed, after refactoring the menu to jQuery widget
-      this.subMenu = menuBar.find("ol"); // is only one element
-
-      if (this.update) {
-        // remove all that are greater than the maxItems number,
-        // but not the last one, because it contains the ellipsis hint for "there are more results".
-        this.subMenu.children(":gt(" + (this.maxItems - 1) + ")").filter(":not(:last)").remove();
+Tobago.Suggest.substring = function(strings) {
+  return function findMatches(query, callback) {
+    // an array that will be populated with substring matches
+    var matches = [];
+
+    // regex used to determine if a string contains the substring `query`
+    var substringRegex = new RegExp(query, 'i');
+
+    // iterate through the pool of strings and for any string that
+    // contains the substring `query`, add it to the `matches` array
+    $.each(strings, function(i, string) {
+      if (substringRegex.test(string)) {
+        matches.push(string);
       }
+    });
 
-      if (this.totalCount <= this.maxItems) {
-        this.subMenu.children(":last").remove();
-      }
-
-      // init the input element
-      this.input = jQuery(Tobago.Utils.escapeClientId(this.element.data("tobago-for")));
-
-      if (this.input.data("tobago-suggest") == null) {
-        this.input.on(compatibleKeyEvent(), function (event) {
-
-          var handled = false;
-
-          var code = event.which;
-          if (code == 0) {
-            code = event.keyCode;
-          }
-          console.info(code); // @DEV_ONLY
-          var oldItem;
-          var newItem;
-
-          var input = jQuery(this);
-          var widget = input.data("tobago-suggest");
-          var keyCode = jQuery.ui.keyCode;
-          switch (code) {
-            case keyCode.ESCAPE:
-              input.val(input.data("tobago-last-value"));
-              widget._closeMenu();
-              // XXX todo
-              handled = true;
-              break;
-            case keyCode.ENTER:
-//           case keyCode.NUMPAD_ENTER: todo: hides the 'l' character
-              var wasOpen = widget._isMenuOpen();
-              widget._closeMenu();
-              handled = wasOpen;
-              break;
-            case keyCode.UP:
-              oldItem = widget._selectedItem();
-              if (oldItem.length == 1) {
-                newItem = oldItem.prev(); // prev()
-              } else {
-                newItem = widget._lastItem();
-              }
-              widget._select(oldItem, newItem);
-              handled = true;
-              widget._openMenu();
-              break;
-            case keyCode.DOWN:
-              oldItem = widget._selectedItem();
-              if (oldItem.length == 1) {
-                newItem = oldItem.next(); // next()
-              } else {
-                newItem = widget._firstItem();
-              }
-              widget._select(oldItem, newItem);
-              handled = true;
-              widget._openMenu();
-              break;
-            case keyCode.PAGE_UP:
-            case keyCode.PAGE_DOWN:
-            case keyCode.END:
-            case keyCode.HOME:
-            case keyCode.LEFT:
-            case keyCode.RIGHT:
-              // nothing special to do
-              break;
-            default:
-              console.info("input.val() = " + input.val()); // @DEV_ONLY
-              widget._scheduleUpdate();
-              break;
-          }
-
-          // TODO: case keyCode.TAB:
-
-          return !handled;
-        });
-      }
-      this.input.data("tobago-suggest", this); // set or update
+    callback(matches);
+  };
+};
 
-      this.element.css("left", this.input.css("left"));
-      this.element.css("top", this.input.css("top"));
+Tobago.Suggest.init = function (elements) {
 
-      // this must be done after the menu initialization
-      this._on(this.input, {
-        focus: function (event) {
-          this._openMenu();
-        },
-        blur: function (event) {
-          this._closeMenu();
-        }});
-
-      // when the input has focus at loading time, open the menu now
-      if (this.input.get(0) == document.activeElement) { // has focus
-        this._openMenu();
-      }
-    },
+  var suggests = Tobago.Utils.selectWithJQuery(elements, ".tobago-suggest");
+  suggests.each(function () {
+    var suggest = jQuery(this);
 
-    _destroy: function () {
-      this.subMenu.remove();
-    },
-
-    _openMenu: function() {
-      if (this.input.val().length >= this.minChars) { // check min length
-        if (! this._isMenuOpen()) {
-          var menuBar = this.element.find(".tobago-menuBar");
-          var menu = menuBar.children("li");
-          var anchor = menu.children("a");
-          Tobago.Menu.switchOn(menuBar, menu);
-          anchor.each(Tobago.Menu.open);
-          this.subMenu.outerWidth(this.input.outerWidth());
-        }
-      }
-    },
+    var minChars = suggest.data("tobago-suggest-min-chars");
 
-    _closeMenu: function() {
-      if (this._isMenuOpen()) {
-        var menuBar = this.element.find(".tobago-menuBar");
-        Tobago.Menu.switchOff(menuBar);
-      }
-    },
+    var delay = suggest.data("tobago-suggest-delay"); // todo
+    var maxItems = suggest.data("tobago-suggest-max-items"); // todo
+    var update = suggest.data("tobago-suggest-update"); // todo
+    var totalCount = suggest.data("tobago-suggest-total-count"); // todo
+
+    var list = [];
+    suggest.children("ol").children("li").children("ol").children("li").each(function () {
+      var li = jQuery(this);
+      list.push(li.text());
+    });
 
-    _isMenuOpen: function() {
-      return this.subMenu.css("visibility") != "hidden";
-    },
-
-    _selectedItem: function () {
-      return this.subMenu.find("li.tobago-menu-markup-selected:first");
-    },
-
-    _scheduleUpdate: function() {
-      clearTimeout(this.scheduled);
-      this.scheduled = null;
-      this.scheduled = this._delay(function () {
-        if (this.input.val().length >= this.minChars) { // check min length
-          if (this.input.val() != this.input.data("tobago-last-value")) {
-            this.input.data("tobago-last-value", this.input.val());
-            Tobago.reloadComponent(this.input.get(0), this.element.attr("id"), this.input.attr("id"),
-                {createOverlay: false});
-          }
-        } else {
-          this._closeMenu();
-        }
-      }, this.delay);
-    },
-
-    _firstItem: function () {
-      return this.subMenu.find("li:first");
-    },
-
-    _lastItem: function () {
-      return this.subMenu.find("li:last");
-    },
-
-    _select: function (oldItem, newItem) {
-      if (oldItem.length == 1) {
-        oldItem.removeClass("tobago-menu-markup-selected");
-        this.input.val(this.input.data("tobago-last-value"));
-      }
-      if (newItem.length == 1) {
-        newItem.addClass("tobago-menu-markup-selected");
-        this.input.val(newItem.children("a").html());
-      } else {
+    var input = jQuery(Tobago.Utils.escapeClientId(suggest.data("tobago-for")));
 
-      }
-    }
+    suggest.hide();
 
+    input.attr("autocomplete", "off");
+    input.typeahead({
+      minLength: minChars
+    }, {
+      //name: 'cars', todo
+      source: Tobago.Suggest.substring(list)
+    });
   });
+};
 
-}(jQuery));
+Tobago.registerListener(Tobago.Suggest.init, Tobago.Phase.DOCUMENT_READY);
+Tobago.registerListener(Tobago.Suggest.init, Tobago.Phase.AFTER_UPDATE);

Added: myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/typeahead/0.11.1/typeahead.js
URL: http://svn.apache.org/viewvc/myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/typeahead/0.11.1/typeahead.js?rev=1691180&view=auto
==============================================================================
--- myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/typeahead/0.11.1/typeahead.js (added)
+++ myfaces/tobago/branches/tobago-3.0.x/tobago-theme/tobago-theme-standard/src/main/resources/org/apache/myfaces/tobago/renderkit/html/standard/standard/typeahead/0.11.1/typeahead.js Wed Jul 15 11:54:40 2015
@@ -0,0 +1,1538 @@
+/*!
+ * typeahead.js 0.11.1
+ * https://github.com/twitter/typeahead.js
+ * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT
+ */
+
+(function(root, factory) {
+  if (typeof define === "function" && define.amd) {
+    define("typeahead.js", [ "jquery" ], function(a0) {
+      return factory(a0);
+    });
+  } else if (typeof exports === "object") {
+    module.exports = factory(require("jquery"));
+  } else {
+    factory(jQuery);
+  }
+})(this, function($) {
+  var _ = function() {
+    "use strict";
+    return {
+      isMsie: function() {
+        return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
+      },
+      isBlankString: function(str) {
+        return !str || /^\s*$/.test(str);
+      },
+      escapeRegExChars: function(str) {
+        return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+      },
+      isString: function(obj) {
+        return typeof obj === "string";
+      },
+      isNumber: function(obj) {
+        return typeof obj === "number";
+      },
+      isArray: $.isArray,
+      isFunction: $.isFunction,
+      isObject: $.isPlainObject,
+      isUndefined: function(obj) {
+        return typeof obj === "undefined";
+      },
+      isElement: function(obj) {
+        return !!(obj && obj.nodeType === 1);
+      },
+      isJQuery: function(obj) {
+        return obj instanceof $;
+      },
+      toStr: function toStr(s) {
+        return _.isUndefined(s) || s === null ? "" : s + "";
+      },
+      bind: $.proxy,
+      each: function(collection, cb) {
+        $.each(collection, reverseArgs);
+        function reverseArgs(index, value) {
+          return cb(value, index);
+        }
+      },
+      map: $.map,
+      filter: $.grep,
+      every: function(obj, test) {
+        var result = true;
+        if (!obj) {
+          return result;
+        }
+        $.each(obj, function(key, val) {
+          if (!(result = test.call(null, val, key, obj))) {
+            return false;
+          }
+        });
+        return !!result;
+      },
+      some: function(obj, test) {
+        var result = false;
+        if (!obj) {
+          return result;
+        }
+        $.each(obj, function(key, val) {
+          if (result = test.call(null, val, key, obj)) {
+            return false;
+          }
+        });
+        return !!result;
+      },
+      mixin: $.extend,
+      identity: function(x) {
+        return x;
+      },
+      clone: function(obj) {
+        return $.extend(true, {}, obj);
+      },
+      getIdGenerator: function() {
+        var counter = 0;
+        return function() {
+          return counter++;
+        };
+      },
+      templatify: function templatify(obj) {
+        return $.isFunction(obj) ? obj : template;
+        function template() {
+          return String(obj);
+        }
+      },
+      defer: function(fn) {
+        setTimeout(fn, 0);
+      },
+      debounce: function(func, wait, immediate) {
+        var timeout, result;
+        return function() {
+          var context = this, args = arguments, later, callNow;
+          later = function() {
+            timeout = null;
+            if (!immediate) {
+              result = func.apply(context, args);
+            }
+          };
+          callNow = immediate && !timeout;
+          clearTimeout(timeout);
+          timeout = setTimeout(later, wait);
+          if (callNow) {
+            result = func.apply(context, args);
+          }
+          return result;
+        };
+      },
+      throttle: function(func, wait) {
+        var context, args, timeout, result, previous, later;
+        previous = 0;
+        later = function() {
+          previous = new Date();
+          timeout = null;
+          result = func.apply(context, args);
+        };
+        return function() {
+          var now = new Date(), remaining = wait - (now - previous);
+          context = this;
+          args = arguments;
+          if (remaining <= 0) {
+            clearTimeout(timeout);
+            timeout = null;
+            previous = now;
+            result = func.apply(context, args);
+          } else if (!timeout) {
+            timeout = setTimeout(later, remaining);
+          }
+          return result;
+        };
+      },
+      stringify: function(val) {
+        return _.isString(val) ? val : JSON.stringify(val);
+      },
+      noop: function() {}
+    };
+  }();
+  var WWW = function() {
+    "use strict";
+    var defaultClassNames = {
+      wrapper: "twitter-typeahead",
+      input: "tt-input",
+      hint: "tt-hint",
+      menu: "tt-menu",
+      dataset: "tt-dataset",
+      suggestion: "tt-suggestion",
+      selectable: "tt-selectable",
+      empty: "tt-empty",
+      open: "tt-open",
+      cursor: "tt-cursor",
+      highlight: "tt-highlight"
+    };
+    return build;
+    function build(o) {
+      var www, classes;
+      classes = _.mixin({}, defaultClassNames, o);
+      www = {
+        css: buildCss(),
+        classes: classes,
+        html: buildHtml(classes),
+        selectors: buildSelectors(classes)
+      };
+      return {
+        css: www.css,
+        html: www.html,
+        classes: www.classes,
+        selectors: www.selectors,
+        mixin: function(o) {
+          _.mixin(o, www);
+        }
+      };
+    }
+    function buildHtml(c) {
+      return {
+        wrapper: '<span class="' + c.wrapper + '"></span>',
+        menu: '<div class="' + c.menu + '"></div>'
+      };
+    }
+    function buildSelectors(classes) {
+      var selectors = {};
+      _.each(classes, function(v, k) {
+        selectors[k] = "." + v;
+      });
+      return selectors;
+    }
+    function buildCss() {
+      var css = {
+        wrapper: {
+          position: "relative",
+          display: "inline-block"
+        },
+        hint: {
+          position: "absolute",
+          top: "0",
+          left: "0",
+          borderColor: "transparent",
+          boxShadow: "none",
+          opacity: "1"
+        },
+        input: {
+          position: "relative",
+          verticalAlign: "top",
+          backgroundColor: "transparent"
+        },
+        inputWithNoHint: {
+          position: "relative",
+          verticalAlign: "top"
+        },
+        menu: {
+          position: "absolute",
+          top: "100%",
+          left: "0",
+          zIndex: "100",
+          display: "none"
+        },
+        ltr: {
+          left: "0",
+          right: "auto"
+        },
+        rtl: {
+          left: "auto",
+          right: " 0"
+        }
+      };
+      if (_.isMsie()) {
+        _.mixin(css.input, {
+          backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
+        });
+      }
+      return css;
+    }
+  }();
+  var EventBus = function() {
+    "use strict";
+    var namespace, deprecationMap;
+    namespace = "typeahead:";
+    deprecationMap = {
+      render: "rendered",
+      cursorchange: "cursorchanged",
+      select: "selected",
+      autocomplete: "autocompleted"
+    };
+    function EventBus(o) {
+      if (!o || !o.el) {
+        $.error("EventBus initialized without el");
+      }
+      this.$el = $(o.el);
+    }
+    _.mixin(EventBus.prototype, {
+      _trigger: function(type, args) {
+        var $e;
+        $e = $.Event(namespace + type);
+        (args = args || []).unshift($e);
+        this.$el.trigger.apply(this.$el, args);
+        return $e;
+      },
+      before: function(type) {
+        var args, $e;
+        args = [].slice.call(arguments, 1);
+        $e = this._trigger("before" + type, args);
+        return $e.isDefaultPrevented();
+      },
+      trigger: function(type) {
+        var deprecatedType;
+        this._trigger(type, [].slice.call(arguments, 1));
+        if (deprecatedType = deprecationMap[type]) {
+          this._trigger(deprecatedType, [].slice.call(arguments, 1));
+        }
+      }
+    });
+    return EventBus;
+  }();
+  var EventEmitter = function() {
+    "use strict";
+    var splitter = /\s+/, nextTick = getNextTick();
+    return {
+      onSync: onSync,
+      onAsync: onAsync,
+      off: off,
+      trigger: trigger
+    };
+    function on(method, types, cb, context) {
+      var type;
+      if (!cb) {
+        return this;
+      }
+      types = types.split(splitter);
+      cb = context ? bindContext(cb, context) : cb;
+      this._callbacks = this._callbacks || {};
+      while (type = types.shift()) {
+        this._callbacks[type] = this._callbacks[type] || {
+              sync: [],
+              async: []
+            };
+        this._callbacks[type][method].push(cb);
+      }
+      return this;
+    }
+    function onAsync(types, cb, context) {
+      return on.call(this, "async", types, cb, context);
+    }
+    function onSync(types, cb, context) {
+      return on.call(this, "sync", types, cb, context);
+    }
+    function off(types) {
+      var type;
+      if (!this._callbacks) {
+        return this;
+      }
+      types = types.split(splitter);
+      while (type = types.shift()) {
+        delete this._callbacks[type];
+      }
+      return this;
+    }
+    function trigger(types) {
+      var type, callbacks, args, syncFlush, asyncFlush;
+      if (!this._callbacks) {
+        return this;
+      }
+      types = types.split(splitter);
+      args = [].slice.call(arguments, 1);
+      while ((type = types.shift()) && (callbacks = this._callbacks[type])) {
+        syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args));
+        asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args));
+        syncFlush() && nextTick(asyncFlush);
+      }
+      return this;
+    }
+    function getFlush(callbacks, context, args) {
+      return flush;
+      function flush() {
+        var cancelled;
+        for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) {
+          cancelled = callbacks[i].apply(context, args) === false;
+        }
+        return !cancelled;
+      }
+    }
+    function getNextTick() {
+      var nextTickFn;
+      if (window.setImmediate) {
+        nextTickFn = function nextTickSetImmediate(fn) {
+          setImmediate(function() {
+            fn();
+          });
+        };
+      } else {
+        nextTickFn = function nextTickSetTimeout(fn) {
+          setTimeout(function() {
+            fn();
+          }, 0);
+        };
+      }
+      return nextTickFn;
+    }
+    function bindContext(fn, context) {
+      return fn.bind ? fn.bind(context) : function() {
+        fn.apply(context, [].slice.call(arguments, 0));
+      };
+    }
+  }();
+  var highlight = function(doc) {
+    "use strict";
+    var defaults = {
+      node: null,
+      pattern: null,
+      tagName: "strong",
+      className: null,
+      wordsOnly: false,
+      caseSensitive: false
+    };
+    return function hightlight(o) {
+      var regex;
+      o = _.mixin({}, defaults, o);
+      if (!o.node || !o.pattern) {
+        return;
+      }
+      o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
+      regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
+      traverse(o.node, hightlightTextNode);
+      function hightlightTextNode(textNode) {
+        var match, patternNode, wrapperNode;
+        if (match = regex.exec(textNode.data)) {
+          wrapperNode = doc.createElement(o.tagName);
+          o.className && (wrapperNode.className = o.className);
+          patternNode = textNode.splitText(match.index);
+          patternNode.splitText(match[0].length);
+          wrapperNode.appendChild(patternNode.cloneNode(true));
+          textNode.parentNode.replaceChild(wrapperNode, patternNode);
+        }
+        return !!match;
+      }
+      function traverse(el, hightlightTextNode) {
+        var childNode, TEXT_NODE_TYPE = 3;
+        for (var i = 0; i < el.childNodes.length; i++) {
+          childNode = el.childNodes[i];
+          if (childNode.nodeType === TEXT_NODE_TYPE) {
+            i += hightlightTextNode(childNode) ? 1 : 0;
+          } else {
+            traverse(childNode, hightlightTextNode);
+          }
+        }
+      }
+    };
+    function getRegex(patterns, caseSensitive, wordsOnly) {
+      var escapedPatterns = [], regexStr;
+      for (var i = 0, len = patterns.length; i < len; i++) {
+        escapedPatterns.push(_.escapeRegExChars(patterns[i]));
+      }
+      regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
+      return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
+    }
+  }(window.document);
+  var Input = function() {
+    "use strict";
+    var specialKeyCodeMap;
+    specialKeyCodeMap = {
+      9: "tab",
+      27: "esc",
+      37: "left",
+      39: "right",
+      13: "enter",
+      38: "up",
+      40: "down"
+    };
+    function Input(o, www) {
+      o = o || {};
+      if (!o.input) {
+        $.error("input is missing");
+      }
+      www.mixin(this);
+      this.$hint = $(o.hint);
+      this.$input = $(o.input);
+      this.query = this.$input.val();
+      this.queryWhenFocused = this.hasFocus() ? this.query : null;
+      this.$overflowHelper = buildOverflowHelper(this.$input);
+      this._checkLanguageDirection();
+      if (this.$hint.length === 0) {
+        this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
+      }
+    }
+    Input.normalizeQuery = function(str) {
+      return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
+    };
+    _.mixin(Input.prototype, EventEmitter, {
+      _onBlur: function onBlur() {
+        this.resetInputValue();
+        this.trigger("blurred");
+      },
+      _onFocus: function onFocus() {
+        this.queryWhenFocused = this.query;
+        this.trigger("focused");
+      },
+      _onKeydown: function onKeydown($e) {
+        var keyName = specialKeyCodeMap[$e.which || $e.keyCode];
+        this._managePreventDefault(keyName, $e);
+        if (keyName && this._shouldTrigger(keyName, $e)) {
+          this.trigger(keyName + "Keyed", $e);
+        }
+      },
+      _onInput: function onInput() {
+        this._setQuery(this.getInputValue());
+        this.clearHintIfInvalid();
+        this._checkLanguageDirection();
+      },
+      _managePreventDefault: function managePreventDefault(keyName, $e) {
+        var preventDefault;
+        switch (keyName) {
+          case "up":
+          case "down":
+            preventDefault = !withModifier($e);
+            break;
+
+          default:
+            preventDefault = false;
+        }
+        preventDefault && $e.preventDefault();
+      },
+      _shouldTrigger: function shouldTrigger(keyName, $e) {
+        var trigger;
+        switch (keyName) {
+          case "tab":
+            trigger = !withModifier($e);
+            break;
+
+          default:
+            trigger = true;
+        }
+        return trigger;
+      },
+      _checkLanguageDirection: function checkLanguageDirection() {
+        var dir = (this.$input.css("direction") || "ltr").toLowerCase();
+        if (this.dir !== dir) {
+          this.dir = dir;
+          this.$hint.attr("dir", dir);
+          this.trigger("langDirChanged", dir);
+        }
+      },
+      _setQuery: function setQuery(val, silent) {
+        var areEquivalent, hasDifferentWhitespace;
+        areEquivalent = areQueriesEquivalent(val, this.query);
+        hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false;
+        this.query = val;
+        if (!silent && !areEquivalent) {
+          this.trigger("queryChanged", this.query);
+        } else if (!silent && hasDifferentWhitespace) {
+          this.trigger("whitespaceChanged", this.query);
+        }
+      },
+      bind: function() {
+        var that = this, onBlur, onFocus, onKeydown, onInput;
+        onBlur = _.bind(this._onBlur, this);
+        onFocus = _.bind(this._onFocus, this);
+        onKeydown = _.bind(this._onKeydown, this);
+        onInput = _.bind(this._onInput, this);
+        this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
+        if (!_.isMsie() || _.isMsie() > 9) {
+          this.$input.on("input.tt", onInput);
+        } else {
+          this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
+            if (specialKeyCodeMap[$e.which || $e.keyCode]) {
+              return;
+            }
+            _.defer(_.bind(that._onInput, that, $e));
+          });
+        }
+        return this;
+      },
+      focus: function focus() {
+        this.$input.focus();
+      },
+      blur: function blur() {
+        this.$input.blur();
+      },
+      getLangDir: function getLangDir() {
+        return this.dir;
+      },
+      getQuery: function getQuery() {
+        return this.query || "";
+      },
+      setQuery: function setQuery(val, silent) {
+        this.setInputValue(val);
+        this._setQuery(val, silent);
+      },
+      hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() {
+        return this.query !== this.queryWhenFocused;
+      },
+      getInputValue: function getInputValue() {
+        return this.$input.val();
+      },
+      setInputValue: function setInputValue(value) {
+        this.$input.val(value);
+        this.clearHintIfInvalid();
+        this._checkLanguageDirection();
+      },
+      resetInputValue: function resetInputValue() {
+        this.setInputValue(this.query);
+      },
+      getHint: function getHint() {
+        return this.$hint.val();
+      },
+      setHint: function setHint(value) {
+        this.$hint.val(value);
+      },
+      clearHint: function clearHint() {
+        this.setHint("");
+      },
+      clearHintIfInvalid: function clearHintIfInvalid() {
+        var val, hint, valIsPrefixOfHint, isValid;
+        val = this.getInputValue();
+        hint = this.getHint();
+        valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
+        isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
+        !isValid && this.clearHint();
+      },
+      hasFocus: function hasFocus() {
+        return this.$input.is(":focus");
+      },
+      hasOverflow: function hasOverflow() {
+        var constraint = this.$input.width() - 2;
+        this.$overflowHelper.text(this.getInputValue());
+        return this.$overflowHelper.width() >= constraint;
+      },
+      isCursorAtEnd: function() {
+        var valueLength, selectionStart, range;
+        valueLength = this.$input.val().length;
+        selectionStart = this.$input[0].selectionStart;
+        if (_.isNumber(selectionStart)) {
+          return selectionStart === valueLength;
+        } else if (document.selection) {
+          range = document.selection.createRange();
+          range.moveStart("character", -valueLength);
+          return valueLength === range.text.length;
+        }
+        return true;
+      },
+      destroy: function destroy() {
+        this.$hint.off(".tt");
+        this.$input.off(".tt");
+        this.$overflowHelper.remove();
+        this.$hint = this.$input = this.$overflowHelper = $("<div>");
+      }
+    });
+    return Input;
+    function buildOverflowHelper($input) {
+      return $('<pre aria-hidden="true"></pre>').css({
+        position: "absolute",
+        visibility: "hidden",
+        whiteSpace: "pre",
+        fontFamily: $input.css("font-family"),
+        fontSize: $input.css("font-size"),
+        fontStyle: $input.css("font-style"),
+        fontVariant: $input.css("font-variant"),
+        fontWeight: $input.css("font-weight"),
+        wordSpacing: $input.css("word-spacing"),
+        letterSpacing: $input.css("letter-spacing"),
+        textIndent: $input.css("text-indent"),
+        textRendering: $input.css("text-rendering"),
+        textTransform: $input.css("text-transform")
+      }).insertAfter($input);
+    }
+    function areQueriesEquivalent(a, b) {
+      return Input.normalizeQuery(a) === Input.normalizeQuery(b);
+    }
+    function withModifier($e) {
+      return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;
+    }
+  }();
+  var Dataset = function() {
+    "use strict";
+    var keys, nameGenerator;
+    keys = {
+      val: "tt-selectable-display",
+      obj: "tt-selectable-object"
+    };
+    nameGenerator = _.getIdGenerator();
+    function Dataset(o, www) {
+      o = o || {};
+      o.templates = o.templates || {};
+      o.templates.notFound = o.templates.notFound || o.templates.empty;
+      if (!o.source) {
+        $.error("missing source");
+      }
+      if (!o.node) {
+        $.error("missing node");
+      }
+      if (o.name && !isValidName(o.name)) {
+        $.error("invalid dataset name: " + o.name);
+      }
+      www.mixin(this);
+      this.highlight = !!o.highlight;
+      this.name = o.name || nameGenerator();
+      this.limit = o.limit || 5;
+      this.displayFn = getDisplayFn(o.display || o.displayKey);
+      this.templates = getTemplates(o.templates, this.displayFn);
+      this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source;
+      this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async;
+      this._resetLastSuggestion();
+      this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name);
+    }
+    Dataset.extractData = function extractData(el) {
+      var $el = $(el);
+      if ($el.data(keys.obj)) {
+        return {
+          val: $el.data(keys.val) || "",
+          obj: $el.data(keys.obj) || null
+        };
+      }
+      return null;
+    };
+    _.mixin(Dataset.prototype, EventEmitter, {
+      _overwrite: function overwrite(query, suggestions) {
+        suggestions = suggestions || [];
+        if (suggestions.length) {
+          this._renderSuggestions(query, suggestions);
+        } else if (this.async && this.templates.pending) {
+          this._renderPending(query);
+        } else if (!this.async && this.templates.notFound) {
+          this._renderNotFound(query);
+        } else {
+          this._empty();
+        }
+        this.trigger("rendered", this.name, suggestions, false);
+      },
+      _append: function append(query, suggestions) {
+        suggestions = suggestions || [];
+        if (suggestions.length && this.$lastSuggestion.length) {
+          this._appendSuggestions(query, suggestions);
+        } else if (suggestions.length) {
+          this._renderSuggestions(query, suggestions);
+        } else if (!this.$lastSuggestion.length && this.templates.notFound) {
+          this._renderNotFound(query);
+        }
+        this.trigger("rendered", this.name, suggestions, true);
+      },
+      _renderSuggestions: function renderSuggestions(query, suggestions) {
+        var $fragment;
+        $fragment = this._getSuggestionsFragment(query, suggestions);
+        this.$lastSuggestion = $fragment.children().last();
+        this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions));
+      },
+      _appendSuggestions: function appendSuggestions(query, suggestions) {
+        var $fragment, $lastSuggestion;
+        $fragment = this._getSuggestionsFragment(query, suggestions);
+        $lastSuggestion = $fragment.children().last();
+        this.$lastSuggestion.after($fragment);
+        this.$lastSuggestion = $lastSuggestion;
+      },
+      _renderPending: function renderPending(query) {
+        var template = this.templates.pending;
+        this._resetLastSuggestion();
+        template && this.$el.html(template({
+          query: query,
+          dataset: this.name
+        }));
+      },
+      _renderNotFound: function renderNotFound(query) {
+        var template = this.templates.notFound;
+        this._resetLastSuggestion();
+        template && this.$el.html(template({
+          query: query,
+          dataset: this.name
+        }));
+      },
+      _empty: function empty() {
+        this.$el.empty();
+        this._resetLastSuggestion();
+      },
+      _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) {
+        var that = this, fragment;
+        fragment = document.createDocumentFragment();
+        _.each(suggestions, function getSuggestionNode(suggestion) {
+          var $el, context;
+          context = that._injectQuery(query, suggestion);
+          $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable);
+          fragment.appendChild($el[0]);
+        });
+        this.highlight && highlight({
+          className: this.classes.highlight,
+          node: fragment,
+          pattern: query
+        });
+        return $(fragment);
+      },
+      _getFooter: function getFooter(query, suggestions) {
+        return this.templates.footer ? this.templates.footer({
+          query: query,
+          suggestions: suggestions,
+          dataset: this.name
+        }) : null;
+      },
+      _getHeader: function getHeader(query, suggestions) {
+        return this.templates.header ? this.templates.header({
+          query: query,
+          suggestions: suggestions,
+          dataset: this.name
+        }) : null;
+      },
+      _resetLastSuggestion: function resetLastSuggestion() {
+        this.$lastSuggestion = $();
+      },
+      _injectQuery: function injectQuery(query, obj) {
+        return _.isObject(obj) ? _.mixin({
+          _query: query
+        }, obj) : obj;
+      },
+      update: function update(query) {
+        var that = this, canceled = false, syncCalled = false, rendered = 0;
+        this.cancel();
+        this.cancel = function cancel() {
+          canceled = true;
+          that.cancel = $.noop;
+          that.async && that.trigger("asyncCanceled", query);
+        };
+        this.source(query, sync, async);
+        !syncCalled && sync([]);
+        function sync(suggestions) {
+          if (syncCalled) {
+            return;
+          }
+          syncCalled = true;
+          suggestions = (suggestions || []).slice(0, that.limit);
+          rendered = suggestions.length;
+          that._overwrite(query, suggestions);
+          if (rendered < that.limit && that.async) {
+            that.trigger("asyncRequested", query);
+          }
+        }
+        function async(suggestions) {
+          suggestions = suggestions || [];
+          if (!canceled && rendered < that.limit) {
+            that.cancel = $.noop;
+            rendered += suggestions.length;
+            that._append(query, suggestions.slice(0, that.limit - rendered));
+            that.async && that.trigger("asyncReceived", query);
+          }
+        }
+      },
+      cancel: $.noop,
+      clear: function clear() {
+        this._empty();
+        this.cancel();
+        this.trigger("cleared");
+      },
+      isEmpty: function isEmpty() {
+        return this.$el.is(":empty");
+      },
+      destroy: function destroy() {
+        this.$el = $("<div>");
+      }
+    });
+    return Dataset;
+    function getDisplayFn(display) {
+      display = display || _.stringify;
+      return _.isFunction(display) ? display : displayFn;
+      function displayFn(obj) {
+        return obj[display];
+      }
+    }
+    function getTemplates(templates, displayFn) {
+      return {
+        notFound: templates.notFound && _.templatify(templates.notFound),
+        pending: templates.pending && _.templatify(templates.pending),
+        header: templates.header && _.templatify(templates.header),
+        footer: templates.footer && _.templatify(templates.footer),
+        suggestion: templates.suggestion || suggestionTemplate
+      };
+      function suggestionTemplate(context) {
+        return $("<div>").text(displayFn(context));
+      }
+    }
+    function isValidName(str) {
+      return /^[_a-zA-Z0-9-]+$/.test(str);
+    }
+  }();
+  var Menu = function() {
+    "use strict";
+    function Menu(o, www) {
+      var that = this;
+      o = o || {};
+      if (!o.node) {
+        $.error("node is required");
+      }
+      www.mixin(this);
+      this.$node = $(o.node);
+      this.query = null;
+      this.datasets = _.map(o.datasets, initializeDataset);
+      function initializeDataset(oDataset) {
+        var node = that.$node.find(oDataset.node).first();
+        oDataset.node = node.length ? node : $("<div>").appendTo(that.$node);
+        return new Dataset(oDataset, www);
+      }
+    }
+    _.mixin(Menu.prototype, EventEmitter, {
+      _onSelectableClick: function onSelectableClick($e) {
+        this.trigger("selectableClicked", $($e.currentTarget));
+      },
+      _onRendered: function onRendered(type, dataset, suggestions, async) {
+        this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
+        this.trigger("datasetRendered", dataset, suggestions, async);
+      },
+      _onCleared: function onCleared() {
+        this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
+        this.trigger("datasetCleared");
+      },
+      _propagate: function propagate() {
+        this.trigger.apply(this, arguments);
+      },
+      _allDatasetsEmpty: function allDatasetsEmpty() {
+        return _.every(this.datasets, isDatasetEmpty);
+        function isDatasetEmpty(dataset) {
+          return dataset.isEmpty();
+        }
+      },
+      _getSelectables: function getSelectables() {
+        return this.$node.find(this.selectors.selectable);
+      },
+      _removeCursor: function _removeCursor() {
+        var $selectable = this.getActiveSelectable();
+        $selectable && $selectable.removeClass(this.classes.cursor);
+      },
+      _ensureVisible: function ensureVisible($el) {
+        var elTop, elBottom, nodeScrollTop, nodeHeight;
+        elTop = $el.position().top;
+        elBottom = elTop + $el.outerHeight(true);
+        nodeScrollTop = this.$node.scrollTop();
+        nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10);
+        if (elTop < 0) {
+          this.$node.scrollTop(nodeScrollTop + elTop);
+        } else if (nodeHeight < elBottom) {
+          this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight));
+        }
+      },
+      bind: function() {
+        var that = this, onSelectableClick;
+        onSelectableClick = _.bind(this._onSelectableClick, this);
+        this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
+        _.each(this.datasets, function(dataset) {
+          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);
+        });
+        return this;
+      },
+      isOpen: function isOpen() {
+        return this.$node.hasClass(this.classes.open);
+      },
+      open: function open() {
+        this.$node.addClass(this.classes.open);
+      },
+      close: function close() {
+        this.$node.removeClass(this.classes.open);
+        this._removeCursor();
+      },
+      setLanguageDirection: function setLanguageDirection(dir) {
+        this.$node.attr("dir", dir);
+      },
+      selectableRelativeToCursor: function selectableRelativeToCursor(delta) {
+        var $selectables, $oldCursor, oldIndex, newIndex;
+        $oldCursor = this.getActiveSelectable();
+        $selectables = this._getSelectables();
+        oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1;
+        newIndex = oldIndex + delta;
+        newIndex = (newIndex + 1) % ($selectables.length + 1) - 1;
+        newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex;
+        return newIndex === -1 ? null : $selectables.eq(newIndex);
+      },
+      setCursor: function setCursor($selectable) {
+        this._removeCursor();
+        if ($selectable = $selectable && $selectable.first()) {
+          $selectable.addClass(this.classes.cursor);
+          this._ensureVisible($selectable);
+        }
+      },
+      getSelectableData: function getSelectableData($el) {
+        return $el && $el.length ? Dataset.extractData($el) : null;
+      },
+      getActiveSelectable: function getActiveSelectable() {
+        var $selectable = this._getSelectables().filter(this.selectors.cursor).first();
+        return $selectable.length ? $selectable : null;
+      },
+      getTopSelectable: function getTopSelectable() {
+        var $selectable = this._getSelectables().first();
+        return $selectable.length ? $selectable : null;
+      },
+      update: function update(query) {
+        var isValidUpdate = query !== this.query;
+        if (isValidUpdate) {
+          this.query = query;
+          _.each(this.datasets, updateDataset);
+        }
+        return isValidUpdate;
+        function updateDataset(dataset) {
+          dataset.update(query);
+        }
+      },
+      empty: function empty() {
+        _.each(this.datasets, clearDataset);
+        this.query = null;
+        this.$node.addClass(this.classes.empty);
+        function clearDataset(dataset) {
+          dataset.clear();
+        }
+      },
+      destroy: function destroy() {
+        this.$node.off(".tt");
+        this.$node = $("<div>");
+        _.each(this.datasets, destroyDataset);
+        function destroyDataset(dataset) {
+          dataset.destroy();
+        }
+      }
+    });
+    return Menu;
+  }();
+  var DefaultMenu = function() {
+    "use strict";
+    var s = Menu.prototype;
+    function DefaultMenu() {
+      Menu.apply(this, [].slice.call(arguments, 0));
+    }
+    _.mixin(DefaultMenu.prototype, Menu.prototype, {
+      open: function open() {
+        !this._allDatasetsEmpty() && this._show();
+        return s.open.apply(this, [].slice.call(arguments, 0));
+      },
+      close: function close() {
+        this._hide();
+        return s.close.apply(this, [].slice.call(arguments, 0));
+      },
+      _onRendered: function onRendered() {
+        if (this._allDatasetsEmpty()) {
+          this._hide();
+        } else {
+          this.isOpen() && this._show();
+        }
+        return s._onRendered.apply(this, [].slice.call(arguments, 0));
+      },
+      _onCleared: function onCleared() {
+        if (this._allDatasetsEmpty()) {
+          this._hide();
+        } else {
+          this.isOpen() && this._show();
+        }
+        return s._onCleared.apply(this, [].slice.call(arguments, 0));
+      },
+      setLanguageDirection: function setLanguageDirection(dir) {
+        this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl);
+        return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0));
+      },
+      _hide: function hide() {
+        this.$node.hide();
+      },
+      _show: function show() {
+        this.$node.css("display", "block");
+      }
+    });
+    return DefaultMenu;
+  }();
+  var Typeahead = function() {
+    "use strict";
+    function Typeahead(o, www) {
+      var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged;
+      o = o || {};
+      if (!o.input) {
+        $.error("missing input");
+      }
+      if (!o.menu) {
+        $.error("missing menu");
+      }
+      if (!o.eventBus) {
+        $.error("missing event bus");
+      }
+      www.mixin(this);
+      this.eventBus = o.eventBus;
+      this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
+      this.input = o.input;
+      this.menu = o.menu;
+      this.enabled = true;
+      this.active = false;
+      this.input.hasFocus() && this.activate();
+      this.dir = this.input.getLangDir();
+      this._hacks();
+      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);
+      onFocused = c(this, "activate", "open", "_onFocused");
+      onBlurred = c(this, "deactivate", "_onBlurred");
+      onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed");
+      onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed");
+      onEscKeyed = c(this, "isActive", "_onEscKeyed");
+      onUpKeyed = c(this, "isActive", "open", "_onUpKeyed");
+      onDownKeyed = c(this, "isActive", "open", "_onDownKeyed");
+      onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed");
+      onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed");
+      onQueryChanged = c(this, "_openIfActive", "_onQueryChanged");
+      onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged");
+      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);
+    }
+    _.mixin(Typeahead.prototype, {
+      _hacks: function hacks() {
+        var $input, $menu;
+        $input = this.input.$input || $("<div>");
+        $menu = this.menu.$node || $("<div>");
+        $input.on("blur.tt", function($e) {
+          var active, isActive, hasActive;
+          active = document.activeElement;
+          isActive = $menu.is(active);
+          hasActive = $menu.has(active).length > 0;
+          if (_.isMsie() && (isActive || hasActive)) {
+            $e.preventDefault();
+            $e.stopImmediatePropagation();
+            _.defer(function() {
+              $input.focus();
+            });
+          }
+        });
+        $menu.on("mousedown.tt", function($e) {
+          $e.preventDefault();
+        });
+      },
+      _onSelectableClicked: function onSelectableClicked(type, $el) {
+        this.select($el);
+      },
+      _onDatasetCleared: function onDatasetCleared() {
+        this._updateHint();
+      },
+      _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
+        this._updateHint();
+        this.eventBus.trigger("render", suggestions, async, dataset);
+      },
+      _onAsyncRequested: function onAsyncRequested(type, dataset, query) {
+        this.eventBus.trigger("asyncrequest", query, dataset);
+      },
+      _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) {
+        this.eventBus.trigger("asynccancel", query, dataset);
+      },
+      _onAsyncReceived: function onAsyncReceived(type, dataset, query) {
+        this.eventBus.trigger("asyncreceive", query, dataset);
+      },
+      _onFocused: function onFocused() {
+        this._minLengthMet() && this.menu.update(this.input.getQuery());
+      },
+      _onBlurred: function onBlurred() {
+        if (this.input.hasQueryChangedSinceLastFocus()) {
+          this.eventBus.trigger("change", this.input.getQuery());
+        }
+      },
+      _onEnterKeyed: function onEnterKeyed(type, $e) {
+        var $selectable;
+        if ($selectable = this.menu.getActiveSelectable()) {
+          this.select($selectable) && $e.preventDefault();
+        }
+      },
+      _onTabKeyed: function onTabKeyed(type, $e) {
+        var $selectable;
+        if ($selectable = this.menu.getActiveSelectable()) {
+          this.select($selectable) && $e.preventDefault();
+        } else if ($selectable = this.menu.getTopSelectable()) {
+          this.autocomplete($selectable) && $e.preventDefault();
+        }
+      },
+      _onEscKeyed: function onEscKeyed() {
+        this.close();
+      },
+      _onUpKeyed: function onUpKeyed() {
+        this.moveCursor(-1);
+      },
+      _onDownKeyed: function onDownKeyed() {
+        this.moveCursor(+1);
+      },
+      _onLeftKeyed: function onLeftKeyed() {
+        if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
+          this.autocomplete(this.menu.getTopSelectable());
+        }
+      },
+      _onRightKeyed: function onRightKeyed() {
+        if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
+          this.autocomplete(this.menu.getTopSelectable());
+        }
+      },
+      _onQueryChanged: function onQueryChanged(e, query) {
+        this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty();
+      },
+      _onWhitespaceChanged: function onWhitespaceChanged() {
+        this._updateHint();
+      },
+      _onLangDirChanged: function onLangDirChanged(e, dir) {
+        if (this.dir !== dir) {
+          this.dir = dir;
+          this.menu.setLanguageDirection(dir);
+        }
+      },
+      _openIfActive: function openIfActive() {
+        this.isActive() && this.open();
+      },
+      _minLengthMet: function minLengthMet(query) {
+        query = _.isString(query) ? query : this.input.getQuery() || "";
+        return query.length >= this.minLength;
+      },
+      _updateHint: function updateHint() {
+        var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match;
+        $selectable = this.menu.getTopSelectable();
+        data = this.menu.getSelectableData($selectable);
+        val = this.input.getInputValue();
+        if (data && !_.isBlankString(val) && !this.input.hasOverflow()) {
+          query = Input.normalizeQuery(val);
+          escapedQuery = _.escapeRegExChars(query);
+          frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
+          match = frontMatchRegEx.exec(data.val);
+          match && this.input.setHint(val + match[1]);
+        } else {
+          this.input.clearHint();
+        }
+      },
+      isEnabled: function isEnabled() {
+        return this.enabled;
+      },
+      enable: function enable() {
+        this.enabled = true;
+      },
+      disable: function disable() {
+        this.enabled = false;
+      },
+      isActive: function isActive() {
+        return this.active;
+      },
+      activate: function activate() {
+        if (this.isActive()) {
+          return true;
+        } else if (!this.isEnabled() || this.eventBus.before("active")) {
+          return false;
+        } else {
+          this.active = true;
+          this.eventBus.trigger("active");
+          return true;
+        }
+      },
+      deactivate: function deactivate() {
+        if (!this.isActive()) {
+          return true;
+        } else if (this.eventBus.before("idle")) {
+          return false;
+        } else {
+          this.active = false;
+          this.close();
+          this.eventBus.trigger("idle");
+          return true;
+        }
+      },
+      isOpen: function isOpen() {
+        return this.menu.isOpen();
+      },
+      open: function open() {
+        if (!this.isOpen() && !this.eventBus.before("open")) {
+          this.menu.open();
+          this._updateHint();
+          this.eventBus.trigger("open");
+        }
+        return this.isOpen();
+      },
+      close: function close() {
+        if (this.isOpen() && !this.eventBus.before("close")) {
+          this.menu.close();
+          this.input.clearHint();
+          this.input.resetInputValue();
+          this.eventBus.trigger("close");
+        }
+        return !this.isOpen();
+      },
+      setVal: function setVal(val) {
+        this.input.setQuery(_.toStr(val));
+      },
+      getVal: function getVal() {
+        return this.input.getQuery();
+      },
+      select: function select($selectable) {
+        var data = this.menu.getSelectableData($selectable);
+        if (data && !this.eventBus.before("select", data.obj)) {
+          this.input.setQuery(data.val, true);
+          this.eventBus.trigger("select", data.obj);
+          this.close();
+          return true;
+        }
+        return false;
+      },
+      autocomplete: function autocomplete($selectable) {
+        var query, data, isValid;
+        query = this.input.getQuery();
+        data = this.menu.getSelectableData($selectable);
+        isValid = data && query !== data.val;
+        if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
+          this.input.setQuery(data.val);
+          this.eventBus.trigger("autocomplete", data.obj);
+          return true;
+        }
+        return false;
+      },
+      moveCursor: function moveCursor(delta) {
+        var query, $candidate, data, payload, cancelMove;
+        query = this.input.getQuery();
+        $candidate = this.menu.selectableRelativeToCursor(delta);
+        data = this.menu.getSelectableData($candidate);
+        payload = data ? data.obj : null;
+        cancelMove = this._minLengthMet() && this.menu.update(query);
+        if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
+          this.menu.setCursor($candidate);
+          if (data) {
+            this.input.setInputValue(data.val);
+          } else {
+            this.input.resetInputValue();
+            this._updateHint();
+          }
+          this.eventBus.trigger("cursorchange", payload);
+          return true;
+        }
+        return false;
+      },
+      destroy: function destroy() {
+        this.input.destroy();
+        this.menu.destroy();
+      }
+    });
+    return Typeahead;
+    function c(ctx) {
+      var methods = [].slice.call(arguments, 1);
+      return function() {
+        var args = [].slice.call(arguments);
+        _.each(methods, function(method) {
+          return ctx[method].apply(ctx, args);
+        });
+      };
+    }
+  }();
+  (function() {
+    "use strict";
+    var old, keys, methods;
+    old = $.fn.typeahead;
+    keys = {
+      www: "tt-www",
+      attrs: "tt-attrs",
+      typeahead: "tt-typeahead"
+    };
+    methods = {
+      initialize: function initialize(o, datasets) {
+        var www;
+        datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
+        o = o || {};
+        www = WWW(o.classNames);
+        return this.each(attach);
+        function attach() {
+          var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
+          _.each(datasets, function(d) {
+            d.highlight = !!o.highlight;
+          });
+          $input = $(this);
+          $wrapper = $(www.html.wrapper);
+          $hint = $elOrNull(o.hint);
+          $menu = $elOrNull(o.menu);
+          defaultHint = o.hint !== false && !$hint;
+          defaultMenu = o.menu !== false && !$menu;
+          defaultHint && ($hint = buildHintFromInput($input, www));
+          defaultMenu && ($menu = $(www.html.menu).css(www.css.menu));
+          $hint && $hint.val("");
+          $input = prepInput($input, www);
+          if (defaultHint || defaultMenu) {
+            $wrapper.css(www.css.wrapper);
+            $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint);
+            $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null);
+          }
+          MenuConstructor = defaultMenu ? DefaultMenu : Menu;
+          eventBus = new EventBus({
+            el: $input
+          });
+          input = new Input({
+            hint: $hint,
+            input: $input
+          }, www);
+          menu = new MenuConstructor({
+            node: $menu,
+            datasets: datasets
+          }, www);
+          typeahead = new Typeahead({
+            input: input,
+            menu: menu,
+            eventBus: eventBus,
+            minLength: o.minLength
+          }, www);
+          $input.data(keys.www, www);
+          $input.data(keys.typeahead, typeahead);
+        }
+      },
+      isEnabled: function isEnabled() {
+        var enabled;
+        ttEach(this.first(), function(t) {
+          enabled = t.isEnabled();
+        });
+        return enabled;
+      },
+      enable: function enable() {
+        ttEach(this, function(t) {
+          t.enable();
+        });
+        return this;
+      },
+      disable: function disable() {
+        ttEach(this, function(t) {
+          t.disable();
+        });
+        return this;
+      },
+      isActive: function isActive() {
+        var active;
+        ttEach(this.first(), function(t) {
+          active = t.isActive();
+        });
+        return active;
+      },
+      activate: function activate() {
+        ttEach(this, function(t) {
+          t.activate();
+        });
+        return this;
+      },
+      deactivate: function deactivate() {
+        ttEach(this, function(t) {
+          t.deactivate();
+        });
+        return this;
+      },
+      isOpen: function isOpen() {
+        var open;
+        ttEach(this.first(), function(t) {
+          open = t.isOpen();
+        });
+        return open;
+      },
+      open: function open() {
+        ttEach(this, function(t) {
+          t.open();
+        });
+        return this;
+      },
+      close: function close() {
+        ttEach(this, function(t) {
+          t.close();
+        });
+        return this;
+      },
+      select: function select(el) {
+        var success = false, $el = $(el);
+        ttEach(this.first(), function(t) {
+          success = t.select($el);
+        });
+        return success;
+      },
+      autocomplete: function autocomplete(el) {
+        var success = false, $el = $(el);
+        ttEach(this.first(), function(t) {
+          success = t.autocomplete($el);
+        });
+        return success;
+      },
+      moveCursor: function moveCursoe(delta) {
+        var success = false;
+        ttEach(this.first(), function(t) {
+          success = t.moveCursor(delta);
+        });
+        return success;
+      },
+      val: function val(newVal) {
+        var query;
+        if (!arguments.length) {
+          ttEach(this.first(), function(t) {
+            query = t.getVal();
+          });
+          return query;
+        } else {
+          ttEach(this, function(t) {
+            t.setVal(newVal);
+          });
+          return this;
+        }
+      },
+      destroy: function destroy() {
+        ttEach(this, function(typeahead, $input) {
+          revert($input);
+          typeahead.destroy();
+        });
+        return this;
+      }
+    };
+    $.fn.typeahead = function(method) {
+      if (methods[method]) {
+        return methods[method].apply(this, [].slice.call(arguments, 1));
+      } else {
+        return methods.initialize.apply(this, arguments);
+      }
+    };
+    $.fn.typeahead.noConflict = function noConflict() {
+      $.fn.typeahead = old;
+      return this;
+    };
+    function ttEach($els, fn) {
+      $els.each(function() {
+        var $input = $(this), typeahead;
+        (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input);
+      });
+    }
+    function buildHintFromInput($input, www) {
+      return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
+        autocomplete: "off",
+        spellcheck: "false",
+        tabindex: -1
+      });
+    }
+    function prepInput($input, www) {
+      $input.data(keys.attrs, {
+        dir: $input.attr("dir"),
+        autocomplete: $input.attr("autocomplete"),
+        spellcheck: $input.attr("spellcheck"),
+        style: $input.attr("style")
+      });
+      $input.addClass(www.classes.input).attr({
+        autocomplete: "off",
+        spellcheck: false
+      });
+      try {
+        !$input.attr("dir") && $input.attr("dir", "auto");
+      } catch (e) {}
+      return $input;
+    }
+    function getBackgroundStyles($el) {
+      return {
+        backgroundAttachment: $el.css("background-attachment"),
+        backgroundClip: $el.css("background-clip"),
+        backgroundColor: $el.css("background-color"),
+        backgroundImage: $el.css("background-image"),
+        backgroundOrigin: $el.css("background-origin"),
+        backgroundPosition: $el.css("background-position"),
+        backgroundRepeat: $el.css("background-repeat"),
+        backgroundSize: $el.css("background-size")
+      };
+    }
+    function revert($input) {
+      var www, $wrapper;
+      www = $input.data(keys.www);
+      $wrapper = $input.parent().filter(www.selectors.wrapper);
+      _.each($input.data(keys.attrs), function(val, key) {
+        _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
+      });
+      $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input);
+      if ($wrapper.length) {
+        $input.detach().insertAfter($wrapper);
+        $wrapper.remove();
+      }
+    }
+    function $elOrNull(obj) {
+      var isValid, $el;
+      isValid = _.isJQuery(obj) || _.isElement(obj);
+      $el = isValid ? $(obj).first() : [];
+      return $el.length ? $el : null;
+    }
+  })();
+});
\ No newline at end of file