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 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