Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / tracing / analysis / tab_view.html
1 <!DOCTYPE html>
2 <!--
3 Copyright (c) 2014 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
6 -->
7
8 <polymer-element name="tracing-analysis-tab-view"
9     constructor="TracingAnalysisTabView">
10   <template>
11     <style>
12       :host {
13         display: flex;
14         flex-flow: column nowrap;
15         height: 100%;
16         overflow: hidden;
17       }
18
19       tab-strip {
20         background-image: -webkit-gradient(linear, 0 0, 100% 0,
21                                            from(#E5E5E5), to(#D1D1D1));
22         border-bottom: 1px solid #8e8e8e;
23         border-top: 1px solid #fff;
24         display: flex;
25         flex: 0 0 auto;
26         flex-flow: row wrap;
27         height: 23px;
28         overflow-y: auto;
29       }
30
31       tab-button {
32         /* By default, a tab is not selected, its background color is
33          * transparent, so this button has no background color set. */
34         border-right: 1px solid #8e8e8e;
35         cursor: pointer;
36         display: block;
37         flex: 0 0 auto;
38         height: 19px;
39         padding: 2px 10px 2px 10px;
40       }
41
42       tab-button[selected='true'] {
43         background-color: #aaa;
44       }
45
46       tabs-content-container {
47         display: flex;
48         flex: 1 1 auto;
49         overflow: auto;
50         width: 100%;
51       }
52
53       ::content > * {
54         flex: 1 1 auto;
55       }
56
57       ::content > *:not([selected]) {
58         display: none;
59       }
60
61       button-label {
62         display: inline;
63         font-size: 12px;
64         font-weight: bold;
65         vertical-align: middle;
66       }
67     </style>
68
69     <tab-strip>
70       <template repeat="{{tab in tabs_}}">
71         <tab-button
72             button-id="{{ tab.id }}"
73             on-click="{{ tabButtonSelectHandler_ }}"
74             selected="{{ selectedTab_.id === tab.id }}">
75           <button-label>{{ tab.label ? tab.label : 'No Label'}}</button-label>
76         </tab-button>
77       </template>
78     </tab-strip>
79
80     <tabs-content-container id='content-container'>
81         <content></content>
82     </tabs-content-container>
83
84   </template>
85
86   <script>
87   'use strict';
88   Polymer({
89
90     get selectedTab() {
91       // Do not give access to the user to the inner data structure.
92       // A user should only be able to mutate the added tab content.
93       if (this.selectedTab_)
94         return this.selectedTab_.content;
95       return undefined;
96     },
97
98     set selectedTab(content) {
99       if (!content)
100         return;
101       // Make sure we process any pending children additions / removals, before
102       // trying to select a tab. Otherwise, we might not find some children.
103       this.childrenUpdated_(
104         this.childrenObserver_.takeRecords(), this.childrenObserver_);
105       // Search for the specific node in our tabs list.
106       // If it is not there print a warning.
107       var contentTabId = undefined;
108       for (var i = 0; i < this.tabs_.length; i++)
109         if (this.tabs_[i].content === content) {
110           contentTabId = this.tabs_[i].id;
111           break;
112         }
113
114       if (contentTabId === undefined) {
115         console.warn('Tab not in tabs list. Ignoring changed selection.');
116         return;
117       }
118
119       this.saveCurrentTabScrollPosition_();
120       this.clearSelectedTab_();
121       this.setSelectedTabById_(contentTabId);
122       this.restoreCurrentTabScrollPosition_();
123     },
124
125
126     ready: function() {
127       // A tab is represented by the following tuple:
128       // (id, label, content, observer, savedScrollTop, savedScrollLeft).
129       // The properties are used in the following way:
130       // id: Uniquely identifies a tab. It is the same number as the index
131       //     in the tabs array. Used primarily by the on-click event attached
132       //     to buttons.
133       // label: A string, representing the label printed on the tab button.
134       // content: The light-dom child representing the contents of the tab.
135       //     The content is appended to this tab-view by the user.
136       // observers: The observers attached to the content node to watch for
137       //     attribute changes. The attributes of interest are: 'selected',
138       //     and 'tab-label'.
139       // savedScrollTop/Left: Used to return the scroll position upon switching
140       //     tabs. The values are generally saved when a tab switch occurs.
141       //
142       // The order of the tabs is relevant for the tab ordering.
143       this.tabs_ = [];
144       this.selectedTab_ = undefined;
145
146       // Register any already existing children.
147       for (var i = 0; i < this.children.length; i++)
148         this.processAddedChild_(this.children[i]);
149
150       // In case the user decides to add more tabs, make sure we watch for
151       // any child mutations.
152       this.childrenObserver_ = new MutationObserver(
153           this.childrenUpdated_.bind(this));
154       this.childrenObserver_.observe(this, { childList : 'true' });
155     },
156
157
158     /**
159      * Function called on light-dom child addition.
160      */
161     processAddedChild_: function(child) {
162       var observerAttributeSelected = new MutationObserver(
163           this.childAttributesChanged_.bind(this));
164       var observerAttributeTabLabel = new MutationObserver(
165           this.childAttributesChanged_.bind(this));
166       var tabObject = {
167         id: this.tabs_.length,
168         content: child,
169         label: child.getAttribute('tab-label'),
170         observers: {
171           forAttributeSelected: observerAttributeSelected,
172           forAttributeTabLabel: observerAttributeTabLabel
173         },
174         savedScrollTop: 0,
175         savedScrollLeft: 0
176       };
177
178       this.tabs_.push(tabObject);
179       if (child.hasAttribute('selected')) {
180         // When receiving a child with the selected attribute, if we have no
181         // selected tab, mark the child as the selected tab, otherwise keep
182         // the previous selection.
183         if (this.selectedTab_)
184           child.removeAttribute('selected');
185         else
186           this.setSelectedTabById_(tabObject.id);
187       }
188
189       // This is required because the user might have set the selected
190       // property before we got to process the child.
191       var previousSelected = child.selected;
192
193       var tabView = this;
194
195       Object.defineProperty(
196           child,
197           'selected', {
198             configurable: true,
199             set: function(value) {
200               if (value) {
201                 tabView.saveCurrentTabScrollPosition_();
202                 tabView.clearSelectedTab_();
203                 tabView.setSelectedTabById_(tabObject.id);
204                 tabView.restoreCurrentTabScrollPosition_();
205               } else if (tabView.selectedTab_ === tabObject) {
206                 tabView.saveCurrentTabScrollPosition_();
207                 tabView.clearSelectedTab_();
208               }
209             },
210             get: function() {
211               return this.hasAttribute('selected');
212             }
213           });
214
215       if (previousSelected)
216         child.selected = previousSelected;
217
218       observerAttributeSelected.observe(child,
219           { attributeFilter: ['selected'] });
220       observerAttributeTabLabel.observe(child,
221           { attributeFilter: ['tab-label'] });
222
223     },
224
225     /**
226      * Function called on light-dom child removal.
227      */
228     processRemovedChild_: function(child) {
229       for (var i = 0; i < this.tabs_.length; i++) {
230         // Make sure ids are the same as the tab position after removal.
231         this.tabs_[i].id = i;
232         if (this.tabs_[i].content === child) {
233           this.tabs_[i].observers.forAttributeSelected.disconnect();
234           this.tabs_[i].observers.forAttributeTabLabel.disconnect();
235           // The user has removed the currently selected tab.
236           if (this.tabs_[i] === this.selectedTab_)
237             this.clearSelectedTab_();
238           child.removeAttribute('selected');
239           delete child.selected;
240           // Remove the observer since we no longer care about this child.
241           this.tabs_.splice(i, 1);
242           i--;
243         }
244       }
245     },
246
247
248     /**
249      * This function handles child attribute changes. The only relevant
250      * attributes for the tab-view are 'tab-label' and 'selected'.
251      */
252     childAttributesChanged_: function(mutations, observer) {
253       var tabObject = undefined;
254       // First figure out which child has been changed.
255       for (var i = 0; i < this.tabs_.length; i++) {
256         var observers = this.tabs_[i].observers;
257         if (observers.forAttributeSelected === observer ||
258             observers.forAttributeTabLabel === observer) {
259             tabObject = this.tabs_[i];
260             break;
261         }
262       }
263
264       // This should not happen, unless the user has messed with our internal
265       // data structure.
266       if (!tabObject)
267         return;
268
269       // Next handle the attribute changes.
270       for (var i = 0; i < mutations.length; i++) {
271         var node = tabObject.content;
272         // 'tab-label' attribute has been changed.
273         if (mutations[i].attributeName === 'tab-label')
274           tabObject.label = node.getAttribute('tab-label');
275         // 'selected' attribute has been changed.
276         if (mutations[i].attributeName === 'selected') {
277           // The attribute has been set.
278           var nodeIsSelected = node.hasAttribute('selected');
279           this.saveCurrentTabScrollPosition_();
280           this.clearSelectedTab_();
281           if (nodeIsSelected) {
282             this.setSelectedTabById_(tabObject.id);
283             this.restoreCurrentTabScrollPosition_();
284           }
285         }
286       }
287     },
288
289     /**
290      * This function handles light-dom additions and removals from the
291      * tab-view component.
292      */
293     childrenUpdated_: function(mutations, observer) {
294       mutations.forEach(function (mutation) {
295         for (var i = 0; i < mutation.removedNodes.length; i++)
296           this.processRemovedChild_(mutation.removedNodes[i]);
297         for (var i = 0; i < mutation.addedNodes.length; i++)
298           this.processAddedChild_(mutation.addedNodes[i]);
299       }, this);
300     },
301
302
303     /**
304      * This function updates the currently selected tab based on its internal
305      * id. The corresponding light-dom element receives the selected attribute.
306      */
307     setSelectedTabById_: function(id) {
308       this.selectedTab_ = this.tabs_[id];
309       // Disconnect observer while we mutate the child.
310       this.selectedTab_.observers.forAttributeSelected.disconnect();
311       this.selectedTab_.content.setAttribute('selected', 'selected');
312       // Reconnect the observer to watch for changes in the future.
313       this.selectedTab_.observers.forAttributeSelected.observe(
314           this.selectedTab_.content, { attributeFilter: ['selected'] });
315
316     },
317
318     saveCurrentTabScrollPosition_: function() {
319       if (this.selectedTab_) {
320         this.selectedTab_.savedScrollTop =
321             this.$['content-container'].scrollTop;
322         this.selectedTab_.savedScrollLeft =
323             this.$['content-container'].scrollLeft;
324       }
325     },
326
327     restoreCurrentTabScrollPosition_: function() {
328       if (this.selectedTab_) {
329         this.$['content-container'].scrollTop =
330             this.selectedTab_.savedScrollTop;
331         this.$['content-container'].scrollLeft =
332             this.selectedTab_.savedScrollLeft;
333       }
334     },
335
336     /**
337      * This function clears the currently selected tab. This handles removal
338      * of the selected attribute from the light-dom element.
339      */
340     clearSelectedTab_: function() {
341       if (this.selectedTab_) {
342         // Disconnect observer while we mutate the child.
343         this.selectedTab_.observers.forAttributeSelected.disconnect();
344         this.selectedTab_.content.removeAttribute('selected');
345         // Reconnect the observer to watch for changes in the future.
346         this.selectedTab_.observers.forAttributeSelected.observe(
347             this.selectedTab_.content, { attributeFilter: ['selected'] });
348         this.selectedTab_ = undefined;
349       }
350     },
351
352     /**
353      * Handler called when a click event happens on any of the tab buttons.
354      */
355     tabButtonSelectHandler_: function(event, detail, sender) {
356       this.saveCurrentTabScrollPosition_();
357       this.clearSelectedTab_();
358       this.setSelectedTabById_(sender.getAttribute('button-id'));
359       this.restoreCurrentTabScrollPosition_();
360     },
361   });
362   </script>
363 </polymer-element>