Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / polymer / polymer-selector / polymer-selector.html
1 <!--
2 Copyright 2013 The Polymer Authors. All rights reserved.
3 Use of this source code is governed by a BSD-style
4 license that can be found in the LICENSE file.
5 -->
6 <!--
7 /**
8  * @module Polymer Elements
9  */
10 /**
11  * polymer-selector is used to manage a list of elements that can be selected.
12  * The attribute "selected" indicates which item element is being selected.
13  * The attribute "multi" indicates if multiple items can be selected at once.
14  * Tapping on the item element would fire "polymer-activate" event. Use
15  * "polymer-select" event to listen for selection changes.
16  *
17  * Example:
18  *
19  *     <polymer-selector selected="0">
20  *       <div>Item 1</div>
21  *       <div>Item 2</div>
22  *       <div>Item 3</div>
23  *     </polymer-selector>
24  *
25  * polymer-selector is not styled.  So one needs to use "polymer-selected" CSS
26  * class to style the selected element.
27  * 
28  *     <style>
29  *       .item.polymer-selected {
30  *         background: #eee;
31  *       }
32  *     </style>
33  *     ...
34  *     <polymer-selector>
35  *       <div class="item">Item 1</div>
36  *       <div class="item">Item 2</div>
37  *       <div class="item">Item 3</div>
38  *     </polymer-selector>
39  *
40  * @class polymer-selector
41  */
42 /**
43  * Fired when an item's selection state is changed. This event is fired both
44  * when an item is selected or deselected. The `isSelected` detail property
45  * contains the selection state.
46  * 
47  * @event polymer-select
48  * @param {Object} detail
49  *   @param {boolean} detail.isSelected true for selection and false for deselection
50  *   @param {Object} detail.item the item element
51  */
52 /**
53  * Fired when an item element is tapped.
54  * 
55  * @event polymer-activate
56  * @param {Object} detail
57  *   @param {Object} detail.item the item element
58  */
59 -->
60 <link rel="import" href="../polymer/polymer.html">
61 <link rel="import" href="../polymer-selection/polymer-selection.html">
62
63 <polymer-element name="polymer-selector"
64     attributes="selected multi valueattr selectedClass selectedProperty selectedItem selectedModel selectedIndex notap target itemsSelector activateEvent">
65   <template>
66     <polymer-selection id="selection" multi="{{multi}}" on-polymer-select="{{selectionSelect}}"></polymer-selection>
67     <content id="items" select="*"></content>
68   </template>
69   <script>
70     Polymer('polymer-selector', {
71       /**
72        * Gets or sets the selected element.  Default to use the index
73        * of the item element.
74        *
75        * If you want a specific attribute value of the element to be
76        * used instead of index, set "valueattr" to that attribute name.
77        *
78        * Example:
79        *
80        *     <polymer-selector valueattr="label" selected="foo">
81        *       <div label="foo"></div>
82        *       <div label="bar"></div>
83        *       <div label="zot"></div>
84        *     </polymer-selector>
85        *
86        * In multi-selection this should be an array of values.
87        *
88        * Example:
89        *
90        *     <polymer-selector id="selector" valueattr="label" multi>
91        *       <div label="foo"></div>
92        *       <div label="bar"></div>
93        *       <div label="zot"></div>
94        *     </polymer-selector>
95        *
96        *     this.$.selector.selected = ['foo', 'zot'];
97        *
98        * @attribute selected
99        * @type Object
100        * @default null
101        */
102       selected: null,
103       /**
104        * If true, multiple selections are allowed.
105        *
106        * @attribute multi
107        * @type boolean
108        * @default false
109        */
110       multi: false,
111       /**
112        * Specifies the attribute to be used for "selected" attribute.
113        *
114        * @attribute valueattr
115        * @type string
116        * @default 'name'
117        */
118       valueattr: 'name',
119       /**
120        * Specifies the CSS class to be used to add to the selected element.
121        * 
122        * @attribute selectedClass
123        * @type string
124        * @default 'polymer-selected'
125        */
126       selectedClass: 'polymer-selected',
127       /**
128        * Specifies the property to be used to set on the selected element
129        * to indicate its active state.
130        *
131        * @attribute selectedProperty
132        * @type string
133        * @default 'active'
134        */
135       selectedProperty: 'active',
136       /**
137        * Returns the currently selected element. In multi-selection this returns
138        * an array of selected elements.
139        * 
140        * @attribute selectedItem
141        * @type Object
142        * @default null
143        */
144       selectedItem: null,
145       /**
146        * In single selection, this returns the model associated with the
147        * selected element.
148        * 
149        * @attribute selectedModel
150        * @type Object
151        * @default null
152        */
153       selectedModel: null,
154       /**
155        * In single selection, this returns the selected index.
156        *
157        * @attribute selectedIndex
158        * @type number
159        * @default -1
160        */
161       selectedIndex: -1,
162       /**
163        * The target element that contains items.  If this is not set 
164        * polymer-selector is the container.
165        * 
166        * @attribute target
167        * @type Object
168        * @default null
169        */
170       target: null,
171       /**
172        * This can be used to query nodes from the target node to be used for 
173        * selection items.  Note this only works if the 'target' property is set.
174        *
175        * Example:
176        *
177        *     <polymer-selector target="{{$.myForm}}" itemsSelector="input[type=radio]"></polymer-selector>
178        *     <form id="myForm">
179        *       <label><input type="radio" name="color" value="red"> Red</label> <br>
180        *       <label><input type="radio" name="color" value="green"> Green</label> <br>
181        *       <label><input type="radio" name="color" value="blue"> Blue</label> <br>
182        *       <p>color = {{color}}</p>
183        *     </form>
184        * 
185        * @attribute itemSelector
186        * @type string
187        * @default ''
188        */
189       itemsSelector: '',
190       /**
191        * The event that would be fired from the item element to indicate
192        * it is being selected.
193        *
194        * @attribute activateEvent
195        * @type string
196        * @default 'tap'
197        */
198       activateEvent: 'tap',
199       notap: false,
200       ready: function() {
201         this.activateListener = this.activateHandler.bind(this);
202         this.observer = new MutationObserver(this.updateSelected.bind(this));
203         if (!this.target) {
204           this.target = this;
205         }
206       },
207       get items() {
208         var nodes = this.target !== this ? (this.itemsSelector ? 
209             this.target.querySelectorAll(this.itemsSelector) : 
210                 this.target.children) : this.$.items.getDistributedNodes();
211         return Array.prototype.filter.call(nodes || [], function(n) {
212           return n && n.localName !== 'template';
213         });
214       },
215       targetChanged: function(old) {
216         if (old) {
217           this.removeListener(old);
218           this.observer.disconnect();
219         }
220         if (this.target) {
221           this.addListener(this.target);
222           this.observer.observe(this.target, {childList: true});
223         }
224       },
225       addListener: function(node) {
226         node.addEventListener(this.activateEvent, this.activateListener);
227       },
228       removeListener: function(node) {
229         node.removeEventListener(this.activateEvent, this.activateListener);
230       },
231       get selection() {
232         return this.$.selection.getSelection();
233       },
234       selectedChanged: function() {
235         this.updateSelected();
236       },
237       updateSelected: function() {
238         this.validateSelected();
239         if (this.multi) {
240           this.clearSelection();
241           this.selected && this.selected.forEach(function(s) {
242             this.valueToSelection(s);
243           }, this);
244         } else {
245           this.valueToSelection(this.selected);
246         }
247       },
248       validateSelected: function() {
249         // convert to an array for multi-selection
250         if (this.multi && !Array.isArray(this.selected) && 
251             this.selected !== null && this.selected !== undefined) {
252           this.selected = [this.selected];
253         }
254       },
255       clearSelection: function() {
256         if (this.multi) {
257           this.selection.slice().forEach(function(s) {
258             this.$.selection.setItemSelected(s, false);
259           }, this);
260         } else {
261           this.$.selection.setItemSelected(this.selection, false);
262         }
263         this.selectedItem = null;
264         this.$.selection.clear();
265       },
266       valueToSelection: function(value) {
267         var item = (value === null || value === undefined) ? 
268             null : this.items[this.valueToIndex(value)];
269         this.$.selection.select(item);
270       },
271       updateSelectedItem: function() {
272         this.selectedItem = this.selection;
273       },
274       selectedItemChanged: function() {
275         if (this.selectedItem) {
276           var t = this.selectedItem.templateInstance;
277           this.selectedModel = t ? t.model : undefined;
278         } else {
279           this.selectedModel = null;
280         }
281         this.selectedIndex = this.selectedItem ? 
282             parseInt(this.valueToIndex(this.selected)) : -1;
283       },
284       valueToIndex: function(value) {
285         // find an item with value == value and return it's index
286         for (var i=0, items=this.items, c; (c=items[i]); i++) {
287           if (this.valueForNode(c) == value) {
288             return i;
289           }
290         }
291         // if no item found, the value itself is probably the index
292         return value;
293       },
294       valueForNode: function(node) {
295         return node[this.valueattr] || node.getAttribute(this.valueattr);
296       },
297       // events fired from <polymer-selection> object
298       selectionSelect: function(e, detail) {
299         this.updateSelectedItem();
300         if (detail.item) {
301           this.applySelection(detail.item, detail.isSelected);
302         }
303       },
304       applySelection: function(item, isSelected) {
305         if (this.selectedClass) {
306           item.classList.toggle(this.selectedClass, isSelected);
307         }
308         if (this.selectedProperty) {
309           item[this.selectedProperty] = isSelected;
310         }
311       },
312       // event fired from host
313       activateHandler: function(e) {
314         if (!this.notap) {
315           var i = this.findDistributedTarget(e.target, this.items);
316           if (i >= 0) {
317             var item = this.items[i];
318             var s = this.valueForNode(item) || i;
319             if (this.multi) {
320               if (this.selected) {
321                 this.addRemoveSelected(s);
322               } else {
323                 this.selected = [s];
324               }
325             } else {
326               this.selected = s;
327             }
328             this.asyncFire('polymer-activate', {item: item});
329           }
330         }
331       },
332       addRemoveSelected: function(value) {
333         var i = this.selected.indexOf(value);
334         if (i >= 0) {
335           this.selected.splice(i, 1);
336         } else {
337           this.selected.push(value);
338         }
339         this.valueToSelection(value);
340       },
341       findDistributedTarget: function(target, nodes) {
342         // find first ancestor of target (including itself) that
343         // is in nodes, if any
344         while (target && target != this) {
345           var i = Array.prototype.indexOf.call(nodes, target);
346           if (i >= 0) {
347             return i;
348           }
349           target = target.parentNode;
350         }
351       }
352     });
353   </script>
354 </polymer-element>