Web Inspector: Convert TreeElement classes to ES6
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / GeneralTreeElement.js
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.GeneralTreeElement = class GeneralTreeElement extends WebInspector.TreeElement
27 {
28     constructor(classNames, title, subtitle, representedObject, hasChildren)
29     {
30         super("", representedObject, hasChildren);
31
32         this.classNames = classNames;
33
34         this._tooltipHandledSeparately = false;
35         this._mainTitle = title || "";
36         this._subtitle = subtitle || "";
37         this._status = "";
38     }
39
40     // Public
41
42     get element()
43     {
44         return this._listItemNode;
45     }
46
47     get iconElement()
48     {
49         this._createElementsIfNeeded();
50         return this._iconElement;
51     }
52
53     get titlesElement()
54     {
55         this._createElementsIfNeeded();
56         return this._titlesElement;
57     }
58
59     get mainTitleElement()
60     {
61         this._createElementsIfNeeded();
62         return this._mainTitleElement;
63     }
64
65     get subtitleElement()
66     {
67         this._createElementsIfNeeded();
68         this._createSubtitleElementIfNeeded();
69         return this._subtitleElement;
70     }
71
72     get classNames()
73     {
74         return this._classNames;
75     }
76
77     set classNames(x)
78     {
79         if (this._listItemNode && this._classNames) {
80             for (var i = 0; i < this._classNames.length; ++i)
81                 this._listItemNode.classList.remove(this._classNames[i]);
82         }
83
84         if (typeof x === "string")
85             x = [x];
86
87         this._classNames = x || [];
88
89         if (this._listItemNode) {
90             for (var i = 0; i < this._classNames.length; ++i)
91                 this._listItemNode.classList.add(this._classNames[i]);
92         }
93     }
94
95     addClassName(className)
96     {
97         if (this._classNames.contains(className))
98             return;
99
100         this._classNames.push(className);
101
102         if (this._listItemNode)
103             this._listItemNode.classList.add(className);
104     }
105
106     removeClassName(className)
107     {
108         if (!this._classNames.contains(className))
109             return;
110
111         this._classNames.remove(className);
112
113         if (this._listItemNode)
114             this._listItemNode.classList.remove(className);
115     }
116
117     get small()
118     {
119         return this._small;
120     }
121
122     set small(x)
123     {
124         this._small = x;
125
126         if (this._listItemNode) {
127             if (this._small)
128                 this._listItemNode.classList.add(WebInspector.GeneralTreeElement.SmallStyleClassName);
129             else
130                 this._listItemNode.classList.remove(WebInspector.GeneralTreeElement.SmallStyleClassName);
131         }
132     }
133
134     get twoLine()
135     {
136         return this._twoLine;
137     }
138
139     set twoLine(x)
140     {
141         this._twoLine = x;
142
143         if (this._listItemNode) {
144             if (this._twoLine)
145                 this._listItemNode.classList.add(WebInspector.GeneralTreeElement.TwoLineStyleClassName);
146             else
147                 this._listItemNode.classList.remove(WebInspector.GeneralTreeElement.TwoLineStyleClassName);
148         }
149     }
150
151     get mainTitle()
152     {
153         return this._mainTitle;
154     }
155
156     set mainTitle(x)
157     {
158         this._mainTitle = x || "";
159         this._updateTitleElements();
160         this.didChange();
161         this.dispatchEventToListeners(WebInspector.GeneralTreeElement.Event.MainTitleDidChange);
162     }
163
164     get subtitle()
165     {
166         return this._subtitle;
167     }
168
169     set subtitle(x)
170     {
171         this._subtitle = x || "";
172         this._updateTitleElements();
173         this.didChange();
174     }
175
176     get status()
177     {
178         return this._status;
179     }
180
181     set status(x)
182     {
183         if (this._status === x)
184             return;
185
186         if (!this._statusElement) {
187             this._statusElement = document.createElement("div");
188             this._statusElement.className = WebInspector.GeneralTreeElement.StatusElementStyleClassName;
189         }
190
191         this._status = x || "";
192         this._updateStatusElement();
193     }
194
195     get filterableData()
196     {
197         return {text: [this.mainTitle, this.subtitle]};
198     }
199
200     get tooltipHandledSeparately()
201     {
202         return this._tooltipHandledSeparately;
203     }
204
205     set tooltipHandledSeparately(x)
206     {
207         this._tooltipHandledSeparately = x || false;
208     }
209
210     // Overrides from TreeElement (Private)
211
212     isEventWithinDisclosureTriangle(event)
213     {
214         return event.target === this._disclosureButton;
215     }
216
217     onattach()
218     {
219         this._createElementsIfNeeded();
220         this._updateTitleElements();
221
222         this._listItemNode.classList.add("item");
223
224         if (this._classNames) {
225             for (var i = 0; i < this._classNames.length; ++i)
226                 this._listItemNode.classList.add(this._classNames[i]);
227         }
228
229         if (this._small)
230             this._listItemNode.classList.add(WebInspector.GeneralTreeElement.SmallStyleClassName);
231
232         if (this._twoLine)
233             this._listItemNode.classList.add(WebInspector.GeneralTreeElement.TwoLineStyleClassName);
234
235         this._listItemNode.appendChild(this._disclosureButton);
236         this._listItemNode.appendChild(this._iconElement);
237         this._listItemNode.appendChild(this._titlesElement);
238
239         if (this.oncontextmenu && typeof this.oncontextmenu === "function") {
240             this._boundContextMenuEventHandler = this.oncontextmenu.bind(this);
241             this._listItemNode.addEventListener("contextmenu", this._boundContextMenuEventHandler, true);
242         }
243
244         if (!this._boundContextMenuEventHandler && this.treeOutline.oncontextmenu && typeof this.treeOutline.oncontextmenu === "function") {
245             this._boundContextMenuEventHandler = function(event) { this.treeOutline.oncontextmenu(event, this); }.bind(this);
246             this._listItemNode.addEventListener("contextmenu", this._boundContextMenuEventHandler, true);
247         }
248
249         this._updateStatusElement();
250     }
251
252     ondetach()
253     {
254         if (this._boundContextMenuEventHandler) {
255             this._listItemNode.removeEventListener("contextmenu", this._boundContextMenuEventHandler, true);
256             delete this._boundContextMenuEventHandler;
257         }
258     }
259
260     onreveal()
261     {
262         if (this._listItemNode)
263             this._listItemNode.scrollIntoViewIfNeeded(false);
264     }
265
266     // Protected
267
268     callFirstAncestorFunction(functionName, args)
269     {
270         // Call the first ancestor that implements a function named functionName (if any).
271         var currentNode = this.parent;
272         while (currentNode) {
273             if (typeof currentNode[functionName] === "function") {
274                 currentNode[functionName].apply(currentNode, args);
275                 break;
276             }
277
278             currentNode = currentNode.parent;
279         }
280     }
281
282     // Private
283
284     _createElementsIfNeeded()
285     {
286         if (this._createdElements)
287             return;
288
289         this._disclosureButton = document.createElement("button");
290         this._disclosureButton.className = WebInspector.GeneralTreeElement.DisclosureButtonStyleClassName;
291
292         // Don't allow the disclosure button to be keyboard focusable. The TreeOutline is focusable and has
293         // its own keybindings for toggling expand and collapse.
294         this._disclosureButton.tabIndex = -1;
295
296         this._iconElement = document.createElement("img");
297         this._iconElement.className = WebInspector.GeneralTreeElement.IconElementStyleClassName;
298
299         this._titlesElement = document.createElement("div");
300         this._titlesElement.className = WebInspector.GeneralTreeElement.TitlesElementStyleClassName;
301
302         this._mainTitleElement = document.createElement("span");
303         this._mainTitleElement.className = WebInspector.GeneralTreeElement.MainTitleElementStyleClassName;
304         this._titlesElement.appendChild(this._mainTitleElement);
305
306         this._createdElements = true;
307     }
308
309     _createSubtitleElementIfNeeded()
310     {
311         if (this._subtitleElement)
312             return;
313
314         this._subtitleElement = document.createElement("span");
315         this._subtitleElement.className = WebInspector.GeneralTreeElement.SubtitleElementStyleClassName;
316         this._titlesElement.appendChild(this._subtitleElement);
317     }
318
319     _updateTitleElements()
320     {
321         if (!this._createdElements)
322             return;
323
324         if (typeof this._mainTitle === "string") {
325             if (this._mainTitleElement.textContent !== this._mainTitle)
326                 this._mainTitleElement.textContent = this._mainTitle;
327         } else if (this._mainTitle instanceof Node) {
328             this._mainTitleElement.removeChildren();
329             this._mainTitleElement.appendChild(this._mainTitle);
330         }
331
332         if (typeof this._subtitle === "string" && this._subtitle) {
333             this._createSubtitleElementIfNeeded();
334             if (this._subtitleElement.textContent !== this._subtitle)
335                 this._subtitleElement.textContent = this._subtitle;
336             this._titlesElement.classList.remove(WebInspector.GeneralTreeElement.NoSubtitleStyleClassName);
337         } else if (this._subtitle instanceof Node) {
338             this._createSubtitleElementIfNeeded();
339             this._subtitleElement.removeChildren();
340             this._subtitleElement.appendChild(this._subtitle);
341         } else {
342             if (this._subtitleElement)
343                 this._subtitleElement.textContent = "";
344             this._titlesElement.classList.add(WebInspector.GeneralTreeElement.NoSubtitleStyleClassName);
345         }
346
347         // Set a default tooltip if there isn't a custom one already assigned.
348         if (!this.tooltip && !this._tooltipHandledSeparately) {
349             console.assert(this._listItemNode);
350
351             // Get the textContent for the elements since they can contain other nodes,
352             // and the tool tip only cares about the text.
353             var mainTitleText = this._mainTitleElement.textContent;
354             var subtitleText = this._subtitleElement ? this._subtitleElement.textContent : "";
355
356             if (mainTitleText && subtitleText)
357                 this._listItemNode.title = mainTitleText + (this._small && !this._twoLine ? " \u2014 " : "\n") + subtitleText;
358             else if (mainTitleText)
359                 this._listItemNode.title = mainTitleText;
360             else
361                 this._listItemNode.title = subtitleText;
362         }
363     }
364
365     _updateStatusElement()
366     {
367         if (!this._statusElement)
368             return;
369
370         if (!this._statusElement.parentNode && this._listItemNode)
371             this._listItemNode.insertBefore(this._statusElement, this._titlesElement);
372
373         if (this._status instanceof Node) {
374             this._statusElement.removeChildren();
375             this._statusElement.appendChild(this._status);
376         } else
377             this._statusElement.textContent = this._status;
378     }
379 };
380
381 WebInspector.GeneralTreeElement.DisclosureButtonStyleClassName = "disclosure-button";
382 WebInspector.GeneralTreeElement.IconElementStyleClassName = "icon";
383 WebInspector.GeneralTreeElement.StatusElementStyleClassName = "status";
384 WebInspector.GeneralTreeElement.TitlesElementStyleClassName = "titles";
385 WebInspector.GeneralTreeElement.MainTitleElementStyleClassName = "title";
386 WebInspector.GeneralTreeElement.SubtitleElementStyleClassName = "subtitle";
387 WebInspector.GeneralTreeElement.NoSubtitleStyleClassName = "no-subtitle";
388 WebInspector.GeneralTreeElement.SmallStyleClassName = "small";
389 WebInspector.GeneralTreeElement.TwoLineStyleClassName = "two-line";
390
391 WebInspector.GeneralTreeElement.Event = {
392     MainTitleDidChange: "general-tree-element-main-title-did-change"
393 };