| OLD | NEW |
| (Empty) |
| 1 <!-- | |
| 2 Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
| 3 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt | |
| 4 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | |
| 5 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt | |
| 6 Code distributed by Google as part of the polymer project is also | |
| 7 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt | |
| 8 --> | |
| 9 | |
| 10 <!-- | |
| 11 @group Polymer Core Elements | |
| 12 | |
| 13 `<core-selector>` is used to manage a list of elements that can be selected. | |
| 14 | |
| 15 The attribute `selected` indicates which item element is being selected. | |
| 16 The attribute `multi` indicates if multiple items can be selected at once. | |
| 17 Tapping on the item element would fire `core-activate` event. Use | |
| 18 `core-select` event to listen for selection changes. | |
| 19 | |
| 20 Example: | |
| 21 | |
| 22 <core-selector selected="0"> | |
| 23 <div>Item 1</div> | |
| 24 <div>Item 2</div> | |
| 25 <div>Item 3</div> | |
| 26 </core-selector> | |
| 27 | |
| 28 `<core-selector>` is not styled. Use the `core-selected` CSS class to style the
selected element. | |
| 29 | |
| 30 <style> | |
| 31 .item.core-selected { | |
| 32 background: #eee; | |
| 33 } | |
| 34 </style> | |
| 35 ... | |
| 36 <core-selector> | |
| 37 <div class="item">Item 1</div> | |
| 38 <div class="item">Item 2</div> | |
| 39 <div class="item">Item 3</div> | |
| 40 </core-selector> | |
| 41 | |
| 42 @element core-selector | |
| 43 @status stable | |
| 44 @homepage github.io | |
| 45 --> | |
| 46 | |
| 47 <!-- | |
| 48 Fired when an item's selection state is changed. This event is fired both | |
| 49 when an item is selected or deselected. The `isSelected` detail property | |
| 50 contains the selection state. | |
| 51 | |
| 52 @event core-select | |
| 53 @param {Object} detail | |
| 54 @param {boolean} detail.isSelected true for selection and false for deselectio
n | |
| 55 @param {Object} detail.item the item element | |
| 56 --> | |
| 57 <!-- | |
| 58 Fired when an item element is tapped. | |
| 59 | |
| 60 @event core-activate | |
| 61 @param {Object} detail | |
| 62 @param {Object} detail.item the item element | |
| 63 --> | |
| 64 | |
| 65 <link rel="import" href="../polymer/polymer.html"> | |
| 66 <link rel="import" href="../core-selection/core-selection.html"> | |
| 67 | |
| 68 <polymer-element name="core-selector" | |
| 69 attributes="selected multi valueattr selectedClass selectedProperty selected
Attribute selectedItem selectedModel selectedIndex notap excludedLocalNames targ
et itemsSelector activateEvent"> | |
| 70 | |
| 71 <template> | |
| 72 <core-selection id="selection" multi="{{multi}}" on-core-select="{{selection
Select}}"></core-selection> | |
| 73 <content id="items" select="*"></content> | |
| 74 </template> | |
| 75 | |
| 76 <script> | |
| 77 | |
| 78 Polymer('core-selector', { | |
| 79 | |
| 80 /** | |
| 81 * Gets or sets the selected element. Default to use the index | |
| 82 * of the item element. | |
| 83 * | |
| 84 * If you want a specific attribute value of the element to be | |
| 85 * used instead of index, set "valueattr" to that attribute name. | |
| 86 * | |
| 87 * Example: | |
| 88 * | |
| 89 * <core-selector valueattr="label" selected="foo"> | |
| 90 * <div label="foo"></div> | |
| 91 * <div label="bar"></div> | |
| 92 * <div label="zot"></div> | |
| 93 * </core-selector> | |
| 94 * | |
| 95 * In multi-selection this should be an array of values. | |
| 96 * | |
| 97 * Example: | |
| 98 * | |
| 99 * <core-selector id="selector" valueattr="label" multi> | |
| 100 * <div label="foo"></div> | |
| 101 * <div label="bar"></div> | |
| 102 * <div label="zot"></div> | |
| 103 * </core-selector> | |
| 104 * | |
| 105 * this.$.selector.selected = ['foo', 'zot']; | |
| 106 * | |
| 107 * @attribute selected | |
| 108 * @type Object | |
| 109 * @default null | |
| 110 */ | |
| 111 selected: null, | |
| 112 | |
| 113 /** | |
| 114 * If true, multiple selections are allowed. | |
| 115 * | |
| 116 * @attribute multi | |
| 117 * @type boolean | |
| 118 * @default false | |
| 119 */ | |
| 120 multi: false, | |
| 121 | |
| 122 /** | |
| 123 * Specifies the attribute to be used for "selected" attribute. | |
| 124 * | |
| 125 * @attribute valueattr | |
| 126 * @type string | |
| 127 * @default 'name' | |
| 128 */ | |
| 129 valueattr: 'name', | |
| 130 | |
| 131 /** | |
| 132 * Specifies the CSS class to be used to add to the selected element. | |
| 133 * | |
| 134 * @attribute selectedClass | |
| 135 * @type string | |
| 136 * @default 'core-selected' | |
| 137 */ | |
| 138 selectedClass: 'core-selected', | |
| 139 | |
| 140 /** | |
| 141 * Specifies the property to be used to set on the selected element | |
| 142 * to indicate its active state. | |
| 143 * | |
| 144 * @attribute selectedProperty | |
| 145 * @type string | |
| 146 * @default '' | |
| 147 */ | |
| 148 selectedProperty: '', | |
| 149 | |
| 150 /** | |
| 151 * Specifies the attribute to set on the selected element to indicate | |
| 152 * its active state. | |
| 153 * | |
| 154 * @attribute selectedAttribute | |
| 155 * @type string | |
| 156 * @default 'active' | |
| 157 */ | |
| 158 selectedAttribute: 'active', | |
| 159 | |
| 160 /** | |
| 161 * Returns the currently selected element. In multi-selection this returns | |
| 162 * an array of selected elements. | |
| 163 * Note that you should not use this to set the selection. Instead use | |
| 164 * `selected`. | |
| 165 * | |
| 166 * @attribute selectedItem | |
| 167 * @type Object | |
| 168 * @default null | |
| 169 */ | |
| 170 selectedItem: null, | |
| 171 | |
| 172 /** | |
| 173 * In single selection, this returns the model associated with the | |
| 174 * selected element. | |
| 175 * Note that you should not use this to set the selection. Instead use | |
| 176 * `selected`. | |
| 177 * | |
| 178 * @attribute selectedModel | |
| 179 * @type Object | |
| 180 * @default null | |
| 181 */ | |
| 182 selectedModel: null, | |
| 183 | |
| 184 /** | |
| 185 * In single selection, this returns the selected index. | |
| 186 * Note that you should not use this to set the selection. Instead use | |
| 187 * `selected`. | |
| 188 * | |
| 189 * @attribute selectedIndex | |
| 190 * @type number | |
| 191 * @default -1 | |
| 192 */ | |
| 193 selectedIndex: -1, | |
| 194 | |
| 195 /** | |
| 196 * Nodes with local name that are in the list will not be included | |
| 197 * in the selection items. In the following example, `items` returns four | |
| 198 * `core-item`'s and doesn't include `h3` and `hr`. | |
| 199 * | |
| 200 * <core-selector excludedLocalNames="h3 hr"> | |
| 201 * <h3>Header</h3> | |
| 202 * <core-item>Item1</core-item> | |
| 203 * <core-item>Item2</core-item> | |
| 204 * <hr> | |
| 205 * <core-item>Item3</core-item> | |
| 206 * <core-item>Item4</core-item> | |
| 207 * </core-selector> | |
| 208 * | |
| 209 * @attribute excludedLocalNames | |
| 210 * @type string | |
| 211 * @default '' | |
| 212 */ | |
| 213 excludedLocalNames: '', | |
| 214 | |
| 215 /** | |
| 216 * The target element that contains items. If this is not set | |
| 217 * core-selector is the container. | |
| 218 * | |
| 219 * @attribute target | |
| 220 * @type Object | |
| 221 * @default null | |
| 222 */ | |
| 223 target: null, | |
| 224 | |
| 225 /** | |
| 226 * This can be used to query nodes from the target node to be used for | |
| 227 * selection items. Note this only works if `target` is set | |
| 228 * and is not `core-selector` itself. | |
| 229 * | |
| 230 * Example: | |
| 231 * | |
| 232 * <core-selector target="{{$.myForm}}" itemsSelector="input[type=radi
o]"></core-selector> | |
| 233 * <form id="myForm"> | |
| 234 * <label><input type="radio" name="color" value="red"> Red</label>
<br> | |
| 235 * <label><input type="radio" name="color" value="green"> Green</lab
el> <br> | |
| 236 * <label><input type="radio" name="color" value="blue"> Blue</label
> <br> | |
| 237 * <p>color = {{color}}</p> | |
| 238 * </form> | |
| 239 * | |
| 240 * @attribute itemsSelector | |
| 241 * @type string | |
| 242 * @default '' | |
| 243 */ | |
| 244 itemsSelector: '', | |
| 245 | |
| 246 /** | |
| 247 * The event that would be fired from the item element to indicate | |
| 248 * it is being selected. | |
| 249 * | |
| 250 * @attribute activateEvent | |
| 251 * @type string | |
| 252 * @default 'tap' | |
| 253 */ | |
| 254 activateEvent: 'tap', | |
| 255 | |
| 256 /** | |
| 257 * Set this to true to disallow changing the selection via the | |
| 258 * `activateEvent`. | |
| 259 * | |
| 260 * @attribute notap | |
| 261 * @type boolean | |
| 262 * @default false | |
| 263 */ | |
| 264 notap: false, | |
| 265 | |
| 266 defaultExcludedLocalNames: 'template', | |
| 267 | |
| 268 ready: function() { | |
| 269 this.activateListener = this.activateHandler.bind(this); | |
| 270 this.itemFilter = this.filterItem.bind(this); | |
| 271 this.excludedLocalNamesChanged(); | |
| 272 this.observer = new MutationObserver(this.updateSelected.bind(this)); | |
| 273 if (!this.target) { | |
| 274 this.target = this; | |
| 275 } | |
| 276 }, | |
| 277 | |
| 278 /** | |
| 279 * Returns an array of all items. | |
| 280 * | |
| 281 * @property items | |
| 282 */ | |
| 283 get items() { | |
| 284 if (!this.target) { | |
| 285 return []; | |
| 286 } | |
| 287 var nodes = this.target !== this ? (this.itemsSelector ? | |
| 288 this.target.querySelectorAll(this.itemsSelector) : | |
| 289 this.target.children) : this.$.items.getDistributedNodes(); | |
| 290 return Array.prototype.filter.call(nodes, this.itemFilter); | |
| 291 }, | |
| 292 | |
| 293 filterItem: function(node) { | |
| 294 return !this._excludedNames[node.localName]; | |
| 295 }, | |
| 296 | |
| 297 excludedLocalNamesChanged: function() { | |
| 298 this._excludedNames = {}; | |
| 299 var s = this.defaultExcludedLocalNames; | |
| 300 if (this.excludedLocalNames) { | |
| 301 s += ' ' + this.excludedLocalNames; | |
| 302 } | |
| 303 s.split(/\s+/g).forEach(function(n) { | |
| 304 this._excludedNames[n] = 1; | |
| 305 }, this); | |
| 306 }, | |
| 307 | |
| 308 targetChanged: function(old) { | |
| 309 if (old) { | |
| 310 this.removeListener(old); | |
| 311 this.observer.disconnect(); | |
| 312 this.clearSelection(); | |
| 313 } | |
| 314 if (this.target) { | |
| 315 this.addListener(this.target); | |
| 316 this.observer.observe(this.target, {childList: true}); | |
| 317 this.updateSelected(); | |
| 318 } | |
| 319 }, | |
| 320 | |
| 321 addListener: function(node) { | |
| 322 Polymer.addEventListener(node, this.activateEvent, this.activateListener
); | |
| 323 }, | |
| 324 | |
| 325 removeListener: function(node) { | |
| 326 Polymer.removeEventListener(node, this.activateEvent, this.activateListe
ner); | |
| 327 }, | |
| 328 | |
| 329 /** | |
| 330 * Returns the selected item(s). If the `multi` property is true, | |
| 331 * this will return an array, otherwise it will return | |
| 332 * the selected item or undefined if there is no selection. | |
| 333 */ | |
| 334 get selection() { | |
| 335 return this.$.selection.getSelection(); | |
| 336 }, | |
| 337 | |
| 338 selectedChanged: function() { | |
| 339 this.updateSelected(); | |
| 340 }, | |
| 341 | |
| 342 updateSelected: function() { | |
| 343 this.validateSelected(); | |
| 344 if (this.multi) { | |
| 345 this.clearSelection(); | |
| 346 this.selected && this.selected.forEach(function(s) { | |
| 347 this.valueToSelection(s); | |
| 348 }, this); | |
| 349 } else { | |
| 350 this.valueToSelection(this.selected); | |
| 351 } | |
| 352 }, | |
| 353 | |
| 354 validateSelected: function() { | |
| 355 // convert to an array for multi-selection | |
| 356 if (this.multi && !Array.isArray(this.selected) && | |
| 357 this.selected !== null && this.selected !== undefined) { | |
| 358 this.selected = [this.selected]; | |
| 359 } | |
| 360 }, | |
| 361 | |
| 362 clearSelection: function() { | |
| 363 if (this.multi) { | |
| 364 this.selection.slice().forEach(function(s) { | |
| 365 this.$.selection.setItemSelected(s, false); | |
| 366 }, this); | |
| 367 } else { | |
| 368 this.$.selection.setItemSelected(this.selection, false); | |
| 369 } | |
| 370 this.selectedItem = null; | |
| 371 this.$.selection.clear(); | |
| 372 }, | |
| 373 | |
| 374 valueToSelection: function(value) { | |
| 375 var item = (value === null || value === undefined) ? | |
| 376 null : this.items[this.valueToIndex(value)]; | |
| 377 this.$.selection.select(item); | |
| 378 }, | |
| 379 | |
| 380 updateSelectedItem: function() { | |
| 381 this.selectedItem = this.selection; | |
| 382 }, | |
| 383 | |
| 384 selectedItemChanged: function() { | |
| 385 if (this.selectedItem) { | |
| 386 var t = this.selectedItem.templateInstance; | |
| 387 this.selectedModel = t ? t.model : undefined; | |
| 388 } else { | |
| 389 this.selectedModel = null; | |
| 390 } | |
| 391 this.selectedIndex = this.selectedItem ? | |
| 392 parseInt(this.valueToIndex(this.selected)) : -1; | |
| 393 }, | |
| 394 | |
| 395 valueToIndex: function(value) { | |
| 396 // find an item with value == value and return it's index | |
| 397 for (var i=0, items=this.items, c; (c=items[i]); i++) { | |
| 398 if (this.valueForNode(c) == value) { | |
| 399 return i; | |
| 400 } | |
| 401 } | |
| 402 // if no item found, the value itself is probably the index | |
| 403 return value; | |
| 404 }, | |
| 405 | |
| 406 valueForNode: function(node) { | |
| 407 return node[this.valueattr] || node.getAttribute(this.valueattr); | |
| 408 }, | |
| 409 | |
| 410 // events fired from <core-selection> object | |
| 411 selectionSelect: function(e, detail) { | |
| 412 this.updateSelectedItem(); | |
| 413 if (detail.item) { | |
| 414 this.applySelection(detail.item, detail.isSelected); | |
| 415 } | |
| 416 }, | |
| 417 | |
| 418 applySelection: function(item, isSelected) { | |
| 419 if (this.selectedClass) { | |
| 420 item.classList.toggle(this.selectedClass, isSelected); | |
| 421 } | |
| 422 if (this.selectedProperty) { | |
| 423 item[this.selectedProperty] = isSelected; | |
| 424 } | |
| 425 if (this.selectedAttribute && item.setAttribute) { | |
| 426 if (isSelected) { | |
| 427 item.setAttribute(this.selectedAttribute, ''); | |
| 428 } else { | |
| 429 item.removeAttribute(this.selectedAttribute); | |
| 430 } | |
| 431 } | |
| 432 }, | |
| 433 | |
| 434 // event fired from host | |
| 435 activateHandler: function(e) { | |
| 436 if (!this.notap) { | |
| 437 var i = this.findDistributedTarget(e.target, this.items); | |
| 438 if (i >= 0) { | |
| 439 var item = this.items[i]; | |
| 440 var s = this.valueForNode(item) || i; | |
| 441 if (this.multi) { | |
| 442 if (this.selected) { | |
| 443 this.addRemoveSelected(s); | |
| 444 } else { | |
| 445 this.selected = [s]; | |
| 446 } | |
| 447 } else { | |
| 448 this.selected = s; | |
| 449 } | |
| 450 this.asyncFire('core-activate', {item: item}); | |
| 451 } | |
| 452 } | |
| 453 }, | |
| 454 | |
| 455 addRemoveSelected: function(value) { | |
| 456 var i = this.selected.indexOf(value); | |
| 457 if (i >= 0) { | |
| 458 this.selected.splice(i, 1); | |
| 459 } else { | |
| 460 this.selected.push(value); | |
| 461 } | |
| 462 this.valueToSelection(value); | |
| 463 }, | |
| 464 | |
| 465 findDistributedTarget: function(target, nodes) { | |
| 466 // find first ancestor of target (including itself) that | |
| 467 // is in nodes, if any | |
| 468 while (target && target != this) { | |
| 469 var i = Array.prototype.indexOf.call(nodes, target); | |
| 470 if (i >= 0) { | |
| 471 return i; | |
| 472 } | |
| 473 target = target.parentNode; | |
| 474 } | |
| 475 }, | |
| 476 | |
| 477 selectIndex: function(index) { | |
| 478 var item = this.items[index]; | |
| 479 if (item) { | |
| 480 this.selected = this.valueForNode(item) || index; | |
| 481 return item; | |
| 482 } | |
| 483 }, | |
| 484 | |
| 485 /** | |
| 486 * Selects the previous item. This should be used in single selection onl
y. | |
| 487 * | |
| 488 * @method selectPrevious | |
| 489 * @param {boolean} wrap if true and it is already at the first item, wrap
to the end | |
| 490 * @returns the previous item or undefined if there is none | |
| 491 */ | |
| 492 selectPrevious: function(wrap) { | |
| 493 var i = wrap && !this.selectedIndex ? this.items.length - 1 : this.selec
tedIndex - 1; | |
| 494 return this.selectIndex(i); | |
| 495 }, | |
| 496 | |
| 497 /** | |
| 498 * Selects the next item. This should be used in single selection only. | |
| 499 * | |
| 500 * @method selectNext | |
| 501 * @param {boolean} wrap if true and it is already at the last item, wrap
to the front | |
| 502 * @returns the next item or undefined if there is none | |
| 503 */ | |
| 504 selectNext: function(wrap) { | |
| 505 var i = wrap && this.selectedIndex >= this.items.length - 1 ? 0 : this.s
electedIndex + 1; | |
| 506 return this.selectIndex(i); | |
| 507 } | |
| 508 | |
| 509 }); | |
| 510 </script> | |
| 511 </polymer-element> | |
| OLD | NEW |