| <!-- |
| @license |
| Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
| This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
| The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
| Code distributed by Google as part of the polymer project is also |
| subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
| --> |
| |
| <link rel="import" href="../polymer/polymer.html"> |
| <link rel="import" href="iron-selection.html"> |
| |
| <script> |
| |
| /** @polymerBehavior */ |
| Polymer.IronSelectableBehavior = { |
| |
| /** |
| * Fired when iron-selector is activated (selected or deselected). |
| * It is fired before the selected items are changed. |
| * Cancel the event to abort selection. |
| * |
| * @event iron-activate |
| */ |
| |
| /** |
| * Fired when an item is selected |
| * |
| * @event iron-select |
| */ |
| |
| /** |
| * Fired when an item is deselected |
| * |
| * @event iron-deselect |
| */ |
| |
| /** |
| * Fired when the list of selectable items changes (e.g., items are |
| * added or removed). The detail of the event is a mutation record that |
| * describes what changed. |
| * |
| * @event iron-items-changed |
| */ |
| |
| properties: { |
| |
| /** |
| * If you want to use an attribute value or property of an element for |
| * `selected` instead of the index, set this to the name of the attribute |
| * or property. Hyphenated values are converted to camel case when used to |
| * look up the property of a selectable element. Camel cased values are |
| * *not* converted to hyphenated values for attribute lookup. It's |
| * recommended that you provide the hyphenated form of the name so that |
| * selection works in both cases. (Use `attr-or-property-name` instead of |
| * `attrOrPropertyName`.) |
| */ |
| attrForSelected: { |
| type: String, |
| value: null |
| }, |
| |
| /** |
| * Gets or sets the selected element. The default is to use the index of the item. |
| * @type {string|number} |
| */ |
| selected: { |
| type: String, |
| notify: true |
| }, |
| |
| /** |
| * Returns the currently selected item. |
| * |
| * @type {?Object} |
| */ |
| selectedItem: { |
| type: Object, |
| readOnly: true, |
| notify: true |
| }, |
| |
| /** |
| * The event that fires from items when they are selected. Selectable |
| * will listen for this event from items and update the selection state. |
| * Set to empty string to listen to no events. |
| */ |
| activateEvent: { |
| type: String, |
| value: 'tap', |
| observer: '_activateEventChanged' |
| }, |
| |
| /** |
| * This is a CSS selector string. If this is set, only items that match the CSS selector |
| * are selectable. |
| */ |
| selectable: String, |
| |
| /** |
| * The class to set on elements when selected. |
| */ |
| selectedClass: { |
| type: String, |
| value: 'iron-selected' |
| }, |
| |
| /** |
| * The attribute to set on elements when selected. |
| */ |
| selectedAttribute: { |
| type: String, |
| value: null |
| }, |
| |
| /** |
| * Default fallback if the selection based on selected with `attrForSelected` |
| * is not found. |
| */ |
| fallbackSelection: { |
| type: String, |
| value: null |
| }, |
| |
| /** |
| * The list of items from which a selection can be made. |
| */ |
| items: { |
| type: Array, |
| readOnly: true, |
| notify: true, |
| value: function() { |
| return []; |
| } |
| }, |
| |
| /** |
| * The set of excluded elements where the key is the `localName` |
| * of the element that will be ignored from the item list. |
| * |
| * @default {template: 1} |
| */ |
| _excludedLocalNames: { |
| type: Object, |
| value: function() { |
| return { |
| 'template': 1 |
| }; |
| } |
| } |
| }, |
| |
| observers: [ |
| '_updateAttrForSelected(attrForSelected)', |
| '_updateSelected(selected)', |
| '_checkFallback(fallbackSelection)' |
| ], |
| |
| created: function() { |
| this._bindFilterItem = this._filterItem.bind(this); |
| this._selection = new Polymer.IronSelection(this._applySelection.bind(this)); |
| }, |
| |
| attached: function() { |
| this._observer = this._observeItems(this); |
| this._updateItems(); |
| if (!this._shouldUpdateSelection) { |
| this._updateSelected(); |
| } |
| this._addListener(this.activateEvent); |
| }, |
| |
| detached: function() { |
| if (this._observer) { |
| Polymer.dom(this).unobserveNodes(this._observer); |
| } |
| this._removeListener(this.activateEvent); |
| }, |
| |
| /** |
| * Returns the index of the given item. |
| * |
| * @method indexOf |
| * @param {Object} item |
| * @returns Returns the index of the item |
| */ |
| indexOf: function(item) { |
| return this.items.indexOf(item); |
| }, |
| |
| /** |
| * Selects the given value. |
| * |
| * @method select |
| * @param {string|number} value the value to select. |
| */ |
| select: function(value) { |
| this.selected = value; |
| }, |
| |
| /** |
| * Selects the previous item. |
| * |
| * @method selectPrevious |
| */ |
| selectPrevious: function() { |
| var length = this.items.length; |
| var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % length; |
| this.selected = this._indexToValue(index); |
| }, |
| |
| /** |
| * Selects the next item. |
| * |
| * @method selectNext |
| */ |
| selectNext: function() { |
| var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.length; |
| this.selected = this._indexToValue(index); |
| }, |
| |
| /** |
| * Selects the item at the given index. |
| * |
| * @method selectIndex |
| */ |
| selectIndex: function(index) { |
| this.select(this._indexToValue(index)); |
| }, |
| |
| /** |
| * Force a synchronous update of the `items` property. |
| * |
| * NOTE: Consider listening for the `iron-items-changed` event to respond to |
| * updates to the set of selectable items after updates to the DOM list and |
| * selection state have been made. |
| * |
| * WARNING: If you are using this method, you should probably consider an |
| * alternate approach. Synchronously querying for items is potentially |
| * slow for many use cases. The `items` property will update asynchronously |
| * on its own to reflect selectable items in the DOM. |
| */ |
| forceSynchronousItemUpdate: function() { |
| this._updateItems(); |
| }, |
| |
| get _shouldUpdateSelection() { |
| return this.selected != null; |
| }, |
| |
| _checkFallback: function() { |
| if (this._shouldUpdateSelection) { |
| this._updateSelected(); |
| } |
| }, |
| |
| _addListener: function(eventName) { |
| this.listen(this, eventName, '_activateHandler'); |
| }, |
| |
| _removeListener: function(eventName) { |
| this.unlisten(this, eventName, '_activateHandler'); |
| }, |
| |
| _activateEventChanged: function(eventName, old) { |
| this._removeListener(old); |
| this._addListener(eventName); |
| }, |
| |
| _updateItems: function() { |
| var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*'); |
| nodes = Array.prototype.filter.call(nodes, this._bindFilterItem); |
| this._setItems(nodes); |
| }, |
| |
| _updateAttrForSelected: function() { |
| if (this._shouldUpdateSelection) { |
| this.selected = this._indexToValue(this.indexOf(this.selectedItem)); |
| } |
| }, |
| |
| _updateSelected: function() { |
| this._selectSelected(this.selected); |
| }, |
| |
| _selectSelected: function(selected) { |
| this._selection.select(this._valueToItem(this.selected)); |
| // Check for items, since this array is populated only when attached |
| // Since Number(0) is falsy, explicitly check for undefined |
| if (this.fallbackSelection && this.items.length && (this._selection.get() === undefined)) { |
| this.selected = this.fallbackSelection; |
| } |
| }, |
| |
| _filterItem: function(node) { |
| return !this._excludedLocalNames[node.localName]; |
| }, |
| |
| _valueToItem: function(value) { |
| return (value == null) ? null : this.items[this._valueToIndex(value)]; |
| }, |
| |
| _valueToIndex: function(value) { |
| if (this.attrForSelected) { |
| for (var i = 0, item; item = this.items[i]; i++) { |
| if (this._valueForItem(item) == value) { |
| return i; |
| } |
| } |
| } else { |
| return Number(value); |
| } |
| }, |
| |
| _indexToValue: function(index) { |
| if (this.attrForSelected) { |
| var item = this.items[index]; |
| if (item) { |
| return this._valueForItem(item); |
| } |
| } else { |
| return index; |
| } |
| }, |
| |
| _valueForItem: function(item) { |
| var propValue = item[Polymer.CaseMap.dashToCamelCase(this.attrForSelected)]; |
| return propValue != undefined ? propValue : item.getAttribute(this.attrForSelected); |
| }, |
| |
| _applySelection: function(item, isSelected) { |
| if (this.selectedClass) { |
| this.toggleClass(this.selectedClass, isSelected, item); |
| } |
| if (this.selectedAttribute) { |
| this.toggleAttribute(this.selectedAttribute, isSelected, item); |
| } |
| this._selectionChange(); |
| this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item}); |
| }, |
| |
| _selectionChange: function() { |
| this._setSelectedItem(this._selection.get()); |
| }, |
| |
| // observe items change under the given node. |
| _observeItems: function(node) { |
| return Polymer.dom(node).observeNodes(function(mutation) { |
| this._updateItems(); |
| |
| if (this._shouldUpdateSelection) { |
| this._updateSelected(); |
| } |
| |
| // Let other interested parties know about the change so that |
| // we don't have to recreate mutation observers everywhere. |
| this.fire('iron-items-changed', mutation, { |
| bubbles: false, |
| cancelable: false |
| }); |
| }); |
| }, |
| |
| _activateHandler: function(e) { |
| var t = e.target; |
| var items = this.items; |
| while (t && t != this) { |
| var i = items.indexOf(t); |
| if (i >= 0) { |
| var value = this._indexToValue(i); |
| this._itemActivate(value, t); |
| return; |
| } |
| t = t.parentNode; |
| } |
| }, |
| |
| _itemActivate: function(value, item) { |
| if (!this.fire('iron-activate', |
| {selected: value, item: item}, {cancelable: true}).defaultPrevented) { |
| this.select(value); |
| } |
| } |
| |
| }; |
| |
| </script> |