1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 6 /** 7 * @fileoverview This implements a tab control. 8 * 9 * An individual tab within a tab control is, unsurprisingly, a Tab. 10 * Tabs must be explicitly added/removed from the control. 11 * 12 * Tab titles are based on the label attribute of each child: 13 * 14 * <div> 15 * <div label='Tab 1'>Hello</div> 16 * <div label='Tab 2'>World</div> 17 * </div> 18 * 19 * Results in: 20 * 21 * --------------- 22 * | Tab1 | Tab2 | 23 * | --------------------------------------- 24 * | Hello World | 25 * ----------------------------------------- 26 * 27 */ 28 cr.define('gpu', function() { 29 /** 30 * Creates a new tab element. A tab element is one of multiple tabs 31 * within a TabControl. 32 * @constructor 33 * @param {Object=} opt_propertyBag Optional properties. 34 * @extends {HTMLDivElement} 35 */ 36 var Tab = cr.ui.define('div'); 37 Tab.prototype = { 38 __proto__: HTMLDivElement.prototype, 39 40 decorate: function() { 41 } 42 }; 43 44 /** 45 * Title for the tab. 46 * @type {String} 47 */ 48 cr.defineProperty(Tab, 'label', cr.PropertyKind.ATTR); 49 50 /** 51 * Whether the item is selected. 52 * @type {boolean} 53 */ 54 cr.defineProperty(Tab, 'selected', cr.PropertyKind.BOOL_ATTR); 55 56 57 /** 58 * Creates a new tab button element in the tabstrip 59 * @constructor 60 * @param {Object=} opt_propertyBag Optional properties. 61 * @extends {HTMLDivElement} 62 */ 63 var TabButton = cr.ui.define('a'); 64 TabButton.prototype = { 65 __proto__: HTMLAnchorElement.prototype, 66 67 decorate: function() { 68 this.classList.add('tab-button'); 69 this.onclick = function() { 70 if (this.tab_) 71 this.parentNode.parentNode.selectedTab = this.tab_; 72 }.bind(this); 73 }, 74 get tab() { 75 return this.tab_; 76 }, 77 set tab(tab) { 78 if (this.tab_) 79 throw Error('Cannot set tab once set.'); 80 this.tab_ = tab; 81 this.tab_.addEventListener('titleChange', this.onTabChanged_.bind(this)); 82 this.tab_.addEventListener('selectedChange', 83 this.onTabChanged_.bind(this)); 84 this.onTabChanged_(); 85 }, 86 87 onTabChanged_: function(e) { 88 if (this.tab_) { 89 this.textContent = this.tab_.label; 90 this.selected = this.tab_.selected; 91 } 92 } 93 94 }; 95 96 /** 97 * Whether the TabButton is selected. 98 * @type {boolean} 99 */ 100 cr.defineProperty(TabButton, 'selected', cr.PropertyKind.BOOL_ATTR); 101 102 103 /** 104 * Creates a new tab control element. 105 * @param {Object=} opt_propertyBag Optional properties. 106 * @constructor 107 * @extends {HTMLDivElement} 108 */ 109 var TabControl = cr.ui.define('div'); 110 TabControl.prototype = { 111 __proto__: HTMLDivElement.prototype, 112 113 selectedTab_: null, 114 115 /** 116 * Initializes the tab control element. 117 * Any child elements pre-existing on the element will become tabs. 118 */ 119 decorate: function() { 120 this.classList.add('tab-control'); 121 122 this.tabStrip_ = this.ownerDocument.createElement('div'); 123 this.tabStrip_.classList.add('tab-strip'); 124 125 this.tabs_ = this.ownerDocument.createElement('div'); 126 this.tabs_.classList.add('tabs'); 127 128 this.insertBefore(this.tabs_, this.firstChild); 129 this.insertBefore(this.tabStrip_, this.firstChild); 130 131 this.boundOnTabSelectedChange_ = this.onTabSelectedChange_.bind(this); 132 133 // Reparent existing tabs to the tabs_ div. 134 while (this.children.length > 2) 135 this.addTab(this.children[2]); 136 }, 137 138 /** 139 * Adds an element to the tab control. 140 */ 141 addTab: function(tab) { 142 if (tab.parentNode == this.tabs_) 143 throw Error('Tab is already part of this control.'); 144 if (!(tab instanceof Tab)) 145 throw Error('Provided element is not instanceof Tab.'); 146 this.tabs_.appendChild(tab); 147 148 tab.addEventListener('selectedChange', this.boundOnTabSelectedChange_); 149 150 var button = new TabButton(); 151 button.tab = tab; 152 tab.tabStripButton_ = button; 153 154 this.tabStrip_.appendChild(button); 155 156 if (this.tabs_.length == 1) 157 this.tabs_.children[0].selected = true; 158 }, 159 160 /** 161 * Removes a tab from the tab control. 162 * changing the selected tab if needed. 163 */ 164 removeTab: function(tab) { 165 if (tab.parentNode != this.tabs_) 166 throw new Error('Tab is not attached to this control.'); 167 168 tab.removeEventListener('selectedChange', this.boundOnTabSelectedChange_); 169 170 if (this.selectedTab_ == tab) { 171 if (this.tabs_.children.length) { 172 this.tabs_.children[0].selected = true; 173 } else { 174 this.selectedTab_ = undefined; 175 } 176 } 177 178 this.tabs_.removeChild(tab); 179 tab.tabStripButton_.parentNode.removeChild( 180 tab.tabStripButton_); 181 }, 182 183 /** 184 * Gets the currently selected tab element. 185 */ 186 get selectedTab() { 187 return this.selectedTab_; 188 }, 189 190 /** 191 * Sets the currently selected tab element. 192 */ 193 set selectedTab(tab) { 194 if (tab.parentNode != this.tabs_) 195 throw Error('Tab is not part of this TabControl.'); 196 tab.selected = true; 197 }, 198 199 /** 200 * Hides the previously selected tab element and dispatches a 201 * 'selectedTabChanged' event. 202 */ 203 onTabSelectedChange_: function(e) { 204 var tab = e.target; 205 if (!e.newValue) { 206 // Usually we can ignore this event, as the tab becoming unselected 207 // needs no corrective action. However, if the currently selected 208 // tab is deselected, we do need to do some work. 209 if (tab == this.selectedTab_) { 210 var previousTab = this.selectedTab_; 211 var newTab; 212 for (var i = 0; i < this.tabs_.children.length; ++i) { 213 if (this.tabs_.children[i] != tab) { 214 newTab = this.tabs_.children[i]; 215 break; 216 } 217 } 218 if (newTab) { 219 newTab.selected = true; 220 } else { 221 this.selectedTab_ = undefined; 222 cr.dispatchPropertyChange( 223 this, 'selectedTab', this.selectedTab_, previousTab); 224 } 225 } 226 } else { 227 var previousTab = this.selectedTab_; 228 this.selectedTab_ = tab; 229 if (previousTab) 230 previousTab.selected = false; 231 cr.dispatchPropertyChange( 232 this, 'selectedTab', this.selectedTab_, previousTab); 233 } 234 }, 235 236 /** 237 * Returns an array of all the tabs within this control. This is 238 * not the same as this.children because the actual tab elements are 239 * attached to the tabs_ element. 240 */ 241 get tabs() { 242 return Array.prototype.slice.call(this.tabs_.children); 243 } 244 }; 245 246 return { 247 Tab: Tab, 248 TabControl: TabControl 249 }; 250 }); 251