Source: enyo-x/source/widgets/combobox.js

/*jshint indent:2, curly:true, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
regexp:true, undef:true, trailing:true, white:true, browser:true */
/*global XT:true, XV:true, XM:true, Backbone:true, enyo:true, _:true */

(function () {

  /**
    @name XV.ComboboxWidget
    @class An input control consisting of fittable columns:
      label, decorator, and combobox.<br />
    Used to implement a styled combobox
    @extends XV.Input
   */
  enyo.kind(
    /** @lends XV.CheckboxWidget# */{
    name: "XV.ComboboxWidget",
    kind: "XV.InputWidget",
    published: {
      attr: null,
      collection: "",
      filter: null,
      disabled: false,
      keyAttribute: "name",
      tabStop: true
    },
    classes: "xv-combobox xv-input",
    handlers: {
      onValueChange: "controlValueChanged"
    },
    components: [
      {controlClasses: 'enyo-inline', name: "container", components: [
        {name: "label", classes: "xv-label"},
        {kind: "onyx.InputDecorator", tag: "div", classes: "xv-icon-decorator", components: [
          {name: "input", kind: "onyx.Input", onkeyup: "keyUp", onkeydown: "keyDown",
            onblur: "receiveBlur", onchange: "inputChanged"},
          {kind: "onyx.IconButton", name: "iconButton", ontap: "toggleCompleter",
            classes: "icon-sort"}
        ]},
        {name: "completer", kind: "XV.Completer", onSelect: "itemSelected"}
      ]}
    ],
    /**
    @todo Document the create method.
    */
    create: function () {
      this.inherited(arguments);
      this.collectionChanged();
      this.filterChanged();
      this.tabStopChanged();
    },
    /**
    @todo Document the autocomplete method.
    */
    autocomplete: function () {
      var key = this.getKeyAttribute(),
        value = this.$.input.getValue(),
        models = this._collection ? this._collection.models : null,
        regexp = value ? new RegExp("^" + value, "i") : null,
        res;

      if (models && models.length && regexp) {
        res = _.find(models, function (model) {
          var value = model.get(key) || "";
          return value.match(regexp);
        });
      }
      if (res) { this.setValue(res.get(key)); }
    },
    /**
    @todo Document the buildList method.
    */
    buildList: function () {
      var key = this.getKeyAttribute(),
        value = this.$.input.getValue(),
        models = this.filteredList();
      this.$.completer.buildList(key, value, models);
    },
    /**
    @todo Document the clear method.
    */
    clear: function (options) {
      this.setValue(false, options);
    },
    /**
      The value sent to setCollection() can be an object or a string.
      If it's a string, resolve it via the cache.
      The actual collection will be this._collection
    */
    collectionChanged: function () {
      var that = this,
        callback;

      if (_.isObject(this.collection)) {
        this._collection = this.collection;
      } else {
        this._collection = XT.getObjectByName(this.collection);
        // If we don't have data yet, try again after start up tasks complete
        if (!this._collection) {
          callback = function () {
            that.collectionChanged();
          };
          XT.getStartupManager().registerCallback(callback);
          return;
        }
      }
      this.buildList();
    },
    controlValueChanged: function (inSender, inEvent) {
      if (inEvent.originator !== this) {
        this.setValue(inEvent.value);
        return true;
      }
    },
    setDisabled: function (isDisabled) {
      this.inherited(arguments);
      this.$.iconButton.setDisabled(isDisabled);
    },
    /**
    @todo Document the keyDown method.
    */
    keyDown: function (inSender, inEvent) {
      this.inherited(arguments);

      // If tabbed out...
      if (inEvent.keyCode === XV.KEY_TAB) {
        this.$.completer.hide();
        this.autocomplete();
      }
    },
    /**
    @todo Document the keyUp method.
    */
    keyUp: function (inSender, inEvent) {
      if (inEvent.keyCode === XV.KEY_TAB) { return; }
      inEvent.activator = this;
      if (this.$.completer.controls.length) {
        this.$.completer.waterfall("onRequestHideMenu", inEvent);
      }
      this.buildList();
      if (this.$.completer.controls.length) {
        this.$.completer.waterfall("onRequestShowMenu", inEvent);
      }
    },
    /**
    @todo Document the itemSelected method.
    */
    itemSelected: function (inSender, inEvent) {
      this.setValue(inEvent.originator.content);
      this.$.completer.hide();
      return true;
    },
    /**
    @todo Document the filterChanged method.
    */
    filterChanged: function () {
      this.buildList();
    },
    /**
    @todo Document the filteredLists method.
    */
    filteredList: function (inSender, inEvent) {
      var models = this._collection ? this._collection.models : null,
        filter = this.getFilter();
      if (filter) {
        models = filter(models);
      }
      return models;
    },
    /**
    @todo Document the receiveBlur method.
    */
    receiveBlur: function (inSender, inEvent) {
      this.autocomplete();
    },
    tabStopChanged: function () {
      this.$.input.setAttribute("tabIndex", this.getTabStop() ? null: -1);
    },
    /**
    @todo Document the toggleCompleter method.
    */
    toggleCompleter: function (inSender, inEvent) {
      inEvent.activator = this;
      var completer = this.$.completer;
      if (completer.showing) {
        completer.waterfall("onRequestHideMenu", inEvent);
      } else {
        completer.waterfall("onRequestShowMenu", inEvent);
      }
    }
  });

}());