Web Inspector: Elements: Styles: add icons for various CSS rule types
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / FilterBar.js
1 /*
2  * Copyright (C) 2013, 2015 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 WI.FilterBar = class FilterBar extends WI.Object
27 {
28     constructor(element)
29     {
30         super();
31
32         this._element = element || document.createElement("div");
33         this._element.classList.add("filter-bar");
34
35         this._filtersNavigationBar = new WI.NavigationBar;
36         this._element.appendChild(this._filtersNavigationBar.element);
37
38         this._filterFunctionsMap = new Map;
39
40         this._inputField = document.createElement("input");
41         this._inputField.type = "search";
42         this._inputField.placeholder = WI.UIString("Filter");
43         this._inputField.spellcheck = false;
44         this._inputField.incremental = true;
45         this._inputField.addEventListener("search", this._handleFilterChanged.bind(this));
46         this._inputField.addEventListener("input", this._handleFilterInputEvent.bind(this));
47         this._element.appendChild(this._inputField);
48
49         this._lastFilterValue = this.filters;
50     }
51
52     // Public
53
54     get element()
55     {
56         return this._element;
57     }
58
59     get inputField()
60     {
61         return this._inputField;
62     }
63
64     get placeholder()
65     {
66         return this._inputField.placeholder;
67     }
68
69     set placeholder(text)
70     {
71         this._inputField.placeholder = text;
72     }
73
74     get incremental()
75     {
76         return this._inputField.incremental;
77     }
78
79     set incremental(incremental)
80     {
81         this._inputField.incremental = incremental;
82     }
83
84     get filters()
85     {
86         return {text: this._inputField.value, functions: [...this._filterFunctionsMap.values()]};
87     }
88
89     set filters(filters)
90     {
91         filters = filters || {};
92
93         var oldTextValue = this._inputField.value;
94         this._inputField.value = filters.text || "";
95         if (oldTextValue !== this._inputField.value)
96             this._handleFilterChanged();
97     }
98
99     get indicatingProgress()
100     {
101         return this._element.classList.contains("indicating-progress");
102     }
103
104     set indicatingProgress(progress)
105     {
106         this._element.classList.toggle("indicating-progress", !!progress);
107     }
108
109     get indicatingActive()
110     {
111         return this._element.classList.contains("active");
112     }
113
114     set indicatingActive(active)
115     {
116         this._element.classList.toggle("active", !!active);
117     }
118
119     focus()
120     {
121         // FIXME: Workaround for: <https://webkit.org/b/149504> Caret missing from <input> after clearing text and calling select()
122         if (!this._inputField.value.length)
123             this._inputField.focus();
124         else
125             this._inputField.select();
126     }
127
128     clear()
129     {
130         this._filterFunctionsMap.clear();
131         this.filters = null;
132
133         // Only toggle the `WI.FilterBarButton`s after clearing the function map, as otherwise each
134         // toggle will fire another WI.FilterBar.Event.FilterDidChange event.
135         for (let navigationItem of this._filtersNavigationBar.navigationItems) {
136             if (navigationItem instanceof WI.FilterBarButton)
137                 navigationItem.toggle(false);
138         }
139
140         this._inputField.value = null; // Get the placeholder to show again.
141     }
142
143     addFilterBarButton(identifier, filterFunction, activatedByDefault, defaultToolTip, activatedToolTip, image, imageWidth, imageHeight)
144     {
145         var filterBarButton = new WI.FilterBarButton(identifier, filterFunction, activatedByDefault, defaultToolTip, activatedToolTip, image, imageWidth, imageHeight);
146         filterBarButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleFilterBarButtonClicked, this);
147         filterBarButton.addEventListener(WI.FilterBarButton.Event.ActivatedStateToggled, this._handleFilterButtonToggled, this);
148         this._filtersNavigationBar.addNavigationItem(filterBarButton);
149         if (filterBarButton.activated) {
150             this._filterFunctionsMap.set(filterBarButton.identifier, filterBarButton.filterFunction);
151             this._handleFilterChanged();
152         }
153     }
154
155     hasActiveFilters()
156     {
157         return !!this._inputField.value || !!this._filterFunctionsMap.size;
158     }
159
160     hasFilterChanged()
161     {
162         var currentFunctions = this.filters.functions;
163
164         if (this._lastFilterValue.text !== this._inputField.value || this._lastFilterValue.functions.length !== currentFunctions.length)
165             return true;
166
167         for (var i = 0; i < currentFunctions.length; ++i) {
168             if (this._lastFilterValue.functions[i] !== currentFunctions[i])
169                 return true;
170         }
171
172         return false;
173     }
174
175     // Private
176
177     _handleFilterBarButtonClicked(event)
178     {
179         var filterBarButton = event.target;
180         filterBarButton.toggle();
181     }
182
183     _handleFilterButtonToggled(event)
184     {
185         var filterBarButton = event.target;
186         if (filterBarButton.activated)
187             this._filterFunctionsMap.set(filterBarButton.identifier, filterBarButton.filterFunction);
188         else
189             this._filterFunctionsMap.delete(filterBarButton.identifier);
190         this._handleFilterChanged();
191     }
192
193     _handleFilterChanged()
194     {
195         if (this.hasFilterChanged()) {
196             this._lastFilterValue = this.filters;
197             this.dispatchEventToListeners(WI.FilterBar.Event.FilterDidChange);
198         }
199     }
200
201     _handleFilterInputEvent(event)
202     {
203         // When not incremental we still want to detect if the field becomes empty.
204
205         if (this.incremental)
206             return;
207
208         if (!this._inputField.value)
209             this._handleFilterChanged();
210     }
211 };
212
213 WI.FilterBar.Event = {
214     FilterDidChange: "filter-bar-text-filter-did-change"
215 };