<!-- 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 list of mutation * records that describe what changed. * * @event iron-items-changed */ properties: { /** * If you want to use the attribute value of an element for `selected` instead of the index, * set this to the name of the attribute. */ attrForSelected: { type: String, value: null }, /** * Gets or sets the selected element. The default is to use the index of the item. */ selected: { type: String, notify: true }, /** * Returns the currently selected item. */ 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 }, /** * The set of excluded elements where the key is the `localName` * of the element that will be ignored from the item list. * * @type {object} * @default {template: 1} */ _excludedLocalNames: { type: Object, value: function() { return { 'template': 1 }; } } }, observers: [ '_updateSelected(attrForSelected, selected)' ], created: function() { this._bindFilterItem = this._filterItem.bind(this); this._selection = new Polymer.IronSelection(this._applySelection.bind(this)); // TODO(cdata): When polymer/polymer#2535 lands, we do not need to do this // book keeping anymore: this.__listeningForActivate = false; }, attached: function() { this._observer = this._observeItems(this); this._contentObserver = this._observeContent(this); if (!this.selectedItem && this.selected) { this._updateSelected(this.attrForSelected,this.selected) } this._addListener(this.activateEvent); }, detached: function() { if (this._observer) { this._observer.disconnect(); } if (this._contentObserver) { this._contentObserver.disconnect(); } this._removeListener(this.activateEvent); }, /** * Returns an array of selectable items. * * @property items * @type Array */ get items() { var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*'); return Array.prototype.filter.call(nodes, this._bindFilterItem); }, /** * 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} 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); }, _addListener: function(eventName) { if (!this.isAttached || this.__listeningForActivate) { return; } this.__listeningForActivate = true; this.listen(this, eventName, '_activateHandler'); }, _removeListener: function(eventName) { this.unlisten(this, eventName, '_activateHandler'); this.__listeningForActivate = false; }, _activateEventChanged: function(eventName, old) { this._removeListener(old); this._addListener(eventName); }, _updateSelected: function() { this._selectSelected(this.selected); }, _selectSelected: function(selected) { this._selection.select(this._valueToItem(this.selected)); }, _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) { return item[this.attrForSelected] || 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 content changes under the given node. _observeContent: function(node) { var content = node.querySelector('content'); if (content && content.parentElement === node) { return this._observeItems(node.domHost); } }, // observe items change under the given node. _observeItems: function(node) { // TODO(cdata): Update this when we get distributed children changed. var observer = new MutationObserver(function(mutations) { // Let other interested parties know about the change so that // we don't have to recreate mutation observers everywher. this.fire('iron-items-changed', mutations, { bubbles: false, cancelable: false }); if (this.selected != null) { this._updateSelected(); } }.bind(this)); observer.observe(node, { childList: true, subtree: true }); return observer; }, _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>