Chromium Code Reviews| 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> |