Home | History | Annotate | Download | only in iron-collapse
      1 <!--
      2 @license
      3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
      4 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
      5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
      6 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
      7 Code distributed by Google as part of the polymer project is also
      8 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
      9 -->
     10 
     11 <link rel="import" href="../polymer/polymer.html">
     12 <link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html">
     13 
     14 <!--
     15 `iron-collapse` creates a collapsible block of content.  By default, the content
     16 will be collapsed.  Use `opened` or `toggle()` to show/hide the content.
     17 
     18     <button on-click="toggle">toggle collapse</button>
     19 
     20     <iron-collapse id="collapse">
     21       <div>Content goes here...</div>
     22     </iron-collapse>
     23 
     24     ...
     25 
     26     toggle: function() {
     27       this.$.collapse.toggle();
     28     }
     29 
     30 `iron-collapse` adjusts the max-height/max-width of the collapsible element to show/hide
     31 the content.  So avoid putting padding/margin/border on the collapsible directly,
     32 and instead put a div inside and style that.
     33 
     34     <style>
     35       .collapse-content {
     36         padding: 15px;
     37         border: 1px solid #dedede;
     38       }
     39     </style>
     40 
     41     <iron-collapse>
     42       <div class="collapse-content">
     43         <div>Content goes here...</div>
     44       </div>
     45     </iron-collapse>
     46 
     47 @group Iron Elements
     48 @hero hero.svg
     49 @demo demo/index.html
     50 @element iron-collapse
     51 -->
     52 
     53 <dom-module id="iron-collapse">
     54 
     55   <template>
     56 
     57     <style>
     58       :host {
     59         display: block;
     60         transition-duration: 300ms;
     61         overflow: visible;
     62       }
     63 
     64       :host(.iron-collapse-closed) {
     65         display: none;
     66       }
     67 
     68       :host(:not(.iron-collapse-opened)) {
     69         overflow: hidden;
     70       }
     71     </style>
     72 
     73     <content></content>
     74 
     75   </template>
     76 
     77 </dom-module>
     78 
     79 <script>
     80 
     81   Polymer({
     82 
     83     is: 'iron-collapse',
     84 
     85     behaviors: [
     86       Polymer.IronResizableBehavior
     87     ],
     88 
     89     properties: {
     90 
     91       /**
     92        * If true, the orientation is horizontal; otherwise is vertical.
     93        *
     94        * @attribute horizontal
     95        */
     96       horizontal: {
     97         type: Boolean,
     98         value: false,
     99         observer: '_horizontalChanged'
    100       },
    101 
    102       /**
    103        * Set opened to true to show the collapse element and to false to hide it.
    104        *
    105        * @attribute opened
    106        */
    107       opened: {
    108         type: Boolean,
    109         value: false,
    110         notify: true,
    111         observer: '_openedChanged'
    112       },
    113 
    114       /**
    115        * Set noAnimation to true to disable animations
    116        *
    117        * @attribute noAnimation
    118        */
    119       noAnimation: {
    120         type: Boolean
    121       },
    122 
    123     },
    124 
    125     get dimension() {
    126       return this.horizontal ? 'width' : 'height';
    127     },
    128 
    129     /**
    130      * `maxWidth` or `maxHeight`.
    131      * @private
    132      */    
    133     get _dimensionMax() {
    134       return this.horizontal ? 'maxWidth' : 'maxHeight';
    135     },
    136 
    137     /**
    138      * `max-width` or `max-height`.
    139      * @private
    140      */
    141     get _dimensionMaxCss() {
    142       return this.horizontal ? 'max-width' : 'max-height';
    143     },
    144 
    145     hostAttributes: {
    146       role: 'group',
    147       'aria-hidden': 'true',
    148       'aria-expanded': 'false'
    149     },
    150 
    151     listeners: {
    152       transitionend: '_transitionEnd'
    153     },
    154 
    155     attached: function() {
    156       // It will take care of setting correct classes and styles.
    157       this._transitionEnd();
    158     },
    159 
    160     /**
    161      * Toggle the opened state.
    162      *
    163      * @method toggle
    164      */
    165     toggle: function() {
    166       this.opened = !this.opened;
    167     },
    168 
    169     show: function() {
    170       this.opened = true;
    171     },
    172 
    173     hide: function() {
    174       this.opened = false;
    175     },
    176 
    177     /**
    178      * Updates the size of the element.
    179      * @param {!String} size The new value for `maxWidth`/`maxHeight` as css property value, usually `auto` or `0px`.
    180      * @param {boolean=} animated if `true` updates the size with an animation, otherwise without.
    181      */     
    182     updateSize: function(size, animated) {
    183       // No change!
    184       var curSize = this.style[this._dimensionMax];
    185       if (curSize === size || (size === 'auto' && !curSize)) {
    186         return;
    187       }
    188 
    189       this._updateTransition(false);
    190       // If we can animate, must do some prep work.
    191       if (animated && !this.noAnimation && this._isDisplayed) {
    192         // Animation will start at the current size.
    193         var startSize = this._calcSize();
    194         // For `auto` we must calculate what is the final size for the animation.
    195         // After the transition is done, _transitionEnd will set the size back to `auto`.
    196         if (size === 'auto') {
    197           this.style[this._dimensionMax] = '';
    198           size = this._calcSize();
    199         }
    200         // Go to startSize without animation.
    201         this.style[this._dimensionMax] = startSize;
    202         // Force layout to ensure transition will go. Set offsetHeight to itself
    203         // so that compilers won't remove it.
    204         this.offsetHeight = this.offsetHeight;
    205         // Enable animation.
    206         this._updateTransition(true);
    207       }
    208       // Set the final size.
    209       if (size === 'auto') {
    210         this.style[this._dimensionMax] = '';
    211       } else {
    212         this.style[this._dimensionMax] = size;
    213       }
    214     },
    215 
    216     /**
    217      * enableTransition() is deprecated, but left over so it doesn't break existing code.
    218      * Please use `noAnimation` property instead.
    219      *
    220      * @method enableTransition
    221      * @deprecated since version 1.0.4
    222      */
    223     enableTransition: function(enabled) {
    224       Polymer.Base._warn('`enableTransition()` is deprecated, use `noAnimation` instead.');
    225       this.noAnimation = !enabled;
    226     },
    227 
    228     _updateTransition: function(enabled) {
    229       this.style.transitionDuration = (enabled && !this.noAnimation) ? '' : '0s';
    230     },
    231 
    232     _horizontalChanged: function() {
    233       this.style.transitionProperty = this._dimensionMaxCss;
    234       var otherDimension = this._dimensionMax === 'maxWidth' ? 'maxHeight' : 'maxWidth';
    235       this.style[otherDimension] = '';
    236       this.updateSize(this.opened ? 'auto' : '0px', false);
    237     },
    238 
    239     _openedChanged: function() {
    240       this.setAttribute('aria-expanded', this.opened);
    241       this.setAttribute('aria-hidden', !this.opened);
    242 
    243       this.toggleClass('iron-collapse-closed', false);
    244       this.toggleClass('iron-collapse-opened', false);
    245       this.updateSize(this.opened ? 'auto' : '0px', true);
    246 
    247       // Focus the current collapse.
    248       if (this.opened) {
    249         this.focus();
    250       }
    251       if (this.noAnimation) {
    252         this._transitionEnd();
    253       }
    254     },
    255 
    256     _transitionEnd: function() {
    257       if (this.opened) {
    258         this.style[this._dimensionMax] = '';
    259       }
    260       this.toggleClass('iron-collapse-closed', !this.opened);
    261       this.toggleClass('iron-collapse-opened', this.opened);
    262       this._updateTransition(false);
    263       this.notifyResize();
    264     },
    265 
    266     /**
    267      * Simplistic heuristic to detect if element has a parent with display: none
    268      *
    269      * @private
    270      */
    271     get _isDisplayed() {
    272       var rect = this.getBoundingClientRect();
    273       for (var prop in rect) {
    274         if (rect[prop] !== 0) return true;
    275       }
    276       return false;
    277     },
    278 
    279     _calcSize: function() {
    280       return this.getBoundingClientRect()[this.dimension] + 'px';
    281     }
    282 
    283   });
    284 
    285 </script>
    286