Web Inspector: replace TypeVerifier with subclasses of WI.Collection
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / CollectionContentView.js
1 /*
2  * Copyright (C) 2016 Devin Rousso <webkit@devinrousso.com>. 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 WI.CollectionContentView = class CollectionContentView extends WI.ContentView
27 {
28     constructor(collection, contentViewConstructor, contentPlaceholderText)
29     {
30         console.assert(collection instanceof WI.Collection);
31
32         super(collection);
33
34         this.element.classList.add("collection");
35
36         this._contentPlaceholderText = contentPlaceholderText || collection.displayName;
37         this._contentViewConstructor = contentViewConstructor;
38         this._contentViewMap = new Map;
39         this._handleClickMap = new WeakMap;
40         this._selectedItem = null;
41         this._selectionEnabled = false;
42     }
43
44     // Public
45
46     get supplementalRepresentedObjects()
47     {
48         if (this._selectedItem)
49             return [this._selectedItem];
50         return [];
51     }
52
53     get selectionEnabled()
54     {
55         return this._selectionEnabled;
56     }
57
58     set selectionEnabled(value)
59     {
60         if (this._selectionEnabled === value)
61             return;
62
63         this._selectionEnabled = value;
64         if (!this._selectionEnabled)
65             this._selectItem(null);
66     }
67
68     setSelectedItem(item)
69     {
70         console.assert(this._selectionEnabled, "Attempted to set selected item when selection is disabled.");
71         if (!this._selectionEnabled)
72             return;
73
74         let contentView = this._contentViewMap.get(item);
75         console.assert(contentView, "Missing contet view for item.", item);
76         if (!contentView)
77             return;
78
79         this._selectItem(item);
80         contentView.element.scrollIntoViewIfNeeded();
81     }
82
83     // Protected
84
85     addContentViewForItem(item)
86     {
87         if (!this._contentViewConstructor)
88             return;
89
90         if (this._contentViewMap.has(item)) {
91             console.assert(false, "Already added ContentView for item.", item);
92             return;
93         }
94
95         this._hideContentPlaceholder();
96
97         let contentView = new this._contentViewConstructor(item);
98         console.assert(contentView instanceof WI.ContentView);
99
100         let handleClick = (event) => {
101             if (event.button !== 0 || event.ctrlKey)
102                 return;
103
104             if (this._selectionEnabled)
105                 this._selectItem(item);
106             else
107                 WI.showRepresentedObject(item);
108         };
109
110         this._contentViewMap.set(item, contentView);
111         this._handleClickMap.set(item, handleClick);
112         contentView.element.addEventListener("click", handleClick);
113
114         this.addSubview(contentView);
115         this.contentViewAdded(contentView);
116
117         contentView.shown();
118     }
119
120     removeContentViewForItem(item)
121     {
122         if (!this._contentViewConstructor)
123             return;
124
125         let contentView = this._contentViewMap.get(item);
126         console.assert(contentView);
127         if (!contentView)
128             return;
129
130         if (this._selectedItem === item)
131             this._selectItem(null);
132
133         this.removeSubview(contentView);
134         this._contentViewMap.delete(item);
135         this.contentViewRemoved(contentView);
136
137         contentView.hidden();
138
139         contentView.removeEventListener(null, null, this);
140
141         let handleClick = this._handleClickMap.get(item);
142         console.assert(handleClick);
143
144         if (handleClick) {
145             contentView.element.removeEventListener("click", handleClick);
146             this._handleClickMap.delete(item);
147         }
148
149         if (!this.subviews.length)
150             this._showContentPlaceholder();
151     }
152
153     contentViewAdded(contentView)
154     {
155         // Implemented by subclasses.
156     }
157
158     contentViewRemoved(contentView)
159     {
160         // Implemented by subclasses.
161     }
162
163     initialLayout()
164     {
165         let items = this.representedObject.items;
166         if (!items.size || !this._contentViewConstructor) {
167             this._showContentPlaceholder();
168             return;
169         }
170     }
171
172     attached()
173     {
174         super.attached();
175
176         this.representedObject.addEventListener(WI.Collection.Event.ItemAdded, this._handleItemAdded, this);
177         this.representedObject.addEventListener(WI.Collection.Event.ItemRemoved, this._handleItemRemoved, this);
178
179         for (let item of this._contentViewMap.keys()) {
180             if (this.representedObject.items.has(item))
181                 continue;
182
183             this.removeContentViewForItem(item);
184             if (this._selectedItem === item)
185                 this._selectItem(null);
186         }
187
188         for (let item of this.representedObject.items) {
189             if (!this._contentViewMap.has(item))
190                 this.addContentViewForItem(item);
191         }
192     }
193
194     detached()
195     {
196         this.representedObject.removeEventListener(null, null, this);
197
198         super.detached();
199     }
200
201      // Private
202
203     _handleItemAdded(event)
204     {
205         let item = event.data.item;
206         if (!item)
207             return;
208
209         this.addContentViewForItem(item);
210     }
211
212     _handleItemRemoved(event)
213     {
214         let item = event.data.item;
215         if (!item)
216             return;
217
218         this.removeContentViewForItem(item);
219     }
220
221     _handleContentError(event)
222     {
223         if (event && event.target)
224             this._removeContentViewForItem(event.target.representedObject);
225     }
226
227     _selectItem(item)
228     {
229         if (this._selectedItem === item)
230             return;
231
232         if (this._selectedItem) {
233             let contentView = this._contentViewMap.get(this._selectedItem);
234             console.assert(contentView, "Missing ContentView for deselected item.", this._selectedItem);
235             contentView.element.classList.remove("selected");
236         }
237
238         this._selectedItem = item;
239
240         if (this._selectedItem) {
241             let selectedContentView = this._contentViewMap.get(this._selectedItem);
242             console.assert(selectedContentView, "Missing ContentView for selected item.", this._selectedItem);
243             selectedContentView.element.classList.add("selected");
244         }
245
246         this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange);
247     }
248
249     _showContentPlaceholder()
250     {
251         if (!this._contentPlaceholder)
252             this._contentPlaceholder = new WI.TitleView(this._contentPlaceholderText);
253
254         if (!this._contentPlaceholder.parentView)
255             this.addSubview(this._contentPlaceholder);
256     }
257
258     _hideContentPlaceholder()
259     {
260         this.addSubview.cancelDebounce();
261
262         if (this._contentPlaceholder && this._contentPlaceholder.parentView)
263             this.removeSubview(this._contentPlaceholder);
264     }
265 };