Index: tracing/tracing/ui/base/tab_view.html |
diff --git a/tracing/tracing/ui/base/tab_view.html b/tracing/tracing/ui/base/tab_view.html |
index a9615663af31edae3eaaccc8b97fb458b92cee7e..288fa0779cd15151edf6e177ce7b26c33ae05dcf 100644 |
--- a/tracing/tracing/ui/base/tab_view.html |
+++ b/tracing/tracing/ui/base/tab_view.html |
@@ -5,452 +5,142 @@ Use of this source code is governed by a BSD-style license that can be |
found in the LICENSE file. |
--> |
+<!-- |
+@fileoverview A series of tabs for the analysis view that controls which |
+analysis sub-view is being displayed. |
+ |
+We follow a fairly standard web convention of backing our tabs with hidden radio |
+buttons but visible radio button labels (the tabs themselves) which toggle the |
+input element when clicked. Using hidden radio buttons makes sense, as both tabs |
+and radio buttons are input elements that allow user selection through clicking |
+and limit users to having one option selected at a time. |
+--> |
<dom-module id='tr-ui-a-tab-view'> |
<template> |
<style> |
- :host { |
- display: flex; |
- flex-flow: column nowrap; |
- overflow: hidden; |
- box-sizing: border-box; |
- } |
- |
- tab-strip.hidden { |
- display: none; |
- } |
- |
- tab-strip { |
- background-color: rgb(236, 236, 236); |
- border-bottom: 1px solid #8e8e8e; |
- display: flex; |
- flex: 0 0 auto; |
- flex-flow: row; |
- overflow-x: auto; |
- padding: 0 10px 0 10px; |
+ #selection_description, #tabs { |
font-size: 12px; |
} |
- tab-button { |
- display: block; |
- flex: 0 0 auto; |
- padding: 4px 15px 1px 15px; |
- margin-top: 2px; |
- } |
- |
- tab-button[selected=true] { |
- background-color: white; |
- border: 1px solid rgb(163, 163, 163); |
- border-bottom: none; |
- padding: 3px 14px 1px 14px; |
- } |
- |
- tabs-content-container { |
- display: flex; |
- flex: 1 1 auto; |
- overflow: auto; |
- width: 100%; |
+ #selection_description { |
+ display: inline-block; |
+ font-weight: bold; |
+ margin: 9px 0px 4px 20px; |
} |
- tabs-content-container ::content > * { |
- flex: 1 1 auto; |
+ #tabs { |
+ display: block; |
+ border-top: 1px solid #8e8e8e; |
+ border-bottom: 1px solid #8e8e8e; |
+ background-color: #ececec; |
+ overflow: hidden; |
+ margin: 0; |
} |
- tabs-content-container ::content > *:not([selected]) { |
+ #tabs input[type=radio] { |
display: none; |
} |
- button-label { |
- display: inline; |
+ #tabs tab label { |
+ cursor: pointer; |
+ display: inline-block; |
+ border: 1px solid #ececec; |
+ margin: 5px 0px 0px 15px; |
+ padding: 3px 10px 3px 10px; |
} |
- tab-strip-heading { |
- display: block; |
- flex: 0 0 auto; |
- padding: 4px 15px 1px 15px; |
- margin-top: 2px; |
- margin-before: 20px; |
- margin-after: 10px; |
- } |
- #tsh { |
- display: inline; |
- font-weight: bold; |
+ #tabs input[type=radio]:checked ~ label { |
+ background-color: white; |
+ border: 1px solid #8e8e8e; |
+ border-bottom: 1px solid white; |
} |
</style> |
- |
- <tab-strip> |
- <tab-strip-heading id="tshh"> |
- <span id="tsh"></span> |
- </tab-strip-heading> |
- <template is="dom-repeat" items="{{tabs_}}"> |
- <tab-button |
- id="{{item.id}}" |
- on-click="tabButtonSelectHandler_" |
- selected="{{computeTabSelectState_(selectedTab_, item)}}"> |
- <button-label>{{computeTabLabel_(item)}}</button-label> |
- </tab-button> |
+ <div id='tabs'> |
+ <label id=selection_description>[[label_]]</label> |
+ <template is=dom-repeat items=[[subViews_]]> |
+ <tab> |
+ <input type=radio name=tabs id$=[[item.tagName]] |
+ on-change='onTabChanged_' |
+ checked$='[[isChecked_(item)]]' /> |
+ <label for$=[[item.tagName]]>[[item.tabLabel]]</label> |
+ </tab> |
</template> |
- </tab-strip> |
- |
- <tabs-content-container id='content-container'> |
- <content></content> |
- </tabs-content-container> |
- |
+ </div> |
+ <div id='subView'></div> |
+ <content> |
+ </content> |
</template> |
</dom-module> |
<script> |
'use strict'; |
-window.TracingAnalysisTabView = Polymer({ |
+ |
+Polymer({ |
is: 'tr-ui-a-tab-view', |
properties: { |
- tabs_: { |
+ label_: { |
+ type: String, |
+ value: () => '' |
+ }, |
+ subViews_: { |
type: Array, |
value: () => [] |
}, |
- selectedTab_: { |
- type: Object, |
- value: () => {} |
- } |
- }, |
- |
- ready: function() { |
- this.$.tshh.style.display = 'none'; |
- |
- // A tab is represented by the following tuple: |
- // (id, label, content, observer, savedScrollTop, savedScrollLeft). |
- // The properties are used in the following way: |
- // id: Uniquely identifies a tab. It is the same number as the index |
- // in the tabs array. Used primarily by the on-click event attached |
- // to buttons. |
- // label: A string, representing the label printed on the tab button. |
- // content: The light-dom child representing the contents of the tab. |
- // The content is appended to this tab-view by the user. |
- // observers: The observers attached to the content node to watch for |
- // attribute changes. The attributes of interest are: 'selected', |
- // and 'tab-label'. |
- // savedScrollTop/Left: Used to return the scroll position upon switching |
- // tabs. The values are generally saved when a tab switch occurs. |
- // |
- // The order of the tabs is relevant for the tab ordering. |
- |
- // Register any already existing children. |
- for (var i = 0; i < Polymer.dom(this).children.length; i++) |
- this.processAddedChild_(Polymer.dom(this).children[i]); |
- |
- // In case the user decides to add more tabs, make sure we watch for |
- // any child mutations. |
- this.childrenObserver_ = new MutationObserver( |
- this.childrenUpdated_.bind(this)); |
- this.childrenObserver_.observe( |
- this.$['content-container'], { childList: 'true' }); |
- }, |
- |
- get tabStripHeadingText() { |
- return Polymer.dom(this.$.tsh).textContent; |
- }, |
- |
- set tabStripHeadingText(tabStripHeadingText) { |
- Polymer.dom(this.$.tsh).textContent = tabStripHeadingText; |
- if (!!tabStripHeadingText) |
- this.$.tshh.style.display = ''; |
- else |
- this.$.tshh.style.display = 'none'; |
- }, |
- |
- get selectedTab() { |
- // Make sure we process any pending children additions / removals, before |
- // trying to select a tab. Otherwise, we might not find some children. |
- this.childrenUpdated_( |
- this.childrenObserver_.takeRecords(), this.childrenObserver_); |
- |
- // Do not give access to the user to the inner data structure. |
- // A user should only be able to mutate the added tab content. |
- var selectedTab = this.get('selectedTab_'); |
- if (selectedTab) |
- return selectedTab.content; |
- |
- return undefined; |
- }, |
- |
- set selectedTab(content) { |
- // Make sure we process any pending children additions / removals, before |
- // trying to select a tab. Otherwise, we might not find some children. |
- this.childrenUpdated_( |
- this.childrenObserver_.takeRecords(), this.childrenObserver_); |
- |
- if (content === undefined || content === null) { |
- this.changeSelectedTabById_(undefined); |
- return; |
- } |
- |
- // Search for the specific node in our tabs list. |
- // If it is not there print a warning. |
- var contentTabId = undefined; |
- for (var i = 0; i < this.tabs_.length; i++) { |
- if (this.get('tabs_.' + i).content === content) { |
- contentTabId = this.get('tabs_.' + i).id; |
- break; |
- } |
- } |
- |
- if (contentTabId === undefined) |
- return; |
- |
- this.changeSelectedTabById_(contentTabId); |
- }, |
- |
- get tabsHidden() { |
- var ts = Polymer.dom(this.root).querySelector('tab-strip'); |
- return ts.classList.contains('hidden'); |
+ selectedSubView_: Object |
}, |
- set tabsHidden(tabsHidden) { |
- tabsHidden = !!tabsHidden; |
- var ts = Polymer.dom(this.root).querySelector('tab-strip'); |
- if (tabsHidden) |
- ts.classList.add('hidden'); |
- else |
- ts.classList.remove('hidden'); |
+ set label(newLabel) { |
+ this.set('label_', newLabel); |
}, |
get tabs() { |
- return this.tabs_.map(function(tabObject) { |
- return tabObject.content; |
- }); |
+ return this.get('subViews_'); |
}, |
- /** |
- * Function called on light-dom child addition. |
- */ |
- processAddedChild_: function(child) { |
- var observerAttributeSelected = new MutationObserver( |
- this.childAttributesChanged_.bind(this)); |
- var observerAttributeTabLabel = new MutationObserver( |
- this.childAttributesChanged_.bind(this)); |
- var tabObject = { |
- id: this.tabs_.length, |
- content: child, |
- label: child.getAttribute('tab-label'), |
- observers: { |
- forAttributeSelected: observerAttributeSelected, |
- forAttributeTabLabel: observerAttributeTabLabel |
- } |
- }; |
- // this.tabs_.push(tabObject); |
- this.push('tabs_', tabObject); |
- if (child.hasAttribute('selected')) { |
- // When receiving a child with the selected attribute, if we have no |
- // selected tab, mark the child as the selected tab, otherwise keep |
- // the previous selection. |
- if (this.get('selectedTab_')) |
- Polymer.dom(child).removeAttribute('selected'); |
- else |
- this.setSelectedTabById_(tabObject.id); |
- } |
- |
- // This is required because the user might have set the selected |
- // property before we got to process the child. |
- var previousSelected = child.selected; |
- |
- var tabView = this; |
- |
- Object.defineProperty( |
- child, |
- 'selected', { |
- configurable: true, |
- set: function(value) { |
- if (value) { |
- tabView.changeSelectedTabById_(tabObject.id); |
- return; |
- } |
- |
- var wasSelected = (tabView.get('selectedTab_') === tabObject); |
- if (wasSelected) |
- tabView.changeSelectedTabById_(undefined); |
- }, |
- get: function() { |
- return this.hasAttribute('selected'); |
- } |
- }); |
- |
- if (previousSelected) |
- child.selected = previousSelected; |
- |
- observerAttributeSelected.observe(child, { |
- attributeFilter: ['selected'] |
- }); |
- observerAttributeTabLabel.observe(child, { |
- attributeFilter: ['tab-label'] |
- }); |
+ get selectedSubView() { |
+ return this.selectedSubView_; |
}, |
- /** |
- * Function called on light-dom child removal. |
- */ |
- processRemovedChild_: function(child) { |
- for (var i = 0; i < this.get('tabs_').length; i++) { |
- var tab = this.get('tabs_.' + i); |
- // Make sure ids are the same as the tab position after removal. |
- tab.id = i; |
- if (tab.content === child) { |
- tab.observers.forAttributeSelected.disconnect(); |
- tab.observers.forAttributeTabLabel.disconnect(); |
- // The user has removed the currently selected tab. |
- if (tab === this.get('selectedTab_')) { |
- this.clearSelectedTab_(); |
- this.fire('selected-tab-change'); |
- } |
- Polymer.dom(child).removeAttribute('selected'); |
- delete child.selected; |
- // Remove the observer since we no longer care about this child. |
- this.splice('tabs_', i, 1); |
- i--; |
- } |
- } |
- }, |
- |
- |
- /** |
- * This function handles child attribute changes. The only relevant |
- * attributes for the tab-view are 'tab-label' and 'selected'. |
- */ |
- childAttributesChanged_: function(mutations, observer) { |
- var tabObject = undefined; |
- // First figure out which child has been changed. |
- for (var i = 0; i < this.tabs_.length; i++) { |
- var observers = this.get('tabs_.' + i).observers; |
- if (observers.forAttributeSelected === observer || |
- observers.forAttributeTabLabel === observer) { |
- tabObject = this.get('tabs_.' + i); |
- break; |
- } |
- } |
- |
- // This should not happen, unless the user has messed with our internal |
- // data structure. |
- if (!tabObject) |
+ set selectedSubView(subView) { |
+ if (subView === this.selectedSubView_) |
return; |
- // Next handle the attribute changes. |
- for (var i = 0; i < mutations.length; i++) { |
- var node = tabObject.content; |
- // 'tab-label' attribute has been changed. |
- if (mutations[i].attributeName === 'tab-label') |
- tabObject.label = node.getAttribute('tab-label'); |
- // 'selected' attribute has been changed. |
- if (mutations[i].attributeName === 'selected') { |
- // The attribute has been set. |
- var nodeIsSelected = node.hasAttribute('selected'); |
- if (nodeIsSelected) |
- this.changeSelectedTabById_(tabObject.id); |
- else |
- this.changeSelectedTabById_(undefined); |
- } |
- } |
- }, |
+ if (this.selectedSubView_) |
+ Polymer.dom(this.$.subView).removeChild(this.selectedSubView_); |
- /** |
- * This function handles light-dom additions and removals from the |
- * tab-view component. |
- */ |
- childrenUpdated_: function(mutations, observer) { |
- mutations.forEach(function(mutation) { |
- for (var i = 0; i < mutation.removedNodes.length; i++) |
- this.processRemovedChild_(mutation.removedNodes[i]); |
- for (var i = 0; i < mutation.addedNodes.length; i++) |
- this.processAddedChild_(mutation.addedNodes[i]); |
- }, this); |
- }, |
+ this.set('selectedSubView_', subView); |
- /** |
- * Handler called when a click event happens on any of the tab buttons. |
- */ |
- tabButtonSelectHandler_: function(event) { |
- this.changeSelectedTabById_(event.currentTarget.id); |
- }, |
- |
- /** |
- * This does the actual work. :) |
- */ |
- changeSelectedTabById_: function(id) { |
- var newTab = id !== undefined ? this.get('tabs_.' + id) : undefined; |
- var changed = this.get('selectedTab_') !== newTab; |
- this.saveCurrentTabScrollPosition_(); |
- this.clearSelectedTab_(); |
- if (id !== undefined) { |
- this.setSelectedTabById_(id); |
- this.restoreCurrentTabScrollPosition_(); |
- } |
- |
- if (changed) |
- this.fire('selected-tab-change'); |
- }, |
- |
- /** |
- * This function updates the currently selected tab based on its internal |
- * id. The corresponding light-dom element receives the selected attribute. |
- */ |
- setSelectedTabById_: function(id) { |
- this.set('selectedTab_', this.get('tabs_.' + id)); |
- // Disconnect observer while we mutate the child. |
- this.get('selectedTab_').observers.forAttributeSelected.disconnect(); |
- Polymer.dom(this.get('selectedTab_').content) |
- .setAttribute('selected', 'selected'); |
- // Reconnect the observer to watch for changes in the future. |
- this.get('selectedTab_').observers.forAttributeSelected.observe( |
- this.get('selectedTab_').content, { attributeFilter: ['selected'] }); |
+ if (subView) |
+ Polymer.dom(this.$.subView).appendChild(subView); |
aiolos (Not reviewing)
2016/06/01 17:40:46
You *might* need to flush the dom here, depending
charliea (OOO until 10-5)
2016/06/01 18:08:47
Acknowledged. I think my preference here is to onl
aiolos (Not reviewing)
2016/06/01 18:19:57
sgtm.
|
+ this.fire('selected-tab-change'); |
}, |
- saveTabStates: function() { |
- // Scroll positions of unselected tabs have already been saved. |
- this.saveCurrentTabScrollPosition_(); |
+ clearSubViews: function() { |
+ this.splice('subViews_', 0, this.subViews_.length); |
+ this.selectedSubView = undefined; |
}, |
- saveCurrentTabScrollPosition_: function() { |
- var selectedTab = this.get('selectedTab_'); |
- if (selectedTab) { |
- selectedTab.content._savedScrollTop = |
- this.$['content-container'].scrollTop; |
- selectedTab.content._savedScrollLeft = |
- this.$['content-container'].scrollLeft; |
+ addSubView: function(subView) { |
+ if (!(subView instanceof HTMLElement) || |
+ !subView.behaviors || |
+ subView.behaviors.indexOf(Catapult.tr_ui_a_sub_view_behavior) < 0) { |
+ throw new Error('Sub-view being added must be a registered Polymer ' + |
+ 'element with the sub-view behavior'); |
} |
- }, |
- restoreCurrentTabScrollPosition_: function() { |
- var selectedTab = this.get('selectedTab_'); |
- if (selectedTab) { |
- this.$['content-container'].scrollTop = |
- selectedTab.content._savedScrollTop || 0; |
- this.$['content-container'].scrollLeft = |
- selectedTab.content._savedScrollLeft || 0; |
- } |
- }, |
+ if (!this.selectedSubView_) |
+ this.selectedSubView = subView; |
- /** |
- * This function clears the currently selected tab. This handles removal |
- * of the selected attribute from the light-dom element. |
- */ |
- clearSelectedTab_: function() { |
- var selectedTab = this.get('selectedTab_'); |
- if (selectedTab) { |
- // Disconnect observer while we mutate the child. |
- selectedTab.observers.forAttributeSelected.disconnect(); |
- Polymer.dom(selectedTab.content).removeAttribute('selected'); |
- // Reconnect the observer to watch for changes in the future. |
- selectedTab.observers.forAttributeSelected.observe( |
- selectedTab.content, { attributeFilter: ['selected'] }); |
- this.set('selectedTab_', undefined); |
- } |
+ this.push('subViews_', subView); |
}, |
- computeTabLabel_: function(tab) { |
- return tab.label ? tab.label : 'No Label'; |
+ onTabChanged_: function(event) { |
+ this.selectedSubView = event.model.item; |
}, |
- computeTabSelectState_: function(selectedTab, tab) { |
- return selectedTab.id === tab.id; |
+ isChecked_: function(subView) { |
+ return this.selectedSubView_ === subView; |
} |
- |
}); |
</script> |