| 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 |