Source: src/main/javascript/oracle/oj/ojtabs/ojtabs.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/**
 * Copyright (c) 2014, Oracle and/or its affiliates.
 * All rights reserved.
 */

/**
 * @preserve Copyright 2013 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

/**
 * @ojcomponent oj.ojTabs
 * @augments oj.baseComponent
 * @since 0.6
 * 
 * @classdesc
 * <h3 id="tabsOverview-section">
 *   JET Tabs Component
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#tabsOverview-section"></a>
 * </h3>
 * 
 * <p>Description: Themeable, WAI-ARIA-compliant tabs with mouse and keyboard interactions for navigation.
 * 
 * <p>A JET Tabs can be created from a <code class="prettyprint">div</code> element as long as the root element has a tab bar in a <code class="prettyprint">ul</code> with individual tabs in <code class="prettyprint">li</code> preceding the content divs. Each <code class="prettyprint">li</code> will be matched by position with its content in the html DOM. 
 * 
 * <pre class="prettyprint">
 * <code>
 * <div id="tabs">
 *   <ul>
 *     <li><span>Tab 1</span></li>
 *     <li><span>Tab 2</span></li>
 *     <li><span>Tab 3</span></li>
 *   </ul>
 *   <div id="tab1">
 *     <p>Tab 1 content</p>
 *     <p>Tab 1 more content</p>
 *   </div>
 *   <div id="tab2">
 *     <p>Tab 2 content</p>
 *   </div>
 *   <div id="tab3">
 *     <p>Tab 3 content</p>
 *   </div>
 * </div>
 * </code></pre>
 *
 * <h3 id="touch-section">
 *   Touch End User Information
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#touch-section"></a>
 * </h3>
 *
 * {@ojinclude "name":"touchDoc"}
 *
 *
 * <h3 id="keyboard-section">
 *   Keyboard End User Information
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#keyboard-section"></a>
 * </h3>
 *
 * {@ojinclude "name":"keyboardDoc"}
 *
 * <h3 id="styling-section">
 *   Styling
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#styling-section"></a>
 * </h3>
 *
 * <table class="generic-table styling-table">
 *   <thead>
 *     <tr>
 *       <th>Class(es)</th>
 *       <th>Description</th>
 *     </tr>
 *   </thead>
 *   <tbody>
 *     <tr>
 *       <td>oj-tabs-icon-only</td>
 *       <td>Applied to an ojtabs if the tab headers contain only icons.</td>
 *     </tr>
 *     <tr>
 *       <td>oj-tabs-text-icon<br>
 *       <td>Applied to an ojtabs if the tab headers contain both icons and text.</td>
 *     </tr>
 *   </tbody>
 * </table>
 *
 * <h3 id="rtl-section">
 *   Reading direction
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#rtl-section"></a>
 * </h3>
 * 
 * <p>As with any JET component, in the unusual case that the directionality (LTR or RTL) changes post-init, the tabs must be <code class="prettyprint">refresh()</code>ed.
 * 
 * <h3 id="pseudos-section">
 *   Pseudo-selectors
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#pseudos-section"></a>
 * </h3>
 * 
 * <p>The <code class="prettyprint">:oj-tabs</code> pseudo-selector can be used in jQuery expressions to select JET Tabs.  For example:
 * 
 * <pre class="prettyprint">
 * <code>$( ":oj-tabs" ) // selects all JET Tabs on the page
 * $myEventTarget.closest( ":oj-tabs" ) // selects the closest ancestor that is a JET Tabs
 * </code></pre>
 * 
 * 
 * <h3 id="jqui2jet-section">
 *   JET for jQuery UI developers
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#jqui2jet-section"></a>
 * </h3>
 * 
 * <ol>
 *   <ul>
 *   <li>JQUI Tabs expects the tabs titles either in an ordered or unordered list followed by their content elements. Each tab must have an anchor with the href points to its content element.
 *     <pre class="prettyprint">
 *     <code>
 *       <div id="tabs">
 *         <ul>
 *           <li><a href="#tabs-1">Tab 1 Title</a></li>
 *           <li><a href="#tabs-2">Tab 2 Title</a></li>
 *         </ul>
 *         <div id="tabs-1">
 *           <p>Tab 1 content.</p>
 *         </div>
 *         <div id="tabs-2">
 *           <p>Tab 2 content.</p>
 *           <p>More Tab 2 content.</p>
 *         </div
 *       </div>
 *     </code></pre>
 *   </li>
 *
 *   <li>JET Tabs requires a DOM structures like the JQuery Tabs, except the tab header and their content are matched by position. It requires no anchors and pointers to the contents.
 *     <pre class="prettyprint">
 *     <code>
 *       <div id="tabs">
 *          <ul>
 *            <li><span>Tab 1</span></li>
 *            <li><span>Tab 2</span></li>
 *          </ul>
 *          <div id="tab1">
 *            <p>Tab 1 content</p>
 *          </div>
 *          <div id="tab2">
 *            <p>Tab 2 content</p>
 *          </div>
 *       </div>
 *     </code></pre>
 *   </li>
 *   </ul>
 *   <li>JET Tabs supports <code class="prettyprint">edge</code> option: to be placed the tab bar at top(default), bottom, start or end</li>
 *   <li>JET Tabs supports <code class="prettyprint">removable</code> option by adding a close icon to each tab header which when clicked remove the tab from the DOM.</li>
 *   <li>JET Tabs supports <code class="prettyprint">reorderable</code> option allow the tab to be reordered by drag and drop within the Tab bar</li>
 * </ol>
 * 
 * <p>Also, event names for all JET components are prefixed with "oj", instead of component-specific prefixes like "tabs".  
 * 
 * <!-- - - - - Above this point, the tags are for the class.
 *              Below this point, the tags are for the constructor (initializer). - - - - - - -->
 * 
 * @desc Creates a JET Tabs. 
 * @example <caption>Initialize the tabs with no options specified:</caption>
 * $( ".selector" ).ojTabs();
 * 
 * @example <caption>Initialize the tabs with some options specified:</caption>
 * $( ".selector" ).ojTabs( { "edge": "start" } );
 * 
 * @example <caption>Initialize the tabs via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
 * <div id="tabs" data-bind="ojComponent: { component: 'ojTabs', edge: 'end'}">
 *
 */
(function ()
{
  var _CLOSE_ICON = "oj-tabs-close-icon";
  var _CLOSE_ICON_SIZE = 28;
  var _ID_PREFIX = "ojtabs-id_";
  var _DELETE_KEY = 46;

  //Context Menu: menu item id's
  var /** @const */ _arMenuCmdMap = { "cut"          : "ojtabscut",
                                      "paste-before" : "ojtabspastebefore",
                                      "paste-after"  : "ojtabspasteafter"
                                    } ;

  // Context Menu translation keys
  var  /** @const */  _arMenuKeyMap = { "cut"    : "labelCut",
                                        "paste-before" : "labelPasteBefore",
                                        "paste-after"  : "labelPasteAfter"
                                      } ;

  oj.__registerWidget("oj.ojTabs", $['oj']['baseComponent'], 
  {
    widgetEventPrefix : "oj", 
    delay : 300, 
    options : 
    {
      /** 
       * The id or zero-based index of the tab that is selected.<p>
       * Setter value: either an id or index.<p>
       * Getter value: id or index. If the selected tab has a page author provided id, that id is returned, otherwise that tab's index will be returned.
       *
       * @expose 
       * @memberof! oj.ojTabs
       * @instance
       * @type {number|string}
       * @default <code class="prettyprint">0</code>
       *
       * @example <caption>Initialize the tabs with the 
       * <code class="prettyprint">selected</code> option specified:</caption>
       * $( ".selector" ).ojTabs( { "selected": "myTabDiv" } );
       * 
       * @example <caption>Get or set the <code class="prettyprint">selected</code> 
       * option after initialization:</caption>
       * // getter
       * var selected = $( ".selector" ).ojTabs( "option", "selected" );
       * 
       * // setter
       * $( ".selector" ).ojTabs( "option", "selected", "myTabDiv" );
       */
      selected : 0, 

      /** 
       * Array contains either ids or zero-based indices of the tabs that should be disabled.<p> 
       * Setter value: array of either ids or indices.<p>
       * Getter value: array of either ids or indices. If a disabled tab has a page author provided id, that id is returned, otherwise that tab's index will be returned.
       *
       * @expose 
       * @memberof! oj.ojTabs
       * @instance
       * @default <code class="prettyprint">false</code>
       * @type {Array}
       *
       * @example [ 0, "myTabDiv" ] would disable the first tab and the tab with id="myTabDiv"
       * @example <caption>Initialize the tabs with the 
       * <code class="prettyprint">disabledTabs</code> option specified:</caption>
       * $( ".selector" ).ojTabs( { "disabledTabs": [0, "myTabDiv"] } );
       * 
       */
      disabledTabs : null, 

      /** 
       * Truncation option applies to the tab titles when there is not enough room to display
       * all tabs.
       * Valid Values: none, progressive and auto.
       *
       * <ul>
       *  <li> none - tabs always take up the space needed by the title texts. When there
       *   is not enough room, the conveyorBelt's navigation arrows are displayed to allow
       *   the title texts be scrolled within the conveyor.</li>
       *
       *  <li> progressive - If not enough space is available to display all of the tabs, 
       *   then the width of each tab title is restricted just enough to allow all tabs to fit. 
       *   All tab titles that are truncated are displayed with ellipses. However the width 
       *   of each tab title will not be truncated below tabLabelMinWidth. If after all truncation 
       *   has been applied, there still is not enough room, then the conveyorBelt's navigation 
       *   arrows will appear. When the container of the tabs is resized the truncation will 
       *   be reevaluated.</li>
       *
       *  <li> auto - same as "progressive".</li>
       * </ul>
       *
       * @expose 
       * @memberof! oj.ojTabs
       * @instance
       * @type {string}
       * @default <code class="prettyprint">auto</code>
       *
       * @example <caption>Get or set the <code class="prettyprint">truncation</code> option for
       *      an ojTabs after initialization:</caption>
       * // getter
       * var truncation = $( ".selector" ).ojTabs( "option", "truncation" );
       * 
       * // setter
       * $( ".selector" ).ojTabs( "option", "truncation", "none" );
       */
      truncation : "auto",

      /** 
       * The type of event to select the tab. 
       * To select a tab on hover, use "mouseover".
       *
       * @expose 
       * @memberof! oj.ojTabs
       * @instance
       * @type {string}
       * @default <code class="prettyprint">"click"</code>
       *
       * @example <caption>Get or set the <code class="prettyprint">selectOn</code> option for
       *      an ojTabs after initialization:</caption>
       * // getter
       * var selectOn = $( ".selector" ).ojTabs( "option", "selectOn" );
       * 
       * // setter
       * $( ".selector" ).ojTabs( "option", "selectOn", "mouseover" );
       */
      selectOn : "click", 

      /** 
       * The orientation of the tab bar. 
       * Valid Values: horizontal and vertical
       *
       * @deprecated use the <code class="prettyprint">edge</code> option instead. 
       * If the tabs is initialized without an <code class="prettyprint">edge</code> specified,
       * orientation value is used to convert to an equivalent <code class="prettyprint">edge</code>:
       * <code>horizontal</code> to <code>top</code> and <code>vertical</code> to <code>start</code>.
       * @expose 
       * @memberof! oj.ojTabs
       * @instance
       * @type {string}
       * @default <code class="prettyprint">"horizontal"</code>
       *
       * @example <caption>Get or set the <code class="prettyprint">orientation</code> option for
       *      an ojTabs after initialization:</caption>
       * // getter
       * var orientation = $( ".selector" ).ojTabs( "option", "orientation" );
       * 
       * // setter
       * $( ".selector" ).ojTabs( "option", "orientation", "vertical" );
       */
      orientation : "horizontal", 

      /** 
       * The position of the tab bar. 
       * Valid Values: top, bottom, start and end.
       *
       * @expose 
       * @memberof! oj.ojTabs
       * @instance
       * @type {string}
       * @default <code class="prettyprint">top</code>
       *
       * @example <caption>Get or set the <code class="prettyprint">edge</code> option for
       *      an ojTabs after initialization:</caption>
       * // getter
       * var edge = $( ".selector" ).ojTabs( "option", "edge" );
       * 
       * // setter
       * $( ".selector" ).ojTabs( "option", "edge", "end" );
       */
      edge : "top", 

      /** 
       * Specifies if the tabs can be closed (removed)
       *
       * @expose 
       * @memberof! oj.ojTabs
       * @instance
       * @type {boolean}
       * @default <code class="prettyprint">false</code>
       *
       * @example <caption>Get or set the <code class="prettyprint">removable</code> option for
       *      an ojTabs after initialization:</caption>
       * // getter
       * var removable = $( ".selector" ).ojTabs( "option", "removable" );
       * 
       * // setter
       * $( ".selector" ).ojTabs( "option", "removable", true );
       */
      removable : false, 

      /** 
       * Specifies if the tabs can be reordered within the tab bar by drag-and-drop
       *
       * @expose 
       * @memberof! oj.ojTabs
       * @instance
       * @type {boolean}
       * @default <code class="prettyprint">false</code>
       *
       * @example <caption>Get or set the <code class="prettyprint">reorderable</code> option for
       *      an ojTabs after initialization:</caption>
       * // getter
       * var reorderable = $( ".selector" ).ojTabs( "option", "reorderable" );
       * 
       * // setter
       * $( ".selector" ).ojTabs( "option", "reorderable", true );
       */
      reorderable : false,

      /**
       * Identifies the JET Menu that the component should launch as a context menu on right-click or
       * <kbd>Shift-F10</kbd>. If specified, the browser's native context menu will be replaced by the
       * specified JET Menu.
       * 
       * <p>To specify a JET context menu on a DOM element that is not a JET component, see the
       * <code class="prettyprint">ojContextMenu</code> binding.  
       * 
       * <p>To make the page semantically accurate from the outset, applications are encouraged to specify the
       * context menu via the standard HTML5 syntax shown in the below example.  When the component is
       * initialized, the context menu thus specified will be set on the component.
       *
       * <p>When defining a contextMenu, ojTabs will provide built-in behavior for "cut" and "paste"
       *  if the following format for menu <li> item's is used (no <a> 
       *  elements are required):
       * <ul><li> <li data-oj-command="oj-tabs-cut" /></li>
       *     <li> <li data-oj-command="oj-tabs-paste-before" /></li>
       *     <li> <li data-oj-command="oj-tabs-paste-after" /></li>
       * </ul>
       * The available translated text will be applied to menu items defined this way.
       *
       * <p>The JET Menu should be initialized before any component using it as a context menu.
       * 
       * @member
       * @name contextMenu
       * @memberof! oj.ojTabs
       * @instance
       * @type {string | null}
       * @default <code class="prettyprint">null</code>
       * 
       * @example <caption>Initialize a JET Tabs with a context menu:</caption>
       * // via recommended HTML5 syntax:
       * <div id="myTabs" contextmenu="myMenu" data-bind="ojComponent: { ... }>
       * 
       * // via JET initializer (less preferred) :
       * $( ".selector" ).ojTabs({ "contextMenu": "#myContextMenu"  ... } });
       * 
       * @example <caption>Get or set the <code class="prettyprint">contextMenu</code> option for
       *      an ojTabs after initialization:</caption>
       * // getter
       * var menu = $( ".selector" ).ojTabs( "option", "contextMenu" );
       * 
       * // setter
       * $( ".selector" ).ojTabs( "option", "contextMenu", "#myContextMenu" );
       */

      // callbacks
      /**
       * Triggered immediately before a tab is selected.<p>
       * beforeSelect can be canceled to prevent the content from selecting by returning a false in the event listener.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojTabs
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.fromTab The tab being navigated from
       * @property {jQuery} ui.fromContent The content being navigated from
       * @property {jQuery} ui.toTab The tab being navigated to
       * @property {jQuery} ui.toContent The content being navigated to
       * 
       * @example <caption>Initialize the tabs with the <code class="prettyprint">beforeSelect</code> callback specified:</caption>
       * $( ".selector" ).ojTabs({
       *     "beforeSelect": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojbeforeselect</code> event:</caption>
       * $( ".selector" ).on( "ojbeforeselect", function( event, ui ) {} );
       */
      beforeSelect : null,

      /**
       * Triggered after a tab has been selected.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojTabs
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.fromTab The tab being navigated from
       * @property {jQuery} ui.fromContent The content being navigated from
       * @property {jQuery} ui.toTab The tab being navigated to
       * @property {jQuery} ui.toContent The content being navigated to
       * 
       * @example <caption>Initialize the tabs with the <code class="prettyprint">select</code> callback specified:</caption>
       * $( ".selector" ).ojTabs({
       *     "select": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojselect</code> event:</caption>
       * $( ".selector" ).on( "ojselect", function( event, ui ) {} );
       */
      select : null, 

      /**
       * Triggered immediately before a tab is deselected.<p>
       * beforeDeselect can be canceled to prevent the content from deselecting by returning a false in the event listener.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojTabs
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.fromTab The tab being navigated from
       * @property {jQuery} ui.fromContent The content being navigated from
       * @property {jQuery} ui.toTab The tab being navigated to
       * @property {jQuery} ui.toContent The content being navigated to
       * 
       * @example <caption>Initialize the tabs with the <code class="prettyprint">beforeDeselect</code> callback specified:</caption>
       * $( ".deselector" ).ojTabs({
       *     "beforeDeselect": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojbeforedeselect</code> event:</caption>
       * $( ".deselector" ).on( "ojbeforedeselect", function( event, ui ) {} );
       */
      beforeDeselect : null,

      /**
       * Triggered after a tab has been deselected.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojTabs
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.fromTab The tab being navigated from
       * @property {jQuery} ui.fromContent The content being navigated from
       * @property {jQuery} ui.toTab The tab being navigated to
       * @property {jQuery} ui.toContent The content being navigated to
       * 
       * @example <caption>Initialize the tabs with the <code class="prettyprint">deselect</code> callback specified:</caption>
       * $( ".deselector" ).ojTabs({
       *     "deselect": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojdeselect</code> event:</caption>
       * $( ".deselector" ).on( "ojdeselect", function( event, ui ) {} );
       */
      deselect : null, 

      /**
       * Triggered immediately before a tab is removed.
       * beforeRemove can be canceled to prevent the content from removeing by returning a false in the event listener.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojTabs
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.tab The tab that is about to be removed.
       * @property {jQuery} ui.content The content that is about to be removed.
       * 
       * @example <caption>Initialize the tabs with the <code class="prettyprint">beforeRemove</code> callback specified:</caption>
       * $( ".selector" ).ojTabs({
       *     "beforeRemove": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojbeforeremove</code> event:</caption>
       * $( ".selector" ).on( "ojbeforeremove", function( event, ui ) {} );
       */
      beforeRemove : null,

      /**
       * Triggered after a tab has been removed.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojTabs
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.tab The tab that was just removed.
       * @property {jQuery} ui.content The content that was just removed.
       * 
       * @example <caption>Initialize the tabs with the <code class="prettyprint">remove</code> callback specified:</caption>
       * $( ".selector" ).ojTabs({
       *     "remove": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojremove</code> event:</caption>
       * $( ".selector" ).on( "ojremove", function( event, ui ) {} );
       */
      remove : null, 

      /**
       * Triggered immediately before a tab is reordered.
       * beforeReorder can be canceled to prevent the content from reordering by returning a false in the event listener.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojTabs
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.tab The tab that is about to be reordered.
       * @property {jQuery} ui.content The content that is about to be reordered.
       * 
       * @example <caption>Initialize the tabs with the <code class="prettyprint">beforeReorder</code> callback specified:</caption>
       * $( ".selector" ).ojTabs({
       *     "beforeReorder": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojbeforereorder</code> event:</caption>
       * $( ".selector" ).on( "ojbeforereorder", function( event, ui ) {} );
       */
      beforeReorder : null,

      /**
       * Triggered after a tab has been reordered.
       *
       * @expose 
       * @event 
       * @memberof! oj.ojTabs
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {jQuery} ui.tab The tab that was just reordered.
       * @property {jQuery} ui.content The content that was just reordered.
       * 
       * @example <caption>Initialize the tabs with the <code class="prettyprint">reorder</code> callback specified:</caption>
       * $( ".selector" ).ojTabs({
       *     "reorder": function( event, ui ) {}
       * });
       *
       * @example <caption>Bind an event listener to the <code class="prettyprint">ojreorder</code> event:</caption>
       * $( ".selector" ).on( "ojreorder", function( event, ui ) {} );
       */
      reorder : null, 

      /**
       * Fired whenever a supported component option changes, whether due to user interaction or programmatic
       * intervention.  If the new value is the same as the previous value, no event will be fired.
       *
       * @expose
       * @event
       * @memberof! oj.ojTabs
       * @instance
       * @property {Event} event <code class="prettyprint">jQuery</code> event object
       * @property {Object} ui Parameters
       * @property {string} ui.option the name of the option that is changing
       * @property {Object} ui.previousValue the previous value of the option
       * @property {Object} ui.value the current value of the option
       * @property {Object} ui.optionMetadata information about the option that is changing
       * @property {string} ui.optionMetadata.writeback <code class="prettyprint">"shouldWrite"</code> or
       *           <code class="prettyprint">"shouldNotWrite"</code>.  For use by the JET writeback mechanism.
       *
       */
      optionChange: null

    },

    _ComponentCreate : function ()
    {
      var self = this, 
          options = this.options;

      this._super();
      this.running = false;

      // don't fire events during creation
      this._initialRender = true;

      this._setupEdge(options.edge);

      // flag if an old markup is used
      this._isOldMarkup = (this.element.children("ul").length == 0);

      this._createTabbarFromOldMarkup();
      this._processTabs();

      //Bug 18297736 - ojtabs touch support issues
      //only handle context menu if not touch device
      this._menu = {};
      this._menu.usermenu = false;
      this._menu.$container = false;
      this._menu.$elemPasteBefore = false;
      this._menu.$elemPasteAfter = false;

      this._initMenu();
      this._applyMenu();

      //update disabledTabs to contains ids if provided, index otherwise
//      this._updateDisabledTabs();        

      this._refresh();

      //Bug 20861325 - for tabs w/ pre 1.1 markup, selected & disabledtabs options doesn't work w/ ids
      var selectedIndex = this._getIndexByTabOrContentId(options.selected);
      if (selectedIndex === undefined)
        selectedIndex = 0;

      //Bug 20028458 - tabs shouldn't implement _init()
      this._fireSelectEvents(selectedIndex);
      this._initialRender = undefined;
    },

    //TODO: selectedIndex
    _fireSelectEvents : function(selectedIndex)
    {
      //Bug 18270242 - When ojtab first displays beforeSelect & select events are not raised  
      //Bug 18539151 - ojtabs should not let user set focus on disabled tabs
      //if the selected tab is disabled or undefined, select the next enabled tab
      if (this._isTabDisabled(selectedIndex))
      {
        selectedIndex = this._getNextEnabledTab(selectedIndex);
      }
      this._activate((selectedIndex === undefined) ? undefined : $(this.tabs[selectedIndex]));
    },

    /**
      *  Notification that the user has invoked the context menu via the default
      *  gestures: right-click, pressHold, and Shift-F10.
      *  @param {Object} menu       The JET Menu to open as a context menu
      *  @param {Event}  event      Triggering event
      *  @param {string} eventType  Triggering event type (e.g. 'keyboard', 'mouse', 'touch')
      *  @private	
      */
    _NotifyContextMenuGesture: function(menu, event, eventType)
    {
      //Bug 18269248 - ojtabs:context menu shouldn't open - right mouse is clicked inside content area
      //Bug 20933655 - contextmenu issues: presshold should launch the contextmenu on touch devices
      if (this._isInATab(event.target) && ! (this._touchProxy && this._touchProxy.touchMoved()))
      {
        var keyboard = (eventType === 'keyboard') ;

        if ((event.type != "contextmenu") && (!keyboard) && (eventType != 'touch')) {
          return ;
        }

//        console.log("_NotifyContextMenuGesture() eventType=" + eventType + " event.type=" + event.type);

        //got here either by right mouse click (event.which == 3)
        //or <shift F10> key (event.keyCode = 121 and event.shiftKey = true)
        //or pressHold

        // get the tab acted on
        var openOptions = {"launcher": $(event.target).closest("li")};
        this._menu.tab = keyboard ? this.active : openOptions["launcher"];
        if (! this._menu.tab) {
          event.preventDefault();
          return;
        }

        // Set menu "Paste" disable state, depending on whether there's been a
        // previous "cut"
        if (this._menu.usermenu && 
            (this._menu.$elemPasteBefore || this._menu.$elemPasteAfter)) {

          var disabledState = (! this._menu.cutTab);
          var state = this._menu.$elemPasteBefore.hasClass("oj-disabled");

          if (state != disabledState) {
            if (disabledState) {
              this._menu.$elemPasteBefore.addClass("oj-disabled");
              this._menu.$elemPasteAfter.addClass("oj-disabled");
            }
            else {
              this._menu.$elemPasteBefore.removeClass("oj-disabled");
              this._menu.$elemPasteAfter.removeClass("oj-disabled");
            }
            this._menu.$container.ojMenu("refresh");
          }
        }

        this._OpenContextMenu(event, eventType, openOptions);
      }
    },

    _tabKeydown : function (event)
    {
      /*jshint maxcomplexity:15*/
       if (this._handlePageNav(event))
        return;

      var focusedTab = $(this.document[0].activeElement).closest("li"),
          enabledTabs = this._getEnabledTabs(),
          selectedIndex = enabledTabs.index(focusedTab),
          length = enabledTabs.length;

      switch (event.keyCode)
      {
        case $.ui.keyCode.RIGHT:
        case $.ui.keyCode.DOWN:
          selectedIndex = (selectedIndex + 1) % length;

          break;
        case $.ui.keyCode.UP:
        case $.ui.keyCode.LEFT:
          selectedIndex = ((selectedIndex == 0 ? length : selectedIndex) - 1) % length;
          break;
        case $.ui.keyCode.END:
          selectedIndex = length - 1;
          break;
        case $.ui.keyCode.HOME:
          selectedIndex = 0;
          break;

        case _DELETE_KEY:
          if (this.options.removable)
          {
            // James: remove tab keystroke doesn't seem to work with JAWS. 
            // ALT+DEL seems to conflict with a JAWS keystroke. I have raised an issue on the Authoring 
            // Practices for this. Could we just use Delete as well or does that sound like a bad idea?
            // simulate a click on the close icon of the current selected header
            var anchor = this._getCloseIcons(this.active);
            if (anchor)
            {
              event.preventDefault();
              this._removeTabHandler(
                {
                  target : anchor, 
                  currentTarget : anchor, 
                  preventDefault : $.noop
                });
            }
          }
          return;

        default :
          return;
      }

      // Focus the appropriate tab, based on which key was pressed
      event.preventDefault();
      clearTimeout(this.activating);

      var selTab = $(enabledTabs[selectedIndex]);
      selTab.focus();

      // Navigating with control key will prevent automatic activation
      if (!event.ctrlKey)
      {
        // Update aria-selected immediately so that AT think the tab is already selected.
        // Otherwise AT may confuse the user by stating that they need to select the tab,
        // but the tab will already be selected by the time the announcement finishes.
        focusedTab.attr("aria-selected", "false");
        selTab.attr("aria-selected", "true");
        var idOrIndex = this._getTabIdOrIndex(selTab);

        var self = this;
        this.activating = this._delay(function ()
        {
          //qunit test failure
          if (! self || !self.tabs)
            return;

          //Note: need setOption on ojtabs to display the selected panel
          self.option("selected", idOrIndex,
                      {'_context': {originalEvent: event}});
        },
        this.delay);
      }
    },

    _panelKeydown : function (event)
    {
      //In the nested tabs case, handle keydown for the closest tabs only
      if ($(event.target).closest(".oj-tabs-selected").attr("id") !== 
          this.element.children(".oj-tabs-selected").attr("id"))
        return;

      if (this._handlePageNav(event))
        return;

      // Ctrl+up moves focus to the current tab
      if (event.ctrlKey && event.keyCode === $.ui.keyCode.UP)
      {
        event.preventDefault();
        this.active.focus();
      }

    },

    // Ctrl+page up/down moves focus to the previous/next tab (and selects)
    _handlePageNav : function (event)
    {
      var selectedIndex = this._getSelectedIndex();

      if (event.ctrlKey && event.keyCode === $.ui.keyCode.PAGE_UP)
      {
        this._activate(this._focusNextTab(selectedIndex, false));
        return true;
      }
      if (event.ctrlKey && event.keyCode === $.ui.keyCode.PAGE_DOWN)
      {
        this._activate(this._focusNextTab(selectedIndex, true));
        return true;
      }
    },

    _isTabDisabled : function (index)
    {
      if (index >=0 && index < this.tabs.length)
        return $(this.tabs[index]).hasClass("oj-disabled");
      return false;
    },

    //TODO: return tab
    _focusNextTab : function (index, goingForward)
    {
      var enabledTabs = this._getEnabledTabs(),
      currentIndex = enabledTabs.index(this.tabs[index]),
      length = enabledTabs.length;
      if (goingForward)
        currentIndex = (currentIndex + 1) % length;
      else
        currentIndex = ((currentIndex == 0 ? length: currentIndex) - 1) % length;

      var selTab = $(enabledTabs[currentIndex]);
      selTab.focus();
      return selTab;
    },

    //return index
    //TODO: index: tabIndex (not panelId) 
    _getNextEnabledTab : function (index)
    {
      var next = index + 1,          
          lastTabIndex = this.tabs.length - 1;

      while (next <= lastTabIndex) 
      {
        if (! this._isTabDisabled(next))
        {
          return next;
        }
        next++;
      }
      next = index - 1;
      while (next >= 0)
      {
        if (! this._isTabDisabled(next))
        {
          return next;
        }
        next--;
      }
      return undefined;
    },

    _isHorizontal : function ()
    {
      return (this.options.edge == "top" || this.options.edge == "bottom");
    },

    _setOption : function (key, value, flags)
    {
      if (key === "selected")
      {
        value = this._getTab(value);
        if (value === undefined)
          return;

        // _activate() will update this.options
        this._activate(value);
        return;
      }

      if (key === "disabledTabs")
      {
        if (Array.isArray(value))
        {
          this._setOjDisabledOnTab(value);

          //in case the selected tab is disabled
          var selectedIndex = this._getSelectedIndex();
          if (this._isTabDisabled(selectedIndex))
            this._fireSelectEvents(selectedIndex);

          //Bug 18679648 - when observable is used to update disabled tabs, tabs do not function right
          this.refresh();
        }
        return;
      }

      if (key === "removable" || key === "truncation")
      {
        if (value != this.options[key])
        {
          this._super(key, value, flags);
          this.refresh();
        }
        return;
      }

      //allow drag and drop a tab within the tab bar
      if (key === "reorderable")
      {
        if (value !== this.options.reorderable)
        {
          this._super(key, value, flags);
//          this._setupReorder();

          //Bug 19263316 - if reorderable option is changed to true programmatically,
          //it doesn't take affect
          this.refresh();
        }
        return;
      }

      //change orientation need refresh
      if (key === "orientation")
      {
        if (! this._edgeSpecified) {
          var edge = this._orientationToEdge(value);
          if (edge && this._setupEdge(edge)) {
            this.refresh();
          }
        }
        return;
      }

      //change edge need refresh
      if (key === "edge")
      {
        if (this._setupEdge(value)) {
          this._edgeSpecified = true;
          this._super(key, value, flags);
          this.refresh();
        }
        return;
      }

      if (key === "selectOn")
      {
        this._tearDownEvents(true);
        this._super(key, value, flags);
        this._setupEvents();
        return;
      }

      if (key === "contextMenu")
      {
        this._clearMenu();
        if (value)
          this._initMenu(value);
      }

      this._super(key, value, flags);

      //Bug 19510701 - specifying 'translations.removecuetext' as option does not set the aria-label
      //problem is setOption is called after aria-label already set 
      if (key === "translations" && flags && flags['subkey'] === "removeCueText" && this.tablist)
      {
        this._getCloseIcons(this.tablist).attr("aria-label", 
                                               value ? value['removeCueText'] : "");
      }

    },

    /**
     * Refreshes the visual state of the tabs. JET components require a <code class="prettyprint">refresh()</code> or re-init after the DOM is 
     * programmatically changed underneath the component.
     *
     * <p>This method does not accept any arguments.
     * 
     * @expose 
     * @memberof! oj.ojTabs
     * @instance
     * 
     * @example <caption>Invoke the <code class="prettyprint">refresh</code> method:</caption>
     * $( ".selector" ).ojTabs( "refresh" );
     */
    refresh : function ()
    {
      this._super();

      //Bug 19934265 - open/close of tabs leaks sortable
//      this._destroyCloseIcons();
      this._processTabs();

      this._refresh();
    },

    _refresh : function ()
    {
      var options = this.options;

      // check for length avoids error when initializing empty list
      var selectedPanel = this.element.children(".oj-tabs-selected");
      if (selectedPanel.length)
        this.active = this.tablist.children(".oj-selected");
      else
        this.active = $();

      this._createCloseIcons();

      //Bug 19934265 - open/close of tabs leaks sortable
      this._setupEvents();

      this.tabs.not(this.active).attr(
      {
        "aria-selected" : "false", 
        "tabIndex" : "-1"
      });
      this.panels.not(this._getPanelForTab(this.active)).hide().attr(
      {
        "aria-expanded" : "false", 
        "aria-hidden" : "true"
      });

      // Make sure one tab is in the tab order
      if (this.active.length)
      {
        this.active.addClass("oj-selected").attr(
        {
          "aria-selected" : "true", 
          "tabIndex" : "0"
        });

        selectedPanel.show()
          .attr("aria-expanded", "true")
          .removeAttr("aria-hidden");
      }
      else 
      {
        $(this.tabs[0]).attr("tabIndex", "0");
      }

      if (this._isHorizontal())
      {
        if (this.tabs.length > 0) {
          this._truncateBeforeOverflow();
          this._addConveyor();
        }

        //no tab in the tab bar
        else {
          this._getTabbarWrapper();
        }

        //Bug 18283133 - er for placing buttons alongside tabs
        this._addFacets();

      }

      if (this.options.selected === undefined || 
          this._getSelectedIndex() == 0)
        this.element.addClass("oj-first-child-selected");
      else
        this.element.removeClass("oj-first-child-selected");

      //Bug 18269323 - After a tab is deleted, reorder does not work
      this._setupReorder();
    },

    _addConveyor : function ()
    {
      var tabsId = this.tablist.uniqueId().attr("id");
      var conveyorDiv = this._getTabbarWrapper().parent();

      this.conveyor = conveyorDiv.ojConveyorBelt(
        {
          orientation: "horizontal", 
          contentParent: "#" + tabsId
        });

      var cparent = this.conveyor.parent();
      if (cparent.hasClass("oj-tabs-conveyorbelt-wrapper"))
      {
        var flex = "0 1 " + this._getConveyorWrapperMaxWidth() + "px";
        cparent.css("flex", flex);
        cparent.css("-webkit-flex", flex);
        cparent.css("-ms-flex", flex);
      }

    },

    _processTabs : function ()
    {
      var self = this,
          edge = this.options.edge;

      //destroy the old tab bar before creating a new one
      this._destroyTabBar();

      //Bug# 20217708 - use case where gauges are displayed in tabs title bar does not work 
      //create a tab bar
      this._createTabbar();

      //find the tab bar 
      this.tablist = this.element.children("ul")
        .addClass("oj-tabs-nav oj-helper-clearfix")
        .attr("role", "tablist");

      var tabbarIndex = this.tablist.index();

      //Bug 18283133 - er for placing buttons alongside tabs
      //add oj-start class to the facets before the tab bar
      this.element.children(".oj-tabs-facet").each(
        function ()
        {
          var facet = $(this);
          if (facet.index() < tabbarIndex) {
            facet.addClass("oj-start");
          }
          else {
            facet.removeClass("oj-start");
          }
        });

      //Bug 19278960 - support bottom positioned tabs
      var tabContents = this.element.children(".oj-tabs-panel");
      if (edge == "start" || edge == "top")
      {
        // add before the first tab content
        $(tabContents[0]).before(this.tablist); // @HTMLUpdateOK
      }
      else
      {
        // add after the last tab content
        $(tabContents[tabContents.length - 1]).after(this.tablist); // @HTMLUpdateOK
      }

      //list of tabs
      this.tabs = $();
      //list of contents
      this.panels = $();

      //Bug 19587990 - tabs lose selection when moved (sortable tabs)
      this.tablist.children("li").each(function(index)
      {
        var tab = $(this)
          .addClass("oj-tabs-tab")
          .attr({
            "role" : "tab", 
            "tabIndex" : "-1"
          })
          .removeAttr("aria-hidden");

        var div = tab.children();
        div.addClass("oj-tabs-tab-content");

        var anchor = div.children();
        anchor.addClass("oj-tabs-anchor")
          .attr(
            {
              "role" : "presentation", 
              "tabIndex" : "-1"
            });

        var header = anchor.children()
          .addClass("oj-tabs-title")
          .removeAttr("aria-hidden");

        self.tabs = self.tabs.add(tab);

        var anchorId = anchor.uniqueId().attr("id");
        var panelId = tab.attr("data-content");
        var panel = self.element.find(self._sanitizeSelector("#" + panelId));

/*
        if (tab.hasClass("oj-disabled")) 
        {
          tab.attr("aria-disabled", "true");
          //Bug 18270899 - clicking on disabled tab takes user to the top of the page
          anchor.removeAttr("href");
        }
*/
        tab.attr(
          {
            "aria-controls" : panelId,
            "aria-labelledby" : anchorId
          });

        self.panels = self.panels.add(panel);

        panel.attr("aria-labelledby", anchorId)
             .attr("role", "tabpanel");  
      });

      if (edge == "start" || edge == "end")
      {
        //In vertical, "oj-tabs-nav-root" is on the same element as the element with "oj-tabs-nav"
        //as long as the theming doesn't care whether the root is on the same element or not
        self.tablist.addClass("oj-tabs-nav-root");
      }

      //set oj-disabled on the tabs
      if (this._initialRender)
        this._setOjDisabledOnTab(this.options.disabledTabs);

    },

    _setupEvents : function ()
    {
      var events = 
      {
        'keydown' : this._tabKeydown
      };

      var event = this.options.selectOn;
      var selectOnClick = false;
      if (event)
      {
        var self = this;
        $.each(event.split(" "), function (index, eventName)
        {
          //Bug 19377933 - mousedown is a better default for jtab selecton than click 
          if (eventName == "click")
            selectOnClick = true;

          //security test
          if (oj.DomUtils.isValidIdentifier(eventName))
          {
            events[eventName] = self._eventHandler;
          }
        });
      }

      var enabledTabs = this._getEnabledTabs();

      this._on(enabledTabs, events);

      //Bug 19377933 - mousedown is a better default for jtab selecton than click 
      if (! selectOnClick) {
        this._on(enabledTabs,
        {
	  // prevent the default action
          'click' : function(e) {e.preventDefault();}
        });
      }

      //Bug 20028472 - ojtabs: call jquery "on"/"off" methods instead of jquery ui "_on"/"_off"
      this.panels.on("keydown" + this.eventNamespace, this._panelKeydown.bind(this));

      //add listeners on close icon
      if (this.options.removable)
      {
        var revents = 
        {
          "click" : this._removeTabHandler
        }
        this._on(this._getCloseIcons(enabledTabs), revents);
      }

      this._focusable(enabledTabs);
      this._hoverable(enabledTabs);
      this._activeable(enabledTabs);

    },

    _tearDownEvents : function (noCloseIcon)
    {
      var enabledTabs = this._getEnabledTabs();
      //Bug 19934265 - open/close of tabs leaks sortable
      //Bug 20028472 - ojtabs: call jquery "on"/"off" methods instead of jquery ui "_on"/"_off"
      if (! noCloseIcon)
        this._UnregisterChildNode(this._getCloseIcons(enabledTabs));

      this._UnregisterChildNode(enabledTabs);

      if (this.panels)
        this.panels.off("keydown" + this.eventNamespace);
    },

    _eventHandler : function (event)
    {
      var options = this.options, 
          active = this.active, 
          tab = $(event.currentTarget).closest("li"), 
          clickedIsActive = (active && tab[0] === active[0]), 
          oToContent = this._getPanelForTab(tab),
          oFromContent = (active && active.length) ? this._getPanelForTab(active) : $(),

          eventData = 
          {
            /** @expose */
            fromTab : active,
            /** @expose */
            fromContent : oFromContent,
            /** @expose */
            toTab : tab,
            /** @expose */
            toContent : oToContent
          };

      event.preventDefault();

      if (tab.hasClass("oj-disabled") || 

          // can't switch durning an animation
          this.running || 
          // click on active header, 
          clickedIsActive || 

          // allow canceling deselect
          // Bug 18681075 - ojbeforedeseselect & ojdeselect events shouldn't be raised
          // when tab is rendered initially
          (oFromContent && oFromContent.length &&
           this._trigger("beforeDeselect", event, eventData) === false) ||

          // allow canceling select
          // don't fire initial option change events
          (! this._initialRender && this._trigger("beforeSelect", event, eventData) === false))
      {
        return;
      }

      //Bug 20028458 - tabs shouldn't implement _init()
      //don't fire initial option change events
      if (this._initialRender) {
        this.options.selected = this._getTabIdOrIndex(tab);
      }
      else {
        this.option("selected", this._getTabIdOrIndex(tab),
                    {'_context': {originalEvent: event, internalSet: true}});
      }

      this.active = tab;

      //add or remove oj-selected on the corresponding panel
      oToContent.addClass("oj-tabs-selected");
      if (oFromContent && oFromContent.length)
        oFromContent.removeClass("oj-tabs-selected");

      this._toggle(event, eventData);
    },

    // handles show/hide for selecting tabs
    _toggle : function (event, eventData)
    {
      var self = this, 
          toTab = eventData.toTab.closest("li"),
          toShow = eventData.toContent, 
          toHide = eventData.fromContent;

      this.running = true;

      function complete()
      {
        self.running = false;

        //Bug 18681075 - ojbeforedeseselect & ojdeselect events shouldn't be raised 
        //when tab is rendered initially
        // don't fire select event during creation
        if (! self._initialRender) {
          self._trigger("deselect", event, eventData);
          self._trigger("select", event, eventData);
        }
      }

      function show()
      {
        toTab.addClass("oj-selected");
        if (self._isHorizontal() && self.tabs.length > 0) {
          if (toTab.index() == 0)
            self.element.addClass("oj-first-child-selected");
          else
            self.element.removeClass("oj-first-child-selected");
        }

        toShow.show();
        //Bug 19429607 - unable to stretch ojtable in an initially hidden ojtab
        if (toShow.length > 0)
          oj.Components.subtreeShown(toShow[0]);
        complete();
      }

      // start out by hiding, then showing, then completing
      var fromTab = eventData.fromTab.closest("li")
        .removeClass("oj-selected");

      toHide.hide();
      //Bug 19429607 - unable to stretch ojtable in an initially hidden ojtab
      if (toHide.length > 0)
        oj.Components.subtreeHidden(toHide[0]);
      show();

      toHide.attr(
      {
        "aria-expanded" : "false", 
        "aria-hidden" : "true"
      });

      fromTab.attr("aria-selected", "false");

      // If we're switching tabs, remove the old tab from the tab order.
      // If we're opening from collapsed state, remove the previous tab from the tab order.
      if (toShow.length && toHide.length)
      {
        fromTab.attr("tabIndex", "-1");
      }
      else if (toShow.length)
      {
        this.tabs.filter(function ()
        {
          return $(this).attr("tabIndex") === "0";
        })
          .attr("tabIndex", "-1");
      }

      toShow.attr("aria-expanded", "true")
        .removeAttr("aria-hidden");

      toTab.attr(
      {
        "aria-selected" : "true", 
        "tabIndex" : "0"
      });

    },

    //TODO: active: active tab, not panelId
    _activate : function (active)
    {
      if (active === undefined)
        return;

      var anchor;

      // trying to activate the already active panel
      if (this.active && (active[0] === this.active[0]))
        return;

      //Bug 18539151 - ojtabs should not let user set focus on disabled tabs
      //this.options.selected = panelId;

      // simulate a click on the new active header
      anchor = active.find(".oj-tabs-anchor")[0];
      this._eventHandler(
      {
        target : anchor, 
        currentTarget : anchor, 
        preventDefault : $.noop
      });
    },

    _createCloseIcons : function ()
    {
      //Bug 18727052 - for vertical tabs, removable option should be ignored
      //create close icon only if it's horizontal and  not disabled
      if (this.options.removable && (this._isHorizontal()))
      {
        var removeCueText = this.getTranslatedString("removeCueText");
        this._getEnabledTabs().each(function (index)
        {
          var div = $(this).find("> :first-child");
          div.addClass("oj-removable");

          //add cue text for removable icon for screen reader users
          var rmId = _ID_PREFIX + "rm_" + index;
          $(this).attr("aria-describedby", rmId);

          $("<a href='#'>")
            .addClass("oj-tabs-icon oj-component-icon oj-clickable-icon-nocontext " + _CLOSE_ICON)
            .attr(
              {
                "id": rmId,
                "tabIndex" : "-1",
                "aria-label" : removeCueText,
                "role" : "presentation"
              })
            .appendTo(div); // @HTMLUpdateOK

        });
      }
    },

    _getEnabledTabs : function ()
    {
      return $(this.tabs.not(".oj-disabled"));
    },

    _getCloseIcons : function (elem)
    {
      return elem.find("." + _CLOSE_ICON);
    },

    _destroyTabBar : function ()
    {
      //Bug 19934265 - open/close of tabs leaks sortable
      if (this.tabs) {
        //NOTE: also remove panel event handler
        this._tearDownEvents();
      }

      //remove listener
      if (this._hasResizeListener)
      {
        oj.DomUtils.removeResizeListener(this.element[0], this._resizeHandler);
        this._hasResizeListener = false;
        this._originalWidth = undefined;
      }

      //Bug 19934265 - open/close of tabs leaks sortable
      if (this._sortable) {
        //remove touchListeners
        if (oj.DomUtils.isTouchSupported())
          this._tearDownTouchReorder();

        //destroy sortable
        this.tablist.sortable("destroy");
        this._sortable = undefined;
      }

      var navRoot = this.element.children(".oj-tabs-nav-root");
      var navRootNotUL = ! navRoot.hasClass("oj-tabs-nav");

      //Bug 19934265 - open/close of tabs leaks sortable
      if (this.tabs) {
        this._getCloseIcons(this.tabs).remove();

        //Bug 18283133 - er for placing buttons alongside tabs
        if (navRoot.length) {
          var afterMe = navRoot,
              noFacet = true,
              tabbar = this.tablist;

          navRoot.children().each(
            function ()
            {
             var child = $(this);
              if (child.hasClass("oj-tabs-conveyorbelt-wrapper"))
                child = tabbar;

              else if (! child.hasClass("oj-tabs-facet"))
                return;

              noFacet = false;
              afterMe.after(child);
              afterMe = child;
            });

          //move ul and lis
          if (noFacet && navRootNotUL)
            navRoot.after(tabbar);
        }
      }
      if (this.conveyor) {
        this.conveyor.ojConveyorBelt( "destroy" );
        this.conveyor.remove();
        this.conveyor = null;
        this.active = null;
      }

      if (navRootNotUL)
        navRoot.remove();

      this.tabs = null;
      this.tablist = null;

    },

    _destroy : function ()
    {
      this._clearMenu();

      var verticalEdge = ! this._isHorizontal();
      if (verticalEdge)
        this.element.removeClass("oj-tabs oj-component oj-tabs-vertical oj-tabs-start oj-tabs-end oj-helper-clearfix");
      else
        this.element.removeClass("oj-tabs oj-component oj-tabs-horizontal oj-tabs-top oj-tabs-bottom oj-first-child-selected");

      var self = this, tab, div, anchor, header;
      this.tabs.each(function (index) {
        tab = $(this);

        tab.removeAttr("tabIndex")
          .removeAttr("aria-selected")
          .removeAttr("aria-labelledby")
          .removeAttr("aria-hidden")
          .removeAttr("aria-controls")
          .removeAttr("aria-disabled")
          .removeAttr("role")
          .removeAttr("data-content")
          .removeClass("oj-active oj-disabled oj-selected oj-tabs-gen-id oj-tabs-tab")
          .removeUniqueId()
          .css("display", "");

        div = tab.children("div")
          .removeClass("oj-tabs-tab-content");

        anchor = div.children("a")
          .removeClass("oj-tabs-anchor")
          .removeAttr("tabIndex")
          .removeAttr("role");

        header = anchor.children();
        header.removeClass("oj-tabs-title")
          .removeAttr("aria-hidden");

        //if old markup is used, move header back to its own div
        if (self._isOldMarkup)
        {          
          header.prependTo(self.panels.get(index)); // @HTMLUpdateOK
        }
        else
        {
          if (div.hasClass("oj-tabs-gen-div") && anchor.hasClass("oj-tabs-gen-a")) {
            header.prependTo(tab); // @HTMLUpdateOK
            div.remove();
          }
          else if (anchor.hasClass("oj-tabs-gen-a")) {            
            header.prependTo(div); // @HTMLUpdateOK
            anchor.remove();
          }
          else if (div.hasClass("oj-tabs-gen-div")) {
            anchor.prependTo(tab); // @HTMLUpdateOK
            div.remove();
          }
        }

      });

      var tabbar = this.tablist;
      this._destroyTabBar();
      tabbar.removeAttr("tabIndex")
          .removeAttr("role")
          .removeClass("oj-tabs-nav oj-tabs-nav-root oj-helper-clearfix")
          .removeUniqueId();

      //remove ul and lis
      if (this._isOldMarkup)
        tabbar.remove();

      this.panels.each(function (index)
      {
        var panel = $(this);

        panel.removeAttr("tabIndex")
          .removeAttr("aria-expanded")
          .removeAttr("aria-selected")
          .removeAttr("aria-labelledby")
          .removeAttr("aria-hidden")
          .removeAttr("role")
          .removeClass("oj-active oj-tabs-selected oj-tabs-gen-id oj-tabs-panel")
          .removeUniqueId()
          .css("display", "");

      });

      this.element.children(".oj-tabs-facet").removeClass("oj-start");

    },

    _removeTabHandler : function (event)
    {
      var icon = $(event.currentTarget),
          tab = icon.closest("li"),
          panel = this._getPanelForTab(tab),
          eventData = 
          {
            /** @expose */
            tab : tab,
            /** @expose */
            content : panel
          };

      //trigger before delete event and only delete if it's not cancelled
      if (tab && this._trigger("beforeRemove", event, eventData) !== false)
      {
        //Bug 19599328 - deleteing a tab scrolls page back to the top
        event.preventDefault();

        //if tab to be removed is selected, select the next enabled tab
        if (tab.hasClass("oj-selected"))
        {
          //fire select event   
          var nextIndex = this._getNextEnabledTab(this.tabs.index(tab));

          // no enabled tabs left
          if (nextIndex === undefined) {
            this.active = undefined;
            //fire option change event
            this.option("selected", undefined,
                        {'_context': {originalEvent: event, internalSet: true}});
          }
          else {
            //fire selected event
            this._fireSelectEvents(nextIndex);

            //fire option change event
            this.option("selected", this._getTabIdOrIndex($(this.tabs[nextIndex])),
                        {'_context': {originalEvent: event, internalSet: true}});
          }
        }

        //Bug 19934265 - open/close of tabs leaks sortable        
        this._tearDownEvents();
        this.tabs = this.tabs.not(tab);
        tab.remove();
        panel.remove();

        //Bug 18269291 - If user closes tab then index for tabs changes and disabled tab becomes enable
        // update indices of disabled tabs 
        this._updateDisabledTabs();

        this.refresh();

        //Bug 19824494 - tab area grabs focus when tabs are closed
        //set focus on the active
//        if (this.active)
//          this.active.focus();

        this._trigger("remove", event, eventData);
      }
    },

    _wrapLi : function (header, contentId)
    {
      var tab;
      if (header.prop("tagName").toLowerCase() == "li")
      {
        tab = header;
        var div = tab.children("div");
        if (div.length == 1 && div.hasClass("oj-tabs-tab-content")) {
          var anchor = div.children("a");
          if (anchor.length != 1) {
            tab.wrapInner("<a href='#'></a>"); // @HTMLUpdateOK
            anchor.addClass("oj-tabs-gen-a");
          }
        }
        else {
          div = tab.wrapInner("<div><a href='#'></a></div>") // @HTMLUpdateOK
            .children();

          div.addClass("oj-tabs-gen-div oj-tabs-tab-content");
          div.children().addClass("oj-tabs-gen-a");
        }
      }
      else
      {
        tab = header
          .wrap("<li><div><a href='#'></a></div></li>") // @HTMLUpdateOK
          .parent().parent().parent();

        tab.addClass("oj-tabs-gen-li");
        tab.children().addClass("oj-tabs-gen-div oj-tabs-tab-content");
      }

      // set content id on tab
      if (contentId)
        tab.attr("data-content", contentId);

      return tab;
    },

    /**
     * Add a tab to the end of the tabs
     * 
     * @expose 
     * @memberof! oj.ojTabs
     * @instance
     *
     * @param {Object} newTab An Object contains the properties in the following table.
     * @property {jQuery} newTab.tab The new tab
     * @property {jQuery} newTab.content The new content
     * @property {number} newTab.index The index of new tab. Default is -1, newTab is appended to the end
     * 
     * @example <caption>Invoke the <code class="prettyprint">addTab</code> method:</caption>
     * $( ".selector" ).ojTabs( "addTab", 
     *                          {
     *                            "tab" : $("<h3>New Tab</h3>"),
     *                            "content" : $("<div><p>Content of New Tab</p></div>"),
     *                            "index" : 2
     *                          } );
     *
     * Please note that single jQuery object parameter is deprecated
     * $( ".selector" ).ojTabs( "addTab", $("<div><h3>New Tab</h3><p>Content of New Tab</p></div>") );
     */
    addTab : function (newTab)
    {
      var tab, content, index = -1;

      //check if it is a property bag
      if (newTab.tab && newTab.content)
      {
        content = newTab.content;
        tab = this._wrapLi(newTab.tab, this._getUniqueId(content));
        if (newTab.index !== undefined)
          index = newTab.index;
      }

      // Backward compatible: move header to tab bar
      else
      {
        content = newTab;
        tab = this._wrapLi($(newTab).find("> :first-child"),
                           this._getUniqueId(content));
      }

      var navRoot = this.element.children(".oj-tabs-nav-root");
      var tabbar;
      //vertical tabs
      if (navRoot.hasClass("oj-tabs-nav"))
      {
        tabbar = navRoot;
      }
      else
      {
        tabbar = navRoot.length ? navRoot.find(".oj-tabs-nav") : 
        this.element.children(".oj-tabs-nav");
      }

      var isNew = false;
      if (! tabbar.length)
      {
        tabbar = $("<ul>");
        isNew = true;
      }
      if (isNew)
      {
        tabbar.prependTo(this.element); // @HTMLUpdateOK
      }

      //insertion inside
      if (index >= 0 && index < tabbar.children().length)
      {
        var tabAfter = tabbar.children(":eq(" + index + ")")
        var contentAfter = this.element.children("#" + tabAfter.attr("aria-controls"));

        tabAfter.before(tab); // @HTMLUpdateOK
        contentAfter.before(content); // @HTMLUpdateOK
      }
      //insertion at the end
      else
      {
        tab.appendTo(tabbar); // @HTMLUpdateOK
        content.appendTo(this.element); // @HTMLUpdateOK
      }

      this.refresh();

      //if no active selected tab, select the new tab
      if (this.active.length == 0)
        this._fireSelectEvents(0);

      //Bug 19492808 - overflow icons do not appear correctly
      var self = this;

      window.setTimeout(function () {
        //Bug 18427258 - new tabs don't get focus
        if (self.tabs)
          tab[0].scrollIntoView(false);
      }, 0);

    },

    _setupTouchReorder : function ()
    {
      this._touchProxy = oj._TouchProxy.addTouchListeners(this.tablist);
    },

    _tearDownTouchReorder : function ()
    {
      oj._TouchProxy.removeTouchListeners(this.tablist);
    },

    _setupReorder : function ()
    {
      //enable sortable
      if (this.options.reorderable)
      {
        var self = this;

        //Bug 18297736 - ojtabs touch support issues
        if (oj.DomUtils.isTouchSupported())
          this._setupTouchReorder();

        this.tablist.sortable(
        {
          axis: (self._isHorizontal()) ? "x" : "y",
          stop: function(event, ui)
          {
            //find the element that was moved
            var mvTab = ui.item;

            //cancel reorder
            if (! self._doReorder(event, mvTab, mvTab.prev()))
              self.tablist.sortable("cancel");
          }
        })

        //Bug 19934265 - open/close of tabs leaks sortable
        this._sortable = true;
      }
      //disable sortable
      else
      {
        //Bug 18297736 - ojtabs touch support issues
        this._tearDownTouchReorder();

        //Bug 19934265 - open/close of tabs leaks sortable
        //this.tablist.sortable({ disabled: true });
        this._sortable = false;
      }
    },

    _InitOptions: function(originalDefaults, constructorOptions)
    {
      //need to find out if edge is specified
      var edge = constructorOptions["edge"];
      this._edgeSpecified = (edge == "top" || edge == "bottom" || edge == "start" || edge == "end");
      this._super(originalDefaults, constructorOptions);
    },

    _orientationToEdge : function (value)
    {
      if (value == "horizontal")
        return "top";
      else if (value == "vertical")
        return "start";

      return null;
    },

    _setupEdge : function (edge)
    {
      if ((this._initialRender && ! this._edgeSpecified) || ! edge) {
        edge = this._orientationToEdge(this.options.orientation);
      }

      var oEdge = this.options.edge;
      this.element.removeClass("oj-tabs-top oj-tabs-bottom oj-tabs-start oj-tabs-end");

      switch (edge) {
      case "top":
      case "bottom":
        if (this.panels && (oEdge == "start" || oEdge == "end"))
        {
          this.element.removeClass("oj-tabs-vertical oj-helper-clearfix");
        }
        this.element.addClass("oj-tabs oj-component oj-tabs-horizontal");

        //Bug 19278960 - support bottom positioned tabs
        if (edge == "bottom")
          this.element.addClass("oj-tabs-bottom");
        else {
          this.element.addClass("oj-tabs-top");
        }

        // don't fire optionChange event during creation
        if (this._initialRender)
          this.options.orientation = "horizontal";
        else if (this.options.orientation != "horizontal") {
          this.option("orientation", "horizontal",
                      {'_context': {internalSet: true}, 'changed': true});
        }
        break;

      case "start":
      case "end":
        if (this.panels && (oEdge == "top" || oEdge == "bottom"))
        {
          this.element.removeClass("oj-tabs-horizontal");
        }
        this.element.addClass( "oj-tabs oj-component oj-tabs-vertical oj-helper-clearfix" );

        //Bug 19278905 - em:support tabs on right
        if (edge == "end")
          this.element.addClass("oj-tabs-end");
        else {
          this.element.addClass("oj-tabs-start");
        }

        // don't fire optionChange event during creation
        if (this._initialRender)
          this.options.orientation = "vertical";
        else if (this.options.orientation != "vertical") {
          this.option("orientation", "vertical",
                      {'_context': {internalSet: true}, 'changed': true});
        }
        break;

      default:
        return false;
      }

      // use "edge" and deprecate "orientation"
      // don't fire optionChange event during creation
      if (this._initialRender)
        this.options.edge = edge;
      else if (this.options.edge != edge) {
        this.option("edge", edge,
                    {'_context': {internalSet: true}, 'changed': true});
      }
        
      return true;
    },

    _getTabbarWrapper: function()
    {
      var ulParent = this.tablist.parent();
      if (! ulParent.hasClass("oj-tabs-conveyor"))
      {
        ulParent = this.tablist
            .wrap("<div>") // @HTMLUpdateOK
            .parent()
            .addClass("oj-tabs-conveyor");

        //Bug 18283133 - er for placing buttons alongside tabs
        var newTable;

        if (this.element.children(".oj-tabs-facet").length > 0) {
          newTable = ulParent            
            .wrap("<div>") // @HTMLUpdateOK
            .wrap("<div>") // @HTMLUpdateOK
            .parent()
            .parent()
            .addClass("oj-tabs-conveyorbelt-wrapper");
        }
        else
          newTable = ulParent;

        //Bug 19209791 - truncation not working
        //need to create the "oj-tabs-nav-root" before addConveyor
        //otherwise, all skin selectors wont apply

        // add special class so tabs component can skin the conveyor 
        // overflow indicators to addpadding between them and the tabs

        //Bug 18283133 - er for placing buttons alongside tabs
        var navRoot = newTable
          .wrap("<div>") // @HTMLUpdateOK
          .parent()
          .addClass("oj-tabs-nav-root");

        navRoot.uniqueId().attr("id");
      }
      return ulParent;
    },

    _addPrefixId : function (elem)
    {
      if (elem.id.indexOf(_ID_PREFIX) < 0)
        $(elem).attr("id", _ID_PREFIX + elem.id);
    },

    /**
     * Return the subcomponent node represented by the documented locator 
     * attribute values.
     *
     * Test authors should target sub elements using the following names:
     * <ul>
     * <li><b>oj-tabs-tab</b>: tab </li>
     * <li><b>oj-tabs-title</b>: tab title text </li>
     * <li><b>oj-tabs-panel</b>: tab content panel </li>
     * <li><b>oj-tabs-close-icon</b>: tab close icon </li>
     * <li><b>oj-conveyorbelt</b>: conveyorbelt </li>
     * </ul>
     *
     * In addition, tabs requires a zero-based index of the child tab.
     *
     * @example <caption>To get the second tab:</caption>
     * $( ".selector" ).ojTabs('getNodeBySubId', {subId: 'oj-tabs-tab', index:1});
     *
     * @expose
     * @memberof! oj.ojTabs
     * @instance
     * @override
     * @param {Object} locator An Object containing at minimum a subId property 
     *        whose value is a string, documented by the component, that allows 
     *        the component to look up the subcomponent associated with that 
     *        string.  It contains:<p>
     *        component: optional - in the future there may be more than one 
     *        component contained within a page element<p>
     *        subId: the string, documented by the component, that the component 
     *        expects in getNodeBySubId to locate a particular subcomponent.<p>
     *        index: a number, the index of tab.
     * @returns {Element|null} the subcomponent located by the subId string passed
     *          in locator, if found.<p>
     */
    getNodeBySubId: function(locator)
    {
      if (locator == null)
      {
        return this.element ? this.element[0] : null;
      }

      var subId = locator['subId'],
          index = locator['index'];

      if (subId != 'oj-conveyorbelt' &&
          ((typeof index !== 'number') ||
           index < 0 || index >= this.panels.length))
        return null;

      switch (subId)
      {
      case 'oj-conveyorbelt':
        return this.conveyor ? this.conveyor[0] : null;

      case 'oj-tabs-panel':
        return this._getPanelForTab(this.tabs[index])[0];

      case 'oj-tabs-tab':
        return this.tabs[index];

      case 'oj-tabs-title':
        return $(this.tabs[index]).find(".oj-tabs-title")[0];

      case 'oj-tabs-close-icon':
        return $(this.tabs[index]).find("." + subId)[0];
      }

      // Non-null locators have to be handled by the component subclasses
      return null;
    },

    _getConveyorWrapperMaxWidth: function()
    {
      //add extra 10px for buffer
      return this._originalWidth + 10;
    },

    _getTabsWidth: function()
    {
      //Bug 18283133 - er for placing buttons alongside tabs
      var tabbar = this.element.find(".oj-tabs-conveyorbelt-wrapper");
      return (tabbar.length) ? tabbar[0].clientWidth : this.element[0].clientWidth;
    },

    _isOverflow: function()
    {
      return (this._originalWidth > this._getTabsWidth());
    },

    _getTabMaxWidth: function()
    {
      var max = Math.floor(this._getTabsWidth()  / this.tabs.length);

      if (this.options.removable)
        max -= _CLOSE_ICON_SIZE;

      return max;
    },

    _applyTabMaxWidth: function()
    {
      var maxWidth = this._getTabMaxWidth();

      this.tablist.find(".oj-tabs-title").each(function (index)
      {
        $(this).css("max-width", "" + maxWidth + "px")
               .addClass("oj-tabs-title-overflow");
      });

//      this._logMessage("apply max width");
    },

    _removeTabMaxWidth: function()
    {
      this.tablist.find(".oj-tabs-title").each(function (index)
      {
        $(this).css("max-width", "")
               .removeClass("oj-tabs-title-overflow");
      });

//      this._logMessage("remove max width");
    },

    _logMessage: function(msg)
    {
//      console.log(msg);
    },

    /* resize handler */
    _handleResize: function(width, height)
    {
//      this._logMessage("width " + width + " ulWidth " + this._originalWidth +
//                       " clientWidth " + this._getTabsWidth());

      //Bug 20745875 - truncation=none tabs + buttons are not laid out correctly.
      //if no truncation, skip applying tab max width
      if (this._isProgressive())
      {
        if (this._isOverflow())
        {
//        this._logMessage("overflow");
          this._applyTabMaxWidth();
        }
        else
        {
//        this._logMessage("underflow");
          this._removeTabMaxWidth();
        }
      }

    },

    _isProgressive: function()
    {
      return this.options.truncation == "auto" ||
        this.options.truncation == "progressive";
    },

    _truncateBeforeOverflow: function()
    {
      //Bug 20745875 - truncation=none tabs + buttons are not laid out correctly.
      //after adding buttons to the tab bar, the parent of the conveyor is a div with inline-block
      //which prevent the conveyor to get the width correctly.
      //Now tabs needs to add resize listener so it can set size on the conveyor in the size listener
      if (this._isHorizontal() && this.tabs.length > 0)
      {
        if (this._resizeHandler == null)
          this._resizeHandler = this._handleResize.bind(this);

        oj.DomUtils.addResizeListener(this.element[0], this._resizeHandler);
        this._hasResizeListener = true;

        //truncation=none and has facet
        this._originalWidth = this._getTabbarWrapper()[0].scrollWidth;

        //don't need to apply tab max width if not progressive
        //handle initial overflow
        if (this._isProgressive())
        {
          if (this._isOverflow())
            this._applyTabMaxWidth();
        }
      }
    },

    /** 
     * @override
     * @protected
     * @instance
     * @memberof oj.ojtabs
     */
    _NotifyShown: function()
    {
      this._super();
      //19518432(using tab in popup), tab need not be visible while calculating the margin,
      this.refresh();
    },
  
    /** 
     * @override
     * @protected
     * @instance
     * @memberof oj.ojtabs
     */
    _NotifyAttached: function()
    {
      this._super();
      this.refresh();
    },
  

    _buildContextMenuItem : function(cmd)
    {
      return $("<a>")
        .text(this.getTranslatedString(_arMenuKeyMap[cmd]))
        .attr("href", "#")
        .wrap("<li>")  // @HTMLUpdateOK
        .parent()
        .attr("id", _arMenuCmdMap[cmd]);
     },

    /**
     *  Menu "cut" functionality
     *  @private
     */
    _menu_cut : function (obj)
    {
      if (!obj || !obj.length)  {
        return false;
      }

      this._menu.cutTab = obj;
    },

    /**
     *  Menu "paste" functionality
     *  @private
     */
    _menu_paste : function (event, obj, pasteBefore)
    { 
      if (!obj || !obj.length) {
        return false;
      }
      if (! this._menu.cutTab)  {
        return false;
      }

      var mvTab = this._menu.cutTab;
      this._menu.cutTab = false;
      this._doReorder(event, mvTab, obj, pasteBefore);

    },

    /**
     *   Check menu selected to see if it one of tabs predefined cut/paste id's
     *   @private
     */
    _handleContextMenuSelect: function(ev, ui)
    {
      var  id = ui? ui.item.attr("id") : undefined;

      if (id === "ojtabscut") {
        this._menu_cut(this._menu.tab);
      }
      else if (id === "ojtabspastebefore") {
        this._menu_paste(ev, this._menu.tab, true);
      }
      else if (id === "ojtabspasteafter") {
        this._menu_paste(ev, this._menu.tab, false);
      }
    },

    /**
     *  Initialize the context menu.  This is called on startup, or on option
     *  "contextMenu" change.
     *  @param {Object=} newVal   true if called because of an option change.
     *  @private
     */
    _initMenu : function(newVal)
    {
      var  menu, t, html, $html;

      // check for contextmenu attribute on the root element
      if ((! newVal) && (! this.options["contextMenu"])) {
        menu = this.element.attr("contextmenu");
        if (menu) {
          this.options["contextMenu"] = "#" + menu;
        }
      }

      if ((! newVal) && (! this.options["contextMenu"])) {
        return;
      }

      menu =  newVal || this.options["contextMenu"];
      t = $.type(menu);
      if (t == "function") {
        try {
          menu = menu();             // call user's method to get the context menu
        }
        catch (e) {
          menu = null;
        }
        t = $.type(menu);
      }

      if (t !== "string") {
        return;
      }

      var $m = $(menu);                    // get the user's <ul> list   
      if ($m) {
        $m.css("display", "none");         // ensure it's not visible
        var  dm = this._menu;
        if (! dm)
          return;

        dm.$container = $m;
        dm.usermenu   = true;              // have a context menu
      }
      
      // if we have a context menu
      if (this._menu.usermenu) {
        if (newVal) {                      // and it is it being changed
          this._applyMenu();               // complete menu creation/attachnment
        }
      }

      //  If not a new val from options, Menu will be noted at the end of initialization in _start()
    },


    /**
     *   Attach the user menu <ul> list to the tabs div, and set up listeners on it.
     *   @private
     */
    _applyMenu : function()
    {
      if (! this._menu || ! this._menu.usermenu || ! this.options.reorderable) {
        return;
      }

      // Add our listeners so that we can handle build-in cut/paste, etc
      var $menuContainer = this._menu.$container;
      var self = this;

      $menuContainer.on("ojselect", $.proxy(this._handleContextMenuSelect, this));

      // If there are any ojTabs built in menu item ids, construct the menu items
      var listItems = $menuContainer.find("[data-oj-command]");
      var bChanged  = false;

      listItems.each(function()  {
        if ($(this).children('a').length === 0)  {

          var command = $(this).attr('data-oj-command').slice("oj-tabs-".length);
          $(this).replaceWith(self._buildContextMenuItem(command));
          $(this).addClass("oj-menu-item")

          bChanged = true;
        }
      });

      if (bChanged) {
        $menuContainer.ojMenu('refresh');
      }

      //  Note "paste" elements for disabling if no prev "cut"
      this._menu.$elemPasteBefore = $menuContainer.find("#ojtabspastebefore");
      this._menu.$elemPasteAfter = $menuContainer.find("#ojtabspasteafter");
    },
       
    /**
     *  Clear out any contextMenu data.
     *  @private
     */
    _clearMenu : function()
    {
      var menu = this._menu;
      if (menu && menu.usermenu)
      {
        menu.usermenu = false;
        menu.$container.off("ojselect");
        menu.$container = null;
      }
    },

    //Bug 19587990 - tabs lose selection when moved (sortable tabs)
    _doReorder: function (event, mvTab, prevTab, pasteBefore)
    {
      var mvContent = this._getPanelForTab(mvTab);

      var eventData = {
        /** @expose */
        tab : mvTab,
        /** @expose */
        content : mvContent
      };

      //fire beforeReorder event and only reorder when there is no veto
      if (this._trigger("beforeReorder", event, eventData) === false)
        return false;

      var focused = mvTab;
      var mvInd = this.tabs.index(mvTab);

      if (prevTab.length)
      {
        if (this.tabs.index(prevTab) == mvInd)
          return true;

        var prevContent = this._getPanelForTab(prevTab);
        if (pasteBefore) {
          prevTab.before(mvTab);
          prevContent.before(mvContent);
        }
        else {
          prevTab.after(mvTab);
          prevContent.after(mvContent);
        }
      }
      else if (this.tabs.length > 0)
      {
        if (mvInd == 0)
          return true;
        this.tabs.first().before(mvTab);
        this.panels.first().before(mvContent);
      }

      //Bug 18680706 - calling refresh after reordering tabs causes tabs to loose there disabled state 
      //update disabled and active
      this._updateDisabledTabs();

      //Bug 18721093 - add new tab. move it to first place. add another tab.first tab disappears.
      this.refresh();
      mvTab.focus();

      //fire reorder event
      this._trigger("reorder", event, eventData);

      return true;
    },

    //Bug 18290621 - ojtabs do not work with tabs whose id has '::' 
    _sanitizeSelector: function( hash ) {
      return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
    },

    _getPanelForTab : function (tab)
    {
      return this.element.find(this._sanitizeSelector("#" + $(tab).attr("aria-controls")));
    },

    _getUniqueId: function (panel)
    {
      var id = panel.attr("id");
      if (! id) {
        id = panel.uniqueId().attr("id");
        panel.addClass("oj-tabs-gen-id");
      }
      return id;
    },

    _getTab: function (idOrIndex)
    {
      var index = this._getIndexByTabOrContentId(idOrIndex);
      if (index !== -1) {
        return $(this.tabs[index]);
      }
      return undefined;
    },

    //Note: return -1 if not valid id
    _getIndexByTabOrContentId: function (idOrIndex)
    {
      var index = -1;

      if (typeof idOrIndex === 'number') {
        if (idOrIndex >= 0 && idOrIndex < this.tabs.length) {
          index = idOrIndex;
        }
      }
      else if (typeof idOrIndex === 'string') {
        //security test
        var selector = this._sanitizeSelector(idOrIndex);
        if (oj.DomUtils.isValidIdentifier(selector)) {
          var tabOrContent = this.element.find("#" + selector);

          if (tabOrContent.length) {
            index = this.tabs.index(tabOrContent);
            if (index == -1)
              index = this.panels.index(tabOrContent);
          }
        }
      }
      return index;
    },

    _getSelectedIndex: function ()
    {
      var selected = this.options.selected;
      if (typeof selected === 'number')
        return selected;

      return this.tabs.index($("#" + selected));
    },

    /* 
     * return either page author provided id or index
     */
    _getTabIdOrIndex: function (tab) {
      if (tab) {
        var id = tab.attr("id");
        return id ? id : this.tabs.index(tab);
      }
      else {
        return undefined;
      }
    },

    _setOjDisabledOnTab: function (disTabs)
    {
      //clear oj-disabled that were on the tabs
      var tabbar = this.tablist ? this.tablist : this.element.children("ul");
      var children = tabbar.children("li");
      children.removeClass("oj-disabled")
              .removeAttr("aria-disabled");

      var arr  = [];

      //for each disabled tab in otpions.disabledTabs, add oj-disabled to it
      if (disTabs && disTabs.length > 0) {
        var id, tab;

        for (var i = 0; i < disTabs.length; i++) {
          tab = this._getTab(disTabs[i]);
          if (tab)
          {
            tab.addClass("oj-disabled");

            tab.attr("aria-disabled", "true");
            //Bug 18270899 - clicking on disabled tab takes user to the top of the page
            tab.find(".oj-tabs-anchor").removeAttr("href");

            id = tab.attr("id");
            arr.push(id ? id : children.index(tab));
          }
        }
      }
      this._updateDisabledTabs(arr);
    },

    /* 
     * update disabledTabs array
     * entry value is either page author provided id or index
     */
    _updateDisabledTabs: function (arr) {
      if (! arr)
      {
        arr  = [];
        var self = this;
        this.tablist.children().each(
          function(index) {
            var tab = $(this);
            if (tab.hasClass("oj-disabled")) {
              arr.push(self._getTabIdOrIndex(tab));
            }
          });
      }
      if (! oj.Object._compareSet(this.options.disabledTabs, arr))
      {
        //Bug 20028458 - tabs shouldn't implement _init()
        //don't fire initial option change events
        if (this._initialRender) {
          this.options.disabledTabs = arr;
        }
        else {
          var context = {};
          context.internalSet = true;
          context.writeback = true;
          this.option({'disabledTabs': arr}, {'_context': context, 'changed': true});
        }
      }
    },

    _isInATab: function (element) {
      var found = false;
      this.tabs.each(function () {
        if (this === element || $.contains(this, element))
        {
          found = true;
          return false;
        }
      });
      return found;
    },

    //Bug# 20217708 - use case where gauges are displayed in tabs title bar does not work 
    _createTabbar: function () {
      var contentIds = this._getContentIds();

      var self = this,
          tabbar = this.element.children("ul");

      //tabbar exists
      if (tabbar.length > 0) {
        tabbar.children("li").each(function(index) {
          self._wrapLi($(this), contentIds[index]);
        });
      }
    },

    // Backward compatible: move headers to form a tab bar
    _createTabbarFromOldMarkup: function () {
      var tabbar = this.element.children("ul");

      if (tabbar.length == 0) {
        var self = this,
            contentIds = this._getContentIds();
        tabbar = $("<ul>");

        this.element.children().each(
          function (index) {
            var tab = self._wrapLi($(this).find("> :first-child"), contentIds[index]);

            tab.appendTo(tabbar); // @HTMLUpdateOK
          });

        tabbar.prependTo(this.element); // @HTMLUpdateOK
      }
    },

    //Bug 18283133 - er for placing buttons alongside tabs
    _addFacets: function () {
      var navRoot = this.element.children(".oj-tabs-nav-root"),
          self = this,
          totalWidth = 0,
          tabbarWrapper = navRoot.children(".oj-tabs-conveyorbelt-wrapper"),
          tabbarIndex = navRoot.index(),
          last = null;

      this.element.children(".oj-tabs-facet").each(
        function ()
        {
          var facet = $(this);
          var facetId = self._getUniqueId(facet);

          //if facet doesn't exist add it to the tab bar 
          if (navRoot.find("#" + facetId).length == 0)
          {
            //add start facet
            if (facet.hasClass("oj-start")) {
              tabbarWrapper.before(facet);
            }
            //add other facets
            else {
              facet.appendTo(navRoot); // @HTMLUpdateOK
              last = facet;
            }
          }
        });

    },

    _getContentIds: function () {
      var contentIds = [],
          self = this;

      this.element.children(":not(ul):not(.oj-tabs-facet)").each(
        function ()
        {
          var panel = $(this);
          panel.addClass("oj-tabs-panel")
          contentIds.push(self._getUniqueId(panel));
        });

      return contentIds;
    }

    // Fragments:

    /**
     * <table class="keyboard-table">
     *   <thead>
     *     <tr>
     *       <th>Target</th>
     *       <th>Gesture</th>
     *       <th>Action</th>
     *     </tr>
     *   </thead>
     *   <tbody>
     *     <tr>
     *       <td>Tab</td>
     *       <td><kbd>Tap</kbd></td>
     *       <td>Select</td>
     *     </tr>
     *     <tr>
     *       <td>Tab Close Icon</td>
     *       <td><kbd>Tap</kbd></td>
     *       <td>Delete</td>
     *     </tr>
     *     <tr>
     *       <td>Tab</td>
     *       <td><kbd>Press and pan left/right and release</kbd></td>
     *       <td>Reorder</td>
     *     </tr>
     *   </tbody>
     * </table>
     * 
     * @ojfragment touchDoc - Used in touch gesture section of classdesc, and standalone gesture doc
     * @memberof oj.ojTabs
     */
        
    /**
     * <table class="keyboard-table">
     *   <thead>
     *     <tr>
     *       <th>Target</th>
     *       <th>Key</th>
     *       <th>Action</th>
     *     </tr>
     *   </thead>
     *   <tbody>
     *     <tr>
     *       <td>Tab</td>
     *       <td><kbd>Tab</kbd></td>
     *       <td>Only the selected tab is in the tab order.</td>
     *     </tr>
     *     <tr>
     *       <td>Tab</td>
     *       <td><kbd>UpArrow or LeftArrow</kbd> (<kbd>RightArrow</kbd> in RTL)</td>
     *       <td> Move focus to the previous tab and select it.</tr>
     *     </tr>
     *     <tr>
     *       <td>Tab</td>
     *       <td><kbd>DownArrow or RightArrow</kbd> (<kbd>LeftArrow</kbd> in RTL)</td>
     *       <td> Move focus to the next tab and select it.</tr>
     *     </tr>
     *     <tr>
     *       <td>Tab</td>
     *       <td><kbd>Home</kbd></td>
     *       <td> Move focus to the first tabs item.</tr>
     *     </tr>
     *     <tr>
     *       <td>Tab</td>
     *       <td><kbd>End</kbd></td>
     *       <td> Move focus to the last tabs item.</tr>
     *     </tr>
     *     <tr>
     *       <td>Tab</td>
     *       <td><kbd>Delete</kbd></td>
     *       <td> If deletion is allowed, will delete the current tab.</tr>
     *     </tr>
     *     <tr>
     *       <td>Tab content</td>
     *       <td><kbd>Shift+Tab</kbd></td>
     *       <td> move focus to the tab for that tab panel.</tr>
     *     </tr>
     *   </tbody>
     * </table>
     *
     * @ojfragment keyboardDoc - Used in keyboard section of classdesc, and standalone gesture doc
     * @memberof oj.ojTabs
     */

  });




}
());