Rolling forward https://codereview.chromium.org/1155683008/ which was
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / iron-selector / iron-selectable-extracted.js
bloba17f626d0200b9f5af8284d1d74e6c3cb0d9efb1
3   /** @polymerBehavior */
4   Polymer.IronSelectableBehavior = {
6     properties: {
8       /**
9        * If you want to use the attribute value of an element for `selected` instead of the index,
10        * set this to the name of the attribute.
11        *
12        * @attribute attrForSelected
13        * @type {string}
14        */
15       attrForSelected: {
16         type: String,
17         value: null
18       },
20       /**
21        * Gets or sets the selected element. The default is to use the index of the item.
22        *
23        * @attribute selected
24        * @type {string}
25        */
26       selected: {
27         type: String,
28         notify: true
29       },
31       /**
32        * Returns the currently selected item.
33        *
34        * @attribute selectedItem
35        * @type {Object}
36        */
37       selectedItem: {
38         type: Object,
39         readOnly: true,
40         notify: true
41       },
43       /**
44        * The event that fires from items when they are selected. Selectable
45        * will listen for this event from items and update the selection state.
46        * Set to empty string to listen to no events.
47        *
48        * @attribute activateEvent
49        * @type {string}
50        * @default 'tap'
51        */
52       activateEvent: {
53         type: String,
54         value: 'tap',
55         observer: '_activateEventChanged'
56       },
58       /**
59        * This is a CSS selector sting.  If this is set, only items that matches the CSS selector
60        * are selectable.
61        *
62        * @attribute selectable
63        * @type {string}
64        */
65       selectable: String,
67       /**
68        * The class to set on elements when selected.
69        *
70        * @attribute selectedClass
71        * @type {string}
72        */
73       selectedClass: {
74         type: String,
75         value: 'iron-selected'
76       },
78       /**
79        * The attribute to set on elements when selected.
80        *
81        * @attribute selectedAttribute
82        * @type {string}
83        */
84       selectedAttribute: {
85         type: String,
86         value: null
87       }
89     },
91     observers: [
92       '_updateSelected(attrForSelected, selected)'
93     ],
95     excludedLocalNames: {
96       'template': 1
97     },
99     created: function() {
100       this._bindFilterItem = this._filterItem.bind(this);
101       this._selection = new Polymer.IronSelection(this._applySelection.bind(this));
102     },
104     attached: function() {
105       this._observer = this._observeItems(this);
106       this._contentObserver = this._observeContent(this);
107     },
109     detached: function() {
110       if (this._observer) {
111         this._observer.disconnect();
112       }
113       if (this._contentObserver) {
114         this._contentObserver.disconnect();
115       }
116       this._removeListener(this.activateEvent);
117     },
119     /**
120      * Returns an array of selectable items.
121      *
122      * @property items
123      * @type Array
124      */
125     get items() {
126       var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
127       return Array.prototype.filter.call(nodes, this._bindFilterItem);
128     },
130     /**
131      * Returns the index of the given item.
132      *
133      * @method indexOf
134      * @param {Object} item
135      * @returns Returns the index of the item
136      */
137     indexOf: function(item) {
138       return this.items.indexOf(item);
139     },
141     /**
142      * Selects the given value.
143      *
144      * @method select
145      * @param {string} value the value to select.
146      */
147     select: function(value) {
148       this.selected = value;
149     },
151     /**
152      * Selects the previous item.
153      *
154      * @method selectPrevious
155      */
156     selectPrevious: function() {
157       var length = this.items.length;
158       var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % length;
159       this.selected = this._indexToValue(index);
160     },
162     /**
163      * Selects the next item.
164      *
165      * @method selectNext
166      */
167     selectNext: function() {
168       var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.length;
169       this.selected = this._indexToValue(index);
170     },
172     _addListener: function(eventName) {
173       this.listen(this, eventName, '_activateHandler');
174     },
176     _removeListener: function(eventName) {
177       // There is no unlisten yet...
178       // https://github.com/Polymer/polymer/issues/1639
179       //this.removeEventListener(eventName, this._bindActivateHandler);
180     },
182     _activateEventChanged: function(eventName, old) {
183       this._removeListener(old);
184       this._addListener(eventName);
185     },
187     _updateSelected: function() {
188       this._selectSelected(this.selected);
189     },
191     _selectSelected: function(selected) {
192       this._selection.select(this._valueToItem(this.selected));
193     },
195     _filterItem: function(node) {
196       return !this.excludedLocalNames[node.localName];
197     },
199     _valueToItem: function(value) {
200       return (value == null) ? null : this.items[this._valueToIndex(value)];
201     },
203     _valueToIndex: function(value) {
204       if (this.attrForSelected) {
205         for (var i = 0, item; item = this.items[i]; i++) {
206           if (this._valueForItem(item) == value) {
207             return i;
208           }
209         }
210       } else {
211         return Number(value);
212       }
213     },
215     _indexToValue: function(index) {
216       if (this.attrForSelected) {
217         var item = this.items[index];
218         if (item) {
219           return this._valueForItem(item);
220         }
221       } else {
222         return index;
223       }
224     },
226     _valueForItem: function(item) {
227       return item[this.attrForSelected] || item.getAttribute(this.attrForSelected);
228     },
230     _applySelection: function(item, isSelected) {
231       if (this.selectedClass) {
232         this.toggleClass(this.selectedClass, isSelected, item);
233       }
234       if (this.selectedAttribute) {
235         this.toggleAttribute(this.selectedAttribute, isSelected, item);
236       }
237       this._selectionChange();
238       this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item});
239     },
241     _selectionChange: function() {
242       this._setSelectedItem(this._selection.get());
243     },
245     // observe content changes under the given node.
246     _observeContent: function(node) {
247       var content = node.querySelector('content');
248       if (content && content.parentElement === node) {
249         return this._observeItems(node.domHost);
250       }
251     },
253     // observe items change under the given node.
254     _observeItems: function(node) {
255       var observer = new MutationObserver(function() {
256         if (this.selected != null) {
257           this._updateSelected();
258         }
259       }.bind(this));
260       observer.observe(node, {
261         childList: true,
262         subtree: true
263       });
264       return observer;
265     },
267     _activateHandler: function(e) {
268       // TODO: remove this when https://github.com/Polymer/polymer/issues/1639 is fixed so we
269       // can just remove the old event listener.
270       if (e.type !== this.activateEvent) {
271         return;
272       }
273       var t = e.target;
274       var items = this.items;
275       while (t && t != this) {
276         var i = items.indexOf(t);
277         if (i >= 0) {
278           var value = this._indexToValue(i);
279           this._itemActivate(value, t);
280           return;
281         }
282         t = t.parentNode;
283       }
284     },
286     _itemActivate: function(value, item) {
287       if (!this.fire('iron-activate',
288           {selected: value, item: item}, {cancelable: true}).defaultPrevented) {
289         this.select(value);
290       }
291     }
293   };