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