e8589076d6ed4df0e1cad1694a1c6e7b799f1870
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / ui / ViewportDataGrid.js
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.
4
5 /**
6  * @constructor
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
13  */
14 WebInspector.ViewportDataGrid = function(columnsArray, editCallback, deleteCallback, refreshCallback, contextMenuCallback)
15 {
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} */
24     this._inline = false;
25
26     // Wheel target shouldn't be removed from DOM to preserve native kinetic scrolling.
27     /** @type {?Node} */
28     this._wheelTarget = null;
29
30     // Element that was hidden earlier, but hasn't been removed yet.
31     /** @type {?Node} */
32     this._hiddenWheelTarget = null;
33
34     /** @type {boolean} */
35     this._stickToBottom = false;
36     /** @type {boolean} */
37     this._atBottom = true;
38     /** @type {number} */
39     this._lastScrollTop = 0;
40
41     this.setRootNode(new WebInspector.ViewportDataGridNode());
42 }
43
44 WebInspector.ViewportDataGrid.prototype = {
45     /**
46      * @override
47      */
48     onResize: function()
49     {
50         if (this._stickToBottom && this._atBottom)
51             this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.clientHeight;
52         this.scheduleUpdate();
53     },
54
55     /**
56      * @param {boolean} stick
57      */
58     setStickToBottom: function(stick)
59     {
60         this._stickToBottom = stick;
61     },
62
63     /**
64      * @param {?Event} event
65      */
66     _onWheel: function(event)
67     {
68         this._wheelTarget = event.target ? event.target.enclosingNodeOrSelfWithNodeName("tr") : null;
69     },
70
71     /**
72      * @param {?Event} event
73      */
74     _onScroll: function(event)
75     {
76         this._atBottom = this._scrollContainer.isScrolledToBottom();
77         if (this._lastScrollTop !== this._scrollContainer.scrollTop)
78             this.scheduleUpdate();
79     },
80
81     /**
82      * @protected
83      */
84     scheduleUpdate: function()
85     {
86         if (this._updateScheduled)
87             return;
88         this._updateScheduled = true;
89         window.requestAnimationFrame(this._update.bind(this));
90     },
91
92     /**
93      * @override
94      */
95     renderInline: function()
96     {
97         this._inline = true;
98         WebInspector.DataGrid.prototype.renderInline.call(this);
99         this._update();
100     },
101
102     /**
103      * @param {number} clientHeight
104      * @param {number} scrollTop
105      * @return {{topPadding: number, bottomPadding: number, visibleNodes: !Array.<!WebInspector.ViewportDataGridNode>, offset: number}}
106      */
107     _calculateVisibleNodes: function(clientHeight, scrollTop)
108     {
109         var nodes = this._rootNode.children;
110         if (this._inline)
111             return {topPadding: 0, bottomPadding: 0, visibleNodes: nodes, offset: 0};
112
113         var size = nodes.length;
114         var i = 0;
115         var y = 0;
116
117         for (; i < size && y + nodes[i].nodeSelfHeight() < scrollTop; ++i)
118             y += nodes[i].nodeSelfHeight();
119         var start = i;
120         var topPadding = y;
121
122         for (; i < size && y < scrollTop + clientHeight; ++i)
123             y += nodes[i].nodeSelfHeight();
124         var end = i;
125
126         var bottomPadding = 0;
127         for (; i < size; ++i)
128             bottomPadding += nodes[i].nodeSelfHeight();
129
130         return {topPadding: topPadding, bottomPadding: bottomPadding, visibleNodes: nodes.slice(start, end), offset: start};
131     },
132
133     /**
134      * @return {number}
135      */
136     _contentHeight: function()
137     {
138         var nodes = this._rootNode.children;
139         var result = 0;
140         for (var i = 0, size = nodes.length; i < size; ++i)
141             result += nodes[i].nodeSelfHeight();
142         return result;
143     },
144
145     _update: function()
146     {
147         this._updateScheduled = false;
148
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;
157
158         var viewportState = this._calculateVisibleNodes(clientHeight, scrollTop);
159         var visibleNodes = viewportState.visibleNodes;
160         var visibleNodesSet = Set.fromArray(visibleNodes);
161
162         if (this._hiddenWheelTarget && this._hiddenWheelTarget !== this._wheelTarget) {
163             this._hiddenWheelTarget.remove();
164             this._hiddenWheelTarget = null;
165         }
166
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();
173                 else
174                     element.remove();
175                 oldNode.wasDetached();
176             }
177         }
178
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();
187             node.willAttach();
188             element.classList.toggle("odd", (offset + i) % 2 === 0);
189             tBody.insertBefore(element, previousElement.nextSibling);
190             previousElement = element;
191         }
192
193         this.setVerticalPadding(viewportState.topPadding, viewportState.bottomPadding);
194         this._lastScrollTop = scrollTop;
195         if (scrollTop !== currentScrollTop)
196             this._scrollContainer.scrollTop = scrollTop;
197         this._visibleNodes = visibleNodes;
198     },
199
200     /**
201      * @param {!WebInspector.ViewportDataGridNode} node
202      */
203     _revealViewportNode: function(node)
204     {
205         var nodes = this._rootNode.children;
206         var index = nodes.indexOf(node);
207         if (index === -1)
208             return;
209         var fromY = 0;
210         for (var i = 0; i < index; ++i)
211             fromY += nodes[i].nodeSelfHeight();
212         var toY = fromY + node.nodeSelfHeight();
213
214         var scrollTop = this._scrollContainer.scrollTop;
215         if (scrollTop > fromY)
216             scrollTop = fromY;
217         else if (scrollTop + this._scrollContainer.offsetHeight < toY)
218             scrollTop = toY - this._scrollContainer.offsetHeight;
219         this._scrollContainer.scrollTop = scrollTop;
220     },
221
222     __proto__: WebInspector.DataGrid.prototype
223 }
224
225 /**
226  * @constructor
227  * @extends {WebInspector.DataGridNode}
228  * @param {?Object.<string, *>=} data
229  */
230 WebInspector.ViewportDataGridNode = function(data)
231 {
232     WebInspector.DataGridNode.call(this, data, false);
233     /** @type {boolean} */
234     this._stale = false;
235 }
236
237 WebInspector.ViewportDataGridNode.prototype = {
238     /**
239      * @override
240      * @return {!Element}
241      */
242     element: function()
243     {
244         if (!this._element) {
245             this.createElement();
246             this.createCells();
247             this._stale = false;
248         }
249
250         if (this._stale) {
251             this.createCells();
252             this._stale = false;
253         }
254
255         return /** @type {!Element} */ (this._element);
256     },
257
258     /**
259      * @override
260      * @param {!WebInspector.DataGridNode} child
261      * @param {number} index
262      */
263     insertChild: function(child, index)
264     {
265         child.parent = this;
266         child.dataGrid = this.dataGrid;
267         this.children.splice(index, 0, child);
268         child.recalculateSiblings(index);
269         this.dataGrid.scheduleUpdate();
270     },
271
272     /**
273      * @override
274      * @param {!WebInspector.DataGridNode} child
275      */
276     removeChild: function(child)
277     {
278         child.deselect();
279         this.children.remove(child, true);
280
281         if (child.previousSibling)
282             child.previousSibling.nextSibling = child.nextSibling;
283         if (child.nextSibling)
284             child.nextSibling.previousSibling = child.previousSibling;
285
286         this.dataGrid.scheduleUpdate();
287     },
288
289     /**
290      * @override
291      */
292     removeChildren: function()
293     {
294         for (var i = 0; i < this.children.length; ++i)
295             this.children[i].deselect();
296         this.children = [];
297
298         this.dataGrid.scheduleUpdate();
299     },
300
301     /**
302      * @override
303      */
304     expand: function()
305     {
306     },
307
308     /**
309      * @protected
310      */
311     willAttach: function() { },
312
313     /**
314      * @protected
315      * @return {boolean}
316      */
317     attached: function()
318     {
319         return !!(this._element && this._element.parentElement);
320     },
321
322     /**
323      * @override
324      */
325     refresh: function()
326     {
327         if (this.attached()) {
328             this._stale = true;
329             this.dataGrid.scheduleUpdate();
330         } else {
331             this._element = null;
332         }
333     },
334
335     /**
336      * @return {?Element}
337      */
338      abandonElement: function()
339      {
340         var result = this._element;
341         if (result)
342             result.style.display = "none";
343         this._element = null;
344         return result;
345      },
346
347     reveal: function()
348     {
349         this.dataGrid._revealViewportNode(this);
350     },
351
352     __proto__: WebInspector.DataGridNode.prototype
353 }