Ticketfly/ember-ticketfly-accordion

View on GitHub
addon/components/tf-accordion-panel-tab.js

Summary

Maintainability
A
1 hr
Test Coverage
import Component from 'ember-component';
import layout from '../templates/components/tf-accordion-panel-tab';
import get from 'ember-metal/get';
import set from 'ember-metal/set';
import { default as computed, readOnly } from 'ember-computed';
import { once } from 'ember-runloop';

/**
 * @module tf-accordion
 */

/**
 * @class TFAccordionPanelTabComponent
 * @namespace TFAccordion
 * @extends Ember.Component
 */
export default Component.extend({
  layout,
  tagName: 'button',

  attributeBindings: [
    'aria-expanded',
    'aria-selected',
    'aria-controls'
  ],

  classNames: ['tfa-panel-tab'],
  classNameBindings: ['_panelExpandedClassNames'],

  /**
   * Bound to the `role` attribute of the `tf-accordion-panel-tab` component's element.
   *
   * See http://www.w3.org/TR/wai-aria/roles#tab
   *
   * @property ariaRole
   * @type String
   * @default 'tab'
   */
  ariaRole: 'tab',

  /**
   * Bound to the `aria-selected` attribute of the `tf-accordion-panel-tab` component's element.
   *
   * @property aria-selected
   * @type String
   * @default 'false'
   * @see https://www.w3.org/TR/wai-aria/states_and_properties#aria-selected
   * @see https://www.stefanjudis.de/aria-selected-and-when-to-use-it.html
   */
  'aria-selected': 'false',

  /* ---------- API ---------- */

  /**
   * Tracks whether or not the panel that this tab belongs to is expanded.
   * This is bound to the computed function for determining
   * the value of this component's element's `aria-expanded` property
   *
   * @property isPanelExpanded
   * @type Boolean
   * @default false
   */
  isPanelExpanded: false,

  /**
   * The id of the element for this tab's sibling panel body component
   *
   * @property panelBodyID
   * @type String
   */
  panelBodyID: '',

  /**
   * The id of the element for this tab's sibling panel body component
   *
   * @property panelBodyID
   * @type String
   */
  title: '',

  /**
   * The instance object of the `tf-accordion` component that
   * this tab's panel belongs to.
   *
   * @property accordion
   * @type TFAccordion.TFAccordionComponent
   */
  accordion: null,

  /**
   * The instance object of the `tf-accordion-panel` component to
   * which this panel tab belongs.
   *
   * @property panel
   * @type TFAccordion.TFAccordionPanelComponent
   */
  panel: null,

  /**
   * A custom class to apply to the panel tab element when
   * its containing panel is expanded.
   *
   * @property expandedClassName
   * @type String
   */
  expandedClassName: '',

  /* ---------- COMPUTEDS ---------- */

  /**
   * Bound to the `aria-expanded` attribute on this component's element.
   *
   * @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/#accordion}
   * @see {@link https://github.com/BrianSipple/why-am-i-doing-this/blob/master/ember/aria-attribute-binding-in-components.md}
   * @property aria-expanded
   * @type String
   * @default 'false'
   * @readOnly
   */
  'aria-expanded': computed('isPanelExpanded', {
    get() {
      return get(this, 'isPanelExpanded') ? 'true' : 'false';
    }
  }).readOnly(),

  /**
   * Class names to apply to the panel tab element when
   * its containing panel is expanded.
   *
   * @property _panelExpandedClassNames
   * @type String
   * @private
   */
  _panelExpandedClassNames: computed('isPanelExpanded', 'expandedClassName', {
    get() {
      if (get(this, 'isPanelExpanded')) {
        return `tfa-panel-tab--expanded ${get(this, 'expandedClassName') || ''}`;
      }
    }
  }).readOnly(),

  /**
   * Bound to the `aria-controls` attribute on this component's element.
   * This identifies the element -- in this case, our sibling panel body --
   * whose contents or presence are controlled by the current element.
   *
   * (There's a bit of skepticism as to what purpose this property serves, but
   * we'll follow the spec for the time being.)
   *
   * @see {@link https://www.w3.org/TR/wai-aria/states_and_properties#aria-controls}
   * @property aria-controls
   * @type String
   * @readOnly
   */
  'aria-controls': readOnly('panelBodyID'),

  /* ---------- LIFECYCLE ---------- */

  /**
   * @override
   */
  init() {
    this._super(...arguments);

    once(this, this._registerWithPanel);
  },

  /**
   * @override
   */
  willDestroyElement() {
    this._super(...arguments);

    once(this, this._unRegisterWithPanel);
  },

  /* ---------- EVENT HANDLERS ---------- */

  /**
   * When the `click` DOM event fires on the element, trigger the
   * root `tf-accordion` component's action with the panel component
   */
  click(/* event */) {
    get(this, 'accordion').send('_onPanelSelection', get(this, 'panel'));
  },

  /**
   * When the `focusIn` DOM event fires on the element, updated our
   * `aria-selected` binding, and then trigger the root `tf-accordion`
   * component's action with the panel component and the jQuery event.
   */
  focusIn(event) {
    set(this, 'aria-selected', 'true');
    get(this, 'accordion').handlePanelEvent('onPanelTabFocusIn', get(this, 'panel'), event);
  },

  /**
   * When the `focusOut` DOM event fires on the element, updated our
   * `aria-selected` binding, and then trigger the root `tf-accordion`
   * component's action with the panel component and the jQuery event.
   */
  focusOut(event) {
    set(this, 'aria-selected', 'false');
    get(this, 'accordion').handlePanelEvent('onPanelTabFocusOut', get(this, 'panel'), event);
  },

  /* ---------- PRIVATE METHODS ---------- */

  _registerWithPanel() {
    get(this, 'panel').registerTab(this);
  },

  _unRegisterWithPanel() {
    get(this, 'panel').unRegisterTab(this);
  }
});