Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / iron-selector / iron-selectable-extracted.js
blob5c1031b49b8adb14464b6b93fd377de94471e03b
3   /** @polymerBehavior */
4   Polymer.IronSelectableBehavior = {
6       /**
7        *  Fired when iron-selector is activated (selected or deselected).
8        *  It is fired before the selected items are changed.
9        *  Cancel the event to abort selection.
10        *
11        * @event iron-activate
12        *
13        **/
14       /**
15        *  Fired when an item is selected
16        *
17        * @event iron-select
18        *
19        **/
20       /**
21        *  Fired when an item is deselected
22        *
23        * @event iron-deselect
24        *
25        **/
27     properties: {
29       /**
30        * If you want to use the attribute value of an element for `selected` instead of the index,
31        * set this to the name of the attribute.
32        *
33        * @attribute attrForSelected
34        * @type {string}
35        */
36       attrForSelected: {
37         type: String,
38         value: null
39       },
41       /**
42        * Gets or sets the selected element. The default is to use the index of the item.
43        *
44        * @attribute selected
45        * @type {string}
46        */
47       selected: {
48         type: String,
49         notify: true
50       },
52       /**
53        * Returns the currently selected item.
54        *
55        * @attribute selectedItem
56        * @type {Object}
57        */
58       selectedItem: {
59         type: Object,
60         readOnly: true,
61         notify: true
62       },
64       /**
65        * The event that fires from items when they are selected. Selectable
66        * will listen for this event from items and update the selection state.
67        * Set to empty string to listen to no events.
68        *
69        * @attribute activateEvent
70        * @type {string}
71        * @default 'tap'
72        */
73       activateEvent: {
74         type: String,
75         value: 'tap',
76         observer: '_activateEventChanged'
77       },
79       /**
80        * This is a CSS selector string.  If this is set, only items that match the CSS selector
81        * are selectable.
82        *
83        * @attribute selectable
84        * @type {string}
85        */
86       selectable: String,
88       /**
89        * The class to set on elements when selected.
90        *
91        * @attribute selectedClass
92        * @type {string}
93        */
94       selectedClass: {
95         type: String,
96         value: 'iron-selected'
97       },
99       /**
100        * The attribute to set on elements when selected.
101        *
102        * @attribute selectedAttribute
103        * @type {string}
104        */
105       selectedAttribute: {
106         type: String,
107         value: null
108       },
110       /**
111        * The set of excluded elements where the key is the `localName` 
112        * of the element that will be ignored from the item list.
113        *
114        * @type {object}
115        * @default {template: 1}
116        */
118       excludedLocalNames: {
119         type: Object,
120         value: function() {
121           return {
122             'template': 1
123           };
124         }
125       }
126     },
128     observers: [
129       '_updateSelected(attrForSelected, selected)'
130     ],
132     created: function() {
133       this._bindFilterItem = this._filterItem.bind(this);
134       this._selection = new Polymer.IronSelection(this._applySelection.bind(this));
135     },
137     attached: function() {
138       this._observer = this._observeItems(this);
139       this._contentObserver = this._observeContent(this);
140     },
142     detached: function() {
143       if (this._observer) {
144         this._observer.disconnect();
145       }
146       if (this._contentObserver) {
147         this._contentObserver.disconnect();
148       }
149       this._removeListener(this.activateEvent);
150     },
152     /**
153      * Returns an array of selectable items.
154      *
155      * @property items
156      * @type Array
157      */
158     get items() {
159       var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
160       return Array.prototype.filter.call(nodes, this._bindFilterItem);
161     },
163     /**
164      * Returns the index of the given item.
165      *
166      * @method indexOf
167      * @param {Object} item
168      * @returns Returns the index of the item
169      */
170     indexOf: function(item) {
171       return this.items.indexOf(item);
172     },
174     /**
175      * Selects the given value.
176      *
177      * @method select
178      * @param {string} value the value to select.
179      */
180     select: function(value) {
181       this.selected = value;
182     },
184     /**
185      * Selects the previous item.
186      *
187      * @method selectPrevious
188      */
189     selectPrevious: function() {
190       var length = this.items.length;
191       var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % length;
192       this.selected = this._indexToValue(index);
193     },
195     /**
196      * Selects the next item.
197      *
198      * @method selectNext
199      */
200     selectNext: function() {
201       var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.length;
202       this.selected = this._indexToValue(index);
203     },
205     _addListener: function(eventName) {
206       this.listen(this, eventName, '_activateHandler');
207     },
209     _removeListener: function(eventName) {
210       this.unlisten(this, eventName, '_activateHandler');
211     },
213     _activateEventChanged: function(eventName, old) {
214       this._removeListener(old);
215       this._addListener(eventName);
216     },
218     _updateSelected: function() {
219       this._selectSelected(this.selected);
220     },
222     _selectSelected: function(selected) {
223       this._selection.select(this._valueToItem(this.selected));
224     },
226     _filterItem: function(node) {
227       return !this.excludedLocalNames[node.localName];
228     },
230     _valueToItem: function(value) {
231       return (value == null) ? null : this.items[this._valueToIndex(value)];
232     },
234     _valueToIndex: function(value) {
235       if (this.attrForSelected) {
236         for (var i = 0, item; item = this.items[i]; i++) {
237           if (this._valueForItem(item) == value) {
238             return i;
239           }
240         }
241       } else {
242         return Number(value);
243       }
244     },
246     _indexToValue: function(index) {
247       if (this.attrForSelected) {
248         var item = this.items[index];
249         if (item) {
250           return this._valueForItem(item);
251         }
252       } else {
253         return index;
254       }
255     },
257     _valueForItem: function(item) {
258       return item[this.attrForSelected] || item.getAttribute(this.attrForSelected);
259     },
261     _applySelection: function(item, isSelected) {
262       if (this.selectedClass) {
263         this.toggleClass(this.selectedClass, isSelected, item);
264       }
265       if (this.selectedAttribute) {
266         this.toggleAttribute(this.selectedAttribute, isSelected, item);
267       }
268       this._selectionChange();
269       this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item});
270     },
272     _selectionChange: function() {
273       this._setSelectedItem(this._selection.get());
274     },
276     // observe content changes under the given node.
277     _observeContent: function(node) {
278       var content = node.querySelector('content');
279       if (content && content.parentElement === node) {
280         return this._observeItems(node.domHost);
281       }
282     },
284     // observe items change under the given node.
285     _observeItems: function(node) {
286       var observer = new MutationObserver(function() {
287         if (this.selected != null) {
288           this._updateSelected();
289         }
290       }.bind(this));
291       observer.observe(node, {
292         childList: true,
293         subtree: true
294       });
295       return observer;
296     },
298     _activateHandler: function(e) {
299       var t = e.target;
300       var items = this.items;
301       while (t && t != this) {
302         var i = items.indexOf(t);
303         if (i >= 0) {
304           var value = this._indexToValue(i);
305           this._itemActivate(value, t);
306           return;
307         }
308         t = t.parentNode;
309       }
310     },
312     _itemActivate: function(value, item) {
313       if (!this.fire('iron-activate',
314           {selected: value, item: item}, {cancelable: true}).defaultPrevented) {
315         this.select(value);
316       }
317     }
319   };