delite/HasDropDown

delite/HasDropDown is a base class that provides drop-down menu functionality. Widgets like Select, ComboBox, DropDownButton, DateTextBox etc. could use delite/HasDropDown to implement their functionality.

However, note that it's geared towards desktop browsers. It doesn't adjust for phones, which should use an overlay rather than a dropdown, nor does it adjust for iOS tablets, which always enclose the dropdown in a tooltip.

This will be addressed somehow in the future.

Usage

First, make your widget extend delite/HasDropDown:

register("my-widget", [HTMLElement, HasDropDown], {
  ...
});

Then, either set the value of HasDropDown#dropDown to an existing widget:

register("my-widget", [HTMLElement, HasDropDown], {
  dropDown: new MyMenu()
});

Or, override the HasDropDown#loadDropDown() and HasDropDown#isLoaded() methods to allow for lazy instantiation of the drop-down (see "Dynamic & lazy-loading drop-downs", below).

Optional node properties

Note: All of these properties can only be set before Widget#buildRendering() is called.

_buttonNode

By default, delite/HasDropDown will use either focusNode or domNode as the element to click to display the drop-down. If you want to use a specific element to click to display the drop-down instead, attach that element to buttonNode.

_arrowWrapperNode

When delite/HasDropDown is instantiated, a CSS class d-up-arrow-button, d-down-arrow-button, d-right-arrow-button etc. is added to specify which direction the pop-up appears by default relative to the widget. By default, these classes are set on _buttonNode. Attaching an element to _arrowWrapperNode will cause these classes to be applied to that element instead.

_popupStateNode

When a drop-down is opened, a CSS class d-drop-down-open attribute is added to indicate that the drop-down is open. By default, these changes apply to focusNode, or _buttonNode if there is no focusNode. Attaching an element to _popupStateNode will cause these changes to occur on that element instead.

_aroundNode

When the drop-down is opened, it is positioned based on the location of domNode. Attaching an element to aroundNode will cause the drop-down to be positioned relative to that element instead.

Dynamic & lazy-loading drop-downs

By default, HasDropDown assumes that a delite widget has been created and assigned to HasDropDown.dropDown before the widget starts up. This works well for drop-downs that always contain the same content and are available immediately, but it may reduce startup performance and it makes it impossible to create dynamically populated/asynchronous drop-downs. In order to work around these limitations, more advanced drop-down widgets can implement HasDropDown#loadDropDown() and HasDropDown#isLoaded() instead:

register("my-widget", [HTMLElement, HasDropDown], {
      isLoaded: function () {
          // Returns whether or not we are loaded - if our dropdown has an href,
          // then we want to check that.
          var dropDown = this.dropDown;
          return !!dropDown && (!dropDown.href || dropDown.isLoaded);
      },

      loadDropDown: function(callback){
          // Loads our dropdown
          var dropDown = this.dropDown;
          if (!dropDown) { return; }
          if (!this.isLoaded()) {
              var handler = dropDown.on("load", this, function () {
                  handler.remove();
                  callback();
              });
              dropDown.refresh();
          }else{
              callback();
          }
      }
  });
});