Home | History | Annotate | Download | only in paper-dropdown-menu
      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-a11y-keys-behavior/iron-a11y-keys-behavior.html">
     13 <link rel="import" href="../iron-behaviors/iron-button-state.html">
     14 <link rel="import" href="../iron-behaviors/iron-control-state.html">
     15 <link rel="import" href="../iron-form-element-behavior/iron-form-element-behavior.html">
     16 <link rel="import" href="../iron-icon/iron-icon.html">
     17 <link rel="import" href="../iron-validatable-behavior/iron-validatable-behavior.html">
     18 <link rel="import" href="../paper-input/paper-input.html">
     19 <link rel="import" href="../paper-menu-button/paper-menu-button.html">
     20 <link rel="import" href="../paper-ripple/paper-ripple.html">
     21 <link rel="import" href="../paper-styles/default-theme.html">
     22 
     23 <link rel="import" href="paper-dropdown-menu-icons.html">
     24 <link rel="import" href="paper-dropdown-menu-shared-styles.html">
     25 
     26 <!--
     27 Material design: [Dropdown menus](https://www.google.com/design/spec/components/buttons.html#buttons-dropdown-buttons)
     28 
     29 `paper-dropdown-menu` is similar to a native browser select element.
     30 `paper-dropdown-menu` works with selectable content. The currently selected
     31 item is displayed in the control. If no item is selected, the `label` is
     32 displayed instead.
     33 
     34 Example:
     35 
     36     <paper-dropdown-menu label="Your favourite pastry">
     37       <paper-listbox class="dropdown-content">
     38         <paper-item>Croissant</paper-item>
     39         <paper-item>Donut</paper-item>
     40         <paper-item>Financier</paper-item>
     41         <paper-item>Madeleine</paper-item>
     42       </paper-listbox>
     43     </paper-dropdown-menu>
     44 
     45 This example renders a dropdown menu with 4 options.
     46 
     47 The child element with the class `dropdown-content` is used as the dropdown
     48 menu. This can be a [`paper-listbox`](paper-listbox), or any other or
     49 element that acts like an [`iron-selector`](iron-selector).
     50 
     51 Specifically, the menu child must fire an
     52 [`iron-select`](iron-selector#event-iron-select) event when one of its
     53 children is selected, and an [`iron-deselect`](iron-selector#event-iron-deselect)
     54 event when a child is deselected. The selected or deselected item must
     55 be passed as the event's `detail.item` property.
     56 
     57 Applications can listen for the `iron-select` and `iron-deselect` events
     58 to react when options are selected and deselected.
     59 
     60 ### Styling
     61 
     62 The following custom properties and mixins are also available for styling:
     63 
     64 Custom property | Description | Default
     65 ----------------|-------------|----------
     66 `--paper-dropdown-menu` | A mixin that is applied to the element host | `{}`
     67 `--paper-dropdown-menu-disabled` | A mixin that is applied to the element host when disabled | `{}`
     68 `--paper-dropdown-menu-ripple` | A mixin that is applied to the internal ripple | `{}`
     69 `--paper-dropdown-menu-button` | A mixin that is applied to the internal menu button | `{}`
     70 `--paper-dropdown-menu-input` | A mixin that is applied to the internal paper input | `{}`
     71 `--paper-dropdown-menu-icon` | A mixin that is applied to the internal icon | `{}`
     72 
     73 You can also use any of the `paper-input-container` and `paper-menu-button`
     74 style mixins and custom properties to style the internal input and menu button
     75 respectively.
     76 
     77 @group Paper Elements
     78 @element paper-dropdown-menu
     79 @hero hero.svg
     80 @demo demo/index.html
     81 -->
     82 
     83 <dom-module id="paper-dropdown-menu">
     84   <template>
     85     <style include="paper-dropdown-menu-shared-styles"></style>
     86 
     87     <!-- this div fulfills an a11y requirement for combobox, do not remove -->
     88     <div role="button"></div>
     89     <paper-menu-button
     90       id="menuButton"
     91       vertical-align="[[verticalAlign]]"
     92       horizontal-align="[[horizontalAlign]]"
     93       vertical-offset="[[_computeMenuVerticalOffset(noLabelFloat)]]"
     94       disabled="[[disabled]]"
     95       no-animations="[[noAnimations]]"
     96       on-iron-select="_onIronSelect"
     97       on-iron-deselect="_onIronDeselect"
     98       opened="{{opened}}">
     99       <div class="dropdown-trigger">
    100         <paper-ripple></paper-ripple>
    101         <!-- paper-input has type="text" for a11y, do not remove -->
    102         <paper-input
    103           type="text"
    104           invalid="[[invalid]]"
    105           readonly
    106           disabled="[[disabled]]"
    107           value="[[selectedItemLabel]]"
    108           placeholder="[[placeholder]]"
    109           error-message="[[errorMessage]]"
    110           always-float-label="[[alwaysFloatLabel]]"
    111           no-label-float="[[noLabelFloat]]"
    112           label="[[label]]">
    113           <iron-icon icon="paper-dropdown-menu:arrow-drop-down" suffix></iron-icon>
    114         </paper-input>
    115       </div>
    116       <content id="content" select=".dropdown-content"></content>
    117     </paper-menu-button>
    118   </template>
    119 
    120   <script>
    121     (function() {
    122       'use strict';
    123 
    124       Polymer({
    125         is: 'paper-dropdown-menu',
    126 
    127         behaviors: [
    128           Polymer.IronButtonState,
    129           Polymer.IronControlState,
    130           Polymer.IronFormElementBehavior,
    131           Polymer.IronValidatableBehavior
    132         ],
    133 
    134         properties: {
    135           /**
    136            * The derived "label" of the currently selected item. This value
    137            * is the `label` property on the selected item if set, or else the
    138            * trimmed text content of the selected item.
    139            */
    140           selectedItemLabel: {
    141             type: String,
    142             notify: true,
    143             readOnly: true
    144           },
    145 
    146           /**
    147            * The last selected item. An item is selected if the dropdown menu has
    148            * a child with class `dropdown-content`, and that child triggers an
    149            * `iron-select` event with the selected `item` in the `detail`.
    150            *
    151            * @type {?Object}
    152            */
    153           selectedItem: {
    154             type: Object,
    155             notify: true,
    156             readOnly: true
    157           },
    158 
    159           /**
    160            * The value for this element that will be used when submitting in
    161            * a form. It is read only, and will always have the same value
    162            * as `selectedItemLabel`.
    163            */
    164           value: {
    165             type: String,
    166             notify: true,
    167             readOnly: true
    168           },
    169 
    170           /**
    171            * The label for the dropdown.
    172            */
    173           label: {
    174             type: String
    175           },
    176 
    177           /**
    178            * The placeholder for the dropdown.
    179            */
    180           placeholder: {
    181             type: String
    182           },
    183 
    184           /**
    185            * The error message to display when invalid.
    186            */
    187           errorMessage: {
    188               type: String
    189           },
    190 
    191           /**
    192            * True if the dropdown is open. Otherwise, false.
    193            */
    194           opened: {
    195             type: Boolean,
    196             notify: true,
    197             value: false,
    198             observer: '_openedChanged'
    199           },
    200 
    201           /**
    202            * Set to true to disable the floating label. Bind this to the
    203            * `<paper-input-container>`'s `noLabelFloat` property.
    204            */
    205           noLabelFloat: {
    206               type: Boolean,
    207               value: false,
    208               reflectToAttribute: true
    209           },
    210 
    211           /**
    212            * Set to true to always float the label. Bind this to the
    213            * `<paper-input-container>`'s `alwaysFloatLabel` property.
    214            */
    215           alwaysFloatLabel: {
    216             type: Boolean,
    217             value: false
    218           },
    219 
    220           /**
    221            * Set to true to disable animations when opening and closing the
    222            * dropdown.
    223            */
    224           noAnimations: {
    225             type: Boolean,
    226             value: false
    227           },
    228 
    229           /**
    230            * The orientation against which to align the menu dropdown
    231            * horizontally relative to the dropdown trigger.
    232            */
    233           horizontalAlign: {
    234             type: String,
    235             value: 'right'
    236           },
    237 
    238           /**
    239            * The orientation against which to align the menu dropdown
    240            * vertically relative to the dropdown trigger.
    241            */
    242           verticalAlign: {
    243             type: String,
    244             value: 'top'
    245           }
    246         },
    247 
    248         listeners: {
    249           'tap': '_onTap'
    250         },
    251 
    252         keyBindings: {
    253           'up down': 'open',
    254           'esc': 'close'
    255         },
    256 
    257         hostAttributes: {
    258           role: 'combobox',
    259           'aria-autocomplete': 'none',
    260           'aria-haspopup': 'true'
    261         },
    262 
    263         observers: [
    264           '_selectedItemChanged(selectedItem)'
    265         ],
    266 
    267         attached: function() {
    268           // NOTE(cdata): Due to timing, a preselected value in a `IronSelectable`
    269           // child will cause an `iron-select` event to fire while the element is
    270           // still in a `DocumentFragment`. This has the effect of causing
    271           // handlers not to fire. So, we double check this value on attached:
    272           var contentElement = this.contentElement;
    273           if (contentElement && contentElement.selectedItem) {
    274             this._setSelectedItem(contentElement.selectedItem);
    275           }
    276         },
    277 
    278         /**
    279          * The content element that is contained by the dropdown menu, if any.
    280          */
    281         get contentElement() {
    282           return Polymer.dom(this.$.content).getDistributedNodes()[0];
    283         },
    284 
    285         /**
    286          * Show the dropdown content.
    287          */
    288         open: function() {
    289           this.$.menuButton.open();
    290         },
    291 
    292         /**
    293          * Hide the dropdown content.
    294          */
    295         close: function() {
    296           this.$.menuButton.close();
    297         },
    298 
    299         /**
    300          * A handler that is called when `iron-select` is fired.
    301          *
    302          * @param {CustomEvent} event An `iron-select` event.
    303          */
    304         _onIronSelect: function(event) {
    305           this._setSelectedItem(event.detail.item);
    306         },
    307 
    308         /**
    309          * A handler that is called when `iron-deselect` is fired.
    310          *
    311          * @param {CustomEvent} event An `iron-deselect` event.
    312          */
    313         _onIronDeselect: function(event) {
    314           this._setSelectedItem(null);
    315         },
    316 
    317         /**
    318          * A handler that is called when the dropdown is tapped.
    319          *
    320          * @param {CustomEvent} event A tap event.
    321          */
    322         _onTap: function(event) {
    323           if (Polymer.Gestures.findOriginalTarget(event) === this) {
    324             this.open();
    325           }
    326         },
    327 
    328         /**
    329          * Compute the label for the dropdown given a selected item.
    330          *
    331          * @param {Element} selectedItem A selected Element item, with an
    332          * optional `label` property.
    333          */
    334         _selectedItemChanged: function(selectedItem) {
    335           var value = '';
    336           if (!selectedItem) {
    337             value = '';
    338           } else {
    339             value = selectedItem.label || selectedItem.textContent.trim();
    340           }
    341 
    342           this._setValue(value);
    343           this._setSelectedItemLabel(value);
    344         },
    345 
    346         /**
    347          * Compute the vertical offset of the menu based on the value of
    348          * `noLabelFloat`.
    349          *
    350          * @param {boolean} noLabelFloat True if the label should not float
    351          * above the input, otherwise false.
    352          */
    353         _computeMenuVerticalOffset: function(noLabelFloat) {
    354           // NOTE(cdata): These numbers are somewhat magical because they are
    355           // derived from the metrics of elements internal to `paper-input`'s
    356           // template. The metrics will change depending on whether or not the
    357           // input has a floating label.
    358           return noLabelFloat ? -4 : 8;
    359         },
    360 
    361         /**
    362          * Returns false if the element is required and does not have a selection,
    363          * and true otherwise.
    364          * @param {*=} _value Ignored.
    365          * @return {boolean} true if `required` is false, or if `required` is true
    366          * and the element has a valid selection.
    367          */
    368         _getValidity: function(_value) {
    369           return this.disabled || !this.required || (this.required && !!this.value);
    370         },
    371 
    372         _openedChanged: function() {
    373           var openState = this.opened ? 'true' : 'false';
    374           var e = this.contentElement;
    375           if (e) {
    376             e.setAttribute('aria-expanded', openState);
    377           }
    378         }
    379       });
    380     })();
    381   </script>
    382 </dom-module>
    383