API Docs 0.11.3.33c7ec07

import Ember from 'ember';

const { computed, observer } = Ember;

/**
 An Ember component that mimics the behaviour of Bootstrap's collapse.js plugin, see http://getbootstrap.com/javascript/#collapse

 ```hbs
 {{#bs-collapse collapsed=collapsed}}
  <div class="well">
    <h2>Collapse</h2>
    <p>This is collapsible content</p>
  </div>
 {{/bs-collapse}}
 ```

 @class Collapse
 @namespace Components
 @extends Ember.Component
 @public
 */
export default Ember.Component.extend({

  classNameBindings: ['collapse', 'in', 'collapsing'],
  attributeBindings: ['style'],

  /**
   * Collapsed/expanded state
   *
   * @property collapsed
   * @type boolean
   * @default true
   * @public
   */
  collapsed: true,

  /**
   * True if this item is expanded
   *
   * @property active
   * @protected
   */
  active: false,

  collapse: computed.not('transitioning'),
  collapsing: computed.alias('transitioning'),
  'in': computed.and('collapse', 'active'),

  /**
   * true if the component is currently transitioning
   *
   * @property transitioning
   * @type boolean
   * @protected
   */
  transitioning: false,

  /**
   * @property collapseSize
   * @type number
   * @protected
   */
  collapseSize: null,

  /**
   * The size of the element when collapsed. Defaults to 0.
   *
   * @property collapsedSize
   * @type number
   * @default 0
   * @public
   */
  collapsedSize: 0,

  /**
   * The size of the element when expanded. When null the value is calculated automatically to fit the containing elements.
   *
   * @property expandedSize
   * @type number
   * @default null
   * @public
   */
  expandedSize: null,

  /**
   * Usually the size (height) of the element is only set while transitioning, and reseted afterwards. Set to true to always set a size.
   *
   * @property resetSizeWhenNotCollapsing
   * @type boolean
   * @default true
   * @private
   */
  resetSizeWhenNotCollapsing: true,

  /**
   * The direction (height/width) of the collapse animation.
   * When setting this to 'width' you should also define custom CSS transitions for the width property, as the Bootstrap
   * CSS does only support collapsible elements for the height direction.
   *
   * @property collapseDimension
   * @type string
   * @default 'height'
   * @public
   */
  collapseDimension: 'height',

  style: computed('collapseSize', function() {
    let size = this.get('collapseSize');
    let dimension = this.get('collapseDimension');
    if (Ember.isEmpty(size)) {
      return Ember.String.htmlSafe('');
    }
    return Ember.String.htmlSafe(`${dimension}: ${size}px`);
  }),

  /**
   * Triggers the show transition
   *
   * @method show
   * @protected
   */
  show() {
    let complete = function() {
        this.set('transitioning', false);
        if (this.get('resetSizeWhenNotCollapsing')) {
          this.set('collapseSize', null);
        }
        this.sendAction('didShow');
      };

    this.sendAction('willShow');

    this.setProperties({
      transitioning: true,
      collapseSize: this.get('collapsedSize'),
      active: true
    });

    if (!Ember.$.support.transition) {
      return complete.call(this);
    }

    this.$()
      .one('bsTransitionEnd', Ember.run.bind(this, complete))
      // @todo: make duration configurable
      .emulateTransitionEnd(350)
    ;

    Ember.run.next(this, function() {
      if (!this.get('isDestroyed')) {
        this.set('collapseSize', this.getExpandedSize('show'));
      }
    });
  },

  /**
   * Get the size of the element when expanded
   *
   * @method getExpandedSize
   * @param $action
   * @return {Number}
   * @private
   */
  getExpandedSize($action) {
    let expandedSize = this.get('expandedSize');
    if (Ember.isPresent(expandedSize)) {
      return expandedSize;
    }

    let collapseElement = this.$();
    let prefix = $action === 'show' ? 'scroll' : 'offset';
    let measureProperty = Ember.String.camelize(`${prefix}-${this.get('collapseDimension')}`);
    return collapseElement[0][measureProperty];
  },

  /**
   * Triggers the hide transition
   *
   * @method hide
   * @protected
   */
  hide() {

    let complete = function() {
        this.set('transitioning', false);
        if (this.get('resetSizeWhenNotCollapsing')) {
          this.set('collapseSize', null);
        }
        this.sendAction('didHide');
      };

    this.sendAction('willHide');

    this.setProperties({
      transitioning: true,
      collapseSize: this.getExpandedSize('hide'),
      active: false
    });

    if (!Ember.$.support.transition) {
      return complete.call(this);
    }

    this.$()
      .one('bsTransitionEnd', Ember.run.bind(this, complete))
      // @todo: make duration configurable
      .emulateTransitionEnd(350)
    ;

    Ember.run.next(this, function() {
      if (!this.get('isDestroyed')) {
        this.set('collapseSize', this.get('collapsedSize'));
      }
    });
  },

  _onCollapsedChange: observer('collapsed', function() {
    let collapsed = this.get('collapsed');
    let active = this.get('active');
    if (collapsed !== active) {
      return;
    }
    if (collapsed === false) {
      this.show();
    } else {
      this.hide();
    }
  }),

  _onInit: Ember.on('init', function() {
    this.set('active', !this.get('collapsed'));
  }),

  _updateCollapsedSize: observer('collapsedSize', function() {
    if (!this.get('resetSizeWhenNotCollapsing') && this.get('collapsed') && !this.get('collapsing')) {
      this.set('collapseSize', this.get('collapsedSize'));
    }
  }),

  _updateExpandedSize: observer('expandedSize', function() {
    if (!this.get('resetSizeWhenNotCollapsing') && !this.get('collapsed') && !this.get('collapsing')) {
      this.set('collapseSize', this.get('expandedSize'));
    }
  })
});