Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(56)

Side by Side Diff: chrome/browser/resources/shared/js/cr/ui/list.js

Issue 8608007: cr/ui/list.js: Support rows with variable heights. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
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 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698