• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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