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 cr.define('cr.ui', function() { 6 7 /** 8 * Returns the TabBox for a Tab or a TabPanel. 9 * @param {Tab|TabPanel} el The tab or tabpanel element. 10 * @return {TabBox} The tab box if found. 11 */ 12 function getTabBox(el) { 13 return el.parentElement && el.parentElement.parentElement; 14 } 15 16 /** 17 * Set hook for the selected property for Tab and TabPanel. 18 * This sets the selectedIndex on the parent TabBox. 19 * @param {boolean} newValue The new selected value 20 * @param {boolean} oldValue The old selected value. (This is ignored atm.) 21 * @this {Tab|TabPanel} 22 */ 23 function selectedSetHook(newValue, oldValue) { 24 var tabBox; 25 if (newValue && (tabBox = getTabBox(this))) 26 tabBox.selectedIndex = Array.prototype.indexOf.call(p.children, this); 27 } 28 29 /** 30 * Decorates all the children of an element. 31 * @this {HTMLElement} 32 */ 33 function decorateChildren() { 34 var map = { 35 TABBOX: TabBox, 36 TABS: Tabs, 37 TAB: Tab, 38 TABPANELS: TabPanels, 39 TABPANEL: TabPanel 40 }; 41 42 var child; 43 for (var i = 0; child = this.children[i]; i++) { 44 var constr = map[child.tagName]; 45 if (constr) 46 cr.ui.decorate(child, constr); 47 } 48 } 49 50 /** 51 * Set hook for TabBox selectedIndex. 52 * @param {number} selectedIndex The new selected index. 53 * @this {TabBox} 54 */ 55 function selectedIndexSetHook(selectedIndex) { 56 var child, tabChild; 57 for (var i = 0; child = this.children[i]; i++) { 58 for (var j = 0; tabChild = child.children[j]; j++) { 59 tabChild.selected = j == selectedIndex; 60 } 61 } 62 } 63 64 /** 65 * Creates a new tabbox element. 66 * @param {Object=} opt_propertyBag Optional properties. 67 * @constructor 68 * @extends {HTMLElement} 69 */ 70 var TabBox = cr.ui.define('tabbox'); 71 72 TabBox.prototype = { 73 __proto__: HTMLElement.prototype, 74 decorate: function() { 75 decorateChildren.call(this); 76 this.addEventListener('selectedChange', this.handleSelectedChange_, true); 77 this.selectedIndex = 0; 78 }, 79 80 /** 81 * Callback for when a Tab or TabPanel changes its selected property. 82 * @param {Event} e The property change event. 83 * @private 84 */ 85 handleSelectedChange_: function(e) { 86 var target = e.target; 87 if (e.newValue && getTabBox(target) == this) { 88 var index = Array.prototype.indexOf.call(target.parentElement.children, 89 target); 90 this.selectedIndex = index; 91 } 92 }, 93 94 selectedIndex_: -1 95 }; 96 97 /** 98 * The index of the selected tab or -1 if no tab is selected. 99 * @type {number} 100 */ 101 cr.defineProperty(TabBox, 'selectedIndex', cr.PropertyKind.JS_PROP, 102 selectedIndexSetHook); 103 104 /** 105 * Creates a new tabs element. 106 * @param {string} opt_label The text label for the item. 107 * @constructor 108 * @extends {HTMLElement} 109 */ 110 var Tabs = cr.ui.define('tabs'); 111 Tabs.prototype = { 112 __proto__: HTMLElement.prototype, 113 decorate: function() { 114 decorateChildren.call(this); 115 116 // Make the Tabs element fousable. 117 this.tabIndex = 0; 118 this.addEventListener('keydown', this.handleKeyDown_.bind(this)); 119 120 // Get (and initializes a focus outline manager. 121 this.focusOutlineManager_ = 122 cr.ui.FocusOutlineManager.forDocument(this.ownerDocument); 123 }, 124 125 /** 126 * Handle keydown to change the selected tab when the user presses the 127 * arrow keys. 128 * @param {Event} e The keyboard event. 129 * @private 130 */ 131 handleKeyDown_: function(e) { 132 var delta = 0; 133 switch (e.keyIdentifier) { 134 case 'Left': 135 case 'Up': 136 delta = -1; 137 break; 138 case 'Right': 139 case 'Down': 140 delta = 1; 141 break; 142 } 143 144 if (!delta) 145 return; 146 147 var cs = this.ownerDocument.defaultView.getComputedStyle(this); 148 if (cs.direction == 'rtl') 149 delta *= -1; 150 151 var count = this.children.length; 152 var index = this.parentElement.selectedIndex; 153 this.parentElement.selectedIndex = (index + delta + count) % count; 154 155 // Show focus outline since we used the keyboard. 156 this.focusOutlineManager_.visible = true; 157 } 158 }; 159 160 /** 161 * Creates a new tab element. 162 * @param {string} opt_label The text label for the item. 163 * @constructor 164 * @extends {HTMLElement} 165 */ 166 var Tab = cr.ui.define('tab'); 167 Tab.prototype = { 168 __proto__: HTMLElement.prototype, 169 decorate: function() { 170 var self = this; 171 this.addEventListener(cr.isMac ? 'click' : 'mousedown', function() { 172 self.selected = true; 173 }); 174 } 175 }; 176 177 /** 178 * Whether the tab is selected. 179 * @type {boolean} 180 */ 181 cr.defineProperty(Tab, 'selected', cr.PropertyKind.BOOL_ATTR); 182 183 /** 184 * Creates a new tabpanels element. 185 * @param {string} opt_label The text label for the item. 186 * @constructor 187 * @extends {HTMLElement} 188 */ 189 var TabPanels = cr.ui.define('tabpanels'); 190 TabPanels.prototype = { 191 __proto__: HTMLElement.prototype, 192 decorate: decorateChildren 193 }; 194 195 /** 196 * Creates a new tabpanel element. 197 * @param {string} opt_label The text label for the item. 198 * @constructor 199 * @extends {HTMLElement} 200 */ 201 var TabPanel = cr.ui.define('tabpanel'); 202 TabPanel.prototype = { 203 __proto__: HTMLElement.prototype, 204 decorate: function() {} 205 }; 206 207 /** 208 * Whether the tab is selected. 209 * @type {boolean} 210 */ 211 cr.defineProperty(TabPanel, 'selected', cr.PropertyKind.BOOL_ATTR); 212 213 return { 214 TabBox: TabBox, 215 Tabs: Tabs, 216 Tab: Tab, 217 TabPanels: TabPanels, 218 TabPanel: TabPanel 219 }; 220 }); 221