• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!--
2@license
3Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
4This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7Code distributed by Google as part of the polymer project is also
8subject 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<!--
27Material 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
31item is displayed in the control. If no item is selected, the `label` is
32displayed instead.
33
34Example:
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
45This example renders a dropdown menu with 4 options.
46
47The child element with the class `dropdown-content` is used as the dropdown
48menu. This can be a [`paper-listbox`](paper-listbox), or any other or
49element that acts like an [`iron-selector`](iron-selector).
50
51Specifically, the menu child must fire an
52[`iron-select`](iron-selector#event-iron-select) event when one of its
53children is selected, and an [`iron-deselect`](iron-selector#event-iron-deselect)
54event when a child is deselected. The selected or deselected item must
55be passed as the event's `detail.item` property.
56
57Applications can listen for the `iron-select` and `iron-deselect` events
58to react when options are selected and deselected.
59
60### Styling
61
62The following custom properties and mixins are also available for styling:
63
64Custom 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
73You can also use any of the `paper-input-container` and `paper-menu-button`
74style mixins and custom properties to style the internal input and menu button
75respectively.
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    <span role="button"></span>
89    <paper-menu-button
90      id="menuButton"
91      vertical-align="[[verticalAlign]]"
92      horizontal-align="[[horizontalAlign]]"
93      dynamic-align="[[dynamicAlign]]"
94      vertical-offset="[[_computeMenuVerticalOffset(noLabelFloat)]]"
95      disabled="[[disabled]]"
96      no-animations="[[noAnimations]]"
97      on-iron-select="_onIronSelect"
98      on-iron-deselect="_onIronDeselect"
99      opened="{{opened}}"
100      close-on-activate
101      allow-outside-scroll="[[allowOutsideScroll]]"
102      restore-focus-on-close="[[restoreFocusOnClose]]">
103      <div class="dropdown-trigger">
104        <paper-ripple></paper-ripple>
105        <!-- paper-input has type="text" for a11y, do not remove -->
106        <paper-input
107          type="text"
108          invalid="[[invalid]]"
109          readonly
110          disabled="[[disabled]]"
111          value="[[selectedItemLabel]]"
112          placeholder="[[placeholder]]"
113          error-message="[[errorMessage]]"
114          always-float-label="[[alwaysFloatLabel]]"
115          no-label-float="[[noLabelFloat]]"
116          label="[[label]]">
117          <iron-icon icon="paper-dropdown-menu:arrow-drop-down" suffix></iron-icon>
118        </paper-input>
119      </div>
120      <content id="content" select=".dropdown-content"></content>
121    </paper-menu-button>
122  </template>
123
124  <script>
125    (function() {
126      'use strict';
127
128      Polymer({
129        is: 'paper-dropdown-menu',
130
131        behaviors: [
132          Polymer.IronButtonState,
133          Polymer.IronControlState,
134          Polymer.IronFormElementBehavior,
135          Polymer.IronValidatableBehavior
136        ],
137
138        properties: {
139          /**
140           * The derived "label" of the currently selected item. This value
141           * is the `label` property on the selected item if set, or else the
142           * trimmed text content of the selected item.
143           */
144          selectedItemLabel: {
145            type: String,
146            notify: true,
147            readOnly: true
148          },
149
150          /**
151           * The last selected item. An item is selected if the dropdown menu has
152           * a child with class `dropdown-content`, and that child triggers an
153           * `iron-select` event with the selected `item` in the `detail`.
154           *
155           * @type {?Object}
156           */
157          selectedItem: {
158            type: Object,
159            notify: true,
160            readOnly: true
161          },
162
163          /**
164           * The value for this element that will be used when submitting in
165           * a form. It is read only, and will always have the same value
166           * as `selectedItemLabel`.
167           */
168          value: {
169            type: String,
170            notify: true,
171            readOnly: true
172          },
173
174          /**
175           * The label for the dropdown.
176           */
177          label: {
178            type: String
179          },
180
181          /**
182           * The placeholder for the dropdown.
183           */
184          placeholder: {
185            type: String
186          },
187
188          /**
189           * The error message to display when invalid.
190           */
191          errorMessage: {
192              type: String
193          },
194
195          /**
196           * True if the dropdown is open. Otherwise, false.
197           */
198          opened: {
199            type: Boolean,
200            notify: true,
201            value: false,
202            observer: '_openedChanged'
203          },
204
205          /**
206           * By default, the dropdown will constrain scrolling on the page
207           * to itself when opened.
208           * Set to true in order to prevent scroll from being constrained
209           * to the dropdown when it opens.
210           */
211          allowOutsideScroll: {
212            type: Boolean,
213            value: false
214          },
215
216          /**
217           * Set to true to disable the floating label. Bind this to the
218           * `<paper-input-container>`'s `noLabelFloat` property.
219           */
220          noLabelFloat: {
221              type: Boolean,
222              value: false,
223              reflectToAttribute: true
224          },
225
226          /**
227           * Set to true to always float the label. Bind this to the
228           * `<paper-input-container>`'s `alwaysFloatLabel` property.
229           */
230          alwaysFloatLabel: {
231            type: Boolean,
232            value: false
233          },
234
235          /**
236           * Set to true to disable animations when opening and closing the
237           * dropdown.
238           */
239          noAnimations: {
240            type: Boolean,
241            value: false
242          },
243
244          /**
245           * The orientation against which to align the menu dropdown
246           * horizontally relative to the dropdown trigger.
247           */
248          horizontalAlign: {
249            type: String,
250            value: 'right'
251          },
252
253          /**
254           * The orientation against which to align the menu dropdown
255           * vertically relative to the dropdown trigger.
256           */
257          verticalAlign: {
258            type: String,
259            value: 'top'
260          },
261
262          /**
263           * If true, the `horizontalAlign` and `verticalAlign` properties will
264           * be considered preferences instead of strict requirements when
265           * positioning the dropdown and may be changed if doing so reduces
266           * the area of the dropdown falling outside of `fitInto`.
267           */
268          dynamicAlign: {
269            type: Boolean
270          },
271
272          /**
273           * Whether focus should be restored to the dropdown when the menu closes.
274           */
275          restoreFocusOnClose: {
276            type: Boolean,
277            value: true
278          },
279        },
280
281        listeners: {
282          'tap': '_onTap'
283        },
284
285        keyBindings: {
286          'up down': 'open',
287          'esc': 'close'
288        },
289
290        hostAttributes: {
291          role: 'combobox',
292          'aria-autocomplete': 'none',
293          'aria-haspopup': 'true'
294        },
295
296        observers: [
297          '_selectedItemChanged(selectedItem)'
298        ],
299
300        attached: function() {
301          // NOTE(cdata): Due to timing, a preselected value in a `IronSelectable`
302          // child will cause an `iron-select` event to fire while the element is
303          // still in a `DocumentFragment`. This has the effect of causing
304          // handlers not to fire. So, we double check this value on attached:
305          var contentElement = this.contentElement;
306          if (contentElement && contentElement.selectedItem) {
307            this._setSelectedItem(contentElement.selectedItem);
308          }
309        },
310
311        /**
312         * The content element that is contained by the dropdown menu, if any.
313         */
314        get contentElement() {
315          return Polymer.dom(this.$.content).getDistributedNodes()[0];
316        },
317
318        /**
319         * Show the dropdown content.
320         */
321        open: function() {
322          this.$.menuButton.open();
323        },
324
325        /**
326         * Hide the dropdown content.
327         */
328        close: function() {
329          this.$.menuButton.close();
330        },
331
332        /**
333         * A handler that is called when `iron-select` is fired.
334         *
335         * @param {CustomEvent} event An `iron-select` event.
336         */
337        _onIronSelect: function(event) {
338          this._setSelectedItem(event.detail.item);
339        },
340
341        /**
342         * A handler that is called when `iron-deselect` is fired.
343         *
344         * @param {CustomEvent} event An `iron-deselect` event.
345         */
346        _onIronDeselect: function(event) {
347          this._setSelectedItem(null);
348        },
349
350        /**
351         * A handler that is called when the dropdown is tapped.
352         *
353         * @param {CustomEvent} event A tap event.
354         */
355        _onTap: function(event) {
356          if (Polymer.Gestures.findOriginalTarget(event) === this) {
357            this.open();
358          }
359        },
360
361        /**
362         * Compute the label for the dropdown given a selected item.
363         *
364         * @param {Element} selectedItem A selected Element item, with an
365         * optional `label` property.
366         */
367        _selectedItemChanged: function(selectedItem) {
368          var value = '';
369          if (!selectedItem) {
370            value = '';
371          } else {
372            value = selectedItem.label || selectedItem.getAttribute('label') || selectedItem.textContent.trim();
373          }
374
375          this._setValue(value);
376          this._setSelectedItemLabel(value);
377        },
378
379        /**
380         * Compute the vertical offset of the menu based on the value of
381         * `noLabelFloat`.
382         *
383         * @param {boolean} noLabelFloat True if the label should not float
384         * above the input, otherwise false.
385         */
386        _computeMenuVerticalOffset: function(noLabelFloat) {
387          // NOTE(cdata): These numbers are somewhat magical because they are
388          // derived from the metrics of elements internal to `paper-input`'s
389          // template. The metrics will change depending on whether or not the
390          // input has a floating label.
391          return noLabelFloat ? -4 : 8;
392        },
393
394        /**
395         * Returns false if the element is required and does not have a selection,
396         * and true otherwise.
397         * @param {*=} _value Ignored.
398         * @return {boolean} true if `required` is false, or if `required` is true
399         * and the element has a valid selection.
400         */
401        _getValidity: function(_value) {
402          return this.disabled || !this.required || (this.required && !!this.value);
403        },
404
405        _openedChanged: function() {
406          var openState = this.opened ? 'true' : 'false';
407          var e = this.contentElement;
408          if (e) {
409            e.setAttribute('aria-expanded', openState);
410          }
411        }
412      });
413    })();
414  </script>
415</dom-module>
416