OLD | NEW |
1 <!-- | 1 <!-- |
2 @license | 2 @license |
3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. | 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt | 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt | 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
7 Code distributed by Google as part of the polymer project is also | 7 Code distributed by Google as part of the polymer project is also |
8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt | 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
9 --> | 9 --> |
10 | 10 |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
62 <iron-ajax url="data.json" last-response="{{data}}" auto></iron-ajax> | 62 <iron-ajax url="data.json" last-response="{{data}}" auto></iron-ajax> |
63 <iron-list items="[[data]]" as="item"> | 63 <iron-list items="[[data]]" as="item"> |
64 <template> | 64 <template> |
65 <div> | 65 <div> |
66 Name: <span>[[item.name]]</span> | 66 Name: <span>[[item.name]]</span> |
67 </div> | 67 </div> |
68 </template> | 68 </template> |
69 </iron-list> | 69 </iron-list> |
70 </template> | 70 </template> |
71 | 71 |
| 72 ### Styling |
| 73 |
| 74 Use the `--iron-list-items-container` mixin to style the container of items, e.g
. |
| 75 |
| 76 iron-list { |
| 77 --iron-list-items-container: { |
| 78 margin: auto; |
| 79 }; |
| 80 } |
| 81 |
72 ### Resizing | 82 ### Resizing |
73 | 83 |
74 `iron-list` lays out the items when it recives a notification via the `resize` e
vent. | 84 `iron-list` lays out the items when it recives a notification via the `iron-resi
ze` event. |
75 This event is fired by any element that implements `IronResizableBehavior`. | 85 This event is fired by any element that implements `IronResizableBehavior`. |
76 | 86 |
77 By default, elements such as `iron-pages`, `paper-tabs` or `paper-dialog` will t
rigger | 87 By default, elements such as `iron-pages`, `paper-tabs` or `paper-dialog` will t
rigger |
78 this event automatically. If you hide the list manually (e.g. you use `display:
none`) | 88 this event automatically. If you hide the list manually (e.g. you use `display:
none`) |
79 you might want to implement `IronResizableBehavior` or fire this event manually
right | 89 you might want to implement `IronResizableBehavior` or fire this event manually
right |
80 after the list became visible again. e.g. | 90 after the list became visible again. e.g. |
81 | 91 |
82 document.querySelector('iron-list').fire('resize'); | 92 document.querySelector('iron-list').fire('iron-resize'); |
83 | 93 |
84 | 94 |
85 @group Iron Element | 95 @group Iron Element |
86 @element iron-list | 96 @element iron-list |
87 @demo demo/index.html | 97 @demo demo/index.html Simple list |
| 98 @demo demo/selection.html Selection of items |
| 99 @demo demo/collapse.html Collapsable items |
88 --> | 100 --> |
89 | 101 |
90 <dom-module id="iron-list"> | 102 <dom-module id="iron-list"> |
91 <style> | 103 <template> |
| 104 <style> |
| 105 :host { |
| 106 display: block; |
| 107 } |
92 | 108 |
93 :host { | 109 :host(.has-scroller) { |
94 display: block; | 110 overflow: auto; |
95 } | 111 } |
96 | 112 |
97 :host(.has-scroller) { | 113 :host(:not(.has-scroller)) { |
98 overflow: auto; | 114 position: relative; |
99 } | 115 } |
100 | 116 |
101 :host(:not(.has-scroller)) { | 117 #items { |
102 position: relative; | 118 @apply(--iron-list-items-container); |
103 } | 119 position: relative; |
| 120 } |
104 | 121 |
105 #items { | 122 #items > ::content > * { |
106 position: relative; | 123 width: 100%; |
107 } | 124 box-sizing: border-box; |
108 | 125 position: absolute; |
109 #items > ::content > * { | 126 top: 0; |
110 width: 100%; | 127 will-change: transform; |
111 box-sizing: border-box; | 128 } |
112 position: absolute; | 129 </style> |
113 top: 0; | |
114 will-change: transform; | |
115 } | |
116 | |
117 </style> | |
118 <template> | |
119 | 130 |
120 <array-selector id="selector" items="{{items}}" | 131 <array-selector id="selector" items="{{items}}" |
121 selected="{{selectedItems}}" selected-item="{{selectedItem}}"> | 132 selected="{{selectedItems}}" selected-item="{{selectedItem}}"> |
122 </array-selector> | 133 </array-selector> |
123 | 134 |
124 <div id="items"> | 135 <div id="items"> |
125 <content></content> | 136 <content></content> |
126 </div> | 137 </div> |
127 | 138 |
128 </template> | 139 </template> |
129 </dom-module> | 140 </dom-module> |
130 | 141 |
131 <script> | 142 <script> |
(...skipping 23 matching lines...) Expand all Loading... |
155 * The name of the variable to add to the binding scope for the array | 166 * The name of the variable to add to the binding scope for the array |
156 * element associated with a given template instance. | 167 * element associated with a given template instance. |
157 */ | 168 */ |
158 as: { | 169 as: { |
159 type: String, | 170 type: String, |
160 value: 'item' | 171 value: 'item' |
161 }, | 172 }, |
162 | 173 |
163 /** | 174 /** |
164 * The name of the variable to add to the binding scope with the index | 175 * The name of the variable to add to the binding scope with the index |
165 * for the row. If `sort` is provided, the index will reflect the | 176 * for the row. |
166 * sorted order (rather than the original array order). | |
167 */ | 177 */ |
168 indexAs: { | 178 indexAs: { |
169 type: String, | 179 type: String, |
170 value: 'index' | 180 value: 'index' |
171 }, | 181 }, |
172 | 182 |
173 /** | 183 /** |
174 * The name of the variable to add to the binding scope to indicate | 184 * The name of the variable to add to the binding scope to indicate |
175 * if the row is selected. | 185 * if the row is selected. |
176 */ | 186 */ |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
235 }, | 245 }, |
236 | 246 |
237 /** | 247 /** |
238 * The ratio of hidden tiles that should remain in the scroll direction. | 248 * The ratio of hidden tiles that should remain in the scroll direction. |
239 * Recommended value ~0.5, so it will distribute tiles evely in both directi
ons. | 249 * Recommended value ~0.5, so it will distribute tiles evely in both directi
ons. |
240 */ | 250 */ |
241 _ratio: 0.5, | 251 _ratio: 0.5, |
242 | 252 |
243 /** | 253 /** |
244 * The element that controls the scroll | 254 * The element that controls the scroll |
| 255 * @type {?Element} |
245 */ | 256 */ |
246 _scroller: null, | 257 _scroller: null, |
247 | 258 |
248 /** | 259 /** |
249 * The padding-top value of the `scroller` element | 260 * The padding-top value of the `scroller` element |
250 */ | 261 */ |
251 _scrollerPaddingTop: 0, | 262 _scrollerPaddingTop: 0, |
252 | 263 |
253 /** | 264 /** |
254 * This value is the same as `scrollTop`. | 265 * This value is the same as `scrollTop`. |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
316 */ | 327 */ |
317 _scrollHeight: 0, | 328 _scrollHeight: 0, |
318 | 329 |
319 /** | 330 /** |
320 * The size of the viewport | 331 * The size of the viewport |
321 */ | 332 */ |
322 _viewportSize: 0, | 333 _viewportSize: 0, |
323 | 334 |
324 /** | 335 /** |
325 * An array of DOM nodes that are currently in the tree | 336 * An array of DOM nodes that are currently in the tree |
| 337 * @type {?Array<!TemplatizerNode>} |
326 */ | 338 */ |
327 _physicalItems: null, | 339 _physicalItems: null, |
328 | 340 |
329 /** | 341 /** |
330 * An array of heights for each item in `_physicalItems` | 342 * An array of heights for each item in `_physicalItems` |
| 343 * @type {?Array<number>} |
331 */ | 344 */ |
332 _physicalSizes: null, | 345 _physicalSizes: null, |
333 | 346 |
334 /** | 347 /** |
335 * A cached value for the visible index. | 348 * A cached value for the visible index. |
336 * See `firstVisibleIndex` | 349 * See `firstVisibleIndex` |
| 350 * @type {?number} |
337 */ | 351 */ |
338 _firstVisibleIndexVal: null, | 352 _firstVisibleIndexVal: null, |
339 | 353 |
340 /** | 354 /** |
341 * A Polymer collection for the items. | 355 * A Polymer collection for the items. |
| 356 * @type {?Polymer.Collection} |
342 */ | 357 */ |
343 _collection: null, | 358 _collection: null, |
344 | 359 |
345 /** | 360 /** |
346 * True if the current item list was rendered for the first time | 361 * True if the current item list was rendered for the first time |
347 * after attached. | 362 * after attached. |
348 */ | 363 */ |
349 _itemsRendered: false, | 364 _itemsRendered: false, |
350 | 365 |
351 /** | 366 /** |
352 * The bottom of the physical content. | 367 * The bottom of the physical content. |
353 */ | 368 */ |
354 get _physicalBottom() { | 369 get _physicalBottom() { |
355 return this._physicalTop + this._physicalSize; | 370 return this._physicalTop + this._physicalSize; |
356 }, | 371 }, |
357 | 372 |
358 /** | 373 /** |
| 374 * The bottom of the scroll. |
| 375 */ |
| 376 get _scrollBottom() { |
| 377 return this._scrollPosition + this._viewportSize; |
| 378 }, |
| 379 |
| 380 /** |
359 * The n-th item rendered in the last physical item. | 381 * The n-th item rendered in the last physical item. |
360 */ | 382 */ |
361 get _virtualEnd() { | 383 get _virtualEnd() { |
362 return this._virtualStartVal + this._physicalCount - 1; | 384 return this._virtualStartVal + this._physicalCount - 1; |
363 }, | 385 }, |
364 | 386 |
365 /** | 387 /** |
366 * The lowest n-th value for an item such that it can be rendered in `_physi
calStart`. | 388 * The lowest n-th value for an item such that it can be rendered in `_physi
calStart`. |
367 */ | 389 */ |
368 _minVirtualStart: 0, | 390 _minVirtualStart: 0, |
369 | 391 |
370 /** | 392 /** |
371 * The largest n-th value for an item such that it can be rendered in `_phys
icalStart`. | 393 * The largest n-th value for an item such that it can be rendered in `_phys
icalStart`. |
372 */ | 394 */ |
373 get _maxVirtualStart() { | 395 get _maxVirtualStart() { |
374 return this._virtualCount < this._physicalCount ? | 396 return Math.max(0, this._virtualCount - this._physicalCount); |
375 this._virtualCount : this._virtualCount - this._physicalCount; | |
376 }, | 397 }, |
377 | 398 |
378 /** | 399 /** |
379 * The height of the physical content that isn't on the screen. | 400 * The height of the physical content that isn't on the screen. |
380 */ | 401 */ |
381 get _hiddenContentSize() { | 402 get _hiddenContentSize() { |
382 return this._physicalSize - this._viewportSize; | 403 return this._physicalSize - this._viewportSize; |
383 }, | 404 }, |
384 | 405 |
385 /** | 406 /** |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
418 }, | 439 }, |
419 | 440 |
420 /** | 441 /** |
421 * True if the current list is visible. | 442 * True if the current list is visible. |
422 */ | 443 */ |
423 get _isVisible() { | 444 get _isVisible() { |
424 return this._scroller && Boolean(this._scroller.offsetWidth || this._scrol
ler.offsetHeight); | 445 return this._scroller && Boolean(this._scroller.offsetWidth || this._scrol
ler.offsetHeight); |
425 }, | 446 }, |
426 | 447 |
427 /** | 448 /** |
428 * Gets the first visible item in the viewport. | 449 * Gets the index of the first visible item in the viewport. |
429 * | 450 * |
430 * @property firstVisibleIndex | 451 * @type {number} |
431 */ | 452 */ |
432 get firstVisibleIndex() { | 453 get firstVisibleIndex() { |
433 var physicalOffset; | 454 var physicalOffset; |
434 | 455 |
435 if (this._firstVisibleIndexVal === null) { | 456 if (this._firstVisibleIndexVal === null) { |
436 physicalOffset = this._physicalTop; | 457 physicalOffset = this._physicalTop; |
437 | 458 |
438 this._firstVisibleIndexVal = this._iterateItems( | 459 this._firstVisibleIndexVal = this._iterateItems( |
439 function(pidx, vidx) { | 460 function(pidx, vidx) { |
440 physicalOffset += this._physicalSizes[pidx]; | 461 physicalOffset += this._physicalSizes[pidx]; |
(...skipping 18 matching lines...) Expand all Loading... |
459 }, | 480 }, |
460 | 481 |
461 /** | 482 /** |
462 * When the element has been attached to the DOM tree. | 483 * When the element has been attached to the DOM tree. |
463 */ | 484 */ |
464 attached: function() { | 485 attached: function() { |
465 // delegate to the parent's scroller | 486 // delegate to the parent's scroller |
466 // e.g. paper-scroll-header-panel | 487 // e.g. paper-scroll-header-panel |
467 var el = Polymer.dom(this); | 488 var el = Polymer.dom(this); |
468 | 489 |
469 if (el.parentNode && el.parentNode.scroller) { | 490 var parentNode = /** @type {?{scroller: ?Element}} */ (el.parentNode); |
470 this._scroller = el.parentNode.scroller; | 491 if (parentNode && parentNode.scroller) { |
| 492 this._scroller = parentNode.scroller; |
471 } else { | 493 } else { |
472 this._scroller = this; | 494 this._scroller = this; |
473 this.classList.add('has-scroller'); | 495 this.classList.add('has-scroller'); |
474 } | 496 } |
475 | 497 |
476 if (IOS_TOUCH_SCROLLING) { | 498 if (IOS_TOUCH_SCROLLING) { |
477 this._scroller.style.webkitOverflowScrolling = 'touch'; | 499 this._scroller.style.webkitOverflowScrolling = 'touch'; |
478 } | 500 } |
479 | 501 |
480 this._scroller.addEventListener('scroll', this._scrollListener); | 502 this._scroller.addEventListener('scroll', this._scrollListener); |
(...skipping 13 matching lines...) Expand all Loading... |
494 }, | 516 }, |
495 | 517 |
496 /** | 518 /** |
497 * Invoke this method if you dynamically update the viewport's | 519 * Invoke this method if you dynamically update the viewport's |
498 * size or CSS padding. | 520 * size or CSS padding. |
499 * | 521 * |
500 * @method updateViewportBoundaries | 522 * @method updateViewportBoundaries |
501 */ | 523 */ |
502 updateViewportBoundaries: function() { | 524 updateViewportBoundaries: function() { |
503 var scrollerStyle = window.getComputedStyle(this._scroller); | 525 var scrollerStyle = window.getComputedStyle(this._scroller); |
504 this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top']); | 526 this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10); |
505 this._viewportSize = this._scroller.offsetHeight; | 527 this._viewportSize = this._scroller.offsetHeight; |
506 }, | 528 }, |
507 | 529 |
508 /** | 530 /** |
509 * Update the models, the position of the | 531 * Update the models, the position of the |
510 * items in the viewport and recycle tiles as needed. | 532 * items in the viewport and recycle tiles as needed. |
511 */ | 533 */ |
512 _refresh: function() { | 534 _refresh: function() { |
513 var SCROLL_DIRECTION_UP = -1; | |
514 var SCROLL_DIRECTION_DOWN = 1; | |
515 var SCROLL_DIRECTION_NONE = 0; | |
516 | |
517 // clamp the `scrollTop` value | 535 // clamp the `scrollTop` value |
518 // IE 10|11 scrollTop may go above `_maxScrollTop` | 536 // IE 10|11 scrollTop may go above `_maxScrollTop` |
519 // iOS `scrollTop` may go below 0 and above `_maxScrollTop` | 537 // iOS `scrollTop` may go below 0 and above `_maxScrollTop` |
520 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scroller.sc
rollTop)); | 538 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scroller.sc
rollTop)); |
521 | 539 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom; |
522 var tileHeight, kth, recycledTileSet; | |
523 var ratio = this._ratio; | 540 var ratio = this._ratio; |
524 var delta = scrollTop - this._scrollPosition; | 541 var delta = scrollTop - this._scrollPosition; |
525 var direction = SCROLL_DIRECTION_NONE; | |
526 var recycledTiles = 0; | 542 var recycledTiles = 0; |
527 var hiddenContentSize = this._hiddenContentSize; | 543 var hiddenContentSize = this._hiddenContentSize; |
528 var currentRatio = ratio; | 544 var currentRatio = ratio; |
529 var movingUp = []; | 545 var movingUp = []; |
530 | 546 |
531 // track the last `scrollTop` | 547 // track the last `scrollTop` |
532 this._scrollPosition = scrollTop; | 548 this._scrollPosition = scrollTop; |
533 | 549 |
534 // clear cached visible index | 550 // clear cached visible index |
535 this._firstVisibleIndexVal = null; | 551 this._firstVisibleIndexVal = null; |
536 | 552 |
| 553 scrollBottom = this._scrollBottom; |
| 554 |
537 // random access | 555 // random access |
538 if (Math.abs(delta) > this._physicalSize) { | 556 if (Math.abs(delta) > this._physicalSize) { |
539 this._physicalTop += delta; | 557 this._physicalTop += delta; |
540 direction = SCROLL_DIRECTION_NONE; | |
541 recycledTiles = Math.round(delta / this._physicalAverage); | 558 recycledTiles = Math.round(delta / this._physicalAverage); |
542 } | 559 } |
543 // scroll up | 560 // scroll up |
544 else if (delta < 0) { | 561 else if (delta < 0) { |
545 var topSpace = scrollTop - this._physicalTop; | 562 var topSpace = scrollTop - this._physicalTop; |
546 var virtualStart = this._virtualStart; | 563 var virtualStart = this._virtualStart; |
| 564 var physicalBottom = this._physicalBottom; |
547 | 565 |
548 direction = SCROLL_DIRECTION_UP; | |
549 recycledTileSet = []; | 566 recycledTileSet = []; |
550 | 567 |
551 kth = this._physicalEnd; | 568 kth = this._physicalEnd; |
552 currentRatio = topSpace / hiddenContentSize; | 569 currentRatio = topSpace / hiddenContentSize; |
553 | 570 |
554 // move tiles from bottom to top | 571 // move tiles from bottom to top |
555 while ( | 572 while ( |
556 // approximate `currentRatio` to `ratio` | 573 // approximate `currentRatio` to `ratio` |
557 currentRatio < ratio && | 574 currentRatio < ratio && |
558 // recycle less physical items than the total | 575 // recycle less physical items than the total |
559 recycledTiles < this._physicalCount && | 576 recycledTiles < this._physicalCount && |
560 // ensure that these recycled tiles are needed | 577 // ensure that these recycled tiles are needed |
561 virtualStart - recycledTiles > 0 | 578 virtualStart - recycledTiles > 0 && |
| 579 // ensure that the tile is not visible |
| 580 physicalBottom - this._physicalSizes[kth] > scrollBottom |
562 ) { | 581 ) { |
563 | 582 |
564 tileHeight = this._physicalSizes[kth] || this._physicalAverage; | 583 tileHeight = this._physicalSizes[kth]; |
565 currentRatio += tileHeight / hiddenContentSize; | 584 currentRatio += tileHeight / hiddenContentSize; |
566 | 585 physicalBottom -= tileHeight; |
567 recycledTileSet.push(kth); | 586 recycledTileSet.push(kth); |
568 recycledTiles++; | 587 recycledTiles++; |
569 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1; | 588 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1; |
570 } | 589 } |
571 | 590 |
572 movingUp = recycledTileSet; | 591 movingUp = recycledTileSet; |
573 recycledTiles = -recycledTiles; | 592 recycledTiles = -recycledTiles; |
574 | |
575 } | 593 } |
576 // scroll down | 594 // scroll down |
577 else if (delta > 0) { | 595 else if (delta > 0) { |
578 var bottomSpace = this._physicalBottom - (scrollTop + this._viewportSize
); | 596 var bottomSpace = this._physicalBottom - scrollBottom; |
579 var virtualEnd = this._virtualEnd; | 597 var virtualEnd = this._virtualEnd; |
580 var lastVirtualItemIndex = this._virtualCount-1; | 598 var lastVirtualItemIndex = this._virtualCount-1; |
581 | 599 |
582 direction = SCROLL_DIRECTION_DOWN; | |
583 recycledTileSet = []; | 600 recycledTileSet = []; |
584 | 601 |
585 kth = this._physicalStart; | 602 kth = this._physicalStart; |
586 currentRatio = bottomSpace / hiddenContentSize; | 603 currentRatio = bottomSpace / hiddenContentSize; |
587 | 604 |
588 // move tiles from top to bottom | 605 // move tiles from top to bottom |
589 while ( | 606 while ( |
590 // approximate `currentRatio` to `ratio` | 607 // approximate `currentRatio` to `ratio` |
591 currentRatio < ratio && | 608 currentRatio < ratio && |
592 // recycle less physical items than the total | 609 // recycle less physical items than the total |
593 recycledTiles < this._physicalCount && | 610 recycledTiles < this._physicalCount && |
594 // ensure that these recycled tiles are needed | 611 // ensure that these recycled tiles are needed |
595 virtualEnd + recycledTiles < lastVirtualItemIndex | 612 virtualEnd + recycledTiles < lastVirtualItemIndex && |
| 613 // ensure that the tile is not visible |
| 614 this._physicalTop + this._physicalSizes[kth] < scrollTop |
596 ) { | 615 ) { |
597 | 616 |
598 tileHeight = this._physicalSizes[kth] || this._physicalAverage; | 617 tileHeight = this._physicalSizes[kth]; |
599 currentRatio += tileHeight / hiddenContentSize; | 618 currentRatio += tileHeight / hiddenContentSize; |
600 | 619 |
601 this._physicalTop += tileHeight; | 620 this._physicalTop += tileHeight; |
602 recycledTileSet.push(kth); | 621 recycledTileSet.push(kth); |
603 recycledTiles++; | 622 recycledTiles++; |
604 kth = (kth + 1) % this._physicalCount; | 623 kth = (kth + 1) % this._physicalCount; |
605 } | 624 } |
606 } | 625 } |
607 | 626 |
608 if (recycledTiles !== 0) { | 627 if (recycledTiles === 0) { |
| 628 // If the list ever reach this case, the physical average is not signifi
cant enough |
| 629 // to create all the items needed to cover the entire viewport. |
| 630 // e.g. A few items have a height that differs from the average by serve
ral order of magnitude. |
| 631 if (this._increasePoolIfNeeded()) { |
| 632 // yield and set models to the new items |
| 633 this.async(this._update); |
| 634 } |
| 635 } else { |
609 this._virtualStart = this._virtualStart + recycledTiles; | 636 this._virtualStart = this._virtualStart + recycledTiles; |
610 this._update(recycledTileSet, movingUp); | 637 this._update(recycledTileSet, movingUp); |
611 } | 638 } |
612 }, | 639 }, |
613 | 640 |
614 /** | 641 /** |
615 * Update the list of items, starting from the `_virtualStartVal` item. | 642 * Update the list of items, starting from the `_virtualStartVal` item. |
| 643 * @param {!Array<number>=} itemSet |
| 644 * @param {!Array<number>=} movingUp |
616 */ | 645 */ |
617 _update: function(itemSet, movingUp) { | 646 _update: function(itemSet, movingUp) { |
618 // update models | 647 // update models |
619 this._assignModels(itemSet); | 648 this._assignModels(itemSet); |
620 | 649 |
621 // measure heights | 650 // measure heights |
622 // TODO(blasten) pass `recycledTileSet` | 651 this._updateMetrics(itemSet); |
623 this._updateMetrics(); | |
624 | 652 |
625 // adjust offset after measuring | 653 // adjust offset after measuring |
626 if (movingUp) { | 654 if (movingUp) { |
627 while (movingUp.length) { | 655 while (movingUp.length) { |
628 this._physicalTop -= this._physicalSizes[movingUp.pop()]; | 656 this._physicalTop -= this._physicalSizes[movingUp.pop()]; |
629 } | 657 } |
630 } | 658 } |
631 | |
632 // update the position of the items | 659 // update the position of the items |
633 this._positionItems(); | 660 this._positionItems(); |
634 | 661 |
635 // set the scroller size | 662 // set the scroller size |
636 this._updateScrollerSize(); | 663 this._updateScrollerSize(); |
637 | 664 |
638 // increase the pool of physical items if needed | 665 // increase the pool of physical items if needed |
639 if (itemSet = this._increasePoolIfNeeded()) { | 666 if (this._increasePoolIfNeeded()) { |
640 // set models to the new items | 667 // yield set models to the new items |
641 this.async(this._update.bind(this, itemSet)); | 668 this.async(this._update); |
642 } | 669 } |
643 }, | 670 }, |
644 | 671 |
645 /** | 672 /** |
646 * Creates a pool of DOM elements and attaches them to the local dom. | 673 * Creates a pool of DOM elements and attaches them to the local dom. |
647 */ | 674 */ |
648 _createPool: function(size) { | 675 _createPool: function(size) { |
649 var physicalItems = new Array(size); | 676 var physicalItems = new Array(size); |
650 | 677 |
651 this._ensureTemplatized(); | 678 this._ensureTemplatized(); |
652 | 679 |
653 for (var i = 0; i < size; i++) { | 680 for (var i = 0; i < size; i++) { |
654 var inst = this.stamp(null); | 681 var inst = this.stamp(null); |
655 | 682 |
656 // First element child is item; Safari doesn't support children[0] | 683 // First element child is item; Safari doesn't support children[0] |
657 // on a doc fragment | 684 // on a doc fragment |
658 physicalItems[i] = inst.root.querySelector('*'); | 685 physicalItems[i] = inst.root.querySelector('*'); |
659 Polymer.dom(this).appendChild(inst.root); | 686 Polymer.dom(this).appendChild(inst.root); |
660 } | 687 } |
661 | 688 |
662 return physicalItems; | 689 return physicalItems; |
663 }, | 690 }, |
664 | 691 |
665 /** | 692 /** |
666 * Increases the pool size. That is, the physical items in the DOM. | 693 * Increases the pool of physical items only if needed. |
667 * This function will allocate additional physical items | 694 * This function will allocate additional physical items |
668 * (limited by `MAX_PHYSICAL_COUNT`) if the content size is shorter than | 695 * (limited by `MAX_PHYSICAL_COUNT`) if the content size is shorter than |
669 * `_optPhysicalSize` | 696 * `_optPhysicalSize` |
670 * | 697 * |
671 * @return Array | 698 * @return boolean |
672 */ | 699 */ |
673 _increasePoolIfNeeded: function() { | 700 _increasePoolIfNeeded: function() { |
674 if (this._physicalSize >= this._optPhysicalSize || this._physicalAverage =
== 0) { | 701 if (this._physicalAverage === 0) { |
675 return null; | 702 return false; |
676 } | 703 } |
| 704 if (this._physicalBottom < this._scrollBottom || this._physicalTop > this.
_scrollPosition) { |
| 705 return this._increasePool(1); |
| 706 } |
| 707 if (this._physicalSize < this._optPhysicalSize) { |
| 708 return this._increasePool(Math.round((this._optPhysicalSize - this._phys
icalSize) * 1.2 / this._physicalAverage)); |
| 709 } |
| 710 return false; |
| 711 }, |
677 | 712 |
678 // the estimated number of physical items that we will need to reach | 713 /** |
679 // the cap established by `_optPhysicalSize`. | 714 * Increases the pool size. |
680 var missingItems = Math.round( | 715 */ |
681 (this._optPhysicalSize - this._physicalSize) * 1.2 / this._physicalAve
rage | 716 _increasePool: function(missingItems) { |
682 ); | |
683 | |
684 // limit the size | 717 // limit the size |
685 var nextPhysicalCount = Math.min( | 718 var nextPhysicalCount = Math.min( |
686 this._physicalCount + missingItems, | 719 this._physicalCount + missingItems, |
687 this._virtualCount, | 720 this._virtualCount, |
688 MAX_PHYSICAL_COUNT | 721 MAX_PHYSICAL_COUNT |
689 ); | 722 ); |
690 | 723 |
691 var prevPhysicalCount = this._physicalCount; | 724 var prevPhysicalCount = this._physicalCount; |
692 var delta = nextPhysicalCount - prevPhysicalCount; | 725 var delta = nextPhysicalCount - prevPhysicalCount; |
693 | 726 |
694 if (delta <= 0) { | 727 if (delta <= 0) { |
695 return null; | 728 return false; |
696 } | 729 } |
697 | 730 |
698 var newPhysicalItems = this._createPool(delta); | 731 [].push.apply(this._physicalItems, this._createPool(delta)); |
699 var emptyArray = new Array(delta); | 732 [].push.apply(this._physicalSizes, new Array(delta)); |
700 | |
701 [].push.apply(this._physicalItems, newPhysicalItems); | |
702 [].push.apply(this._physicalSizes, emptyArray); | |
703 | 733 |
704 this._physicalCount = prevPhysicalCount + delta; | 734 this._physicalCount = prevPhysicalCount + delta; |
705 | 735 |
706 // fill the array with the new item pos | 736 return true; |
707 while (delta > 0) { | |
708 emptyArray[--delta] = prevPhysicalCount + delta; | |
709 } | |
710 | |
711 return emptyArray; | |
712 }, | 737 }, |
713 | 738 |
714 /** | 739 /** |
715 * Render a new list of items. This method does exactly the same as `update`
, | 740 * Render a new list of items. This method does exactly the same as `update`
, |
716 * but it also ensures that only one `update` cycle is created. | 741 * but it also ensures that only one `update` cycle is created. |
717 */ | 742 */ |
718 _render: function() { | 743 _render: function() { |
719 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; | 744 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; |
720 | 745 |
721 if (this.isAttached && !this._itemsRendered && this._isVisible && requires
Update) { | 746 if (this.isAttached && !this._itemsRendered && this._isVisible && requires
Update) { |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
851 this._virtualCount = this.items ? this.items.length : 0; | 876 this._virtualCount = this.items ? this.items.length : 0; |
852 | 877 |
853 this.debounce('refresh', this._render); | 878 this.debounce('refresh', this._render); |
854 | 879 |
855 } else { | 880 } else { |
856 // update a single item | 881 // update a single item |
857 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); | 882 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); |
858 } | 883 } |
859 }, | 884 }, |
860 | 885 |
| 886 /** |
| 887 * @param {!Array<!PolymerSplice>} splices |
| 888 */ |
861 _adjustVirtualIndex: function(splices) { | 889 _adjustVirtualIndex: function(splices) { |
862 var i, splice, idx; | 890 var i, splice, idx; |
863 | 891 |
864 for (i = 0; i < splices.length; i++) { | 892 for (i = 0; i < splices.length; i++) { |
865 splice = splices[i]; | 893 splice = splices[i]; |
866 | 894 |
867 // deselect removed items | 895 // deselect removed items |
868 splice.removed.forEach(this.$.selector.deselect, this.$.selector); | 896 splice.removed.forEach(this.$.selector.deselect, this.$.selector); |
869 | 897 |
870 idx = splice.index; | 898 idx = splice.index; |
871 // We only need to care about changes happening above the current positi
on | 899 // We only need to care about changes happening above the current positi
on |
872 if (idx >= this._virtualStartVal) { | 900 if (idx >= this._virtualStartVal) { |
873 break; | 901 break; |
874 } | 902 } |
875 | 903 |
876 this._virtualStart = this._virtualStart + | 904 this._virtualStart = this._virtualStart + |
877 Math.max(splice.addedCount - splice.removed.length, idx - this._virt
ualStartVal); | 905 Math.max(splice.addedCount - splice.removed.length, idx - this._virt
ualStartVal); |
878 } | 906 } |
879 }, | 907 }, |
880 | 908 |
881 _scrollHandler: function() { | 909 _scrollHandler: function() { |
882 this._refresh(); | 910 this._refresh(); |
883 }, | 911 }, |
884 | 912 |
885 /** | 913 /** |
886 * Executes a provided function per every physical index in `itemSet` | 914 * Executes a provided function per every physical index in `itemSet` |
887 * `itemSet` default value is equivalent to the entire set of physical index
es. | 915 * `itemSet` default value is equivalent to the entire set of physical index
es. |
| 916 * |
| 917 * @param {!function(number, number)} fn |
| 918 * @param {!Array<number>=} itemSet |
888 */ | 919 */ |
889 _iterateItems: function(fn, itemSet) { | 920 _iterateItems: function(fn, itemSet) { |
890 var pidx, vidx, rtn, i; | 921 var pidx, vidx, rtn, i; |
891 | 922 |
892 if (arguments.length === 2 && itemSet) { | 923 if (arguments.length === 2 && itemSet) { |
893 for (i = 0; i < itemSet.length; i++) { | 924 for (i = 0; i < itemSet.length; i++) { |
894 pidx = itemSet[i]; | 925 pidx = itemSet[i]; |
895 if (pidx >= this._physicalStart) { | 926 if (pidx >= this._physicalStart) { |
896 vidx = this._virtualStartVal + (pidx - this._physicalStart); | 927 vidx = this._virtualStartVal + (pidx - this._physicalStart); |
897 } else { | 928 } else { |
(...skipping 18 matching lines...) Expand all Loading... |
916 for (; pidx < this._physicalStart; pidx++, vidx++) { | 947 for (; pidx < this._physicalStart; pidx++, vidx++) { |
917 if ((rtn = fn.call(this, pidx, vidx)) != null) { | 948 if ((rtn = fn.call(this, pidx, vidx)) != null) { |
918 return rtn; | 949 return rtn; |
919 } | 950 } |
920 } | 951 } |
921 } | 952 } |
922 }, | 953 }, |
923 | 954 |
924 /** | 955 /** |
925 * Assigns the data models to a given set of items. | 956 * Assigns the data models to a given set of items. |
| 957 * @param {!Array<number>=} itemSet |
926 */ | 958 */ |
927 _assignModels: function(itemSet) { | 959 _assignModels: function(itemSet) { |
928 this._iterateItems(function(pidx, vidx) { | 960 this._iterateItems(function(pidx, vidx) { |
929 var el = this._physicalItems[pidx]; | 961 var el = this._physicalItems[pidx]; |
930 var inst = el._templateInstance; | 962 var inst = el._templateInstance; |
931 var item = this.items && this.items[vidx]; | 963 var item = this.items && this.items[vidx]; |
932 | 964 |
933 if (item) { | 965 if (item) { |
934 inst[this.as] = item; | 966 inst[this.as] = item; |
935 inst.__key__ = this._collection.getKey(item); | 967 inst.__key__ = this._collection.getKey(item); |
936 inst[this.selectedAs] = this.$.selector.isSelected(item); | 968 inst[this.selectedAs] = |
| 969 /** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(it
em); |
937 inst[this.indexAs] = vidx; | 970 inst[this.indexAs] = vidx; |
938 el.removeAttribute('hidden'); | 971 el.removeAttribute('hidden'); |
939 this._physicalIndexForKey[inst.__key__] = pidx; | 972 this._physicalIndexForKey[inst.__key__] = pidx; |
940 } else { | 973 } else { |
941 inst.__key__ = null; | 974 inst.__key__ = null; |
942 el.setAttribute('hidden', ''); | 975 el.setAttribute('hidden', ''); |
943 } | 976 } |
944 | 977 |
945 }, itemSet); | 978 }, itemSet); |
946 }, | 979 }, |
947 | 980 |
948 /** | 981 /** |
949 * Updates the height for a given set of items. | 982 * Updates the height for a given set of items. |
| 983 * |
| 984 * @param {!Array<number>=} itemSet |
950 */ | 985 */ |
951 _updateMetrics: function() { | 986 _updateMetrics: function(itemSet) { |
952 var total = 0; | 987 var newPhysicalSize = 0; |
| 988 var oldPhysicalSize = 0; |
953 var prevAvgCount = this._physicalAverageCount; | 989 var prevAvgCount = this._physicalAverageCount; |
954 var prevPhysicalAvg = this._physicalAverage; | 990 var prevPhysicalAvg = this._physicalAverage; |
955 | |
956 // Make sure we distributed all the physical items | 991 // Make sure we distributed all the physical items |
957 // so we can measure them | 992 // so we can measure them |
958 Polymer.dom.flush(); | 993 Polymer.dom.flush(); |
959 | 994 |
960 for (var i = 0; i < this._physicalCount; i++) { | 995 this._iterateItems(function(pidx, vidx) { |
961 this._physicalSizes[i] = this._physicalItems[i].offsetHeight; | 996 oldPhysicalSize += this._physicalSizes[pidx] || 0; |
962 total += this._physicalSizes[i]; | 997 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; |
963 this._physicalAverageCount += this._physicalSizes[i] ? 1 : 0; | 998 newPhysicalSize += this._physicalSizes[pidx]; |
964 } | 999 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; |
| 1000 }, itemSet); |
965 | 1001 |
966 this._physicalSize = total; | 1002 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSiz
e; |
967 this._viewportSize = this._scroller.offsetHeight; | 1003 this._viewportSize = this._scroller.offsetHeight; |
968 | 1004 |
| 1005 // update the average if we measured something |
969 if (this._physicalAverageCount !== prevAvgCount) { | 1006 if (this._physicalAverageCount !== prevAvgCount) { |
970 this._physicalAverage = Math.round( | 1007 this._physicalAverage = Math.round( |
971 ((prevPhysicalAvg * prevAvgCount) + total) / | 1008 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / |
972 this._physicalAverageCount); | 1009 this._physicalAverageCount); |
973 } | 1010 } |
974 }, | 1011 }, |
975 | 1012 |
976 /** | 1013 /** |
977 * Updates the position of the physical items. | 1014 * Updates the position of the physical items. |
978 */ | 1015 */ |
979 _positionItems: function(itemSet) { | 1016 _positionItems: function() { |
980 this._adjustScrollPosition(); | 1017 this._adjustScrollPosition(); |
981 | 1018 |
982 var y = this._physicalTop; | 1019 var y = this._physicalTop; |
983 | 1020 |
984 this._iterateItems(function(pidx) { | 1021 this._iterateItems(function(pidx) { |
985 | 1022 |
986 this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pid
x]); | 1023 this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pid
x]); |
987 y += this._physicalSizes[pidx]; | 1024 y += this._physicalSizes[pidx]; |
988 | 1025 |
989 }, itemSet); | 1026 }); |
990 }, | 1027 }, |
991 | 1028 |
992 /** | 1029 /** |
993 * Adjusts the scroll position when it was overestimated. | 1030 * Adjusts the scroll position when it was overestimated. |
994 */ | 1031 */ |
995 _adjustScrollPosition: function() { | 1032 _adjustScrollPosition: function() { |
996 var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop : | 1033 var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop : |
997 Math.min(this._scrollPosition + this._physicalTop, 0); | 1034 Math.min(this._scrollPosition + this._physicalTop, 0); |
998 | 1035 |
999 if (deltaHeight) { | 1036 if (deltaHeight) { |
(...skipping 11 matching lines...) Expand all Loading... |
1011 */ | 1048 */ |
1012 _resetScrollPosition: function(pos) { | 1049 _resetScrollPosition: function(pos) { |
1013 if (this._scroller) { | 1050 if (this._scroller) { |
1014 this._scroller.scrollTop = pos; | 1051 this._scroller.scrollTop = pos; |
1015 this._scrollPosition = this._scroller.scrollTop; | 1052 this._scrollPosition = this._scroller.scrollTop; |
1016 } | 1053 } |
1017 }, | 1054 }, |
1018 | 1055 |
1019 /** | 1056 /** |
1020 * Sets the scroll height, that's the height of the content, | 1057 * Sets the scroll height, that's the height of the content, |
| 1058 * |
| 1059 * @param {boolean=} forceUpdate If true, updates the height no matter what. |
1021 */ | 1060 */ |
1022 _updateScrollerSize: function(forceUpdate) { | 1061 _updateScrollerSize: function(forceUpdate) { |
1023 this._estScrollHeight = (this._physicalBottom + | 1062 this._estScrollHeight = (this._physicalBottom + |
1024 Math.max(this._virtualCount - this._physicalCount - this._virtualStart
Val, 0) * this._physicalAverage); | 1063 Math.max(this._virtualCount - this._physicalCount - this._virtualStart
Val, 0) * this._physicalAverage); |
1025 | 1064 |
1026 forceUpdate = forceUpdate || this._scrollHeight === 0; | 1065 forceUpdate = forceUpdate || this._scrollHeight === 0; |
1027 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight
- this._physicalSize; | 1066 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight
- this._physicalSize; |
1028 | 1067 |
1029 // amortize height adjustment, so it won't trigger repaints very often | 1068 // amortize height adjustment, so it won't trigger repaints very often |
1030 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >=
this._optPhysicalSize) { | 1069 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >=
this._optPhysicalSize) { |
1031 this.$.items.style.height = this._estScrollHeight + 'px'; | 1070 this.$.items.style.height = this._estScrollHeight + 'px'; |
1032 this._scrollHeight = this._estScrollHeight; | 1071 this._scrollHeight = this._estScrollHeight; |
1033 } | 1072 } |
1034 }, | 1073 }, |
1035 | 1074 |
1036 /** | 1075 /** |
1037 * Scroll to a specific item in the virtual list regardless | 1076 * Scroll to a specific item in the virtual list regardless |
1038 * of the physical items in the DOM tree. | 1077 * of the physical items in the DOM tree. |
1039 * | 1078 * |
1040 * @method scrollToIndex | 1079 * @method scrollToIndex |
1041 * @param {number} idx The index of the item | 1080 * @param {number} idx The index of the item |
1042 */ | 1081 */ |
1043 scrollToIndex: function(idx) { | 1082 scrollToIndex: function(idx) { |
1044 if (typeof idx !== 'number') { | 1083 if (typeof idx !== 'number') { |
1045 return; | 1084 return; |
1046 } | 1085 } |
1047 | 1086 |
1048 var itemSet; | |
1049 var firstVisible = this.firstVisibleIndex; | 1087 var firstVisible = this.firstVisibleIndex; |
1050 | 1088 |
1051 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); | 1089 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); |
1052 | 1090 |
1053 // start at the previous virtual item | 1091 // start at the previous virtual item |
1054 // so we have a item above the first visible item | 1092 // so we have a item above the first visible item |
1055 this._virtualStart = idx - 1; | 1093 this._virtualStart = idx - 1; |
1056 | 1094 |
1057 // assign new models | 1095 // assign new models |
1058 this._assignModels(); | 1096 this._assignModels(); |
(...skipping 19 matching lines...) Expand all Loading... |
1078 // update the scroller size | 1116 // update the scroller size |
1079 this._updateScrollerSize(true); | 1117 this._updateScrollerSize(true); |
1080 | 1118 |
1081 // update the position of the items | 1119 // update the position of the items |
1082 this._positionItems(); | 1120 this._positionItems(); |
1083 | 1121 |
1084 // set the new scroll position | 1122 // set the new scroll position |
1085 this._resetScrollPosition(this._physicalTop + targetOffsetTop + 1); | 1123 this._resetScrollPosition(this._physicalTop + targetOffsetTop + 1); |
1086 | 1124 |
1087 // increase the pool of physical items if needed | 1125 // increase the pool of physical items if needed |
1088 if (itemSet = this._increasePoolIfNeeded()) { | 1126 if (this._increasePoolIfNeeded()) { |
1089 // set models to the new items | 1127 // yield set models to the new items |
1090 this.async(this._update.bind(this, itemSet)); | 1128 this.async(this._update); |
1091 } | 1129 } |
1092 | |
1093 // clear cached visible index | 1130 // clear cached visible index |
1094 this._firstVisibleIndexVal = null; | 1131 this._firstVisibleIndexVal = null; |
1095 }, | 1132 }, |
1096 | 1133 |
1097 /** | 1134 /** |
1098 * Reset the physical average and the average count. | 1135 * Reset the physical average and the average count. |
1099 */ | 1136 */ |
1100 _resetAverage: function() { | 1137 _resetAverage: function() { |
1101 this._physicalAverage = 0; | 1138 this._physicalAverage = 0; |
1102 this._physicalAverageCount = 0; | 1139 this._physicalAverageCount = 0; |
1103 }, | 1140 }, |
1104 | 1141 |
1105 /** | 1142 /** |
1106 * A handler for the `resize` event triggered by `IronResizableBehavior` | 1143 * A handler for the `iron-resize` event triggered by `IronResizableBehavior
` |
1107 * when the element is resized. | 1144 * when the element is resized. |
1108 */ | 1145 */ |
1109 _resizeHandler: function() { | 1146 _resizeHandler: function() { |
1110 this.debounce('resize', function() { | 1147 this.debounce('resize', function() { |
1111 this._render(); | 1148 this._render(); |
1112 if (this._itemsRendered && this._physicalItems && this._isVisible) { | 1149 if (this._itemsRendered && this._physicalItems && this._isVisible) { |
1113 this._resetAverage(); | 1150 this._resetAverage(); |
1114 this.updateViewportBoundaries(); | 1151 this.updateViewportBoundaries(); |
1115 this.scrollToIndex(this.firstVisibleIndex); | 1152 this.scrollToIndex(this.firstVisibleIndex); |
1116 } | 1153 } |
1117 }); | 1154 }); |
1118 }, | 1155 }, |
1119 | 1156 |
1120 _getModelFromItem: function(item) { | 1157 _getModelFromItem: function(item) { |
1121 var key = this._collection.getKey(item); | 1158 var key = this._collection.getKey(item); |
1122 var pidx = this._physicalIndexForKey[key]; | 1159 var pidx = this._physicalIndexForKey[key]; |
1123 | 1160 |
1124 if (pidx !== undefined) { | 1161 if (pidx !== undefined) { |
1125 return this._physicalItems[pidx]._templateInstance; | 1162 return this._physicalItems[pidx]._templateInstance; |
1126 } | 1163 } |
1127 return null; | 1164 return null; |
1128 }, | 1165 }, |
1129 | 1166 |
1130 /** | 1167 /** |
| 1168 * Gets a valid item instance from its index or the object value. |
| 1169 * |
| 1170 * @param {(Object|number)} item The item object or its index |
| 1171 */ |
| 1172 _getNormalizedItem: function(item) { |
| 1173 if (typeof item === 'number') { |
| 1174 item = this.items[item]; |
| 1175 if (!item) { |
| 1176 throw new RangeError('<item> not found'); |
| 1177 } |
| 1178 } else if (this._collection.getKey(item) === undefined) { |
| 1179 throw new TypeError('<item> should be a valid item'); |
| 1180 } |
| 1181 return item; |
| 1182 }, |
| 1183 |
| 1184 /** |
1131 * Select the list item at the given index. | 1185 * Select the list item at the given index. |
1132 * | 1186 * |
1133 * @method selectItem | 1187 * @method selectItem |
1134 * @param {(Object|number)} item the item object or its index | 1188 * @param {(Object|number)} item The item object or its index |
1135 */ | 1189 */ |
1136 selectItem: function(item) { | 1190 selectItem: function(item) { |
1137 if (typeof item === 'number') { | 1191 item = this._getNormalizedItem(item); |
1138 item = this.items[item]; | |
1139 if (!item) { | |
1140 throw new RangeError('<item> not found'); | |
1141 } | |
1142 } else { | |
1143 if (this._collection.getKey(item) === undefined) { | |
1144 throw new TypeError('<item> should be a valid item'); | |
1145 } | |
1146 } | |
1147 | |
1148 var model = this._getModelFromItem(item); | 1192 var model = this._getModelFromItem(item); |
1149 | 1193 |
1150 if (!this.multiSelection && this.selectedItem) { | 1194 if (!this.multiSelection && this.selectedItem) { |
1151 this.deselectItem(this.selectedItem); | 1195 this.deselectItem(this.selectedItem); |
1152 } | 1196 } |
1153 if (model) { | 1197 if (model) { |
1154 model[this.selectedAs] = true; | 1198 model[this.selectedAs] = true; |
1155 } | 1199 } |
1156 this.$.selector.select(item); | 1200 this.$.selector.select(item); |
1157 }, | 1201 }, |
1158 | 1202 |
1159 /** | 1203 /** |
1160 * Deselects the given item list if it is already selected. | 1204 * Deselects the given item list if it is already selected. |
1161 * | 1205 * |
| 1206 |
1162 * @method deselect | 1207 * @method deselect |
1163 * @param {(Object|number)} item the item object or its index | 1208 * @param {(Object|number)} item The item object or its index |
1164 */ | 1209 */ |
1165 deselectItem: function(item) { | 1210 deselectItem: function(item) { |
1166 if (typeof item === 'number') { | 1211 item = this._getNormalizedItem(item); |
1167 item = this.items[item]; | |
1168 if (!item) { | |
1169 throw new RangeError('<item> not found'); | |
1170 } | |
1171 } else { | |
1172 if (this._collection.getKey(item) === undefined) { | |
1173 throw new TypeError('<item> should be a valid item'); | |
1174 } | |
1175 } | |
1176 | |
1177 var model = this._getModelFromItem(item); | 1212 var model = this._getModelFromItem(item); |
1178 | 1213 |
1179 if (model) { | 1214 if (model) { |
1180 model[this.selectedAs] = false; | 1215 model[this.selectedAs] = false; |
1181 } | 1216 } |
1182 this.$.selector.deselect(item); | 1217 this.$.selector.deselect(item); |
1183 }, | 1218 }, |
1184 | 1219 |
1185 /** | 1220 /** |
1186 * Select or deselect a given item depending on whether the item | 1221 * Select or deselect a given item depending on whether the item |
1187 * has already been selected. | 1222 * has already been selected. |
1188 * | 1223 * |
1189 * @method toggleSelectionForItem | 1224 * @method toggleSelectionForItem |
1190 * @param {(Object|number)} item the item object or its index | 1225 * @param {(Object|number)} item The item object or its index |
1191 */ | 1226 */ |
1192 toggleSelectionForItem: function(item) { | 1227 toggleSelectionForItem: function(item) { |
1193 var item = typeof item === 'number' ? this.items[item] : item; | 1228 item = this._getNormalizedItem(item); |
1194 if (this.$.selector.isSelected(item)) { | 1229 if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item
)) { |
1195 this.deselectItem(item); | 1230 this.deselectItem(item); |
1196 } else { | 1231 } else { |
1197 this.selectItem(item); | 1232 this.selectItem(item); |
1198 } | 1233 } |
1199 }, | 1234 }, |
1200 | 1235 |
1201 /** | 1236 /** |
1202 * Clears the current selection state of the list. | 1237 * Clears the current selection state of the list. |
1203 * | 1238 * |
1204 * @method clearSelection | 1239 * @method clearSelection |
1205 */ | 1240 */ |
1206 clearSelection: function() { | 1241 clearSelection: function() { |
1207 function unselect(item) { | 1242 function unselect(item) { |
1208 var model = this._getModelFromItem(item); | 1243 var model = this._getModelFromItem(item); |
1209 if (model) { | 1244 if (model) { |
1210 model[this.selectedAs] = false; | 1245 model[this.selectedAs] = false; |
1211 } | 1246 } |
1212 } | 1247 } |
1213 | 1248 |
1214 if (Array.isArray(this.selectedItems)) { | 1249 if (Array.isArray(this.selectedItems)) { |
1215 this.selectedItems.forEach(unselect, this); | 1250 this.selectedItems.forEach(unselect, this); |
1216 } else if (this.selectedItem) { | 1251 } else if (this.selectedItem) { |
1217 unselect.call(this, this.selectedItem); | 1252 unselect.call(this, this.selectedItem); |
1218 } | 1253 } |
1219 | 1254 |
1220 this.$.selector.clearSelection(); | 1255 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection(); |
1221 }, | 1256 }, |
1222 | 1257 |
1223 /** | 1258 /** |
1224 * Add an event listener to `tap` if `selectionEnabled` is true, | 1259 * Add an event listener to `tap` if `selectionEnabled` is true, |
1225 * it will remove the listener otherwise. | 1260 * it will remove the listener otherwise. |
1226 */ | 1261 */ |
1227 _selectionEnabledChanged: function(selectionEnabled) { | 1262 _selectionEnabledChanged: function(selectionEnabled) { |
1228 if (selectionEnabled) { | 1263 if (selectionEnabled) { |
1229 this.listen(this, 'tap', '_selectionHandler'); | 1264 this.listen(this, 'tap', '_selectionHandler'); |
| 1265 this.listen(this, 'keypress', '_selectionHandler'); |
1230 } else { | 1266 } else { |
1231 this.unlisten(this, 'tap', '_selectionHandler'); | 1267 this.unlisten(this, 'tap', '_selectionHandler'); |
| 1268 this.unlisten(this, 'keypress', '_selectionHandler'); |
1232 } | 1269 } |
1233 }, | 1270 }, |
1234 | 1271 |
1235 /** | 1272 /** |
1236 * Select an item from an event object. | 1273 * Select an item from an event object. |
1237 */ | 1274 */ |
1238 _selectionHandler: function(e) { | 1275 _selectionHandler: function(e) { |
1239 var model = this.modelForElement(e.target); | 1276 if (e.type !== 'keypress' || e.keyCode === 13) { |
1240 if (model) { | 1277 var model = this.modelForElement(e.target); |
1241 this.toggleSelectionForItem(model[this.as]); | 1278 if (model) { |
| 1279 this.toggleSelectionForItem(model[this.as]); |
| 1280 } |
1242 } | 1281 } |
1243 }, | 1282 }, |
1244 | 1283 |
1245 _multiSelectionChanged: function(multiSelection) { | 1284 _multiSelectionChanged: function(multiSelection) { |
1246 this.clearSelection(); | 1285 this.clearSelection(); |
1247 this.$.selector.multi = multiSelection; | 1286 this.$.selector.multi = multiSelection; |
| 1287 }, |
| 1288 |
| 1289 /** |
| 1290 * Updates the size of an item. |
| 1291 * |
| 1292 * @method updateSizeForItem |
| 1293 * @param {(Object|number)} item The item object or its index |
| 1294 */ |
| 1295 updateSizeForItem: function(item) { |
| 1296 item = this._getNormalizedItem(item); |
| 1297 var key = this._collection.getKey(item); |
| 1298 var pidx = this._physicalIndexForKey[key]; |
| 1299 |
| 1300 if (pidx !== undefined) { |
| 1301 this._updateMetrics([pidx]); |
| 1302 this._positionItems(); |
| 1303 } |
1248 } | 1304 } |
1249 }); | 1305 }); |
1250 | 1306 |
1251 })(); | 1307 })(); |
1252 | 1308 |
1253 </script> | 1309 </script> |
OLD | NEW |