1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 * @extends {WebInspector.DataGrid}
8 * @param {!Array.<!WebInspector.DataGrid.ColumnDescriptor>} columnsArray
9 * @param {function(!WebInspector.DataGridNode, string, string, string)=} editCallback
10 * @param {function(!WebInspector.DataGridNode)=} deleteCallback
11 * @param {function()=} refreshCallback
12 * @param {function(!WebInspector.ContextMenu, !WebInspector.DataGridNode)=} contextMenuCallback
14 WebInspector.ViewportDataGrid = function(columnsArray, editCallback, deleteCallback, refreshCallback, contextMenuCallback)
16 WebInspector.DataGrid.call(this, columnsArray, editCallback, deleteCallback, refreshCallback, contextMenuCallback);
17 this._scrollContainer.addEventListener("scroll", this._onScroll.bind(this), true);
18 this._scrollContainer.addEventListener("mousewheel", this._onWheel.bind(this), true);
19 /** @type {!Array.<!WebInspector.ViewportDataGridNode>} */
20 this._visibleNodes = [];
21 /** @type {boolean} */
22 this._updateScheduled = false;
23 /** @type {boolean} */
26 // Wheel target shouldn't be removed from DOM to preserve native kinetic scrolling.
28 this._wheelTarget = null;
30 // Element that was hidden earlier, but hasn't been removed yet.
32 this._hiddenWheelTarget = null;
34 /** @type {boolean} */
35 this._stickToBottom = false;
36 /** @type {boolean} */
37 this._atBottom = true;
39 this._lastScrollTop = 0;
41 this.setRootNode(new WebInspector.ViewportDataGridNode());
44 WebInspector.ViewportDataGrid.prototype = {
50 if (this._stickToBottom && this._atBottom)
51 this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.clientHeight;
52 this.scheduleUpdate();
56 * @param {boolean} stick
58 setStickToBottom: function(stick)
60 this._stickToBottom = stick;
64 * @param {?Event} event
66 _onWheel: function(event)
68 this._wheelTarget = event.target ? event.target.enclosingNodeOrSelfWithNodeName("tr") : null;
72 * @param {?Event} event
74 _onScroll: function(event)
76 this._atBottom = this._scrollContainer.isScrolledToBottom();
77 if (this._lastScrollTop !== this._scrollContainer.scrollTop)
78 this.scheduleUpdate();
84 scheduleUpdate: function()
86 if (this._updateScheduled)
88 this._updateScheduled = true;
89 window.requestAnimationFrame(this._update.bind(this));
95 renderInline: function()
98 WebInspector.DataGrid.prototype.renderInline.call(this);
103 * @param {number} clientHeight
104 * @param {number} scrollTop
105 * @return {{topPadding: number, bottomPadding: number, visibleNodes: !Array.<!WebInspector.ViewportDataGridNode>, offset: number}}
107 _calculateVisibleNodes: function(clientHeight, scrollTop)
109 var nodes = this._rootNode.children;
111 return {topPadding: 0, bottomPadding: 0, visibleNodes: nodes, offset: 0};
113 var size = nodes.length;
117 for (; i < size && y + nodes[i].nodeSelfHeight() < scrollTop; ++i)
118 y += nodes[i].nodeSelfHeight();
122 for (; i < size && y < scrollTop + clientHeight; ++i)
123 y += nodes[i].nodeSelfHeight();
126 var bottomPadding = 0;
127 for (; i < size; ++i)
128 bottomPadding += nodes[i].nodeSelfHeight();
130 return {topPadding: topPadding, bottomPadding: bottomPadding, visibleNodes: nodes.slice(start, end), offset: start};
136 _contentHeight: function()
138 var nodes = this._rootNode.children;
140 for (var i = 0, size = nodes.length; i < size; ++i)
141 result += nodes[i].nodeSelfHeight();
147 this._updateScheduled = false;
149 var clientHeight = this._scrollContainer.clientHeight;
150 var scrollTop = this._scrollContainer.scrollTop;
151 var currentScrollTop = scrollTop;
152 var maxScrollTop = Math.max(0, this._contentHeight() - clientHeight);
153 if (this._stickToBottom && this._atBottom)
154 scrollTop = maxScrollTop;
155 scrollTop = Math.min(maxScrollTop, scrollTop);
156 this._atBottom = scrollTop === maxScrollTop;
158 var viewportState = this._calculateVisibleNodes(clientHeight, scrollTop);
159 var visibleNodes = viewportState.visibleNodes;
160 var visibleNodesSet = Set.fromArray(visibleNodes);
162 if (this._hiddenWheelTarget && this._hiddenWheelTarget !== this._wheelTarget) {
163 this._hiddenWheelTarget.remove();
164 this._hiddenWheelTarget = null;
167 for (var i = 0; i < this._visibleNodes.length; ++i) {
168 var oldNode = this._visibleNodes[i];
169 if (!visibleNodesSet.contains(oldNode)) {
170 var element = oldNode.element();
171 if (element === this._wheelTarget)
172 this._hiddenWheelTarget = oldNode.abandonElement();
175 oldNode.wasDetached();
179 var previousElement = this._topFillerRow;
180 if (previousElement.nextSibling === this._hiddenWheelTarget)
181 previousElement = this._hiddenWheelTarget;
182 var tBody = this.dataTableBody;
183 var offset = viewportState.offset;
184 for (var i = 0; i < visibleNodes.length; ++i) {
185 var node = visibleNodes[i];
186 var element = node.element();
188 element.classList.toggle("odd", (offset + i) % 2 === 0);
189 tBody.insertBefore(element, previousElement.nextSibling);
190 previousElement = element;
193 this.setVerticalPadding(viewportState.topPadding, viewportState.bottomPadding);
194 this._lastScrollTop = scrollTop;
195 if (scrollTop !== currentScrollTop)
196 this._scrollContainer.scrollTop = scrollTop;
197 this._visibleNodes = visibleNodes;
201 * @param {!WebInspector.ViewportDataGridNode} node
203 _revealViewportNode: function(node)
205 var nodes = this._rootNode.children;
206 var index = nodes.indexOf(node);
210 for (var i = 0; i < index; ++i)
211 fromY += nodes[i].nodeSelfHeight();
212 var toY = fromY + node.nodeSelfHeight();
214 var scrollTop = this._scrollContainer.scrollTop;
215 if (scrollTop > fromY)
217 else if (scrollTop + this._scrollContainer.offsetHeight < toY)
218 scrollTop = toY - this._scrollContainer.offsetHeight;
219 this._scrollContainer.scrollTop = scrollTop;
222 __proto__: WebInspector.DataGrid.prototype
227 * @extends {WebInspector.DataGridNode}
228 * @param {?Object.<string, *>=} data
230 WebInspector.ViewportDataGridNode = function(data)
232 WebInspector.DataGridNode.call(this, data, false);
233 /** @type {boolean} */
237 WebInspector.ViewportDataGridNode.prototype = {
244 if (!this._element) {
245 this.createElement();
255 return /** @type {!Element} */ (this._element);
260 * @param {!WebInspector.DataGridNode} child
261 * @param {number} index
263 insertChild: function(child, index)
266 child.dataGrid = this.dataGrid;
267 this.children.splice(index, 0, child);
268 child.recalculateSiblings(index);
269 this.dataGrid.scheduleUpdate();
274 * @param {!WebInspector.DataGridNode} child
276 removeChild: function(child)
279 this.children.remove(child, true);
281 if (child.previousSibling)
282 child.previousSibling.nextSibling = child.nextSibling;
283 if (child.nextSibling)
284 child.nextSibling.previousSibling = child.previousSibling;
286 this.dataGrid.scheduleUpdate();
292 removeChildren: function()
294 for (var i = 0; i < this.children.length; ++i)
295 this.children[i].deselect();
298 this.dataGrid.scheduleUpdate();
311 willAttach: function() { },
319 return !!(this._element && this._element.parentElement);
327 if (this.attached()) {
329 this.dataGrid.scheduleUpdate();
331 this._element = null;
338 abandonElement: function()
340 var result = this._element;
342 result.style.display = "none";
343 this._element = null;
349 this.dataGrid._revealViewportNode(this);
352 __proto__: WebInspector.DataGridNode.prototype