2015-06-19 09:36:51 -07:00
|
|
|
<!--
|
2016-04-08 20:08:50 -07:00
|
|
|
@license
|
2015-06-19 09:36:51 -07:00
|
|
|
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 = {
|
|
|
|
|
2015-09-14 18:17:19 -07:00
|
|
|
/**
|
2015-09-22 21:00:30 -07:00
|
|
|
* Fired when iron-selector is activated (selected or deselected).
|
|
|
|
* It is fired before the selected items are changed.
|
|
|
|
* Cancel the event to abort selection.
|
2015-09-14 18:17:19 -07:00
|
|
|
*
|
|
|
|
* @event iron-activate
|
2015-09-22 21:00:30 -07:00
|
|
|
*/
|
|
|
|
|
2015-09-14 18:17:19 -07:00
|
|
|
/**
|
2015-09-22 21:00:30 -07:00
|
|
|
* Fired when an item is selected
|
2015-09-14 18:17:19 -07:00
|
|
|
*
|
|
|
|
* @event iron-select
|
2015-09-22 21:00:30 -07:00
|
|
|
*/
|
|
|
|
|
2015-09-14 18:17:19 -07:00
|
|
|
/**
|
2015-09-22 21:00:30 -07:00
|
|
|
* Fired when an item is deselected
|
2015-09-14 18:17:19 -07:00
|
|
|
*
|
|
|
|
* @event iron-deselect
|
2015-09-22 21:00:30 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired when the list of selectable items changes (e.g., items are
|
2016-05-17 10:44:17 -07:00
|
|
|
* added or removed). The detail of the event is a mutation record that
|
|
|
|
* describes what changed.
|
2015-09-14 18:17:19 -07:00
|
|
|
*
|
2015-09-22 21:00:30 -07:00
|
|
|
* @event iron-items-changed
|
|
|
|
*/
|
2015-09-14 18:17:19 -07:00
|
|
|
|
2015-06-19 09:36:51 -07:00
|
|
|
properties: {
|
|
|
|
|
|
|
|
/**
|
2016-03-09 17:05:29 -07:00
|
|
|
* 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`.)
|
2015-06-19 09:36:51 -07:00
|
|
|
*/
|
|
|
|
attrForSelected: {
|
|
|
|
type: String,
|
|
|
|
value: null
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets or sets the selected element. The default is to use the index of the item.
|
2016-01-29 19:43:11 -07:00
|
|
|
* @type {string|number}
|
2015-06-19 09:36:51 -07:00
|
|
|
*/
|
|
|
|
selected: {
|
|
|
|
type: String,
|
|
|
|
notify: true
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the currently selected item.
|
2015-12-14 08:43:03 -07:00
|
|
|
*
|
|
|
|
* @type {?Object}
|
2015-06-19 09:36:51 -07:00
|
|
|
*/
|
|
|
|
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'
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-09-14 18:17:19 -07:00
|
|
|
* This is a CSS selector string. If this is set, only items that match the CSS selector
|
2015-06-19 09:36:51 -07:00
|
|
|
* 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
|
2015-09-14 18:17:19 -07:00
|
|
|
},
|
|
|
|
|
2016-04-08 20:08:50 -07:00
|
|
|
/**
|
|
|
|
* Default fallback if the selection based on selected with `attrForSelected`
|
|
|
|
* is not found.
|
|
|
|
*/
|
|
|
|
fallbackSelection: {
|
|
|
|
type: String,
|
|
|
|
value: null
|
|
|
|
},
|
|
|
|
|
2015-12-14 08:43:03 -07:00
|
|
|
/**
|
|
|
|
* The list of items from which a selection can be made.
|
|
|
|
*/
|
|
|
|
items: {
|
|
|
|
type: Array,
|
|
|
|
readOnly: true,
|
2016-02-01 14:01:20 -07:00
|
|
|
notify: true,
|
2015-12-14 08:43:03 -07:00
|
|
|
value: function() {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-09-14 18:17:19 -07:00
|
|
|
/**
|
2015-09-29 21:10:14 -07:00
|
|
|
* The set of excluded elements where the key is the `localName`
|
2015-09-14 18:17:19 -07:00
|
|
|
* of the element that will be ignored from the item list.
|
|
|
|
*
|
|
|
|
* @default {template: 1}
|
|
|
|
*/
|
2015-10-05 19:50:20 -07:00
|
|
|
_excludedLocalNames: {
|
2015-09-14 18:17:19 -07:00
|
|
|
type: Object,
|
|
|
|
value: function() {
|
|
|
|
return {
|
|
|
|
'template': 1
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2015-06-19 09:36:51 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
observers: [
|
2016-02-18 11:20:10 -07:00
|
|
|
'_updateAttrForSelected(attrForSelected)',
|
2016-04-08 20:08:50 -07:00
|
|
|
'_updateSelected(selected)',
|
|
|
|
'_checkFallback(fallbackSelection)'
|
2015-06-19 09:36:51 -07:00
|
|
|
],
|
|
|
|
|
|
|
|
created: function() {
|
|
|
|
this._bindFilterItem = this._filterItem.bind(this);
|
|
|
|
this._selection = new Polymer.IronSelection(this._applySelection.bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
attached: function() {
|
|
|
|
this._observer = this._observeItems(this);
|
2015-12-14 08:43:03 -07:00
|
|
|
this._updateItems();
|
|
|
|
if (!this._shouldUpdateSelection) {
|
2016-02-18 11:20:10 -07:00
|
|
|
this._updateSelected();
|
2015-09-29 21:10:14 -07:00
|
|
|
}
|
2015-10-07 14:42:29 -07:00
|
|
|
this._addListener(this.activateEvent);
|
2015-06-19 09:36:51 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
detached: function() {
|
|
|
|
if (this._observer) {
|
2015-12-14 08:43:03 -07:00
|
|
|
Polymer.dom(this).unobserveNodes(this._observer);
|
2015-06-19 09:36:51 -07:00
|
|
|
}
|
|
|
|
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
|
2016-01-29 19:43:11 -07:00
|
|
|
* @param {string|number} value the value to select.
|
2015-06-19 09:36:51 -07:00
|
|
|
*/
|
|
|
|
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);
|
|
|
|
},
|
|
|
|
|
2016-04-29 21:02:36 -07:00
|
|
|
/**
|
|
|
|
* Selects the item at the given index.
|
|
|
|
*
|
|
|
|
* @method selectIndex
|
|
|
|
*/
|
|
|
|
selectIndex: function(index) {
|
|
|
|
this.select(this._indexToValue(index));
|
|
|
|
},
|
|
|
|
|
2016-02-01 14:01:20 -07:00
|
|
|
/**
|
|
|
|
* 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();
|
|
|
|
},
|
|
|
|
|
2015-12-14 08:43:03 -07:00
|
|
|
get _shouldUpdateSelection() {
|
|
|
|
return this.selected != null;
|
|
|
|
},
|
2015-10-07 14:42:29 -07:00
|
|
|
|
2016-04-08 20:08:50 -07:00
|
|
|
_checkFallback: function() {
|
|
|
|
if (this._shouldUpdateSelection) {
|
|
|
|
this._updateSelected();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-12-14 08:43:03 -07:00
|
|
|
_addListener: function(eventName) {
|
2015-06-19 09:36:51 -07:00
|
|
|
this.listen(this, eventName, '_activateHandler');
|
|
|
|
},
|
|
|
|
|
|
|
|
_removeListener: function(eventName) {
|
2015-09-14 18:17:19 -07:00
|
|
|
this.unlisten(this, eventName, '_activateHandler');
|
2015-06-19 09:36:51 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
_activateEventChanged: function(eventName, old) {
|
|
|
|
this._removeListener(old);
|
|
|
|
this._addListener(eventName);
|
|
|
|
},
|
|
|
|
|
2015-12-14 08:43:03 -07:00
|
|
|
_updateItems: function() {
|
|
|
|
var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
|
|
|
|
nodes = Array.prototype.filter.call(nodes, this._bindFilterItem);
|
|
|
|
this._setItems(nodes);
|
|
|
|
},
|
|
|
|
|
2016-02-18 11:20:10 -07:00
|
|
|
_updateAttrForSelected: function() {
|
|
|
|
if (this._shouldUpdateSelection) {
|
2016-04-08 20:08:50 -07:00
|
|
|
this.selected = this._indexToValue(this.indexOf(this.selectedItem));
|
2016-02-18 11:20:10 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-06-19 09:36:51 -07:00
|
|
|
_updateSelected: function() {
|
|
|
|
this._selectSelected(this.selected);
|
|
|
|
},
|
|
|
|
|
|
|
|
_selectSelected: function(selected) {
|
|
|
|
this._selection.select(this._valueToItem(this.selected));
|
2016-04-08 20:08:50 -07:00
|
|
|
// 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;
|
|
|
|
}
|
2015-06-19 09:36:51 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
_filterItem: function(node) {
|
2015-10-05 19:50:20 -07:00
|
|
|
return !this._excludedLocalNames[node.localName];
|
2015-06-19 09:36:51 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
_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) {
|
2016-03-09 17:05:29 -07:00
|
|
|
var propValue = item[Polymer.CaseMap.dashToCamelCase(this.attrForSelected)];
|
2016-02-04 20:44:29 -07:00
|
|
|
return propValue != undefined ? propValue : item.getAttribute(this.attrForSelected);
|
2015-06-19 09:36:51 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
_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) {
|
2016-05-17 10:44:17 -07:00
|
|
|
return Polymer.dom(node).observeNodes(function(mutation) {
|
2016-02-01 14:01:20 -07:00
|
|
|
this._updateItems();
|
|
|
|
|
|
|
|
if (this._shouldUpdateSelection) {
|
|
|
|
this._updateSelected();
|
|
|
|
}
|
|
|
|
|
2015-09-22 21:00:30 -07:00
|
|
|
// Let other interested parties know about the change so that
|
2016-02-18 11:20:10 -07:00
|
|
|
// we don't have to recreate mutation observers everywhere.
|
2016-05-17 10:44:17 -07:00
|
|
|
this.fire('iron-items-changed', mutation, {
|
2015-09-22 21:00:30 -07:00
|
|
|
bubbles: false,
|
|
|
|
cancelable: false
|
|
|
|
});
|
2015-06-19 09:36:51 -07:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_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>
|