APIs

Show:
/**
  @module ember-flexberry
*/

import $ from 'jquery';
import { typeOf, isNone } from '@ember/utils';
import { assert } from '@ember/debug';
import { observer } from '@ember/object';
import FlexberryBaseComponent from './flexberry-base-component';

/**
  Menu component for Semantic UI.

  Questions:
  - Need {{yield}} in flexberry-menu.hbs?

  @example
    ```javascript
    // app/controllers/menu.js
    ...
    items: [{
      icon: 'search icon',
      title: 'Search',
    },{
      icon: 'settings icon',
      iconAlignment: 'right',
      title: 'Settings',
    },{
      icon: 'list icon',
      title: 'Submenu',
      items: [{
        icon: 'trash icon',
        title: 'Delete',
      }],
    }, {
      title: 'Item',
      buttons: [{
        title: 'edit',
        buttonClasses: 'icon',
        iconClass: 'edit icon',
        disabled: false,
        buttonAction: () => {
          window.alert('clicked edit button');
        }
      }]
    }],
    ...
    actions: {
      ...
      onItemClick(e) {
        let clickedMenuItem = $(e.currentTarget);
        ...
      },
      ...
    },
    ...
    ```

    ```handlebars
    <!-- app/templates/menu.hbs -->
    ...
    {{flexberry-menu
      items=items
      onItemClick=(action 'onItemClick')
    }}
    ...
    ```

  @class FlexberryMenu
  @extends FlexberryBaseComponent
*/
export default FlexberryBaseComponent.extend({
  /**
    See comment on using this.

    @private
    @property _skipDropdownInit
    @type Boolean
    @default false
  */
  _skipDropdownInit: false,

  /**
    Flag: indicates whether to call 'items.[].onClick' callbacks or not.

    @property callItemsOnClickCallbacks
    @type Boolean
    @default true
  */
  callItemsOnClickCallbacks: true,

  /**
    Flag: indicates whether to collapse menu when clicking menuitem or not.

    @property collapseMenuOnItemClick
    @type Boolean
    @default true
  */
  collapseMenuOnItemClick: false,

  /**
    Menu items.

    @property items
    @type Array
    @default null
  */
  items: null,

  /**
    Settings for the dropdown, see [Semantic UI API](https://semantic-ui.com/modules/dropdown.html#/settings) for more info.

    @property settings
    @type Object
  */
  settings: undefined,

  /**
    Array CSS class names.
    [More info](https://emberjs.com/api/ember/release/classes/Component#property_classNames).

    @property classNames
    @type Array
    @readOnly
  */
  classNames: ['flexberry-menu', 'ui', 'icon', 'dropdown', 'button'],

  /**
    Path to component's settings in application configuration (JSON from ./config/environment.js).

    @property appConfigSettingsPath
    @type String
    @default 'APP.components.flexberryMenu'
  */
  appConfigSettingsPath: 'APP.components.flexberryMenu',

  /**
    Indicates than only click handler needed.

    @property onlyClickHandler
    @type Boolean
    @default false
  */
  onlyClickHandler: false,

  /**
    Initializes component.
  */
  init() {
    this._super(...arguments);

    // Component properties will be defined in class prototype,
    // so complex properties like object or arrays, will be shared between all class instances,
    // that's why such properties should be initialized manually in 'init' method.
    let items = this.get('items');
    if (isNone(items)) {
      items = [];
    }

    // Call to 'configurateItems' hook.
    let configurateItems = this.get('configurateItems');

    if (configurateItems) {
      let configurateItemsType = typeOf(configurateItems);

      assert(
        'Wrong type of flexberry-menu \'configurateItems\' propery: ' +
        'actual type is \'' +
        configurateItemsType +
        '\', but \'function\' is expected.', configurateItemsType === 'function');
      configurateItems(items);
    }

    this.set('items', items);
  },

  /**
    Initializes DOM-related component's logic.
  */
  didInsertElement() {
    this._super(...arguments);

    // Bind right context to menu click event handler.
    let onClickHandler = this.get('_onClickHandler').bind(this);
    this.set('_onClickHandler', onClickHandler);

    // Attach menu click event handler.
    this.$().on(this.get('onlyClickHandler') ? 'click' : 'click touchstart', onClickHandler);

    // The dropdown is also initialized in the root flexberry-menuitem component, which can cause various problems.
    // I haven't found a way to fix this without breaking anything.
    if (!this.get('_skipDropdownInit')) {
      this.$().dropdown(this.get('settings'));
    }
  },

  /**
    Cleans up DOM-related component's logic.
  */
  willDestroyElement() {
    this._super(...arguments);

    // Remove menu item click event handler.
    let onClickHandler = this.get('_onClickHandler');
    this.$().off('click touchstart', onClickHandler);
  },

  /**
    Hook which will be called to configure menu items.

    @example
      ```handlebars
      <!-- app/templates/menu.hbs -->
      {{flexberry-menu
        ...
        configurateItems=(action 'configurateItems')
        ...
      }}
      ```

      ```javascript
      // app/controllers/menu.js
      export default Controller.extend({
      ...
        actions: {
          ...
          configurateItems(items) {
            items.push({
              icon: 'edit icon',
              title: 'Edit',
            });
          },
          ...
        }
      ...
      });
      ```

    @method configurateItems
    @param {Array} items Menu items array.
  */
  configurateItems: undefined,

  /**
    Menu click handler.
    Used to delegate menu items clicks handling.

    @method _onClickHandler
    @param {JQuery.Event} e Click event object.
    @private
  */
  _onClickHandler(e) {
    // Find clicked menu item element.
    let itemElement = $(e.target);

    if (itemElement.parent().is('button') || itemElement.is('button')) {
      return;
    }

    if (!itemElement.hasClass('flexberry-menuitem')) {
      itemElement = itemElement.parents('.flexberry-menuitem');
    }

    // Replace currentTarget with clicked menu item element.
    e.currentTarget = itemElement[0];

    // Add clicked item's content to click event object.
    let item = itemElement.data('flexberry-menuitem.item');
    e.item = item;

    // Call onClick handler if it is specified in the given menu item.
    if (item && typeOf(item.onClick) === 'function' && this.get('callItemsOnClickCallbacks')) {
      item.onClick.call(e.currentTarget, e);
    }

    // Send 'onClick' action on clicked 'flexberry-menuitem' component.
    this.get('onItemClick')(e);
  },

  /**
    Menu's collapseMenuOnItemClick observer.
  */
  _collapseMenuOnItemClickDidChange: observer('collapseMenuOnItemClick', function() {
    this.$().dropdown({
      action: this._getActionForMenu(this.get('collapseMenuOnItemClick'))
    }).dropdown('clear');
  }),

  /**
    Method for getting action name for menu based on collapseMenuOnItemClick property.

    @method _getActionForMenu
    @param {Boolean} collapseMenuOnItemClick Flag whether to collapse menu on click or not.
    @return {String} Action name.
    @private
  */
  _getActionForMenu(collapseMenuOnItemClick) {
    return collapseMenuOnItemClick ? 'activate' : 'nothing';
  }
});