Chromium Code Reviews| Index: third_party/WebKit/Source/devtools/front_end/ui/ListControl.js |
| diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js b/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js |
| index a2b92c002f884767e7cee113203b6ff44590424b..4105bf09ca532d863c5f374290ee0e90065bc6ac 100644 |
| --- a/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js |
| +++ b/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js |
| @@ -50,10 +50,11 @@ UI.ListMode = { |
| */ |
| UI.ListControl = class { |
| /** |
| + * @param {!UI.ListModel<T>} model |
| * @param {!UI.ListDelegate<T>} delegate |
| * @param {!UI.ListMode=} mode |
| */ |
| - constructor(delegate, mode) { |
| + constructor(model, delegate, mode) { |
| this.element = createElement('div'); |
| this.element.style.overflowY = 'auto'; |
| this._topElement = this.element.createChild('div'); |
| @@ -64,11 +65,13 @@ UI.ListControl = class { |
| this._topHeight = 0; |
| this._bottomHeight = 0; |
| - /** @type {!Array<T>} */ |
| - this._items = []; |
| + this._model = model; |
| + this._model.addEventListener(UI.ListModel.Events.ItemsReplaced, this._replacedItemsInRange, this); |
| /** @type {!Map<T, !Element>} */ |
| this._itemToElement = new Map(); |
| this._selectedIndex = -1; |
| + /** @type {?T} */ |
| + this._selectedItem = null; |
| this.element.tabIndex = -1; |
| this.element.addEventListener('click', this._onClick.bind(this), false); |
| @@ -88,106 +91,43 @@ UI.ListControl = class { |
| } |
| /** |
| - * @return {number} |
| - */ |
| - length() { |
| - return this._items.length; |
| - } |
| - |
| - /** |
| - * @param {number} index |
| - * @return {T} |
| + * @param {!UI.ListModel<T>} model |
| */ |
| - itemAtIndex(index) { |
| - return this._items[index]; |
| + setModel(model) { |
|
caseq
2017/06/01 01:37:57
I hope this should not be necessary.
dgozman
2017/06/01 20:31:49
I don't see any harm in this.
|
| + this._itemToElement.clear(); |
| + var length = this._model.length(); |
| + this._model.removeEventListener(UI.ListModel.Events.ItemsReplaced, this._replacedItemsInRange, this); |
| + this._model = model; |
| + this._model.addEventListener(UI.ListModel.Events.ItemsReplaced, this._replacedItemsInRange, this); |
| + this.invalidateRange(0, length); |
| } |
| /** |
| - * @param {T} item |
| + * @return {!UI.ListModel<T>} |
| */ |
| - pushItem(item) { |
| - this.replaceItemsInRange(this._items.length, this._items.length, [item]); |
| + model() { |
|
caseq
2017/06/01 01:37:56
ditto.
dgozman
2017/06/01 20:31:49
Removed.
|
| + return this._model; |
| } |
| /** |
| - * @return {T} |
| + * @param {!Common.Event} event |
| */ |
| - popItem() { |
| - return this.removeItemAtIndex(this._items.length - 1); |
| - } |
| + _replacedItemsInRange(event) { |
| + var data = /** @type {{index: number, removed: !Array<T>, inserted: number}} */ (event.data); |
| + var from = data.index; |
| + var to = from + data.removed.length; |
| - /** |
| - * @param {number} index |
| - * @param {T} item |
| - */ |
| - insertItemAtIndex(index, item) { |
| - this.replaceItemsInRange(index, index, [item]); |
| - } |
| - |
| - /** |
| - * @param {T} item |
| - * @param {function(T, T):number} comparator |
| - */ |
| - insertItemWithComparator(item, comparator) { |
| - var index = this._items.lowerBound(item, comparator); |
| - this.insertItemAtIndex(index, item); |
| - } |
| - |
| - /** |
| - * @param {T} item |
| - * @return {number} |
| - */ |
| - indexOfItem(item) { |
| - return this._items.indexOf(item); |
| - } |
| - |
| - /** |
| - * @param {number} index |
| - * @return {T} |
| - */ |
| - removeItemAtIndex(index) { |
| - var result = this._items[index]; |
| - this.replaceItemsInRange(index, index + 1, []); |
| - return result; |
| - } |
| - |
| - /** |
| - * @param {T} item |
| - */ |
| - removeItem(item) { |
| - var index = this._items.indexOf(item); |
| - if (index === -1) { |
| - console.error('Attempt to remove non-existing item'); |
| - return; |
| - } |
| - this.removeItemAtIndex(index); |
| - } |
| - |
| - /** |
| - * @param {number} from |
| - * @param {number} to |
| - * @param {!Array<T>} items |
| - */ |
| - replaceItemsInRange(from, to, items) { |
| - var oldSelectedItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] : null; |
| + var oldSelectedItem = this._selectedItem; |
| var oldSelectedElement = oldSelectedItem ? (this._itemToElement.get(oldSelectedItem) || null) : null; |
| - |
| - for (var i = from; i < to; i++) |
| - this._itemToElement.delete(this._items[i]); |
| - if (items.length < 10000) { |
| - this._items.splice.bind(this._items, from, to - from).apply(null, items); |
| - } else { |
| - // Splice may fail with too many arguments. |
| - var before = this._items.slice(0, from); |
| - var after = this._items.slice(to); |
| - this._items = [].concat(before, items, after); |
| - } |
| - this._invalidate(from, to, items.length); |
| + for (var i = 0; i < data.removed.length; i++) |
|
caseq
2017/06/01 01:37:56
nit: for of?
|
| + this._itemToElement.delete(data.removed[i]); |
| + this._invalidate(from, to, data.inserted); |
| if (this._selectedIndex >= to) { |
| - this._selectedIndex += items.length - (to - from); |
| + this._selectedIndex += data.inserted - (to - from); |
| + this._selectedItem = this._model.itemAtIndex(this._selectedIndex); |
| } else if (this._selectedIndex >= from) { |
| - var index = this._findFirstSelectable(from + items.length, +1, false); |
| + var index = this._findFirstSelectable(from + data.inserted, +1, false); |
| if (index === -1) |
| index = this._findFirstSelectable(from - 1, -1, false); |
| this._select(index, oldSelectedItem, oldSelectedElement); |
| @@ -195,24 +135,16 @@ UI.ListControl = class { |
| } |
| /** |
| - * @param {!Array<T>} items |
| - */ |
| - replaceAllItems(items) { |
| - this.replaceItemsInRange(0, this._items.length, items); |
| - } |
| - |
| - refreshAllItems() { |
| - this.refreshItemsInRange(0, this._items.length); |
| - } |
| - |
| - /** |
| - * @param {number} from |
| - * @param {number} to |
| + * @param {T} item |
| */ |
| - refreshItemsInRange(from, to) { |
| - for (var i = from; i < to; i++) |
| - this._itemToElement.delete(this._items[i]); |
| - this.invalidateRange(from, to); |
| + refreshItem(item) { |
| + var index = this._model.indexOfItem(item); |
| + if (index === -1) { |
| + console.error('Item to refresh is not present'); |
| + return; |
| + } |
| + this._itemToElement.delete(item); |
| + this.invalidateRange(index, index + 1); |
| if (this._selectedIndex !== -1) |
| this._select(this._selectedIndex, null, null); |
| } |
| @@ -241,9 +173,9 @@ UI.ListControl = class { |
| return; |
| } |
| this._fixedHeight = 0; |
| - if (this._items.length) { |
| + if (this._model.length()) { |
| this._itemToElement.clear(); |
| - this._invalidate(0, this._items.length, this._items.length); |
| + this._invalidate(0, this._model.length(), this._model.length()); |
| } |
| } |
| @@ -257,8 +189,8 @@ UI.ListControl = class { |
| if (!node) |
| return null; |
| var element = /** @type {!Element} */ (node); |
| - var index = this._items.findIndex(item => this._itemToElement.get(item) === element); |
| - return index !== -1 ? this._items[index] : null; |
| + var index = this._model.findIndex(item => this._itemToElement.get(item) === element); |
| + return index !== -1 ? this._model.itemAtIndex(index) : null; |
| } |
| /** |
| @@ -266,7 +198,7 @@ UI.ListControl = class { |
| * @param {boolean=} center |
| */ |
| scrollItemIntoView(item, center) { |
| - var index = this._items.indexOf(item); |
| + var index = this._model.indexOfItem(item); |
| if (index === -1) { |
| console.error('Attempt to scroll onto missing item'); |
| return; |
| @@ -278,7 +210,7 @@ UI.ListControl = class { |
| * @return {?T} |
| */ |
| selectedItem() { |
| - return this._selectedIndex === -1 ? null : this._items[this._selectedIndex]; |
| + return this._selectedItem; |
| } |
| /** |
| @@ -296,7 +228,7 @@ UI.ListControl = class { |
| selectItem(item, center, dontScroll) { |
| var index = -1; |
| if (item !== null) { |
| - index = this._items.indexOf(item); |
| + index = this._model.indexOfItem(item); |
| if (index === -1) { |
| console.error('Attempt to select missing item'); |
| return; |
| @@ -320,7 +252,7 @@ UI.ListControl = class { |
| selectPreviousItem(canWrap, center) { |
| if (this._selectedIndex === -1 && !canWrap) |
| return false; |
| - var index = this._selectedIndex === -1 ? this._items.length - 1 : this._selectedIndex - 1; |
| + var index = this._selectedIndex === -1 ? this._model.length() - 1 : this._selectedIndex - 1; |
| index = this._findFirstSelectable(index, -1, !!canWrap); |
| if (index !== -1) { |
| this._scrollIntoView(index, center); |
| @@ -355,7 +287,7 @@ UI.ListControl = class { |
| selectItemPreviousPage(center) { |
| if (this._mode === UI.ListMode.NonViewport) |
| return false; |
| - var index = this._selectedIndex === -1 ? this._items.length - 1 : this._selectedIndex; |
| + var index = this._selectedIndex === -1 ? this._model.length() - 1 : this._selectedIndex; |
| index = this._findPageSelectable(index, -1); |
| if (index !== -1) { |
| this._scrollIntoView(index, center); |
| @@ -444,7 +376,7 @@ UI.ListControl = class { |
| * @return {number} |
| */ |
| _totalHeight() { |
| - return this._offsetAtIndex(this._items.length); |
| + return this._offsetAtIndex(this._model.length()); |
| } |
| /** |
| @@ -454,15 +386,15 @@ UI.ListControl = class { |
| _indexAtOffset(offset) { |
| if (this._mode === UI.ListMode.NonViewport) |
| throw 'There should be no offset conversions in non-viewport mode'; |
| - if (!this._items.length || offset < 0) |
| + if (!this._model.length() || offset < 0) |
| return 0; |
| if (this._mode === UI.ListMode.VariousHeightItems) { |
| return Math.min( |
| - this._items.length - 1, this._variableOffsets.lowerBound(offset, undefined, 0, this._items.length)); |
| + this._model.length() - 1, this._variableOffsets.lowerBound(offset, undefined, 0, this._model.length())); |
| } |
| if (!this._fixedHeight) |
| this._measureHeight(); |
| - return Math.min(this._items.length - 1, Math.floor(offset / this._fixedHeight)); |
| + return Math.min(this._model.length() - 1, Math.floor(offset / this._fixedHeight)); |
| } |
| /** |
| @@ -470,7 +402,7 @@ UI.ListControl = class { |
| * @return {!Element} |
| */ |
| _elementAtIndex(index) { |
| - var item = this._items[index]; |
| + var item = this._model.itemAtIndex(index); |
| var element = this._itemToElement.get(item); |
| if (!element) { |
| element = this._delegate.createElementForItem(item); |
| @@ -486,7 +418,7 @@ UI.ListControl = class { |
| _offsetAtIndex(index) { |
| if (this._mode === UI.ListMode.NonViewport) |
| throw 'There should be no offset conversions in non-viewport mode'; |
| - if (!this._items.length) |
| + if (!this._model.length()) |
| return 0; |
| if (this._mode === UI.ListMode.VariousHeightItems) |
| return this._variableOffsets[index]; |
| @@ -496,7 +428,7 @@ UI.ListControl = class { |
| } |
| _measureHeight() { |
| - this._fixedHeight = this._delegate.heightForItem(this._items[0]); |
| + this._fixedHeight = this._delegate.heightForItem(this._model.itemAtIndex(0)); |
| if (!this._fixedHeight) |
| this._fixedHeight = UI.measurePreferredSize(this._elementAtIndex(0), this.element).height; |
| } |
| @@ -508,11 +440,12 @@ UI.ListControl = class { |
| */ |
| _select(index, oldItem, oldElement) { |
| if (oldItem === undefined) |
| - oldItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] : null; |
| + oldItem = this._selectedItem; |
| if (oldElement === undefined) |
| oldElement = this._itemToElement.get(oldItem) || null; |
| this._selectedIndex = index; |
| - var newItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] : null; |
| + this._selectedItem = index === -1 ? null : this._model.itemAtIndex(index); |
| + var newItem = this._selectedItem; |
| var newElement = this._selectedIndex !== -1 ? this._elementAtIndex(index) : null; |
| this._delegate.selectedItemChanged(oldItem, newItem, /** @type {?Element} */ (oldElement), newElement); |
| } |
| @@ -524,7 +457,7 @@ UI.ListControl = class { |
| * @return {number} |
| */ |
| _findFirstSelectable(index, direction, canWrap) { |
| - var length = this._items.length; |
| + var length = this._model.length(); |
| if (!length) |
| return -1; |
| for (var step = 0; step <= length; step++) { |
| @@ -533,7 +466,7 @@ UI.ListControl = class { |
| return -1; |
| index = (index + length) % length; |
| } |
| - if (this._delegate.isItemSelectable(this._items[index])) |
| + if (this._delegate.isItemSelectable(this._model.itemAtIndex(index))) |
| return index; |
| index += direction; |
| } |
| @@ -550,8 +483,8 @@ UI.ListControl = class { |
| var startOffset = this._offsetAtIndex(index); |
| // Compensate for zoom rounding errors with -1. |
| var viewportHeight = this.element.offsetHeight - 1; |
| - while (index >= 0 && index < this._items.length) { |
| - if (this._delegate.isItemSelectable(this._items[index])) { |
| + while (index >= 0 && index < this._model.length()) { |
| + if (this._delegate.isItemSelectable(this._model.itemAtIndex(index))) { |
| if (Math.abs(this._offsetAtIndex(index) - startOffset) >= viewportHeight) |
| return index; |
| lastSelectable = index; |
| @@ -589,9 +522,11 @@ UI.ListControl = class { |
| } |
| if (this._mode === UI.ListMode.VariousHeightItems) { |
| - this._reallocateVariableOffsets(this._items.length + 1, from + 1); |
| - for (var i = from + 1; i <= this._items.length; i++) |
| - this._variableOffsets[i] = this._variableOffsets[i - 1] + this._delegate.heightForItem(this._items[i - 1]); |
| + this._reallocateVariableOffsets(this._model.length() + 1, from + 1); |
| + for (var i = from + 1; i <= this._model.length(); i++) { |
| + this._variableOffsets[i] = |
| + this._variableOffsets[i - 1] + this._delegate.heightForItem(this._model.itemAtIndex(i - 1)); |
| + } |
| } |
| var viewportHeight = this.element.offsetHeight; |