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.
8 * @module Polymer Elements
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.
19 * <polymer-selector selected="0">
25 * polymer-selector is not styled. So one needs to use "polymer-selected" CSS
26 * class to style the selected element.
29 * .item.polymer-selected {
35 * <div class="item">Item 1</div>
36 * <div class="item">Item 2</div>
37 * <div class="item">Item 3</div>
40 * @class polymer-selector
44 * Fired when an item's selection state is changed. This event is fired both
45 * when an item is selected or deselected. The `isSelected` detail property
46 * contains the selection state.
48 * @event polymer-select
49 * @param {Object} detail
50 * @param {boolean} detail.isSelected true for selection and false for deselection
51 * @param {Object} detail.item the item element
54 * Fired when an item element is tapped.
56 * @event polymer-activate
57 * @param {Object} detail
58 * @param {Object} detail.item the item element
61 <link rel=
"import" href=
"../polymer/polymer.html">
62 <link rel=
"import" href=
"../polymer-selection/polymer-selection.html">
64 <polymer-element name=
"polymer-selector"
65 attributes=
"selected multi valueattr selectedClass selectedProperty selectedAttribute selectedItem selectedModel selectedIndex notap target itemsSelector activateEvent">
67 <polymer-selection id=
"selection" multi=
"{{multi}}" on-polymer-select=
"{{selectionSelect}}"></polymer-selection>
68 <content id=
"items" select=
"*"></content>
71 Polymer('polymer-selector', {
73 * Gets or sets the selected element. Default to use the index
74 * of the item element.
76 * If you want a specific attribute value of the element to be
77 * used instead of index, set "valueattr" to that attribute name.
81 * <polymer-selector valueattr="label" selected="foo">
82 * <div label="foo"></div>
83 * <div label="bar"></div>
84 * <div label="zot"></div>
87 * In multi-selection this should be an array of values.
91 * <polymer-selector id="selector" valueattr="label" multi>
92 * <div label="foo"></div>
93 * <div label="bar"></div>
94 * <div label="zot"></div>
97 * this.$.selector.selected = ['foo', 'zot'];
105 * If true, multiple selections are allowed.
113 * Specifies the attribute to be used for "selected" attribute.
115 * @attribute valueattr
121 * Specifies the CSS class to be used to add to the selected element.
123 * @attribute selectedClass
125 * @default 'polymer-selected'
127 selectedClass
: 'polymer-selected',
129 * Specifies the property to be used to set on the selected element
130 * to indicate its active state.
132 * @attribute selectedProperty
136 selectedProperty
: '',
138 * Specifies the property to be used to set on the selected element
139 * to indicate its active state.
141 * @attribute selectedProperty
145 selectedAttribute
: 'active',
147 * Returns the currently selected element. In multi-selection this returns
148 * an array of selected elements.
150 * @attribute selectedItem
156 * In single selection, this returns the model associated with the
159 * @attribute selectedModel
165 * In single selection, this returns the selected index.
167 * @attribute selectedIndex
173 * The target element that contains items. If this is not set
174 * polymer-selector is the container.
182 * This can be used to query nodes from the target node to be used for
183 * selection items. Note this only works if the 'target' property is set.
187 * <polymer-selector target="{{$.myForm}}" itemsSelector="input[type=radio]"></polymer-selector>
189 * <label><input type="radio" name="color" value="red"> Red</label> <br>
190 * <label><input type="radio" name="color" value="green"> Green</label> <br>
191 * <label><input type="radio" name="color" value="blue"> Blue</label> <br>
192 * <p>color = {{color}}</p>
195 * @attribute itemSelector
201 * The event that would be fired from the item element to indicate
202 * it is being selected.
204 * @attribute activateEvent
208 activateEvent
: 'tap',
211 this.activateListener
= this.activateHandler
.bind(this);
212 this.observer
= new MutationObserver(this.updateSelected
.bind(this));
218 var nodes
= this.target
!== this ? (this.itemsSelector
?
219 this.target
.querySelectorAll(this.itemsSelector
) :
220 this.target
.children
) : this.$.items
.getDistributedNodes();
221 return Array
.prototype.filter
.call(nodes
|| [], function(n
) {
222 return n
&& n
.localName
!== 'template';
225 targetChanged: function(old
) {
227 this.removeListener(old
);
228 this.observer
.disconnect();
231 this.addListener(this.target
);
232 this.observer
.observe(this.target
, {childList
: true});
235 addListener: function(node
) {
236 node
.addEventListener(this.activateEvent
, this.activateListener
);
238 removeListener: function(node
) {
239 node
.removeEventListener(this.activateEvent
, this.activateListener
);
242 return this.$.selection
.getSelection();
244 selectedChanged: function() {
245 this.updateSelected();
247 updateSelected: function() {
248 this.validateSelected();
250 this.clearSelection();
251 this.selected
&& this.selected
.forEach(function(s
) {
252 this.valueToSelection(s
);
255 this.valueToSelection(this.selected
);
258 validateSelected: function() {
259 // convert to an array for multi-selection
260 if (this.multi
&& !Array
.isArray(this.selected
) &&
261 this.selected
!== null && this.selected
!== undefined) {
262 this.selected
= [this.selected
];
265 clearSelection: function() {
267 this.selection
.slice().forEach(function(s
) {
268 this.$.selection
.setItemSelected(s
, false);
271 this.$.selection
.setItemSelected(this.selection
, false);
273 this.selectedItem
= null;
274 this.$.selection
.clear();
276 valueToSelection: function(value
) {
277 var item
= (value
=== null || value
=== undefined) ?
278 null : this.items
[this.valueToIndex(value
)];
279 this.$.selection
.select(item
);
281 updateSelectedItem: function() {
282 this.selectedItem
= this.selection
;
284 selectedItemChanged: function() {
285 if (this.selectedItem
) {
286 var t
= this.selectedItem
.templateInstance
;
287 this.selectedModel
= t
? t
.model
: undefined;
289 this.selectedModel
= null;
291 this.selectedIndex
= this.selectedItem
?
292 parseInt(this.valueToIndex(this.selected
)) : -1;
294 valueToIndex: function(value
) {
295 // find an item with value == value and return it's index
296 for (var i
=0, items
=this.items
, c
; (c
=items
[i
]); i
++) {
297 if (this.valueForNode(c
) == value
) {
301 // if no item found, the value itself is probably the index
304 valueForNode: function(node
) {
305 return node
[this.valueattr
] || node
.getAttribute(this.valueattr
);
307 // events fired from <polymer-selection> object
308 selectionSelect: function(e
, detail
) {
309 this.updateSelectedItem();
311 this.applySelection(detail
.item
, detail
.isSelected
);
314 applySelection: function(item
, isSelected
) {
315 if (this.selectedClass
) {
316 item
.classList
.toggle(this.selectedClass
, isSelected
);
318 if (this.selectedProperty
) {
319 item
[this.selectedProperty
] = isSelected
;
321 if (this.selectedAttribute
&& item
.setAttribute
) {
323 item
.setAttribute(this.selectedAttribute
, '');
325 item
.removeAttribute(this.selectedAttribute
);
329 // event fired from host
330 activateHandler: function(e
) {
332 var i
= this.findDistributedTarget(e
.target
, this.items
);
334 var item
= this.items
[i
];
335 var s
= this.valueForNode(item
) || i
;
338 this.addRemoveSelected(s
);
345 this.asyncFire('polymer-activate', {item
: item
});
349 addRemoveSelected: function(value
) {
350 var i
= this.selected
.indexOf(value
);
352 this.selected
.splice(i
, 1);
354 this.selected
.push(value
);
356 this.valueToSelection(value
);
358 findDistributedTarget: function(target
, nodes
) {
359 // find first ancestor of target (including itself) that
360 // is in nodes, if any
361 while (target
&& target
!= this) {
362 var i
= Array
.prototype.indexOf
.call(nodes
, target
);
366 target
= target
.parentNode
;