util/CommandButtonGroup.js

/**
 * @copyright 2015 Tridium, Inc. All Rights Reserved.
 * @author Logan Byam
 */

/**
 * @module bajaux/util/CommandButtonGroup
 */
define([ 'jquery',
        'Promise',
        'bajaux/events',
        'bajaux/Widget',
        'bajaux/commands/Command',
        'bajaux/commands/CommandGroup',
        'bajaux/util/CommandButton' ], function (
         $,
         Promise,
         events,
         Widget,
         Command,
         CommandGroup,
         CommandButton) {

  'use strict';
  
  var ENABLE_DISABLE = events.ENABLE_EVENT + ' ' + events.DISABLE_EVENT;

  function widgetDefaults() {
    return {
      properties: {
        toolbar: false,
        onDisabled: { value: 'none', hidden: true, transient: true }
      }
    };
  }

  /**
   * A widget for displaying and invoking Commands in a `CommandGroup`.
   *
   * @class
   * @extends module:bajaux/Widget
   * @alias module:bajaux/util/CommandButtonGroup
   *
   * @param {Object} [params]
   * @param {String} [params.properties.onDisabled] specify a policy for handling
   * enabling/disabling of Commands. By default, the `CommandButton` itself
   * will be responsible for updating its own DOM. A value of `hide` will
   * cause the entire element to be hidden when the command is disabled.
   * @param {Boolean} [params.properties.toolbar=false] Set to true to
   * cause the group to render a toolbar button group instead of a group of
   * normal buttons.
   */
  var CommandButtonGroup = function CommandButtonGroup(params) {
    Widget.call(this, { params: params, defaults: widgetDefaults() });
  };
  CommandButtonGroup.prototype = Object.create(Widget.prototype);
  CommandButtonGroup.prototype.constructor = CommandButtonGroup;

  /**
   * @private
   * @returns {Array.<module:bajaux/util/CommandButtonGroup>}
   */
  CommandButtonGroup.prototype.$getButtons = function () {
    return this.jq().children().map(function () {
      return $(this).data('widget');
    }).get();
  };
  /**
   * When a button is enabled or disabled, the group should update its DOM.
   * This will perform the hiding/showing of the element, according to the
   * `onDisabled` parameter passed to the constructor.
   *
   * @private
   * @param {module:bajaux/util/CommandButton} btn the widget
   * that has just been enabled/disabled
   */
  CommandButtonGroup.prototype.$updateButtonDom = function (btn) {
    var jq = btn.jq(),
        enabled = btn.canInvokeCommand();
    
    switch (this.properties().getValue('onDisabled')) {
      case 'hide':
        jq.toggle(enabled);
        break;
    }
  };

  /**
   * Return true if this group is in toolbar mode.
   * 
   * @private
   * @returns {*}
   */
  CommandButtonGroup.prototype.$isToolbar = function () {
    return this.properties().getValue('toolbar');
  };

  /**
   * Return true if this group is a toggle group.
   *
   * @private
   * @returns {*}
   */
  CommandButtonGroup.prototype.$isToggleGroup = function (params) {
    return params && params.toggleGroup;
  };

  /**
   * Creates a DOM element in which to initialize a new `CommandButton`.
   * By default gives a `button` element with a class of `ux-btn-tb`, to turn
   * this group into a toolbar button group. Override if needed (ensure the
   * element gets appended to `this.jq()`).
   *
   * @private
   * @param {module:bajaux/commands/Command} cmd
   * @returns {JQuery}
   */
  CommandButtonGroup.prototype.$createButtonDom = function (cmd) {
    var button = $('<button type="button"></button>');

    button.addClass(this.$isToolbar() ? 'ux-btn-tb' : 'ux-btn');

    return button.appendTo(this.jq());
  };

  /**
   * Create a CommandButton for the given command. Override if your
   * CommandButtonGroup subclass needs more control over what kind of
   * CommandButtons get instantiated.
   *
   * @private
   * @param {module:bajaux/commands/Command} cmd
   * @returns {module:bajaux/util/CommandButton}
   */
  CommandButtonGroup.prototype.$createCommandButton = function (cmd) {
    return new CommandButton();
  };

  /**
   * Adds the `ux-btn-tb-group` class to the DOM to turn this into a toolbar
   * button group.
   *
   * @param {JQuery} dom
   */
  CommandButtonGroup.prototype.doInitialize = function (dom) {
    var that = this;
    
    dom.addClass('CommandButtonGroup');
    
    if (that.$isToolbar()) {
      dom.addClass('ux-btn-tb-group');
    }

    if (that.$isToggleGroup(arguments && arguments[1])) {
      dom.addClass('ux-toggle-btn-group');
    }

    dom.on(ENABLE_DISABLE, '.CommandButton', function (e, cmdButton) {
      that.$updateButtonDom(cmdButton);
    });
  };

  //TODO: how should this handle subgroups? flatten, or make multiple toolbars?
  /**
   * Loads a `CommandGroup`, creating `CommandButton` widgets for each
   * command in the group. Arms event handlers to hide elements for
   * disabled buttons, as specified by the `onDisabled` parameter.
   *
   * @param {module:bajaux/commands/CommandGroup} group
   * @returns {Promise} promise to be resolved when the group is fully
   * loaded and all elements initialized, or rejected if no `CommandGroup`
   * given
   */
  CommandButtonGroup.prototype.doLoad = function (group) {
    if (!(group instanceof CommandGroup)) {
      throw new Error('CommandGroup required');
    }

    var that = this;
    return this.getChildWidgets().destroyAll()
      .then(() => {
        that.jq().empty();
        return Promise.all(group.getChildren().map((kid) => {
          if (!(kid instanceof Command)) {
            return; //skip groups
          }

          return this.$buildFor(kid);
        }));
      });
  };

  /**
   * @private
   * @param {module:bajaux/commands/Command} cmd
   * @returns {Promise} to be resolved after the child button has been
   * initialized and loaded
   */
  CommandButtonGroup.prototype.$buildFor = function (cmd) {
    const cmdButton = this.$createCommandButton(cmd);
    const dom = this.$createButtonDom(cmd);

    return cmdButton.initialize(dom)
      .then(() => {
        this.$updateButtonDom(cmdButton);

        return cmdButton.load(cmd);
      });
  };

  /**
   * Removes the `ux-btn-tb-group` class and destroys all child buttons.
   *
   * @returns {Promise}
   */
  CommandButtonGroup.prototype.doDestroy = function () {
    var jq = this.jq();
    jq.removeClass('CommandButtonGroup ux-btn-tb-group');

    var buttons = jq.children().map(function () {
      return $(this).data('widget');
    }).get();

    return Promise.all(buttons.map(function (button) {
      var jq = button.jq();
      if (jq) {
        jq.removeData('prev').removeData('parent');
      }
      return button.destroy();
    }));
  };

  return CommandButtonGroup;
});