Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // require: array_data_model.js | 5 // require: array_data_model.js |
| 6 // require: list_selection_model.js | 6 // require: list_selection_model.js |
| 7 // require: list_selection_controller.js | 7 // require: list_selection_controller.js |
| 8 // require: list_item.js | 8 // require: list_item.js |
| 9 | 9 |
| 10 /** | 10 /** |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 109 * is needed. Note that lead item is allowed to have a different height, to | 109 * is needed. Note that lead item is allowed to have a different height, to |
| 110 * accommodate lists where a single item at a time can be expanded to show | 110 * accommodate lists where a single item at a time can be expanded to show |
| 111 * more detail. | 111 * more detail. |
| 112 * @type {{height: number, marginVertical: number, width: number, | 112 * @type {{height: number, marginVertical: number, width: number, |
| 113 * marginHorizontal: number}} | 113 * marginHorizontal: number}} |
| 114 * @private | 114 * @private |
| 115 */ | 115 */ |
| 116 measured_: undefined, | 116 measured_: undefined, |
| 117 | 117 |
| 118 /** | 118 /** |
| 119 * The height of the lead item, which is allowed to have a different height | 119 * The height of the lead item, which is allowed to have a different height |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
Is there some way we can simplify this so that we
yoshiki
2011/11/28 11:03:31
Done.
| |
| 120 * than other list items to accommodate lists where a single item at a time | 120 * than other list items to accommodate lists where a single item at a time |
| 121 * can be expanded to show more detail. It is explicitly set by client code | 121 * can be expanded to show more detail. It is explicitly set by client code |
| 122 * when the height of the lead item is changed with {@code set | 122 * when the height of the lead item is changed with {@code set |
| 123 * leadItemHeight}, and presumed equal to {@code itemHeight_} otherwise. | 123 * leadItemHeight}, and presumed equal to {@code itemHeight_} otherwise. |
| 124 * This value will be ignored when {@code fixedHeight_} is false. | |
| 124 * @type {number} | 125 * @type {number} |
| 125 * @private | 126 * @private |
| 126 */ | 127 */ |
| 127 leadItemHeight_: 0, | 128 leadItemHeight_: 0, |
| 128 | 129 |
| 129 /** | 130 /** |
| 130 * Whether or not the list is autoexpanding. If true, the list resizes | 131 * Whether or not the list is autoexpanding. If true, the list resizes |
| 131 * its height to accomadate all children. | 132 * its height to accomadate all children. |
| 132 * @type {boolean} | 133 * @type {boolean} |
| 133 * @private | 134 * @private |
| 134 */ | 135 */ |
| 135 autoExpands_: false, | 136 autoExpands_: false, |
| 136 | 137 |
| 137 /** | 138 /** |
| 139 * Cached list items. | |
| 140 * @type {Array<cr.ui.ListItem>} | |
| 141 * @private | |
| 142 */ | |
| 143 cachedItems_: [], | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
These two arrays needs to go on the instance of th
yoshiki
2011/11/28 11:03:31
Done.
| |
| 144 /** | |
| 145 * Cached sized of list items. | |
| 146 * @type {Array<cr.ui.ListItem>} | |
| 147 * @private | |
| 148 */ | |
| 149 cachedItemSizes_: [], | |
| 150 | |
| 151 /** | |
| 152 * Whether or not the list view has a blank space below the last row. | |
| 153 * @type {boolean} | |
| 154 * @private | |
| 155 */ | |
| 156 remainSpace_: true, | |
| 157 | |
| 158 /** | |
| 138 * Function used to create grid items. | 159 * Function used to create grid items. |
| 139 * @type {function(): !ListItem} | 160 * @type {function(): !ListItem} |
| 140 * @private | 161 * @private |
| 141 */ | 162 */ |
| 142 itemConstructor_: cr.ui.ListItem, | 163 itemConstructor_: cr.ui.ListItem, |
| 143 | 164 |
| 144 /** | 165 /** |
| 145 * Function used to create grid items. | 166 * Function used to create grid items. |
| 146 * @type {function(): !ListItem} | 167 * @type {function(): !ListItem} |
| 147 */ | 168 */ |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 170 this.boundHandleDataModelChange_ = | 191 this.boundHandleDataModelChange_ = |
| 171 this.handleDataModelChange_.bind(this); | 192 this.handleDataModelChange_.bind(this); |
| 172 } | 193 } |
| 173 | 194 |
| 174 if (this.dataModel_) { | 195 if (this.dataModel_) { |
| 175 this.dataModel_.removeEventListener( | 196 this.dataModel_.removeEventListener( |
| 176 'permuted', | 197 'permuted', |
| 177 this.boundHandleDataModelPermuted_); | 198 this.boundHandleDataModelPermuted_); |
| 178 this.dataModel_.removeEventListener('change', | 199 this.dataModel_.removeEventListener('change', |
| 179 this.boundHandleDataModelChange_); | 200 this.boundHandleDataModelChange_); |
| 201 this.dataModel_.removeEventListener('splice', | |
| 202 this.boundHandleDataModelChange_); | |
| 180 } | 203 } |
| 181 | 204 |
| 182 this.dataModel_ = dataModel; | 205 this.dataModel_ = dataModel; |
| 183 | 206 |
| 184 this.cachedItems_ = {}; | 207 this.cachedItems_ = {}; |
| 185 this.selectionModel.clear(); | 208 this.selectionModel.clear(); |
| 186 if (dataModel) | 209 if (dataModel) |
| 187 this.selectionModel.adjustLength(dataModel.length); | 210 this.selectionModel.adjustLength(dataModel.length); |
| 188 | 211 |
| 189 if (this.dataModel_) { | 212 if (this.dataModel_) { |
| 190 this.dataModel_.addEventListener( | 213 this.dataModel_.addEventListener( |
| 191 'permuted', | 214 'permuted', |
| 192 this.boundHandleDataModelPermuted_); | 215 this.boundHandleDataModelPermuted_); |
| 193 this.dataModel_.addEventListener('change', | 216 this.dataModel_.addEventListener('change', |
| 194 this.boundHandleDataModelChange_); | 217 this.boundHandleDataModelChange_); |
| 218 this.dataModel_.addEventListener('splice', | |
| 219 this.boundHandleDataModelChange_); | |
| 195 } | 220 } |
| 196 | 221 |
| 197 this.redraw(); | 222 this.redraw(); |
| 198 } | 223 } |
| 199 }, | 224 }, |
| 200 | 225 |
| 201 get dataModel() { | 226 get dataModel() { |
| 202 return this.dataModel_; | 227 return this.dataModel_; |
| 203 }, | 228 }, |
| 204 | 229 |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 242 return this.autoExpands_; | 267 return this.autoExpands_; |
| 243 }, | 268 }, |
| 244 set autoExpands(autoExpands) { | 269 set autoExpands(autoExpands) { |
| 245 if (this.autoExpands_ == autoExpands) | 270 if (this.autoExpands_ == autoExpands) |
| 246 return; | 271 return; |
| 247 this.autoExpands_ = autoExpands; | 272 this.autoExpands_ = autoExpands; |
| 248 this.redraw(); | 273 this.redraw(); |
| 249 }, | 274 }, |
| 250 | 275 |
| 251 /** | 276 /** |
| 277 * Whether or not the rows on list have various heights. | |
| 278 * @type {boolean} | |
| 279 */ | |
| 280 get fixedHeight() { | |
| 281 return this.fixedHeight; | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
iloop
I assume you meant this.fixedHeight_
yoshiki
2011/11/28 11:03:31
Done.
| |
| 282 }, | |
| 283 set fixedHeight(fixedHeight) { | |
| 284 if (this.fixedHeight_ == fixedHeight) | |
| 285 return; | |
| 286 this.fixedHeight_ = fixedHeight; | |
| 287 this.redraw(); | |
| 288 }, | |
| 289 | |
| 290 /** | |
| 252 * Convenience alias for selectionModel.selectedItem | 291 * Convenience alias for selectionModel.selectedItem |
| 253 * @type {cr.ui.ListItem} | 292 * @type {cr.ui.ListItem} |
| 254 */ | 293 */ |
| 255 get selectedItem() { | 294 get selectedItem() { |
| 256 var dataModel = this.dataModel; | 295 var dataModel = this.dataModel; |
| 257 if (dataModel) { | 296 if (dataModel) { |
| 258 var index = this.selectionModel.selectedIndex; | 297 var index = this.selectionModel.selectedIndex; |
| 259 if (index != -1) | 298 if (index != -1) |
| 260 return dataModel.item(index); | 299 return dataModel.item(index); |
| 261 } | 300 } |
| 262 return null; | 301 return null; |
| 263 }, | 302 }, |
| 264 set selectedItem(selectedItem) { | 303 set selectedItem(selectedItem) { |
| 265 var dataModel = this.dataModel; | 304 var dataModel = this.dataModel; |
| 266 if (dataModel) { | 305 if (dataModel) { |
| 267 var index = this.dataModel.indexOf(selectedItem); | 306 var index = this.dataModel.indexOf(selectedItem); |
| 268 this.selectionModel.selectedIndex = index; | 307 this.selectionModel.selectedIndex = index; |
| 269 } | 308 } |
| 270 }, | 309 }, |
| 271 | 310 |
| 272 /** | 311 /** |
| 273 * The height of the lead item. | 312 * The height of the lead item. |
| 274 * If set to 0, resets to the same height as other items. | 313 * If set to 0, resets to the same height as other items. |
| 275 * @type {number} | 314 * @type {number} |
| 276 */ | 315 */ |
| 277 get leadItemHeight() { | 316 get leadItemHeight() { |
| 278 return this.leadItemHeight_ || this.getItemHeight_(); | 317 return this.leadItemHeight_ || this.getDefaultItemHeight_(); |
| 279 }, | 318 }, |
| 280 set leadItemHeight(height) { | 319 set leadItemHeight(height) { |
| 281 if (height) { | 320 if (height) { |
| 282 var size = this.getItemSize_(); | 321 var size = this.getDefaultItemSize_(); |
| 283 this.leadItemHeight_ = Math.max(0, height + size.marginVertical); | 322 this.leadItemHeight_ = Math.max(0, height + size.marginVertical); |
| 284 } else { | 323 } else { |
| 285 this.leadItemHeight_ = 0; | 324 this.leadItemHeight_ = 0; |
| 286 } | 325 } |
| 287 }, | 326 }, |
| 288 | 327 |
| 289 /** | 328 /** |
| 290 * Convenience alias for selectionModel.selectedItems | 329 * Convenience alias for selectionModel.selectedItems |
| 291 * @type {!Array<cr.ui.ListItem>} | 330 * @type {!Array<cr.ui.ListItem>} |
| 292 */ | 331 */ |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 356 this.addEventListener('blur', this.handleElementBlur_, true); | 395 this.addEventListener('blur', this.handleElementBlur_, true); |
| 357 this.addEventListener('scroll', this.handleScroll.bind(this)); | 396 this.addEventListener('scroll', this.handleScroll.bind(this)); |
| 358 this.setAttribute('role', 'listbox'); | 397 this.setAttribute('role', 'listbox'); |
| 359 | 398 |
| 360 // Make list focusable | 399 // Make list focusable |
| 361 if (!this.hasAttribute('tabindex')) | 400 if (!this.hasAttribute('tabindex')) |
| 362 this.tabIndex = 0; | 401 this.tabIndex = 0; |
| 363 }, | 402 }, |
| 364 | 403 |
| 365 /** | 404 /** |
| 366 * @return {number} The height of an item, measuring it if necessary. | 405 * @return {number} The height of default item, measuring it if necessary. |
| 367 * @private | 406 * @private |
| 368 */ | 407 */ |
| 369 getItemHeight_: function() { | 408 getDefaultItemHeight_: function() { |
| 370 return this.getItemSize_().height; | 409 return this.getDefaultItemSize_().height; |
| 410 }, | |
| 411 | |
| 412 getItemHeightByIndex_: function(index) { | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
missing jsdoc
yoshiki
2011/11/28 11:03:31
Done.
| |
| 413 if (this.cachedItemSizes_[index]) | |
| 414 return this.cachedItemSizes_[index].height; | |
| 415 | |
| 416 var item = this.getListItemByIndex(index); | |
| 417 return item ? this.getItemSize_(item).height : | |
| 418 this.getDefaultItemHeight_(); | |
| 371 }, | 419 }, |
| 372 | 420 |
| 373 /** | 421 /** |
| 374 * @return {number} The width of an item, measuring it if necessary. | 422 * @return {number} The width of default item, measuring it if necessary. |
| 375 * @private | 423 * @private |
| 376 */ | 424 */ |
| 377 getItemWidth_: function() { | 425 getDefaultItemWidth_: function() { |
| 378 return this.getItemSize_().width; | 426 return this.getDefaultItemSize_().width; |
| 379 }, | 427 }, |
| 380 | 428 |
| 381 /** | 429 /** |
| 382 * @return {{height: number, width: number}} The height and width | 430 * @return {{height: number, width: number}} The height and width |
| 383 * of an item, measuring it if necessary. | 431 * of default item, measuring it if necessary. |
| 384 * @private | 432 * @private |
| 385 */ | 433 */ |
| 386 getItemSize_: function() { | 434 getDefaultItemSize_: function() { |
| 387 if (!this.measured_ || !this.measured_.height) { | 435 if (!this.measured_ || !this.measured_.height) { |
| 388 this.measured_ = measureItem(this); | 436 this.measured_ = measureItem(this); |
| 389 } | 437 } |
| 390 return this.measured_; | 438 return this.measured_; |
| 391 }, | 439 }, |
| 392 | 440 |
| 393 /** | 441 /** |
| 442 * @return {{height: number, width: number}} The height and width | |
| 443 * of an item, measuring it if necessary. | |
| 444 * @private | |
| 445 */ | |
| 446 getItemSize_: function(item) { | |
| 447 if (this.cachedItemSizes_[item.listIndex]) | |
| 448 return this.cachedItemSizes_[item.listIndex]; | |
| 449 | |
| 450 var size = measureItem(this, item); | |
| 451 if (!isNaN(size.height) && !isNaN(size.weight)) | |
| 452 this.cachedItemSizes_[item.listIndex] = size; | |
| 453 | |
| 454 return size; | |
| 455 }, | |
| 456 | |
| 457 /** | |
| 394 * Callback for the double click event. | 458 * Callback for the double click event. |
| 395 * @param {Event} e The mouse event object. | 459 * @param {Event} e The mouse event object. |
| 396 * @private | 460 * @private |
| 397 */ | 461 */ |
| 398 handleDoubleClick_: function(e) { | 462 handleDoubleClick_: function(e) { |
| 399 if (this.disabled) | 463 if (this.disabled) |
| 400 return; | 464 return; |
| 401 | 465 |
| 402 var target = this.getListItemAncestor(e.target); | 466 var target = this.getListItemAncestor(e.target); |
| 403 if (target) | 467 if (target) |
| (...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 595 sm.adjustLength(e.newLength); | 659 sm.adjustLength(e.newLength); |
| 596 sm.adjustToReordering(e.permutation); | 660 sm.adjustToReordering(e.permutation); |
| 597 | 661 |
| 598 this.endBatchUpdates(); | 662 this.endBatchUpdates(); |
| 599 | 663 |
| 600 if (sm.leadIndex != -1) | 664 if (sm.leadIndex != -1) |
| 601 this.scrollIndexIntoView(sm.leadIndex); | 665 this.scrollIndexIntoView(sm.leadIndex); |
| 602 }, | 666 }, |
| 603 | 667 |
| 604 handleDataModelChange_: function(e) { | 668 handleDataModelChange_: function(e) { |
| 605 if (e.index >= this.firstIndex_ && e.index < this.lastIndex_) { | 669 if (e.index >= this.firstIndex_ && |
| 670 (e.index < this.lastIndex_ || this.remainSpace_)) { | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
rename to remainingSpace_?
yoshiki
2011/11/28 11:03:31
Done.
| |
| 606 if (this.cachedItems_[e.index]) | 671 if (this.cachedItems_[e.index]) |
| 607 delete this.cachedItems_[e.index]; | 672 delete this.cachedItems_[e.index]; |
| 608 this.redraw(); | 673 this.redraw(); |
| 609 } | 674 } |
| 610 }, | 675 }, |
| 611 | 676 |
| 612 /** | 677 /** |
| 613 * @param {number} index The index of the item. | 678 * @param {number} index The index of the item. |
| 614 * @return {number} The top position of the item inside the list, not taking | 679 * @return {number} The top position of the item inside the list. |
| 615 * into account lead item. May vary in the case of multiple columns. | |
| 616 */ | 680 */ |
| 617 getItemTop: function(index) { | 681 getItemTop: function(index) { |
| 618 return index * this.getItemHeight_(); | 682 if (this.fixedHeight_) { |
| 683 var itemHeight = this.getDefaultItemHeight_(); | |
| 684 var top = index * itemHeight; | |
| 685 if (this.selectionModel.leadIndex > -1 && | |
| 686 this.selectionModel.leadIndex < index) { | |
| 687 top += this.leadItemHeight - itemHeight; | |
| 688 } | |
| 689 return top; | |
| 690 } else { | |
| 691 var top = 0; | |
| 692 for (var i = 0; i < index; i++) | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
Only skip {} for if/else
yoshiki
2011/11/28 11:03:31
Done.
| |
| 693 top += this.getItemHeightByIndex_(i); | |
| 694 return top; | |
| 695 } | |
| 619 }, | 696 }, |
| 620 | 697 |
| 621 /** | 698 /** |
| 622 * @param {number} index The index of the item. | 699 * @param {number} index The index of the item. |
| 623 * @return {number} The row of the item. May vary in the case | 700 * @return {number} The row of the item. May vary in the case |
| 624 * of multiple columns. | 701 * of multiple columns. |
| 625 */ | 702 */ |
| 626 getItemRow: function(index) { | 703 getItemRow: function(index) { |
| 627 return index; | 704 return index; |
| 628 }, | 705 }, |
| 629 | 706 |
| 630 /** | 707 /** |
| 631 * @param {number} row The row. | 708 * @param {number} row The row. |
| 632 * @return {number} The index of the first item in the row. | 709 * @return {number} The index of the first item in the row. |
| 633 */ | 710 */ |
| 634 getFirstItemInRow: function(row) { | 711 getFirstItemInRow: function(row) { |
| 635 return row; | 712 return row; |
| 636 }, | 713 }, |
| 637 | 714 |
| 638 /** | 715 /** |
| 639 * Ensures that a given index is inside the viewport. | 716 * Ensures that a given index is inside the viewport. |
| 640 * @param {number} index The index of the item to scroll into view. | 717 * @param {number} index The index of the item to scroll into view. |
| 641 * @return {boolean} Whether any scrolling was needed. | 718 * @return {boolean} Whether any scrolling was needed. |
| 642 */ | 719 */ |
| 643 scrollIndexIntoView: function(index) { | 720 scrollIndexIntoView: function(index) { |
| 644 var dataModel = this.dataModel; | 721 var dataModel = this.dataModel; |
| 645 if (!dataModel || index < 0 || index >= dataModel.length) | 722 if (!dataModel || index < 0 || index >= dataModel.length) |
| 646 return false; | 723 return false; |
| 647 | 724 |
| 648 var itemHeight = this.getItemHeight_(); | 725 var itemHeight = this.getItemHeightByIndex_(index); |
| 649 var scrollTop = this.scrollTop; | 726 var scrollTop = this.scrollTop; |
| 650 var top = this.getItemTop(index); | 727 var top = this.getItemTop(index); |
| 651 var leadIndex = this.selectionModel.leadIndex; | 728 var clientHeight = this.clientHeight; |
| 652 | 729 |
| 653 // Adjust for the lead item if it is above the given index. | 730 var self = this; |
| 654 if (leadIndex > -1 && leadIndex < index) | 731 // Function to adjust the tops of viewport and row. |
| 655 top += this.leadItemHeight - itemHeight; | 732 var scrollToAdjustTopFunc = function() { |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
function scrollToAdjustTop() {
yoshiki
2011/11/28 11:03:31
Done.
| |
| 656 else if (leadIndex == index) | 733 self.scrollTop = top; |
| 657 itemHeight = this.leadItemHeight; | 734 return true; |
| 735 }; | |
| 736 // Function to adjust the bottoms of viewport and row. | |
| 737 var scrollToAdjustBottomFunc = function () { | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
function scrollToAdjustBottom() {
}
yoshiki
2011/11/28 11:03:31
Done.
| |
| 738 var cs = getComputedStyle(self); | |
| 739 var paddingY = parseInt(cs.paddingTop, 10) + | |
| 740 parseInt(cs.paddingBottom, 10); | |
| 658 | 741 |
| 659 if (top < scrollTop) { | 742 if (top + itemHeight > scrollTop + clientHeight - paddingY) { |
| 660 this.scrollTop = top; | 743 self.scrollTop = top + itemHeight - clientHeight + paddingY; |
| 661 return true; | 744 return true; |
| 745 } | |
| 746 return false; | |
| 747 }; | |
| 748 | |
| 749 if (itemHeight <= clientHeight) { | |
| 750 if (top < scrollTop) | |
| 751 return scrollToAdjustTopFunc(index); | |
| 752 else ((scrollTop + clientHeight) < (top + itemHeight)) | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
Syntax error
yoshiki
2011/11/28 11:03:31
Done.
| |
| 753 return scrollToAdjustBottomFunc(index); | |
| 662 } else { | 754 } else { |
| 663 var clientHeight = this.clientHeight; | 755 if (scrollTop < top) |
| 664 var cs = getComputedStyle(this); | 756 return scrollToAdjustTopFunc(index); |
| 665 var paddingY = parseInt(cs.paddingTop, 10) + | 757 else if ((top + itemHeight) < (scrollTop + clientHeight)) |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
no else after return
arv (Not doing code reviews)
2011/11/21 20:38:01
Too many parentheses
if (top + itemHeight < scrol
yoshiki
2011/11/28 11:03:31
Done.
yoshiki
2011/11/28 11:03:31
Done.
| |
| 666 parseInt(cs.paddingBottom, 10); | 758 return scrollToAdjustBottomFunc(index); |
| 667 | |
| 668 if (top + itemHeight > scrollTop + clientHeight - paddingY) { | |
| 669 this.scrollTop = top + itemHeight - clientHeight + paddingY; | |
| 670 return true; | |
| 671 } | |
| 672 } | 759 } |
| 673 | |
| 674 return false; | 760 return false; |
| 675 }, | 761 }, |
| 676 | 762 |
| 677 /** | 763 /** |
| 678 * @return {!ClientRect} The rect to use for the context menu. | 764 * @return {!ClientRect} The rect to use for the context menu. |
| 679 */ | 765 */ |
| 680 getRectForContextMenu: function() { | 766 getRectForContextMenu: function() { |
| 681 // TODO(arv): Add trait support so we can share more code between trees | 767 // TODO(arv): Add trait support so we can share more code between trees |
| 682 // and lists. | 768 // and lists. |
| 683 var index = this.selectionModel.selectedIndex; | 769 var index = this.selectionModel.selectedIndex; |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 749 | 835 |
| 750 /** | 836 /** |
| 751 * Return the heights (in pixels) of the top of the given item index within | 837 * Return the heights (in pixels) of the top of the given item index within |
| 752 * the list, and the height of the given item itself, accounting for the | 838 * the list, and the height of the given item itself, accounting for the |
| 753 * possibility that the lead item may be a different height. | 839 * possibility that the lead item may be a different height. |
| 754 * @param {number} index The index to find the top height of. | 840 * @param {number} index The index to find the top height of. |
| 755 * @return {{top: number, height: number}} The heights for the given index. | 841 * @return {{top: number, height: number}} The heights for the given index. |
| 756 * @private | 842 * @private |
| 757 */ | 843 */ |
| 758 getHeightsForIndex_: function(index) { | 844 getHeightsForIndex_: function(index) { |
| 759 var itemHeight = this.getItemHeight_(); | 845 var itemHeight = this.getItemHeightByIndex_(index); |
| 760 var top = this.getItemTop(index); | 846 var top = this.getItemTop(index); |
| 761 if (this.selectionModel.leadIndex > -1 && | |
| 762 this.selectionModel.leadIndex < index) { | |
| 763 top += this.leadItemHeight - itemHeight; | |
| 764 } else if (this.selectionModel.leadIndex == index) { | |
| 765 itemHeight = this.leadItemHeight; | |
| 766 } | |
| 767 return {top: top, height: itemHeight}; | 847 return {top: top, height: itemHeight}; |
| 768 }, | 848 }, |
| 769 | 849 |
| 770 /** | 850 /** |
| 771 * Find the index of the list item containing the given y offset (measured | 851 * Find the index of the list item containing the given y offset (measured |
| 772 * in pixels from the top) within the list. In the case of multiple columns, | 852 * in pixels from the top) within the list. In the case of multiple columns, |
| 773 * returns the first index in the row. | 853 * returns the first index in the row. |
| 774 * @param {number} offset The y offset in pixels to get the index of. | 854 * @param {number} offset The y offset in pixels to get the index of. |
| 775 * @return {number} The index of the list item. | 855 * @return {number} The index of the list item. Returns the list size if |
| 856 * given offset exceeds the height of list. | |
| 776 * @private | 857 * @private |
| 777 */ | 858 */ |
| 778 getIndexForListOffset_: function(offset) { | 859 getIndexForListOffset_: function(offset) { |
| 779 var itemHeight = this.getItemHeight_(); | 860 var itemHeight = this.getDefaultItemHeight_(); |
| 780 var leadIndex = this.selectionModel.leadIndex; | 861 if (itemHeight == 0) |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
if (!itemHeight)
yoshiki
2011/11/28 11:03:31
Done.
| |
| 781 var leadItemHeight = this.leadItemHeight; | 862 return this.dataModel.length; |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
fix indentation
yoshiki
2011/11/28 11:03:31
Done.
| |
| 782 if (leadIndex < 0 || leadItemHeight == itemHeight) { | 863 |
| 783 // Simple case: no lead item or lead item height is not different. | 864 if (this.fixedHeight_) { |
| 865 var leadIndex = this.selectionModel.leadIndex; | |
| 866 var leadItemHeight = this.leadItemHeight; | |
| 867 if (leadIndex < 0 || leadItemHeight == itemHeight) { | |
| 868 // Simple case: no lead item or lead item height is not different. | |
| 869 return this.getFirstItemInRow(Math.floor(offset / itemHeight)); | |
| 870 } | |
| 871 var leadTop = this.getItemTop(leadIndex); | |
| 872 // If the given offset is above the lead item, it's also simple. | |
| 873 if (offset < leadTop) | |
| 874 return this.getFirstItemInRow(Math.floor(offset / itemHeight)); | |
| 875 // If the lead item contains the given offset, we just return its index. | |
| 876 if (offset < leadTop + leadItemHeight) | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
please add some newlines somewhere... this could u
yoshiki
2011/11/28 11:03:31
Done.
| |
| 877 return this.getFirstItemInRow(this.getItemRow(leadIndex)); | |
| 878 // The given offset must be below the lead item. Adjust and recalculate. | |
| 879 offset -= leadItemHeight - itemHeight; | |
| 784 return this.getFirstItemInRow(Math.floor(offset / itemHeight)); | 880 return this.getFirstItemInRow(Math.floor(offset / itemHeight)); |
| 881 } else { | |
| 882 // If offset exceeds the height of list. | |
| 883 var lastHeight = 0; | |
| 884 if (this.dataModel.length) { | |
| 885 var h = this.getHeightsForIndex_(this.dataModel.length - 1); | |
| 886 lastHeight = h.top + h.height; | |
| 887 } | |
| 888 if (lastHeight < offset) | |
| 889 return this.dataModel.length; | |
| 890 | |
| 891 // Estimates index. | |
| 892 var estimatedIndex = Math.min(Math.floor(offset / itemHeight), | |
| 893 this.dataModel.length - 1); | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
fix indentation
yoshiki
2011/11/28 11:03:31
Done.
| |
| 894 var isIncrementing = this.getItemTop(estimatedIndex) < offset; | |
| 895 | |
| 896 // Searchs the correct index. | |
| 897 do { | |
| 898 var heights = this.getHeightsForIndex_(estimatedIndex); | |
| 899 var top = heights.top; | |
| 900 var height = heights.height; | |
| 901 | |
| 902 if (top <= offset && offset <= (top + height)) | |
| 903 break; | |
| 904 | |
| 905 isIncrementing ? ++estimatedIndex: --estimatedIndex; | |
| 906 } while (0 < estimatedIndex && estimatedIndex < this.dataModel.length) | |
| 907 | |
| 908 return estimatedIndex; | |
| 785 } | 909 } |
| 786 var leadTop = this.getItemTop(leadIndex); | |
| 787 // If the given offset is above the lead item, it's also simple. | |
| 788 if (offset < leadTop) | |
| 789 return this.getFirstItemInRow(Math.floor(offset / itemHeight)); | |
| 790 // If the lead item contains the given offset, we just return its index. | |
| 791 if (offset < leadTop + leadItemHeight) | |
| 792 return this.getFirstItemInRow(this.getItemRow(leadIndex)); | |
| 793 // The given offset must be below the lead item. Adjust and recalculate. | |
| 794 offset -= leadItemHeight - itemHeight; | |
| 795 return this.getFirstItemInRow(Math.floor(offset / itemHeight)); | |
| 796 }, | 910 }, |
| 797 | 911 |
| 798 /** | 912 /** |
| 799 * Return the number of items that occupy the range of heights between the | 913 * Return the number of items that occupy the range of heights between the |
| 800 * top of the start item and the end offset. | 914 * top of the start item and the end offset. |
| 801 * @param {number} startIndex The index of the first visible item. | 915 * @param {number} startIndex The index of the first visible item. |
| 802 * @param {number} endOffset The y offset in pixels of the end of the list. | 916 * @param {number} endOffset The y offset in pixels of the end of the list. |
| 803 * @return {number} The number of list items visible. | 917 * @return {number} The number of list items visible. |
| 804 * @private | 918 * @private |
| 805 */ | 919 */ |
| 806 countItemsInRange_: function(startIndex, endOffset) { | 920 countItemsInRange_: function(startIndex, endOffset) { |
| 807 var endIndex = this.getIndexForListOffset_(endOffset); | 921 var endIndex = this.getIndexForListOffset_(endOffset); |
| 808 return endIndex - startIndex + 1; | 922 return endIndex - startIndex + 1; |
| 809 }, | 923 }, |
| 810 | 924 |
| 811 /** | 925 /** |
| 812 * Calculates the number of items fitting in viewport given the index of | 926 * Calculates the number of items fitting in the given viewport. |
| 813 * first item and heights. | |
| 814 * @param {number} itemHeight The height of the item. | |
| 815 * @param {number} firstIndex Index of the first item in viewport. | |
| 816 * @param {number} scrollTop The scroll top position. | 927 * @param {number} scrollTop The scroll top position. |
| 817 * @return {number} The number of items in view port. | 928 * @param {number} clientHeight The height of viewport. |
| 929 * @return {{first: number, length: number, last: number}} The index of | |
| 930 * first item in view port, The number of items, The item past the last. | |
| 818 */ | 931 */ |
| 819 getItemsInViewPort: function(itemHeight, firstIndex, scrollTop) { | 932 getItemsInViewPort: function(scrollTop, clientHeight) { |
| 820 // This is a bit tricky. We take the minimum of the available items to | 933 if (this.autoExpands_) { |
| 821 // show and the number we want to show, so as not to go off the end of the | 934 return {first: 0, |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
return {
first: ...
};
yoshiki
2011/11/28 11:03:31
Done.
| |
| 822 // list. For the number we want to show, we take the maximum of the number | 935 length: this.dataModel.length, |
| 823 // that would fit without a differently-sized lead item, and with one. We | 936 last: this.dataModel.length}; |
| 824 // do this so that if the size of the lead item changes without a scroll | 937 } else { |
| 825 // event to trigger redrawing the list, we won't end up with empty space. | 938 var firstIndex = this.getIndexForListOffset_(scrollTop); |
| 826 var clientHeight = this.clientHeight; | 939 var lastIndex = this.getIndexForListOffset_(scrollTop + clientHeight); |
| 827 return this.autoExpands_ ? this.dataModel.length : Math.min( | 940 |
| 828 this.dataModel.length - firstIndex, | 941 return {first: firstIndex, |
| 829 Math.max( | 942 length: lastIndex - firstIndex + 1, |
| 830 Math.ceil(clientHeight / itemHeight) + 1, | 943 last: lastIndex + 1}; |
| 831 this.countItemsInRange_(firstIndex, scrollTop + clientHeight))); | 944 } |
| 832 }, | 945 }, |
| 833 | 946 |
| 834 /** | 947 /** |
| 835 * Adds items to the list and {@code newCachedItems}. | 948 * Adds items to the list and {@code newCachedItems}. |
| 836 * @param {number} firstIndex The index of first item, inclusively. | 949 * @param {number} firstIndex The index of first item, inclusively. |
| 837 * @param {number} lastIndex The index of last item, exclusively. | 950 * @param {number} lastIndex The index of last item, exclusively. |
| 838 * @param {Object.<string, ListItem>} cachedItems Old items cache. | 951 * @param {Object.<string, ListItem>} cachedItems Old items cache. |
| 839 * @param {Object.<string, ListItem>} newCachedItems New items cache. | 952 * @param {Object.<string, ListItem>} newCachedItems New items cache. |
| 840 */ | 953 */ |
| 841 addItems: function(firstIndex, lastIndex, cachedItems, newCachedItems) { | 954 addItems: function(firstIndex, lastIndex, cachedItems, newCachedItems) { |
| 842 var listItem; | 955 var listItem; |
| 843 var dataModel = this.dataModel; | 956 var dataModel = this.dataModel; |
| 844 | 957 |
| 845 window.l = this; | 958 window.l = this; |
| 846 for (var y = firstIndex; y < lastIndex; y++) { | 959 for (var y = firstIndex; y < lastIndex; y++) { |
| 847 var dataItem = dataModel.item(y); | 960 var dataItem = dataModel.item(y); |
| 848 listItem = cachedItems[y] || this.createItem(dataItem); | 961 listItem = cachedItems[y] || this.createItem(dataItem); |
| 849 listItem.listIndex = y; | 962 listItem.listIndex = y; |
| 850 this.appendChild(listItem); | 963 this.appendChild(listItem); |
| 851 newCachedItems[y] = listItem; | 964 newCachedItems[y] = listItem; |
| 965 this.cachedItemSizes_[y] = measureItem(this, listItem); | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
This is bad. it will regress performance since it
yoshiki
2011/11/28 11:03:31
Done.
| |
| 852 } | 966 } |
| 853 }, | 967 }, |
| 854 | 968 |
| 855 /** | 969 /** |
| 856 * Returns the height of after filler in the list. | 970 * Returns the height of after filler in the list. |
| 857 * @param {number} lastIndex The index of item past the last in viewport. | 971 * @param {number} lastIndex The index of item past the last in viewport. |
| 858 * @param {number} itemHeight The height of the item. | 972 * @param {number} itemHeight The height of the item. |
| 859 * @return {number} The height of after filler. | 973 * @return {number} The height of after filler. |
| 860 */ | 974 */ |
| 861 getAfterFillerHeight: function(lastIndex, itemHeight) { | 975 getAfterFillerHeight: function(lastIndex) { |
| 862 return (this.dataModel.length - lastIndex) * itemHeight; | 976 if (this.fixedHeight_) { |
| 977 var itemHeight = this.getDefaultItemHeight_(); | |
| 978 var afterFillerHeight = | |
| 979 (this.dataModel.length - lastIndex) * itemHeight; | |
| 980 | |
| 981 var sm = this.selectionModel; | |
| 982 var leadIndex = sm.leadIndex; | |
| 983 if (leadIndex >= lastIndex) | |
| 984 afterFillerHeight += this.leadItemHeight - itemHeight; | |
| 985 | |
| 986 return afterFillerHeight; | |
| 987 } else { | |
| 988 var height = 0; | |
| 989 for (var i = lastIndex; i < this.dataModel.length; i++) { | |
| 990 var item = this.getListItemByIndex(i); | |
| 991 if (item) | |
| 992 height += this.getItemSize_(item).height; | |
| 993 } | |
| 994 return height; | |
| 995 } | |
| 863 }, | 996 }, |
| 864 | 997 |
| 865 /** | 998 /** |
| 866 * Redraws the viewport. | 999 * Redraws the viewport. |
| 867 */ | 1000 */ |
| 868 redraw: function() { | 1001 redraw: function() { |
| 869 if (this.batchCount_ != 0) | 1002 if (this.batchCount_ != 0) |
| 870 return; | 1003 return; |
| 871 | 1004 |
| 872 var dataModel = this.dataModel; | 1005 var dataModel = this.dataModel; |
| 873 if (!dataModel) { | 1006 if (!dataModel) { |
| 1007 this.cachedItems_ = {}; | |
| 1008 this.firstIndex_ = 0; | |
| 1009 this.lastIndex_ = 0; | |
| 1010 this.remainSpace_ = true; | |
| 874 this.textContent = ''; | 1011 this.textContent = ''; |
| 875 return; | 1012 return; |
| 876 } | 1013 } |
| 877 | 1014 |
| 878 var scrollTop = this.scrollTop; | |
| 879 var clientHeight = this.clientHeight; | |
| 880 | |
| 881 var itemHeight = this.getItemHeight_(); | |
| 882 | |
| 883 // We cache the list items since creating the DOM nodes is the most | 1015 // We cache the list items since creating the DOM nodes is the most |
| 884 // expensive part of redrawing. | 1016 // expensive part of redrawing. |
| 885 var cachedItems = this.cachedItems_ || {}; | 1017 var cachedItems = this.cachedItems_ || {}; |
| 886 var newCachedItems = {}; | 1018 var newCachedItems = {}; |
| 887 | 1019 |
| 888 var desiredScrollHeight = this.getHeightsForIndex_(dataModel.length).top; | 1020 var autoExpands = this.autoExpands_; |
| 1021 var scrollTop = this.scrollTop; | |
| 1022 var clientHeight = this.clientHeight; | |
| 889 | 1023 |
| 890 var autoExpands = this.autoExpands_; | 1024 var lastItemHeights = this.getHeightsForIndex_(dataModel.length - 1); |
| 891 var firstIndex = autoExpands ? 0 : this.getIndexForListOffset_(scrollTop); | 1025 var desiredScrollHeight = lastItemHeights.top + lastItemHeights.height; |
| 892 var itemsInViewPort = this.getItemsInViewPort(itemHeight, firstIndex, | |
| 893 scrollTop); | |
| 894 var lastIndex = firstIndex + itemsInViewPort; | |
| 895 | 1026 |
| 1027 var itemsInViewPort = this.getItemsInViewPort(scrollTop, clientHeight); | |
| 1028 // Draws the hidden rows just above/below the viewport to prevent | |
| 1029 // flashing in scroll. | |
| 1030 var firstIndex = Math.max(0, itemsInViewPort.first - 1); | |
| 1031 var lastIndex = Math.min(itemsInViewPort.last + 1, dataModel.length); | |
| 1032 | |
| 1033 var beforeFillerHeight = | |
| 1034 this.autoExpands ? 0 : this.getItemTop(firstIndex); | |
| 1035 var afterFillerHeight = | |
| 1036 this.autoExpands ? 0 : this.getAfterFillerHeight(lastIndex); | |
| 1037 | |
| 1038 // Clear list and Adds elements on list. | |
| 896 this.textContent = ''; | 1039 this.textContent = ''; |
| 897 | 1040 |
| 898 this.beforeFiller_.style.height = | 1041 this.beforeFiller_.style.height = beforeFillerHeight + 'px'; |
| 899 this.getHeightsForIndex_(firstIndex).top + 'px'; | |
| 900 this.appendChild(this.beforeFiller_); | 1042 this.appendChild(this.beforeFiller_); |
| 901 | 1043 |
| 1044 this.addItems(firstIndex, lastIndex, cachedItems, newCachedItems); | |
|
arv (Not doing code reviews)
2011/11/21 20:38:01
Make sure there are no measuring done in here.
Ba
yoshiki
2011/11/28 11:03:31
I added the function ensureAllItemSizesInCache() a
| |
| 1045 | |
| 1046 this.afterFiller_.style.height = afterFillerHeight + 'px'; | |
| 1047 this.appendChild(this.afterFiller_); | |
| 1048 | |
| 902 var sm = this.selectionModel; | 1049 var sm = this.selectionModel; |
| 903 var leadIndex = sm.leadIndex; | 1050 var leadIndex = sm.leadIndex; |
| 904 | 1051 |
| 905 this.addItems(firstIndex, lastIndex, cachedItems, newCachedItems); | |
| 906 | |
| 907 var afterFillerHeight = this.getAfterFillerHeight(lastIndex, itemHeight); | |
| 908 if (leadIndex >= lastIndex) | |
| 909 afterFillerHeight += this.leadItemHeight - itemHeight; | |
| 910 this.afterFiller_.style.height = afterFillerHeight + 'px'; | |
| 911 this.appendChild(this.afterFiller_); | |
| 912 | |
| 913 // We don't set the lead or selected properties until after adding all | 1052 // We don't set the lead or selected properties until after adding all |
| 914 // items, in case they force relayout in response to these events. | 1053 // items, in case they force relayout in response to these events. |
| 915 var listItem = null; | 1054 var listItem = null; |
| 916 if (newCachedItems[leadIndex]) | 1055 if (leadIndex != -1 && newCachedItems[leadIndex]) |
| 917 newCachedItems[leadIndex].lead = true; | 1056 newCachedItems[leadIndex].lead = true; |
| 918 for (var y = firstIndex; y < lastIndex; y++) { | 1057 for (var y = firstIndex; y < lastIndex; y++) { |
| 919 if (sm.getIndexSelected(y)) | 1058 if (sm.getIndexSelected(y)) |
| 920 newCachedItems[y].selected = true; | 1059 newCachedItems[y].selected = true; |
| 921 else if (y != leadIndex) | 1060 else if (y != leadIndex) |
| 922 listItem = newCachedItems[y]; | 1061 listItem = newCachedItems[y]; |
| 923 } | 1062 } |
| 924 | 1063 |
| 925 this.scrollTop = scrollTop; | 1064 this.scrollTop = scrollTop; |
| 926 | 1065 |
| 927 this.firstIndex_ = firstIndex; | 1066 this.firstIndex_ = firstIndex; |
| 928 this.lastIndex_ = lastIndex; | 1067 this.lastIndex_ = lastIndex; |
| 929 | 1068 |
| 1069 this.remainSpace_ = itemsInViewPort.last > dataModel.length; | |
| 930 this.cachedItems_ = newCachedItems; | 1070 this.cachedItems_ = newCachedItems; |
| 931 | 1071 |
| 932 // Measure again in case the item height has changed due to a page zoom. | 1072 // Measure again in case the item height has changed due to a page zoom. |
| 933 // | 1073 // |
| 934 // The measure above is only done the first time but this measure is done | 1074 // The measure above is only done the first time but this measure is done |
| 935 // after every redraw. It is done in a timeout so it will not trigger | 1075 // after every redraw. It is done in a timeout so it will not trigger |
| 936 // a reflow (which made the redraw speed 3 times slower on my system). | 1076 // a reflow (which made the redraw speed 3 times slower on my system). |
| 937 // By using a timeout the measuring will happen later when there is no | 1077 // By using a timeout the measuring will happen later when there is no |
| 938 // need for a reflow. | 1078 // need for a reflow. |
| 939 if (listItem) { | 1079 if (listItem && this.fixedHeight_) { |
| 940 var list = this; | 1080 var list = this; |
| 941 window.setTimeout(function() { | 1081 window.setTimeout(function() { |
| 942 if (listItem.parentNode == list) { | 1082 if (listItem.parentNode == list) { |
| 943 list.measured_ = measureItem(list, listItem); | 1083 list.measured_ = measureItem(list, listItem); |
| 944 } | 1084 } |
| 945 }); | 1085 }); |
| 946 } | 1086 } |
| 947 }, | 1087 }, |
| 948 | 1088 |
| 949 /** | 1089 /** |
| 950 * Invalidates list by removing cached items. | 1090 * Invalidates list by removing cached items. |
| 951 */ | 1091 */ |
| 952 invalidate: function() { | 1092 invalidate: function() { |
| 953 this.cachedItems_ = {}; | 1093 this.cachedItems_ = {}; |
| 954 }, | 1094 }, |
| 955 | 1095 |
| 956 /** | 1096 /** |
| 957 * Redraws a single item. | 1097 * Redraws a single item. |
| 958 * @param {number} index The row index to redraw. | 1098 * @param {number} index The row index to redraw. |
| 959 */ | 1099 */ |
| 960 redrawItem: function(index) { | 1100 redrawItem: function(index) { |
| 961 if (index >= this.firstIndex_ && index < this.lastIndex_) { | 1101 if (index >= this.firstIndex_ && |
| 1102 (index < this.lastIndex_ || this.remainSpace_)) { | |
| 962 delete this.cachedItems_[index]; | 1103 delete this.cachedItems_[index]; |
| 963 this.redraw(); | 1104 this.redraw(); |
| 964 } | 1105 } |
| 965 }, | 1106 }, |
| 966 | 1107 |
| 967 /** | 1108 /** |
| 968 * Called when a list item is activated, currently only by a double click | 1109 * Called when a list item is activated, currently only by a double click |
| 969 * event. | 1110 * event. |
| 970 * @param {number} index The index of the activated item. | 1111 * @param {number} index The index of the activated item. |
| 971 */ | 1112 */ |
| 972 activateItemAtIndex: function(index) { | 1113 activateItemAtIndex: function(index) { |
| 973 }, | 1114 }, |
| 974 }; | 1115 }; |
| 975 | 1116 |
| 976 cr.defineProperty(List, 'disabled', cr.PropertyKind.BOOL_ATTR); | 1117 cr.defineProperty(List, 'disabled', cr.PropertyKind.BOOL_ATTR); |
| 977 | 1118 |
| 978 /** | 1119 /** |
| 979 * Whether the list or one of its descendents has focus. This is necessary | 1120 * Whether the list or one of its descendents has focus. This is necessary |
| 980 * because list items can contain controls that can be focused, and for some | 1121 * because list items can contain controls that can be focused, and for some |
| 981 * purposes (e.g., styling), the list can still be conceptually focused at | 1122 * purposes (e.g., styling), the list can still be conceptually focused at |
| 982 * that point even though it doesn't actually have the page focus. | 1123 * that point even though it doesn't actually have the page focus. |
| 983 */ | 1124 */ |
| 984 cr.defineProperty(List, 'hasElementFocus', cr.PropertyKind.BOOL_ATTR); | 1125 cr.defineProperty(List, 'hasElementFocus', cr.PropertyKind.BOOL_ATTR); |
| 985 | 1126 |
| 986 return { | 1127 return { |
| 987 List: List | 1128 List: List |
| 988 } | 1129 } |
| 989 }); | 1130 }); |
| OLD | NEW |