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