Home | History | Annotate | Download | only in ui
      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