Source: src/main/javascript/oracle/oj/ojtable/ojtable.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.ojTable
 * @augments oj.baseComponent
 * 
 * @classdesc
 * The ojTable component enhances a HTML table element into one that supports all
 * the features in JET Table. </p>
 * 
 * <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="a11y-section">
 *   Accessibility
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#a11y-section"></a>
 * </h3>
 *
 * <p>Developers should always either specify the <code class="prettyprint">summary</code> attribute or <code class="prettyprint">caption</code> child tag for the table element to conform to accessibility guidelines.</p>
 *
 * <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-table-panel-bottom</td>
 *       <td><p>Used to style a panel that can attach to the bottom of the table 
 *      and match the table colors. An app developer can put a paging control
 *      in a div with this class, for example.
 *
 *           <p>The class is applied as follows:
 * 
 *           <ul>
 *             <li>The class must be applied to the element which is placed immediately below the ojTable element.</li>
 *           </ul>
 *     </tr>
 *   </tbody>
 * </table>
 *
 * <!-- - - - - Above this point, the tags are for the class.
 *              Below this point, the tags are for the constructor (initializer). - - - - - - -->
 *
 *
 * @desc Creates a JET Table.
 *
 * @param {Object=} options a map of option-value pairs to set on the component
 *
 * @example  <caption>Initialize the table via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
 * <table id="table" data-bind="ojComponent: {component: 'ojTable', data: datasource, columns:
 *      [{headerText: 'Department Id', field: 'DepartmentId'},
 *      {headerText: 'Department Name', field: 'DepartmentName'},
 *      {headerText: 'Location Id', field: 'LocationId'},
 *      {headerText: 'Manager Id', field: 'ManagerId'}]}">
 *      
 * @example  <caption>Initialize the table with column templates via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
 * <table id="table" data-bind="ojComponent: {component: 'ojTable', data: datasource, columns:
 *      [{headerText: 'Department Id', field: 'DepartmentId'},
 *      {headerText: 'Department Name', field: 'DepartmentName'},
 *      {headerText: 'Location Id', field: 'LocationId'},
 *      {headerText: 'Manager Id', field: 'ManagerId'}]},
 *      {headerTemplate: 'oracle_link_hdr', template: 'oracle_link'}]}">
 * <script type="text/html" id="oracle_link_hdr">
 *    <th style="padding-left: 5px; padding-right: 5px;">
 *       Oracle Link
 *    </th>
 * </script>
 * <script type="text/html" id="oracle_link">
 *     <td>
 *         <a href="http://www.oracle.com">Oracle</a>
 *     </td>
 * </script>
 *
 * @example  <caption>Initialize the table with rowTemplate via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
 * <table id="table" data-bind="ojComponent: {component: 'ojTable', data: datasource, columns:
 *      [{headerText: 'Department Id', field: 'DepartmentId'},
 *      {headerText: 'Department Name', field: 'DepartmentName'},
 *      {headerText: 'Location Id', field: 'LocationId'},
 *      {headerText: 'Manager Id', field: 'ManagerId'}],
 *      rowTemplate: 'row_tmpl'}">
 * <script type="text/html" id="row_tmpl">
 *   <tr>
 *       <td data-bind="text: DepartmentId">
 *       </td>
 *       <td data-bind="text: DepartmentName">
 *       </td>
 *       <td data-bind="text: LocationId">
 *       </td>
 *       <td data-bind="text: ManagerId">
 *       </td>
 *   </tr>
 * </script>
 */
(function() {
  oj.__registerWidget("oj.ojTable", $['oj']['baseComponent'],
    {
      version: '1.0.0',
      defaultElement: '<table>',
      widgetEventPrefix: 'oj',
      options:
        {
          /** 
           * Accessibility options. 
           * <p>
           * The following options are supported:
           * <ul>
           *   <li>rowHeader: columnId</li>
           * </ul>
           * The td cells in the column specified by the rowHeader
           * attribute will be assigned an id and then referenced by the
           * headers attribute in the rest of the cells in the row.
           * This is required by screenReaders. By default the first column
           * will be taken as the rowHeader.
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {Object.<string, string>|null}
           * @property {string} rowHeader the column id to be used as the row header by screen readers
           * @default <code class="prettyprint">null</code>
           */
          accessibility: null,
          /** 
           * The current row the user has navigated to. Can be an index and/or key value.
           * When both are specified, the index is used as a hint.
           * Returns the current row or null if there is none.
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {Object}
           * @default <code class="prettyprint">null</code>
           * 
           * @example <caption>Get the current row:</caption>
           * $( ".selector" ).ojTable("option", "currentRow");
           * 
           * @example <caption>Set the current row on the table during initialization:</caption>
           * $(".selector").ojTable({"currentRow", {rowKey: '123'}});
           * 
           * @example <caption>Set the current row on the table during initialization:</caption>
           * $(".selector").ojTable({"currentRow", {rowIndex: 1}});
           *
           * @example <caption>Set the current row on the table after initialization:</caption>
           * $(".selector").ojTable("option", "currentRow", {rowKey: '123'});
           * 
           * @example <caption>Set the current row on the table after initialization:</caption>
           * $(".selector").ojTable("option", "currentRow", {rowIndex: 1});
           */
          currentRow: null,
          /** 
           * The data to bind to the component. 
           * <p>
           * Must be of type oj.TableDataSource {@link oj.TableDataSource}
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {oj.TableDataSource|null}
           * @default <code class="prettyprint">null</code>
           */
          data: null,
          /**
           * The text to display when there are no data in the Table. If it is not defined, 
           * then a default empty text is extracted from the resource bundle.
           * 
           * @expose 
           * @memberof! oj.ojTable
           * @instance
           * @type {string|null}
           * @default <code class="prettyprint">"No data to display."</code>
           * @example <caption>Initialize the table with the <code class="prettyprint">emptyText</code> option specified:</caption>
           * <table id="table" data-bind="ojComponent: {component: 'ojTable', data: datasource, emptyText: 'No data', columns:
           * [{headerText: 'Department Id', field: 'DepartmentId'},
           * {headerText: 'Department Name', field: 'DepartmentName']}">
           */
          emptyText: null,
          /** 
           * Whether the horizontal gridlines are to be drawn.
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {string}
           * @default <code class="prettyprint">"enabled"</code>
           */
          horizontalGridVisible: 'enabled',
          /** 
           * The row renderer function to use.
           * <p>
           * The renderer function will be passed in an Object which contains the fields:
           * <ul>
           *   <li>component: Instance of the component</li>
           *   <li>row: Key/value pairs of the row</li>
           *   <li>status: Contains the rowIndex, rowKey, and currentRow</li>
           *   <li>parentElement: Empty rendered TR element</li>
           * </ul>
           * The function returns  either a String or 
           * a DOM element of the content inside the row. If the developer chooses 
           * to manipulate the row element directly, the function should return 
           * nothing.
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {Function|null}
           * @default <code class="prettyprint">null</code>
           */
          rowRenderer: null,
          /**
           * Specifies the mechanism used to scroll the data inside the table. Possible values are: auto and loadMoreOnScroll.
           * When loadMoreOnScroll is specified, additional data are fetched when the user scrolls to the bottom of the table.
           * 
           * @expose 
           * @memberof! oj.ojTable
           * @instance
           * @type {string|null}
           * @default <code class="prettyprint">null</code>
           * 
           * @example <caption>Initialize the table with the <code class="prettyprint">scrollPolicy</code> option specified:</caption>
           * <table id="table" data-bind="ojComponent: {component: 'ojTable', data: datasource, scrollPolicy: 'loadMoreOnScroll', columns:
           * [{headerText: 'Department Id', field: 'DepartmentId'},
           * {headerText: 'Department Name', field: 'DepartmentName']}">
           */
          scrollPolicy: "auto",
          /** 
           * scrollPolicy options. 
           * <p>
           * The following options are supported:
           * <ul>
           *   <li>fetchSize: Fetch size for scroll.</li>
           *   <li>maxCount: Maximum rows which will be displayed before fetching more rows will be stopped.</li>
           * </ul>
           * When scrollPolicy is loadMoreOnScroll, the next block of rows is fetched
           * when the user scrolls to the end of the table. The fetchSize option
           * determines how many rows are fetched in each block.
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {Object.<string, string>|null}
           * @property {string} fetchSize the number of rows to fetch in each block of rows
           * @property {string} maxCount the number of rows which will be displayed before fetching more rows will be stopped
           * @default <code class="prettyprint">{'fetchSize': 25, 'maxCount': 500}</code>
           */
          scrollPolicyOptions: {'fetchSize': 25, 'maxCount': 500},
          /**
           * Specifies the current selections in the table. Can be either an index or key value.
           * When both are specified, the index is used as a hint.
           * Returns an array of range objects, or an empty array if there's no selection.
           * 
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable
           * @type {Array.<Object>}
           * @default <code class="prettyprint">[]</code>
           * 
           * @example <caption>Get the current selection:</caption>
           * $( ".selector" ).ojTable("option", "selection");
           * 
           * @example <caption>Set a row selection on the table during initialization:</caption>
           * $(".selector").ojTable({"selection", [{startIndex: {"row":1}, endIndex:{"row":3}}]});
           * 
           * @example <caption>Set a column selection on the table during initialization:</caption>
           * $(".selector").ojTable({"selection", [{startIndex: {"column":2}, endIndex: {"column":4}}]});
           *
           * @example <caption>Set a row selection on the table after initialization:</caption>
           * $(".selector").ojTable("option", "selection", [{startIndex: {"row":1}, endIndex:{"row":3}}]);
           * 
           * @example <caption>Set a column selection on the table after initialization:</caption>
           * $(".selector").ojTable("option", "selection", [{startIndex: {"column":1}, endIndex: {"column":3}}]);
           * 
           * @example <caption>Set a row selection on the table after initialization:</caption>
           * $(".selector").ojTable("option", "selection", [{startKey: {"row":10}, endKey:{"row":30}}]);
           * 
           * @example <caption>Set a column selection on the table after initialization:</caption>
           * $(".selector").ojTable("option", "selection", [{startKey: {"column": column1}, endKey: {"column": column2}}]);
           */
          selection: [],  
          /** 
           * The row and column selection modes. Both can be either single or multiple.
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {Object.<string, string>|null}
           * @property {string} row single or multiple selection for rows
           * @property {string} column single or multiple selection for columns
           * @default <code class="prettyprint">null</code>
           * @example <caption>Initialize the table with the <code class="prettyprint">selectionMode</code> option specified:</caption>
           * <table id="table" data-bind="ojComponent: {component: 'ojTable', data: datasource, selectionMode: {row: 'multiple', column: 'multiple'}, columns:
           * [{headerText: 'Department Id', field: 'DepartmentId'},
           * {headerText: 'Department Name', field: 'DepartmentName']}">
           */
          selectionMode: null,
          /** 
           * Whether the vertical gridlines are to be drawn.
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {string}
           * @default <code class="prettyprint">"enabled"</code>
           */
          verticalGridVisible: 'enabled',
          /** 
           * An array of column definitions.
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {Array.<Object>|null}
           * @default <code class="prettyprint">null</code>
           * @example <caption>Initialize the table with the <code class="prettyprint">columns</code> option specified:</caption>
           * <table id="table" data-bind="ojComponent: {component: 'ojTable', data: datasource, columns:
           * [{headerText: 'Department Id', field: 'DepartmentId'},
           * {headerText: 'Department Name', field: 'DepartmentName']}">
           */
          columns: [{
              /** 
               * The renderer function that renders the content of the cell. 
               * The function will be passed a context object which contains 
               * the following objects:
               * <ul>
               *   <li>data: The cell data</li>
               *   <li>columnIndex: The column index</li>
               *   <li>component: Instance of the component</li>
               *   <li>datasource: Instance of the datasource used by the table </li>
               *   <li>row: Key/value pairs of the row</li>
               *   <li>status: Contains the rowIndex, rowKey, and currentRow</li>
               *   <li>parentElement: Empty rendered <td> element</li>
               * </ul>
               * The function returns  either a String or 
               * a DOM element of the content inside the header. If the developer chooses 
               * to manipulate the cell element directly, the function should return 
               * nothing. If no renderer is specified, the Table will treat the cell data as a String.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable
               * @alias columns[].renderer
               * @type {Function|null}
               * @default <code class="prettyprint">null</code>
               */
              renderer: null,
              /** 
               * The CSS class to apply to the column cells
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable
               * @alias columns[].className
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              className: null,
              /** 
               * The data field this column refers to. 
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].field
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              field: null,
              /** 
               * The CSS class to apply to the footer cell.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].footerClassName
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              footerClassName: null,
              /** 
               * The renderer function that renders the content of the footer. 
               * The function will be passed a context object which contains 
               * the following objects:
               * <ul>
               *   <li>columnIndex: The column index</li>
               *   <li>component: Instance of the component</li>
               *   <li>datasource: Instance of the datasource used by the table </li>
               *   <li>status: Contains the rowIndex, rowKey, and currentRow</li>
               *   <li>parentElement: Empty rendered <td> element</li>
               * </ul>
               * The function returns  either a String or 
               * a DOM element of the content inside the footer. If the developer chooses 
               * to manipulate the footer element directly, the function should return 
               * nothing. If no renderer is specified, the Table will treat the footer data as a String.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].footerRenderer
               * @type {Function|null}
               * @default <code class="prettyprint">null</code>
               */
              footerRenderer: null,       
              /** 
               * The CSS styling to apply to the footer cell.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].footerStyle
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              footerStyle: null,
              /** 
               * The CSS class to apply to the column header text.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].headerClassName
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              headerClassName: null,
              /** 
               * The renderer function that renders the content of the header. 
               * The function will be passed a context object which contains 
               * the following objects:
               * <ul>
               *   <li>columnIndex: The column index</li>
               *   <li>component: Instance of the component</li>
               *   <li>parentElement: Empty rendered TH element</li>
               *   <li>columnHeaderDefaultRenderer(options, delegateRenderer): If the column 
               *   is not sortable then this function will be included in the context. 
               *   The options parameter specifies the options (future use) for the renderer while the
               *   delegateRenderer parameter specifies the function which the developer would
               *   like to be called during rendering of the column header.</li>
               *   <li>columnHeaderSortableIconRenderer(options, delegateRenderer): If the column 
               *   is sortable then this function will be included in the context. 
               *   The options parameter specifies the options (future use) for the renderer while the
               *   delegateRenderer parameter specifies the function which the developer would
               *   like to be called during rendering of the sortable column header. Calling the
               *   columnHeaderSortableIconRenderer function enables rendering custom header content
               *   while also preserving the sort icons.</li>
               * </ul>
               * The function returns either a String or 
               * a DOM element of the content inside the header. If the developer chooses 
               * to manipulate the cell element directly, the function should return 
               * nothing. If no renderer is specified, the Table will treat the header data as a String.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].headerRenderer
               * @type {Function|null}
               * @default <code class="prettyprint">null</code>
               */
              headerRenderer: null, 
              /** 
               * The CSS styling to apply to the column header text.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].headerStyle
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              headerStyle: null,
              /** 
               * Text to display in the header of the column.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].headerText
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              headerText: null,
              /** 
               * The identifier for the column
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].id
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              id: null,
              /** 
               * Whether or not the column is sortable. 
               * <p>
               * A sortable column has a clickable header that (when clicked) 
               * sorts the table by that column's property. Note that 
               * in order for a column to be sortable, this attribute 
               * must be set to "enabled" and the underlying model must 
               * support sorting by this column's property. If this attribute
               * is set to "auto" then the column will be sortable if the
               * underlying model supports sorting. A value of "none" will
               * disable sorting on the column.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].sortable
               * @type {string|null}
               * @default <code class="prettyprint">"auto"</code>
               */
              sortable: 'auto',
              /** 
               * Indicates the row attribute used for sorting when sort is invoked on this
               * column. Useful for concatenated columns, where the sort is done by only a subset 
               * of the concatenated items.  
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].sortProperty
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              sortProperty: null,
              /** 
               * The CSS styling to apply to the column cells
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columns[].style
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              style: null
              /**
               * The knockout template used to render the content of the column header.
               *
               * This attribute is only exposed via the <code class="prettyprint">ojComponent</code> binding, and is not a 
               * component option. The following
               *   variables are also passed into the template
               *     <ul>
               *       <li>$columnIndex: The column index</li>
               *       <li>$data: The header text</li>
               *       <li>$headerContext: The header context</li>
               *     </ul>
               *   </li>
               *
               * @ojbindingonly
               * @name columns[].headerTemplate
               * @memberof! oj.ojTable
               * @instance
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               * 
               * @example <caption>Specify the column header <code class="prettyprint">template</code> when initializing Table:</caption>
               * // set the template
               * <ul id="table" data-bind="ojComponent: {component: 'ojTable', data: dataSource, columns:
               *      [{headerText: 'Department Id', field: 'DepartmentId'},
               *      {headerText: 'Department Name', field: 'DepartmentName'},
               *      {headerText: 'Location Id', field: 'LocationId'},
               *      {headerText: 'Manager Id', field: 'ManagerId'},
               *      {headerTemplate: 'oracle_link_hdr'}]}"></ul>
               */
              /**
               * The knockout template used to render the content of the column footer.
               *
               * This attribute is only exposed via the <code class="prettyprint">ojComponent</code> binding, and is not a 
               * component option. The following
               *   variables are also passed into the template
               *     <ul>
               *       <li>$columnIndex: The column index</li>
               *       <li>$footerContext: The header context</li>
               *     </ul>
               *   </li>
               *
               * @ojbindingonly
               * @name columns[].footerTemplate
               * @memberof! oj.ojTable
               * @instance
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               * 
               * @example <caption>Specify the column footer <code class="prettyprint">template</code> when initializing Table:</caption>
               * // set the template
               * <ul id="table" data-bind="ojComponent: {component: 'ojTable', data: dataSource, columnsDefault:
               *      [{headerText: 'Department Id', field: 'DepartmentId'},
               *      {headerText: 'Department Name', field: 'DepartmentName'},
               *      {headerText: 'Location Id', field: 'LocationId'},
               *      {headerText: 'Manager Id', field: 'ManagerId'},
               *      {footerTemplate: 'oracle_link_ftr'}]}"></ul>
               */
            }],
          /** 
           * Default values to apply to all columns objects.
           * @expose 
           * @public 
           * @instance
           * @memberof! oj.ojTable 
           * @type {Object.<string, string|null>}
           * @default <code class="prettyprint">null</code>
           * @example <caption>Initialize the table with the <code class="prettyprint">columnsDefault</code> option specified:</caption>
           * <table id="table" data-bind="ojComponent: {component: 'ojTable', data: datasource, columnsDefault: {headerStyle: 'text-align: left; white-space:nowrap;'}, columns:
           * [{headerText: 'Department Id', field: 'DepartmentId'},
           * {headerText: 'Department Name', field: 'DepartmentName']}">
           */
          columnsDefault: {
              /** 
               * The renderer function that renders the content of the cell. 
               * The function will be passed a context object which contains 
               * the following objects:
               * <ul>
               *   <li>data: The cell data</li>
               *   <li>columnIndex: The column index</li>
               *   <li>component: Instance of the component</li>
               *   <li>datasource: Instance of the datasource used by the table </li>
               *   <li>row: Key/value pairs of the row</li>
               *   <li>status: Contains the rowIndex, rowKey, and currentRow</li>
               *   <li>parentElement: Empty rendered <td> element</li>
               * </ul>
               * The function returns  either a String or 
               * a DOM element of the content inside the header. If the developer chooses 
               * to manipulate the cell element directly, the function should return 
               * nothing. If no renderer is specified, the Table will treat the cell data as a String.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable
               * @alias columnsDefault.renderer
               * @type {Function|null}
               * @default <code class="prettyprint">null</code>
               */
              renderer: null,
              /** 
               * The default CSS class for column cells
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.className
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              className: null,
              /** 
               * The default data field for column. 
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.field
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              field: null,
              /** 
               * The CSS class to apply to the footer cell.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.footerClassName
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              footerClassName: null,
              /** 
               * The renderer function that renders the content of the footer. 
               * The function will be passed a context object which contains 
               * the following objects:
               * <ul>
               *   <li>columnIndex: The column index</li>
               *   <li>component: Instance of the component</li>
               *   <li>datasource: Instance of the datasource used by the table </li>
               *   <li>status: Contains the rowIndex, rowKey, and currentRow</li>
               *   <li>parentElement: Empty rendered <td> element</li>
               * </ul>
               * The function returns  either a String or 
               * a DOM element of the content inside the footer. If the developer chooses 
               * to manipulate the footer element directly, the function should return 
               * nothing. If no renderer is specified, the Table will treat the footer data as a String.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.footerRenderer
               * @type {Function|null}
               * @default <code class="prettyprint">null</code>
               */
              footerRenderer: null, 
              /** 
               * The CSS styling to apply to the footer cell.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.footerStyle
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              footerStyle: null,
              /** 
               * The default CSS class to apply to the column header.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.headerClassName
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              headerClassName: null,
              /** 
               * The renderer function that renders the content of the header. 
               * The function will be passed a context object which contains 
               * the following objects:
               * <ul>
               *   <li>columnIndex: The column index</li>
               *   <li>component: Instance of the component</li>
               *   <li>parentElement: Empty rendered TH element</li>
               *   <li>columnHeaderDefaultRenderer(options, delegateRenderer): If the column 
               *   is not sortable then this function will be included in the context. 
               *   The options parameter specifies the options (future use) for the renderer while the
               *   delegateRenderer parameter specifies the function which the developer would
               *   like to be called during rendering of the column header.</li>
               *   <li>columnHeaderSortableIconRenderer(options, delegateRenderer): If the column 
               *   is sortable then this function will be included in the context. 
               *   The options parameter specifies the options (future use) for the renderer while the
               *   delegateRenderer parameter specifies the function which the developer would
               *   like to be called during rendering of the sortable column header. Calling the
               *   columnHeaderSortableIconRenderer function enables rendering custom header content
               *   while also preserving the sort icons.</li>
               * </ul>
               * The function returns either a String or 
               * a DOM element of the content inside the header. If the developer chooses 
               * to manipulate the cell element directly, the function should return 
               * nothing. If no renderer is specified, the Table will treat the header data as a String.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.headerRenderer
               * @type {Function|null}
               * @default <code class="prettyprint">null</code>
               */
              headerRenderer: null, 
              /** 
               * The default CSS styling to apply to the column header.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.headerStyle
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              headerStyle: null,
              /** 
               * Default text to display in the header of the column.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.headerText
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              headerText: null,
              /** 
               * Whether or not the column is sortable. 
               * <p>
               * A sortable column has a clickable header that (when clicked) 
               * sorts the table by that column's property. Note that 
               * in order for a column to be sortable, this attribute 
               * must be set to "enabled" and the underlying model must 
               * support sorting by this column's property. If this attribute
               * is set to "auto" then the column will be sortable if the
               * underlying model supports sorting. A value of "none" will
               * disable sorting on the column.
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.sortable
               * @type {string|null}
               * @default <code class="prettyprint">"auto"</code>
               */
              sortable: 'auto',
              /** 
               * Indicates the row attribute used for sorting when sort is invoked on this
               * column. Useful for concatenated columns, where the sort is done by only a subset 
               * of the concatenated items.  
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.sortProperty
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              sortProperty: null,
              /** 
               * Default CSS styling to apply to the column cells
               * @expose 
               * @public 
               * @instance
               * @memberof! oj.ojTable 
               * @alias columnsDefault.style
               * @type {string|null}
               * @default <code class="prettyprint">null</code>
               */
              style: null
               /**
                * The default knockout template used to render the content of the column header.
                *
                * This attribute is only exposed via the <code class="prettyprint">ojComponent</code> binding, and is not a 
                * component option. The following
                *   variables are also passed into the template
                *     <ul>
                *       <li>$columnIndex: The column index</li>
                *       <li>$data: The header text</li>
                *       <li>$headerContext: The header context</li>
                *     </ul>
                *   </li>
                *
                * @ojbindingonly
                * @name columnsDefault.headerTemplate
                * @memberof! oj.ojTable
                * @instance
                * @type {string|null}
                * @default <code class="prettyprint">null</code>
                * 
                * @example <caption>Specify the column header <code class="prettyprint">template</code> when initializing Table:</caption>
                * // set the template
                * <ul id="table" data-bind="ojComponent: {component: 'ojTable', data: dataSource, columnsDefault:
                *      [{headerText: 'Department Id', field: 'DepartmentId'},
                *      {headerText: 'Department Name', field: 'DepartmentName'},
                *      {headerText: 'Location Id', field: 'LocationId'},
                *      {headerText: 'Manager Id', field: 'ManagerId'},
                *      {headerTemplate: 'oracle_link_hdr'}]}"></ul>
                */
               /**
                * The default knockout template used to render the content of the column footer.
                *
                * This attribute is only exposed via the <code class="prettyprint">ojComponent</code> binding, and is not a 
                * component option. The following
               *   variables are also passed into the template
               *     <ul>
               *       <li>$columnIndex: The column index</li>
               *       <li>$footerContext: The header context</li>
               *     </ul>
               *   </li>
                *
                * @ojbindingonly
                * @name columnsDefault.footerTemplate
                * @memberof! oj.ojTable
                * @instance
                * @type {string|null}
                * @default <code class="prettyprint">null</code>
                * 
                * @example <caption>Specify the column footer <code class="prettyprint">template</code> when initializing Table:</caption>
                * // set the template
                * <ul id="table" data-bind="ojComponent: {component: 'ojTable', data: dataSource, columnsDefault:
                *      [{headerText: 'Department Id', field: 'DepartmentId'},
                *      {headerText: 'Department Name', field: 'DepartmentName'},
                *      {headerText: 'Location Id', field: 'LocationId'},
                *      {headerText: 'Manager Id', field: 'ManagerId'},
                *      {footerTemplate: 'oracle_link_ftr'}]}"></ul>
                */
            },
            /**
              * Triggered before the current row is changed via the <code class="prettyprint">currentRow</code> option or via the UI.
              *
              * @expose 
              * @event 
              * @memberof! oj.ojTable
              * @instance
              * @property {Event} event <code class="prettyprint">jQuery</code> event object
              * @property {Object} ui Parameters
              * @property {Object} ui.currentRow the new current row
              * @property {number} ui.currentRow.rowIndex current row index
              * @property {string} ui.currentRow.rowKey current row key
              * @property {number} ui.previousCurrentRow the previous current row
              * @property {number} ui.previousCurrentRow.rowIndex previous current row index
              * @property {string} ui.previousCurrentRow.rowKey previous current row key
              * 
              * @example <caption>Initialize the table with the <code class="prettyprint">beforeCurrentRow</code> callback specified:</caption>
              * $( ".selector" ).ojTable({
              *     "beforeCurrentRow": function( event, ui ) {}
              * });
              *
              * @example <caption>Bind an event listener to the <code class="prettyprint">ojbeforecurrentrow</code> event:</caption>
              * $( ".selector" ).on( "ojbeforecurrentrow", function( event, ui ) {} );
              */
            beforeCurrentRow: null,
          /**
            * Triggered when the table has finished rendering
            *
            * @expose 
            * @event 
            * @memberof! oj.ojTable
            * @instance
            * 
            * @example <caption>Initialize the table with the <code class="prettyprint">ready</code> callback specified:</caption>
            * $( ".selector" ).ojTable({
            *     "ready": function() {}
            * });
            *
            * @example <caption>Bind an event listener to the <code class="prettyprint">ojready</code> event:</caption>
            * $( ".selector" ).on( "ojready", function() {} );
            */
          ready: null,
            /**
              * Triggered when a sort is performed on the table
              *
              * @expose 
              * @event 
              * @memberof! oj.ojTable
              * @instance
              * @property {Event} event <code class="prettyprint">jQuery</code> event object
              * @property {Object} ui Parameters
              * @property {Element} ui.header the key of the header which was sorted on				 
              * @property {string} ui.direction the direction of the sort ascending/descending
              * 
              * @example <caption>Initialize the table with the <code class="prettyprint">sort</code> callback specified:</caption>
              * $( ".selector" ).ojTable({
              *     "sort": function( event, ui ) {}
              * });
              *
              * @example <caption>Bind an event listener to the <code class="prettyprint">ojsort</code> event:</caption>
              * $( ".selector" ).on( "ojsort", function( event, ui ) {} );
              */
            sort: 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.
              *
              * Currently there are 2 supported options, <code class="prettyprint">"selection"</code> and 
              * <code class="prettyprint">"currentRow"</code>.  Additional options may be supported in the future, 
              * so listeners should verify which option is changing before taking any action.
              *
              * @expose
              * @event
              * @memberof! oj.ojTable
              * @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,
            /** 
              * Translations for the component
              * @expose 
              * @public 
              * @instance
              * @memberof! oj.ojTable
              * @type {Object.<string, string>}
              * @property {string} labelSelectRow Select row label
              * @property {string} labelSelectColumn Select column label
              * @property {string} labelSort Context menu label for sort
              * @property {string} labelSortAsc Context menu label for sort ascending
              * @property {string} labelSortDsc Context menu label for sort descending
              * @property {string} msgFetchingData Fetching data message
              * @property {string} msgNoData No data to display message
              */
            translations: {}
             /**
              * The knockout template used to render the content of the row.
              *
              * This attribute is only exposed via the <code class="prettyprint">ojComponent</code> binding, and is not a 
              * component option. The following
              *   variables are also passed into the template
              *     <ul>
              *       <li>$rowContext: The row context</li>
              *     </ul>
              *   </li>
              *
              * @ojbindingonly
              * @name rowTemplate
              * @memberof! oj.ojTable
              * @instance
              * @type {string|null}
              * @default <code class="prettyprint">null</code>
              * 
              * @example <caption>Specify the row <code class="prettyprint">template</code> when initializing Table:</caption>
              * // set the template
              * <ul id="table" data-bind="ojComponent: {component: 'ojTable', data: dataSource, rowTemplate: 'row_tmpl'}"></ul>
              */
        },
      /**
       * @const
       * @private
       */
      _BUNDLE_KEY:
        {
          _MSG_FETCHING_DATA:                             'msgFetchingData',
          _MSG_NO_DATA:                                   'msgNoData',
          _LABEL_SELECT_COLUMN:                           'labelSelectColumn',
          _LABEL_SELECT_ROW:                              'labelSelectRow'
        },
      /**
       * @const
       * @private
       */
      _LOGGER_MSG:
        {
          _ERR_PRECURRENTROW_ERROR_SUMMARY:                'Did not change current row due to error.',
          _ERR_PRECURRENTROW_ERROR_DETAIL:                 'Error detail: {error}.',
          _ERR_CURRENTROW_UNAVAILABLE_INDEX_SUMMARY:       'Did not change current row due to unavailable row index.',
          _ERR_CURRENTROW_UNAVAILABLE_INDEX_DETAIL:        'Unavailable row index: {rowIdx}.',
          _ERR_REFRESHROW_INVALID_INDEX_SUMMARY:          'Invalid row index value.',
          _ERR_REFRESHROW_INVALID_INDEX_DETAIL:           'Row index: {rowIdx}.',
          _ERR_DATA_INVALID_TYPE_SUMMARY:                 'Invalid data type.',
          _ERR_DATA_INVALID_TYPE_DETAIL:                  'Please specify the appropriate data type.',
          _ERR_ELEMENT_INVALID_TYPE_SUMMARY:               'Invalid element type.',
          _ERR_ELEMENT_INVALID_TYPE_DETAIL:                'Only a <table> element can be specified for ojTable.',
          _ERR_DOM_SCROLLER_MAX_COUNT_SUMMARY:             'Exceeded maximum rows for table scrolling.',
          _ERR_DOM_SCROLLER_MAX_COUNT_DETAIL:              'Please reload with smaller data set.'
        },
      /**
       * @private
       * @const
       * @type {string}
       */
      _COLUMN_HEADER_ID:                                  '_headerColumn',
      /**
       * @private
       * @const
       * @type {string}
       */
      _COLUMN_HEADER_TEXT_ID:                             '_headerColumnText',
      /**
       * @private
       * @const
       * @type {string}
       */
      _COLUMN_HEADER_ASC_ID:                              '_headerColumnAsc',
      /**
       * @private
       * @const
       * @type {string}
       */
      _COLUMN_HEADER_DSC_ID:                              '_headerColumnDsc',
      /**
       * @private
       * @const
       * @type {string}
       */
      _COLUMN_HEADER_ID_PREFIX:                           '_hdrCol',
      /**
       * @private
       * @const
       * @type {string}
       */
      _OPTION_AUTO:                                       'auto',
      /**
       * @private
       * @const
       * @type {string}
       */
      _OPTION_ENABLED:                                    'enabled',
      /**
       * @private
       * @const
       * @type {string}
       */
      _OPTION_DISABLED:                                   'disabled',
      /**
       * @private
       * @const
       * @type {string}
       */
      _OPTION_NONE:                                       'none',
      /**
       * @private
       * @const
       */
      _OPTION_SELECTION_MODES:
        {
          _SINGLE:                                        'single',
          _MULTIPLE:                                      'multiple'
        },
      /**
       * @private
       * @const
       */
        _OPTION_SCROLL_POLICY:
        {
          _AUTO:                                          'auto',
          _LOADMORE_ON_SCROLL:                            'loadMoreOnScroll'
        },
      /**
       * @private
       * @const
       */
      _COLUMN_SORT_ORDER:
        {
          _ASCENDING:                                     'ascending',
          _DESCENDING:                                    'descending'
        },
      /**
       * @private
       * @const
       */
      _KEYBOARD_CODES:
        {
          _KEYBOARD_CODE_SPACEBAR:                        32,
          _KEYBOARD_CODE_ENTER:                           13,
          _KEYBOARD_CODE_UP:                              38,
          _KEYBOARD_CODE_DOWN:                            40,
          _KEYBOARD_CODE_LEFT:                            37,
          _KEYBOARD_CODE_RIGHT:                           39,
          _KEYBOARD_CODE_HOME:                            36,
          _KEYBOARD_CODE_END:                             35,
          _KEYBOARD_CODE_TAB:                             9,
          _KEYBOARD_CODE_ESC:                             27,
          _KEYBOARD_MODIFIER_SHIFT:                       'shiftKey'
        },
      /**** start Public APIs ****/
      /**
       * Return the subcomponent node represented by the documented locator attribute values.
       * <p>
       * To lookup a cell the locator object should have the following:
       * <ul>
       * <li><b>subId</b>: 'oj-table-cell'</li>
       * <li><b>rowIndex</b>: the zero based absolute row index</li>
       * <li><b>columnIndex</b>: the zero based absolute column index</li>
       * </ul>          
       *          
       * To lookup a header the locator object should have the following:
       * <ul>
       * <li><b>subId</b>: 'oj-table-header'</li>
       * <li><b>index</b>: the zero based absolute column index.</li>
       * </ul>
       *          
       * To lookup a sort ascending icon the locator object should have the following:
       * <ul>
       * <li><b>subId</b>: 'oj-table-sort-ascending-icon'</li>
       * <li><b>index</b>: the zero based absolute column index</li>
       * </ul>
       * 
       * To lookup a sort descending icon the locator object should have the following:
       * <ul>
       * <li><b>subId</b>: 'oj-table-sort-descending-icon'</li>
       * <li><b>index</b>: the zero based absolute column index</li>
       * </ul>
       * 
       * To lookup a footer the locator object should have the following:
       * <ul>
       * <li><b>subId</b>: 'oj-table-footer'</li>
       * <li><b>index</b>: the zero based absolute column index.</li>
       * </ul>
       * 
       * @expose
       * @memberof! oj.ojTable
       * @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
       * @returns {Array.<(Element|null)>|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'];

        if (subId === 'oj-table-cell')
        {
          var rowIdx = locator['rowIndex'];
          var columnIdx = locator['columnIndex'];
          return this._getTableDomUtils().getTableBodyCell(rowIdx, columnIdx)[0];
        }
        else if (subId === 'oj-table-header' ||
                 subId === 'oj-table-sort-ascending' ||
                 subId === 'oj-table-sort-descending')
        {
          var columnIdx = locator['index'];
          var tableHeaderColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);
          if (tableHeaderColumn != null)
          {
            if (subId === 'oj-table-header')
            {
              return tableHeaderColumn[0];
            }
            else if (subId === 'oj-table-sort-ascending')
            {
              var tableHeaderColumnSortAsc = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_ASC_LINK_CLASS);
              if (tableHeaderColumnSortAsc.length > 0)
              {
                return tableHeaderColumnSortAsc[0];
              }
            }
            else
            {
              var tableHeaderColumnSortDsc = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_DSC_LINK_CLASS);
              if (tableHeaderColumnSortDsc.length > 0)
              {
                return tableHeaderColumnSortDsc[0];
              }
            }
          }
        }
        else if (subId === 'oj-table-footer')
        {
          var columnIdx = locator['index'];
          var tableFooterCell = this._getTableDomUtils().getTableFooterCell(columnIdx);
          
          if (tableFooterCell != null)
          {
            return tableFooterCell[0];
          }
        }

        // Non-null locators have to be handled by the component subclasses
        return null;
      },              
      /**
       * Refresh the table.
       * @export
       * @expose
       * @memberof! oj.ojTable
       * @instance
       * @example <caption>Invoke the <code class="prettyprint">refresh</code> method:</caption>
       * $( ".selector" ).ojTable( "refresh" );
       */
      'refresh': function()
      {
        this._super();
        this._refresh();
      },
      /**
       * Refresh a row in the table.
       * @param {number} rowIdx  Index of the row to refresh.
       * @return {boolean} true if refreshed, false if not
       * @throws {Error}
       * @export
       * @expose
       * @memberof! oj.ojTable
       * @instance
       * @example <caption>Invoke the <code class="prettyprint">refreshRow</code> method:</caption>
       * $( ".selector" ).ojTable( "refreshRow", 1 );
       */
      'refreshRow': function(rowIdx)
      {
        var data = this._getData();
        // if no data then bail
        if (!data)
        {
          return false;
        }
        
        var tableBodyRows = this._getTableDomUtils().getTableBodyRows();

        if (isNaN(rowIdx) || rowIdx < 0 || (tableBodyRows != null && rowIdx >= tableBodyRows.length))
        {
          // validate rowIdx value
          var errSummary = this._LOGGER_MSG._ERR_REFRESHROW_INVALID_INDEX_SUMMARY;
          var errDetail = oj.Translations.applyParameters(this._LOGGER_MSG._ERR_REFRESHROW_INVALID_INDEX_DETAIL, {'rowIdx': rowIdx.toString()});
          throw new RangeError(errSummary + '\n' + errDetail);
        }

        // get row at rowIdx
        var rowKey = this._getRowKeyForRowIdx(rowIdx);
        var row = data.get(rowKey);
        if (row == null)
        {
          return false;
        }

        var self = this;

        return this._queueTask(function()
        {
          // refresh table row DOM with row
          return row.then(function(row)
          {
            self._refreshTableBodyRow(rowIdx, row);
          }).then(function()
          {
            return true;
          });
        });
      },
      /**
       * Returns a jQuery object containing the root dom element of the table
       * 
       * <p>This method does not accept any arguments.
       * 
       * @expose
       * @override
       * @memberof oj.ojTable
       * @instance
       * @return {jQuery} the root DOM element of table
       */
      'widget' : function ()
      {
        var tableContainer = this._getTableDomUtils().getTableContainer();
        
        if (tableContainer != null)
        {
          return tableContainer;
        }
        return this.element;
      },
      /**** end Public APIs ****/

      /**** start internal widget functions ****/

      /**
       * @override
       * @protected
       * @instance
       * @memberof! oj.ojTable
       */
      _ComponentCreate : function ()
      {
        this._super();
        this._draw();
        this._registerCustomEvents();
        this._on(this._events);
        this._registerDomEventListeners();
        // register event listeners for table on the datasource so that the table
        // component is notified when rows are added, deleted, etc from the datasource.
        this._registerDataSourceEventListeners();
        // cache the options
        this._cachedOptions = $.extend(true, {}, this.options);
      },
      /**
       * Initialize the table after creation
       * @protected
       * @override
       * @memberof! oj.ojTable
       */        
      _AfterCreate: function () 
      {  
        this._super();
        // create the context menu
        this._getTableDomUtils().createContextMenu(this._handleContextMenuSelect.bind(this));
        this._initFetch();
      },
      /**
       * @param {Object} contextMenu The JET Menu to open as a context menu
       * @param {Event} event What triggered the menu launch
       * @param {string} eventType "mouse", "touch", "keyboard"
       * @private	
       */
      _NotifyContextMenuGesture: function(contextMenu, event, eventType)
      {
        var openOptions = {};
        this._contextMenuEvent = event['originalEvent'];
        
        // first check if we are invoking on an editable or clickable element If so bail
        if (this._isNodeEditableOrClickable($(this._contextMenuEvent['target'])))
        {
          return;
        }
        
        var headerColumn = this._getTableDomUtils().getFirstAncestor($(this._contextMenuEvent['target']), 'oj-table-column-header-cell');
        headerColumn = headerColumn == null ? this._getTableDomUtils().getTableHeaderColumn(this._activeColumnIndex) : headerColumn;
        var tableBodyCell = this._getTableDomUtils().getFirstAncestor($(this._contextMenuEvent['target']), 'oj-table-data-cell');
        
        if (tableBodyCell != null)
        {
          var columnIdx = this._getTableDomUtils().getElementColumnIdx(tableBodyCell);
          headerColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);
        }
        
        if (this._contextMenuEvent['type'] === 'keydown')
        {
          var table = this._tableDomUtils.getTable();
          
          if (this._contextMenuEvent['target'] == table[0])
          {
            if (headerColumn != null && headerColumn[0] != null)
            {
              openOptions['position'] = {"my": "start top", "at": "start bottom", "of": headerColumn[0]};
            }
            else
            {
              var focusedRowIdx = this._getFocusedRowIdx();
              if (focusedRowIdx >= 0)
              {
                var tableBodyRow = this._getTableDomUtils().getTableBodyRow(focusedRowIdx);
                openOptions['position'] = {"my": "start top", "at": "start bottom", "of": tableBodyRow[0]};
              }
              else
              {
                openOptions['position'] = {"my": "start top", "at": "start bottom", "of": this._contextMenuEvent['target']};
              }
            }
          }
          else
          {
            openOptions['position'] = {"my": "start top", "at": "start bottom", "of": this._contextMenuEvent['target']};
          }
        }

        if (headerColumn.attr('data-oj-sortable') == this._OPTION_ENABLED)
        {
          $(contextMenu['element']).find('[data-oj-command=oj-table-sortAsc]').removeClass('oj-disabled');
          $(contextMenu['element']).find('[data-oj-command=oj-table-sortDsc]').removeClass('oj-disabled');
        }
        else
        {
          $(contextMenu['element']).find('[data-oj-command=oj-table-sortAsc]').addClass('oj-disabled');
          $(contextMenu['element']).find('[data-oj-command=oj-table-sortDsc]').addClass('oj-disabled');
        }
        this._OpenContextMenu(event, eventType, openOptions);
      },
      /**
       * @override
       * @private
       */
      _destroy: function()
      {
        var data = this._getData();
        // unregister the listeners on the datasource
        this._unregisterDataSourceEventListeners();
        
        this._getTableDomUtils().getTableBody().removeAttr(oj.Components._OJ_CONTAINER_ATTR);

        this.element.children().remove('.' + oj.TableDomUtils.CSS_CLASSES._TABLE_HEADER_CLASS);
        this.element.children().remove('.' + oj.TableDomUtils.CSS_CLASSES._TABLE_BODY_CLASS);
        this.element.children().remove('.' + oj.TableDomUtils.CSS_CLASSES._TABLE_FOOTER_CLASS);
        this.element.children().remove('.' + oj.TableDomUtils.CSS_CLASSES._TABLE_STATUS_MESSAGE_CLASS);
        this.element.children().remove('.' + oj.TableDomUtils.CSS_CLASSES._TABLE_NO_DATA_MESSAGE_CLASS);

        // Bug #19639907 - DomUtils.unwrap() will avoid unwrapping if the node is being destroyed by Knockout
        oj.DomUtils.unwrap(this.element, this._getTableDomUtils().getTableContainer());
        
        this.element.removeClass(oj.TableDomUtils.CSS_CLASSES._TABLE_CLASS);
      },
      /**
       * @override
       * @private
       */
      _draw: function()
      {
        var options = this.options;

        this._setFinalTask(function()
        {
          this._getTableDomUtils().refreshTableDimensions();
          this._setSelection(this.options['selection']);
          // if loadMoreOnScroll then check if we have underflow and do a
          // fetch if we do
          if (this._isLoadMoreOnScroll() && !this._dataFetching)
          {
            this._domScroller.checkViewport().then(this._domScrollerMaxCountFunc, null);
          }
        });
        
        if (!this.element.is('table'))
        {
          var errSummary = this._LOGGER_MSG._ERR_ELEMENT_INVALID_TYPE_SUMMARY;
          var errDetail = this._LOGGER_MSG._ERR_ELEMENT_INVALID_TYPE_DETAIL;
          throw new RangeError(errSummary + '\n' + errDetail);
        }
        
        // add css class to element
        this.element.addClass(oj.TableDomUtils.CSS_CLASSES._TABLE_ELEMENT_CLASS);

        // create the initial table structure
        this._getTableDomUtils().createInitialTable(this._isTableHeaderless(), 
                                                    this._isTableFooterless());
        // style the initial table structure
        this._getTableDomUtils().styleInitialTable();
        
        // populate the table header DOM with header content
        this._refreshTableHeader();

        // populate the table footer DOM with footer content
        this._refreshTableFooter();
        
        this._refreshTableBody();
        
        // resize the table dimensions to accomodate the completed tableheader
        this._getTableDomUtils().refreshTableDimensions();
        
        // initialize a DomScroller if loadMoreOnScroll
        if (this._isLoadMoreOnScroll())
        {
          this._registerDomScroller();
        }

        if (this.options.disabled)
        {
          this.disable();
        }
        
        this._registerResizeListener(this._getTableDomUtils().getTableContainer());
      },
      /**
       * @override
       * @private
       */
      _events:
        {
          /**
           * Reset the keyboard state on blur and set the inactive
           * selected rows
           */
          'blur': function(event)
          {
            // make sure the blur isn't for a focus to an element within
            // the table
            var table = this._getTableDomUtils().getTable();
            if (table.has(event.relatedTarget).length > 0)
            {
              return;
            }
            // In FF we check explicitOriginalTarget
            else if (event.originalEvent != null && event.originalEvent.explicitOriginalTarget == table[0])
            {
              return;
            }

            this._clearKeyboardKeys();
            this._clearFocusedHeaderColumn();
            this._clearFocusedRow(false);
            this._setTableNavigationMode(false);
          },
          /**
           * Check the keyboard state on focus
           */
          'focus': function(event)
          {
            this._checkRowOrHeaderColumnFocus(event);
          },
          /**
           * Check the keyboard state on focus
           */
          'focus .oj-table-column-header-acc-asc-link': function(event)
          {
            this._checkRowOrHeaderColumnFocus(event);
          },
          /**
           * Capture acc selected column event
           */
          'click .oj-table-checkbox-acc-select-column': function(event)
          {
            var columnIdx = this._getTableDomUtils().getElementColumnIdx($(event.currentTarget));
            var selected = $(event.currentTarget).is(':checked');
            // if selected then focus on the column
            if (selected)
            {
              this._setHeaderColumnFocus(columnIdx, true, true, null);
            }
            this._setHeaderColumnSelection(columnIdx, selected, event.currentTarget, event, true);
            event.stopPropagation();
          },
          /**
           * Capture acc selected row event
           */
          'click .oj-table-checkbox-acc-select-row': function(event)
          {
            var rowIdx = this._getTableDomUtils().getElementRowIdx($(event.currentTarget));
            var selected = $(event.currentTarget).is(':checked');
            
            var focused = false;
            
            // if selected then focus on the row
            if (selected)
            {
              focused = this._setRowFocus(rowIdx, true, true, null, event, true);
            }
            
            if (focused)
            {
              this._setRowSelection(rowIdx, selected, event.currentTarget, event, true);
            }
            event.stopPropagation();
          },
          /**
           * Capture keyboard down events
           */
          'keydown': function(event)
          {
            // ignore key event on the footer or target is editable
            if (this._isNodeEditableOrClickable($(event.target)) || 
                this._getTableDomUtils().getTableFooter() != null && 
                this._getTableDomUtils().getTableFooter().has(event.target).length > 0)
            {
              return;
            }
            this._addKeyboardKey(event.keyCode);
            // process single or two key events
            if (this._getKeyboardKeys().length == 1 ||
                (this._getKeyboardKeys().length == 2 && event[this._KEYBOARD_CODES._KEYBOARD_MODIFIER_SHIFT]))
            {
              if (this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_UP) ||
                this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_DOWN) ||
                this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_SPACEBAR) ||
                this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_HOME) ||
                this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_END))
              {
                // need to do this so that these keys don't act on the page. e.g. pressing Down would cause the
                // page to go down as well as the row to change
                event.preventDefault();
                event.stopPropagation();
              }

              if (this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_UP) ||
                this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_DOWN))
              {
                this._handleKeydownUpDown(event);
              }
              else if (this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_LEFT) ||
                this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_RIGHT))
              {
                this._handleKeydownLeftRight(event);
              }
              else if (this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_TAB))
              {
                this._handleKeydownTab(event);
              }
            }
          },
          /**
           * Capture keyboard up events
           */
          'keyup': function(event)
          {
            // ignore key event on the footer or target is editable
            if (this._isNodeEditableOrClickable($(event.target)) || 
                this._getTableDomUtils().getTableFooter() != null && 
                this._getTableDomUtils().getTableFooter().has(event.target).length > 0)
            {
              return;
            }
            // process single or 2 key events
            if (this._getKeyboardKeys().length == 1)
            {
              var keyboardCode1 = this._getKeyboardKeys()[0];

              if (keyboardCode1 == this._KEYBOARD_CODES._KEYBOARD_CODE_SPACEBAR)
              {
                this._handleKeyupSpacebar(event);
              }
              else if (keyboardCode1 == this._KEYBOARD_CODES._KEYBOARD_CODE_ENTER)
              {
                this._handleKeyupEnter(event);
              }
              else if (keyboardCode1 == this._KEYBOARD_CODES._KEYBOARD_CODE_HOME)
              {
                this._handleKeyupHome(event);
              }
              else if (keyboardCode1 == this._KEYBOARD_CODES._KEYBOARD_CODE_END)
              {
                this._handleKeyupEnd(event);
              }
              else if (keyboardCode1 == this._KEYBOARD_CODES._KEYBOARD_CODE_ESC)
              {
                this._handleKeyupEsc(event);
              }
              this._removeKeyboardKey(keyboardCode1);
            }
            // remove the keycode from our internal list of pressed keys.
            this._removeKeyboardKey(event.keyCode);
          },
          /**
           * show the row hover when the mouse enters a table row
           */
          'mouseenter .oj-table-body-row': function(event)
          {
            $(event.currentTarget).addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._HOVER);
          },
          /**
           * hide the row hover when the mouse leaves a table row
           */
          'mouseleave .oj-table-body-row': function(event)
          {
            $(event.currentTarget).removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._HOVER);
          },
          /**
           * show the ascending/descending links when the mouse 
           * enters a column header
           */
          'mouseenter .oj-table-column-header-cell': function(event)
          {
            $(event.currentTarget).addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._HOVER);
            // get the column index of the header element
            var columnIdx = this._getTableDomUtils().getElementColumnIdx($(event.currentTarget));
            // show the asc/dsc links for the header
            this._showTableHeaderColumnSortLink(columnIdx);
          },
          /**
           * hide the ascending/descending links when the mouse 
           * leaves a column header
           */
          'mouseleave .oj-table-column-header-cell': function(event)
          {
            $(event.currentTarget).removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._HOVER);
            // get the column index of the header element
            var columnIdx = this._getTableDomUtils().getElementColumnIdx($(event.currentTarget));
            // hide the asc/dsc links for the header
            this._hideTableHeaderColumnSortLink(columnIdx, true);
            this._hideTableHeaderColumnSortLink(columnIdx, false);
          },
          /**
           * show the cell hover when the mouse enters a table cell
           */
          'mouseenter .oj-table-data-cell': function(event)
          {
            $(event.currentTarget).addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._HOVER);
          },
          /**
           * hide the cell hover when the mouse leaves a table cell
           */
          'mouseleave .oj-table-data-cell': function(event)
          {
            $(event.currentTarget).removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._HOVER);
          },
          /**
           * invoke a sort on the column data when the mouse clicks the ascending link
           */
          'click .oj-table-column-header-asc-link': function(event)
          {
            var columnIdx = this._getTableDomUtils().getElementColumnIdx($(event.target));
            var tableHeaderColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);

            if (!tableHeaderColumn)
            {
              return;
            }

            // check if the column is currently sorted
            var sorted = tableHeaderColumn.data('sorted');
            if (sorted == this._COLUMN_SORT_ORDER._ASCENDING)
            {
              this._handleSortTableHeaderColumn(columnIdx, false, event);
            }
            else
            {
              this._handleSortTableHeaderColumn(columnIdx, true, event);
            }
            event.preventDefault();
            event.stopPropagation();
          },
          'click .oj-table-column-header-acc-asc-link': function(event)
          {
            var columnIdx = this._getTableDomUtils().getElementColumnIdx($(event.target));
            this._handleSortTableHeaderColumn(columnIdx, true, event);
            event.preventDefault();
            event.stopPropagation();
          },
          /**
           * invoke a sort on the column data when the mouse clicks the descending link
           */
          'click .oj-table-column-header-dsc-link': function(event)
          {
            var columnIdx = this._getTableDomUtils().getElementColumnIdx($(event.target));
            var tableHeaderColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);

            if (!tableHeaderColumn)
            {
              return;
            }

            // check if the column is currently sorted
            var sorted = tableHeaderColumn.data('sorted');
            if (sorted == this._COLUMN_SORT_ORDER._DESCENDING)
            {
              this._handleSortTableHeaderColumn(columnIdx, true, event);
            }
            else
            {
              this._handleSortTableHeaderColumn(columnIdx, false, event);
            }
            event.preventDefault();
            event.stopPropagation();
          },
          'click .oj-table-column-header-acc-dsc-link': function(event)
          {
            var columnIdx = this._getTableDomUtils().getElementColumnIdx($(event.target));
            this._handleSortTableHeaderColumn(columnIdx, false, event);
            event.preventDefault();
            event.stopPropagation();
          },
          /**
           * set the row focus or selection when the mouse clicks on a cell.
           * Ctrl + click results in selection and focus. Plain click results in focus.
           * Plain click on a selected row removes the selection.
           */
          'click .oj-table-data-cell': function(event)
          {
            // get the row index of the cell element
            var rowIdx = this._getTableDomUtils().getElementRowIdx($(event.currentTarget));
            var focused = false;
            // set the row focus
            focused = this._setRowFocus(rowIdx, true, true, event.currentTarget, event);
            
            if (!focused)
            {
              return;
            }
            
            // check if we are selecting
            if (event[this._KEYBOARD_CODES._KEYBOARD_MODIFIER_SHIFT])
            {
              var lastSelectedRowIdx = this._getLastRowSelection();
              if (lastSelectedRowIdx != null)
              {
                // remove the selection highlight
                window.getSelection().removeAllRanges()

                // shift selection is always from the last selected row
                if (rowIdx < lastSelectedRowIdx)
                {
                  var i;
                  for (i = rowIdx; i <= lastSelectedRowIdx; i++)
                  {
                    this._setRowSelection(i, true, event.currentTarget, event, true);
                  }
                }
                else
                {
                  var i;
                  for (i = lastSelectedRowIdx; i <= rowIdx; i++)
                    this._setRowSelection(i, true, event.currentTarget, event, true);
                }
              }
            }
            else if (oj.DomUtils.isMetaKeyPressed(event))
            {
              this._setRowSelection(rowIdx, !this._getRowSelection(rowIdx), event.currentTarget, event, true);
            }
            else if (this._getKeyboardKeys().length == 0)
            {
              this._clearSelectedRows();
              this._setRowSelection(rowIdx, !this._getRowSelection(rowIdx), event.currentTarget, event, true);
            }
          },
          /**
           * set current row when the mouse right clicks on a cell.
           */
          'contextmenu .oj-table-data-cell': function(event)
          {
            // get the row index of the cell element
            var rowIdx = this._getTableDomUtils().getElementRowIdx($(event.currentTarget));
            var rowKey = this._getRowKeyForRowIdx(rowIdx);
            this._setCurrentRow({'rowKey': rowKey}, event);
          },
          /**
           * set the column header selection and focus. Plain click results in
           * focus and selection. If Ctrl is not pressed then we have single column selection.
           */
          'click .oj-table-column-header-cell': function(event)
          {
            // get the column index
            var columnIdx = this._getTableDomUtils().getElementColumnIdx($(event.currentTarget));
            // set the column focus
            this._setHeaderColumnFocus(columnIdx, true, true, event);
            // check if we are selecting
            if (event[this._KEYBOARD_CODES._KEYBOARD_MODIFIER_SHIFT])
            {
              var lastSelectedColumnIdx = this._getLastHeaderColumnSelection();
              if (lastSelectedColumnIdx != null)
              {
                // shift selection is always from the last selected column
                if (columnIdx < lastSelectedColumnIdx)
                {
                  var i;
                  for (i = columnIdx; i <= lastSelectedColumnIdx; i++)
                  {
                    this._setHeaderColumnSelection(i, true, event.currentTarget, event, true);
                  }
                }
                else
                {
                  var i;
                  for (i = lastSelectedColumnIdx; i <= columnIdx; i++)
                    this._setHeaderColumnSelection(i, true, event.currentTarget, event, true);
                }
              }
            }
            else if (oj.DomUtils.isMetaKeyPressed(event))
            {
              this._setHeaderColumnSelection(columnIdx, !this._getHeaderColumnSelection(columnIdx), event.currentTarget, event, true);
            }
            else if (this._getKeyboardKeys().length == 0)
            {
              this._clearSelectedHeaderColumns();
              this._setHeaderColumnSelection(columnIdx, !this._getHeaderColumnSelection(columnIdx), event.currentTarget, event, true);
            }
          }
        },
      /**
       * @private
       */
      _refresh: function()
      {
        var startIndex = null;
        var initFetch = false;
        
        if (this._data != this.options['data'])
        {
          this._clearCachedDataMetadata();
          
          if (this._data == null)
          {
            // need to do an initial fetch
            initFetch = true
          }
          else
          {
            startIndex = 0;
          }
          
          if (this._isLoadMoreOnScroll())
          {
            if (this._domScroller != null)
            {
              this._domScroller.destroy();
            }
            this._registerDomScroller();
          }
        }
        if (this._contextMenuId != this._getTableDomUtils().getContextMenuId())
        {
          this._getTableDomUtils().createContextMenu(this._handleContextMenuSelect.bind(this));
        }
        this._getTableDomUtils().clearCachedDom();
        this._getTableDomUtils().refreshContextMenu();
        this._refreshTableStatusMessage();
        
        if (initFetch)
        {
          return this._initFetch();
        }
        else
        {
          var self = this;
          this._queueTask(function()
          {
            return self._invokeDataFetchRows(startIndex);
          });
        }
      },
      /**
       * @override
       * @private
       */
      _setOption: function(key, value)
      {
        this._superApply(arguments);
        var shouldRefresh = this._isTableRefreshNeeded(key, value);
        
        
        
        if (shouldRefresh)
        {
          this._refresh();
        }
        else
        {
          if (key == 'selection')
          {
            this._clearSelectedRows();
            this._clearSelectedHeaderColumns();
            this._setSelection(value);
          }
          else if (key == 'currentRow')
          {
            this._setCurrentRow(value, null, true);
          }
        }
      },
      /**** end internal widget functions ****/

      /**** start internal functions ****/

      /**
       * Add a keyCode to internally track pressed keys. keyCodes should be added on 
       * mouse down and then later removed on mouse up.
       * @param {number} keyCode  KeyCode of the keyboard key.
       * @private
       */
      _addKeyboardKey: function(keyCode)
      {
        var foundCode = false;
        for (var prop in this._KEYBOARD_CODES)
        {
          if (this._KEYBOARD_CODES.hasOwnProperty(prop))
          {
            if (this._KEYBOARD_CODES[prop] == keyCode)
            {
              foundCode = true;
            }
          }
        }

        if (!foundCode)
        {
          // only add keys we are interested in
          return;
        }

        var keyboardKeys = this._getKeyboardKeys();
        var found = false;
        var i;
        for (i = 0; i < keyboardKeys.length; i++)
        {
          if (keyboardKeys[i] == keyCode)
          {
            found = true;
            break;
          }
        }
        if (!found)
        {
          keyboardKeys.push(keyCode);
        }
      },
      /**
       * Add a new tr and refresh the DOM at the row index and refresh the table 
       * dimensions to accomodate the new row
       * @param {number} rowIdx  row index relative to the start of the table
       * @param {Object} row row
       * @param {Object} docFrag  document fragment
       * @param {number} docFragStartIdx  document fragment row start index
       * 
       * @private
       */
      _addSingleTableBodyRow: function(rowIdx, row, docFrag, docFragStartIdx)
      {
        var tableBodyRow = this._getTableDomUtils().createTableBodyRow(rowIdx, row['key']);
        this._getTableDomUtils().styleTableBodyRow(tableBodyRow, true);
        // insert the <tr> element in to the table body DOM
        this._getTableDomUtils().insertTableBodyRow(rowIdx, tableBodyRow, row, docFrag);
        this._refreshTableBodyRow(rowIdx, row, tableBodyRow, docFrag, docFragStartIdx, true);
      },
      /**
       * Check and set the row or header column focus
       * @private
       */
      _checkRowOrHeaderColumnFocus: function(event)
      {
        var focusedRowIdx = this._getFocusedRowIdx();
        var focusedHeaderColumnIdx = this._getFocusedHeaderColumnIdx();

        if (focusedRowIdx == null && focusedHeaderColumnIdx == null)
        {
          // if no row or column is focused then set the focus on the first column or row
          if (this._isTableHeaderless())
          {
            this._setRowFocus(0, true, true, null, event);
          }
          else
          {
            this._setHeaderColumnFocus(0, true, false, event);  
          }
        }  
      },
      /**
       * Clear any cached metadata
       * @private
       */
      _clearCachedMetadata: function()
      {
        this._columnDefArray = null;
        this._setTableNavigationMode(false);
      },
      /**
       * Clear any cached data metadata
       * @private
       */
      _clearCachedDataMetadata: function()
      {
        if (this._data != null)
        {
          this._unregisterDataSourceEventListeners();
        }
        this._data = null;
      },
      /**
       * Clear waiting state and hide the Fetching Data... status message.
       * @private
       */
      _clearDataWaitingState: function()
      {
        this._hideInlineMessage();
        this._hideStatusMessage();
        this._dataFetching = false;
      },
      /**
       * Clear any keyboard keys
       * @private
       */
      _clearKeyboardKeys: function()
      {
        this._keyboardKeys = [];
      },
      /**
       * Clear the focused column header
       * @private
       */
      _clearFocusedHeaderColumn: function()
      {
        var focusedHeaderColumnIdx = this._getFocusedHeaderColumnIdx();
        if (focusedHeaderColumnIdx != null)
        {
          this._setHeaderColumnFocus(focusedHeaderColumnIdx, false, false, null);
        }
        this._activeColumnIndex = -1;
      },
      /**
       * Clear the focused row
       * @param {boolean} updateCurrentRow  whether to update the currentRow
       * @private
       */
      _clearFocusedRow: function(updateCurrentRow)
      {
        var focusedRowIdx = this._getFocusedRowIdx();
        
        if (focusedRowIdx != null)
        {
          this._setRowFocus(-1, true, updateCurrentRow, null, null);
        }
      },
      /**
       * Clear the selected column headers
       * @private
       */
      _clearSelectedHeaderColumns: function()
      {
        var selectedHeaderColumnIdxs = this._getSelectedHeaderColumnIdxs();

        var i;
        for (i = 0; i < selectedHeaderColumnIdxs.length; i++)
        {
          this._setHeaderColumnSelection(selectedHeaderColumnIdxs[i], false, null, null, false);
        }
      },
      /**
       * Clear the selected rows
       * @private
       */
      _clearSelectedRows: function()
      {
        var selectedRowIdxs = this._getSelectedRowIdxs();

        var i;
        for (i = 0; i < selectedRowIdxs.length; i++)
          this._setRowSelection(selectedRowIdxs[i], false, null, null, false);
      },
      /**
       * Clear the sorted column header indicator. Note this does not affect the order
       * of the data. This is just to clear the UI indication.
       * @param {number} columnIdx  column index
       * @private
       */
      _clearSortedHeaderColumn: function(columnIdx)
      {
        var sortedTableHeaderColumnIdx = this._getSortedTableHeaderColumnIdx();
        if (sortedTableHeaderColumnIdx != null)
        {
          var sortedTableHeaderColumn = this._getTableDomUtils().getTableHeaderColumn(sortedTableHeaderColumnIdx);
          var sorted = sortedTableHeaderColumn.data('sorted');
          sortedTableHeaderColumn.data('sorted', null);
          
          if (sortedTableHeaderColumnIdx != columnIdx)
          {
            if (sorted == this._COLUMN_SORT_ORDER._ASCENDING)
            {
              this._hideTableHeaderColumnSortLink(sortedTableHeaderColumnIdx, true);
            }
            else
            {
              this._hideTableHeaderColumnSortLink(sortedTableHeaderColumnIdx, false);
            }
          }
          else
          {
            var sortedTableHeaderColumnAscLink = sortedTableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_ASC_LINK_CLASS);
            sortedTableHeaderColumnAscLink.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
            var sortedTableHeaderColumnDscLink = sortedTableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_DSC_LINK_CLASS);
            sortedTableHeaderColumnDscLink.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
          }
        }
      },
      /**
       * Add all the rows contained in the input array.
       * @param {Array} rows Array of row contexts to add
       * @private
       */
      _executeTableBodyRowsAdd: function(rows)
      {
        var self = this;
        this._queueTask(function()
        {
          // see if we should batch add
          // only batch if we are adding a block of contiguous rows
          var batchAdd = false;
          if (rows.length > 1)
          {
            var i;
            var isContiguous = true;
            for (i = 0; i < rows.length; i++)
            {
              if (i != 0)
              {
                if (rows[i - 1].rowIdx != rows[i].rowIdx - 1)
                {
                  isContiguous = false;
                  break;
                }
              }
            }
            
            if (isContiguous)
            {
              var tableBody = self._getTableDomUtils().getTableBody();
              var tableBodyDocFrag = $(document.createDocumentFragment());
              for (i = 0; i < rows.length; i++)
              {
                self._addSingleTableBodyRow(rows[i].rowIdx, rows[i].row, tableBodyDocFrag, rows[0].rowIdx);
              }
              if (rows[0].rowIdx == 0)
              {
                tableBody.prepend(tableBodyDocFrag); //@HTMLUpdateOK
              }
              else
              {
                var tableBodyRowBefore = self._getTableDomUtils().getTableBodyRow(rows[0].rowIdx);
                if (tableBodyRowBefore != null)
                {
                  tableBody[0].insertBefore(tableBodyDocFrag[0], tableBodyRowBefore[0]);//@HTMLUpdateOK
                }
                else
                {
                  tableBody[0].insertBefore(tableBodyDocFrag[0], null);//@HTMLUpdateOK
                }
              }
              self._getTableDomUtils().clearCachedDomRowData();
              oj.Components.subtreeAttached(tableBody[0]);
              batchAdd = true;
            }
          }

          if (!batchAdd)
          {
            for (i = 0; i < rows.length; i++)
            {
              self._addSingleTableBodyRow(rows[i].rowIdx, rows[i].row);
            }        
          }
          self._getTableDomUtils().clearCachedDomRowData();
          // row values may have changed so refresh the footer
          self._refreshTableFooter();
        });
      },
      /**
       * Change all the rows contained in the input array.
       * @param {Array} rows Array of row contexts to change
       * @param {number} startIndex index of first row in the table in the data source
       * @private
       */
      _executeTableBodyRowsChange: function(rows, startIndex)
      {
        var self = this;
        this._queueTask(function()
        {
          var i;
          for (i = 0; i < rows.length; i++)
          {
            self._refreshTableBodyRow(rows[i].rowIdx, rows[i].row);
          }
          // row values may have changed so refresh the footer
          self._refreshTableFooter();
        });
      },
      /**
       * Change all the rows contained in the input array.
       * @param {Array} rows Array of row contexts to change
       * @private
       */
      _executeTableBodyRowsRemove: function(rows)
      {
        var self = this;
        var currentRow = this._getCurrentRow();
        var currentRowKey = currentRow != null ? currentRow['rowKey'] : null;
        this._queueTask(function()
        {
          var i, rowKey; 
          for (i = 0; i < rows.length; i++)
          {
            self._getTableDomUtils().removeTableBodyRow(rows[i].rowIdx);
          
            // reset the currentRow if needed
            if (currentRowKey !=null)
            {
             rowKey = rows[i].row['id'];
            
              if (oj.Object.compareValues(rowKey, currentRowKey))
              {
                self._setCurrentRow(null, null);
                currentRowKey = null;
              }
            }
          }
          // row values may have changed so refresh the footer
          self._refreshTableFooter();
          var tableBodyRows = self._getTableDomUtils().getTableBodyRows();
          if (tableBodyRows == null || tableBodyRows.length == 0)
          {
            this._showNoDataMessage();
          }
        });
      },
      /**
       * Return the column definitions
       * @return {Array} array of column metadata Objects.
       * @private
       */
      _getColumnDefs: function()
      {
        // cache the columns array in this._columnDefArray
        if (!this._columnDefArray)
        {
          this._columnDefArray = this._getColumnMetadata();
        }
        return this._columnDefArray;
      },
      /**
       * Return the column metadata in sorted oder.
       * @return {Array} array of column metadata Objects.
       * @private
       */
      _getColumnMetadata: function()
      {
        // get the columns metadata
        var columns = this.options['columns'];
        var columnsDefault = this.options['columnsDefault'];

        if ((columns.length == 0 ||
            (columns.length == 1 &&
            columns[0].id == null &&
            columns[0].headerText == null &&
            columns[0].field == null)) &&
            (columnsDefault.headerText == null &&
            columnsDefault.field == null))
        {
          return [];
        }
        
        var defaultedColumns = [];
        var i;
        for (i = 0; i < columns.length; i++)
        {
          defaultedColumns[i] = $.extend({}, columnsDefault, columns[i]);
        }

        var columnsSortedArray = [];
        // add the rest of the columns in the array
        for (i = 0; i < defaultedColumns.length; i++)
        {
          columnsSortedArray.push(defaultedColumns[i]);
        }
        
        var data = this._getData();
        var sortSupportedData = false;
        if (data != null && data.getCapability('sort') == 'full')
        {
          sortSupportedData = true;
        }

        for (i = 0; i < defaultedColumns.length; i++)
        {
          // generate ids for columns which don't have it specified
          if (columnsSortedArray[i]['id'] == null)
          {
            columnsSortedArray[i]['id'] = this._COLUMN_HEADER_ID_PREFIX + i;
          }
          // for the columns which have sortable = 'auto' check the datasource
          // and enable or disable
          if ((columnsSortedArray[i]['sortable'] == null || 
               columnsSortedArray[i]['sortable'] == this._OPTION_AUTO)
               && sortSupportedData)
          {
            columnsSortedArray[i]['sortable'] = this._OPTION_ENABLED;
          }
        }
        return columnsSortedArray;
      },
      /**
       * Return the column index for column key.
       * @param {Object} columnKey column key
       * @return {number|null} column index
       * @private
       */
      _getColumnIdxForColumnKey: function(columnKey)
      {
        var columns = this._getColumnDefs();
      
        if (columns != null)
        {
          var i, column;
          for (i = 0; i < columns.length; i++)
          {
            column = columns[i];
            if (oj.Object.compareValues(column.id, columnKey))
            {
              return i;
            }
          }
        }
        return null;
      },
      /**
       * Return all the column indexes for elements with a particular style class
       * @param {string} styleClass  style class
       * @return {Array} Array of column indexes
       * @private
       */
      _getColumnIdxsForElementsWithStyleClass: function(styleClass)
      {
        var elements = this._getTableDomUtils().getTable().find(styleClass);
        var columnIdxs = [];
        if (elements && elements.length > 0)
        {
          var i, j, alreadyAdded, columnIdx;
          for (i = 0; i < elements.length; i++)
          {
            columnIdx = this._getTableDomUtils().getElementColumnIdx($(elements.get(i)));

            alreadyAdded = false;
            for (j = 0; j < columnIdxs.length; j++)
            {
              if (columnIdxs[j] == columnIdx)
              {
                alreadyAdded = true;
              }
            }
            if (!alreadyAdded)
            {
              columnIdxs.push(columnIdx);
            }
          }
        }

        return columnIdxs;
      },
      /**
       * Return the column key for column index.
       * @param {number} columnIdx column index
       * @return {Object} column key
       * @private
       */
      _getColumnKeyForColumnIdx: function(columnIdx)
      {
        var columns = this._getColumnDefs();
      
        if (columns != null && columnIdx < columns.length)
        {
          return columns[columnIdx]['id'];
        }
        return null;
      },
      /**
       * Return the column renderer
       * @param {number} columnIdx  column index
       * @param {String} type  renderer type
       * @return {Object} renderer
       * @private
       */
      _getColumnRenderer: function(columnIdx, type)
      {
        var columns = this._getColumnDefs();
        var column = columns[columnIdx];
        
        if (type == 'cell')
        {
          return column['renderer'];
        }
        else if (type == 'footer')
        {
          return column['footerRenderer'];
        }
        else if (type == 'header')
        {
          return column['headerRenderer'];
        }

        return null;
      },
      /**
       * Get the current row.
       * @return {Object|null} current row object or null if none.
       * @throws {Error}
       * @private
       */
      _getCurrentRow: function()
      {
        var data = this._getData();
        // if no data then bail
        if (!data)
        {
          return null;
        }
        return this._currentRow;
      },
      /**
       * Return the datasource object defined for this table
       * @return {Object} Datasource object.
       * @throws {Error}
       * @private
       */
      _getData: function()
      {
        if (!this._data && this.options.data != null)
        {
          var data = this.options.data;
          if (data instanceof oj.TableDataSource ||
              data instanceof oj.PagingTableDataSource)
          {
            this._data = data;
          }
          else
          {
            // we only support TableDataSource
            var errSummary = this._LOGGER_MSG._ERR_DATA_INVALID_TYPE_SUMMARY;
            var errDetail = this._LOGGER_MSG._ERR_DATA_INVALID_TYPE_DETAIL;
            throw new Error(errSummary + '\n' + errDetail);
          }
          this._dataMetadata = this.options.data;
          this._registerDataSourceEventListeners();
        }
        return this._data;
      },
      /**
       * Get the focused column header index
       * @return {number|null} the column index
       * @private
       */
      _getFocusedHeaderColumnIdx: function()
      {
        // focused column headers have the focused style class. There should only be one focused header
        return this._getColumnIdxsForElementsWithStyleClass('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_CELL_CLASS + '.' + oj.TableDomUtils.MARKER_STYLE_CLASSES._FOCUS)[0];
      },
      /**
       * Get the focused row index
       * @return {number|null} the row index
       * @private
       */
      _getFocusedRowIdx: function()
      {
        // focused rows have cells with focused style class. There should only be one focused row
        return this._getRowIdxsForElementsWithStyleClass('.' + oj.TableDomUtils.CSS_CLASSES._TABLE_DATA_ROW_CLASS + '.' + oj.TableDomUtils.MARKER_STYLE_CLASSES._FOCUS)[0];
      },
      /**
       * Return whether the column header at the index is focused
       * @param {number} columnIdx  column index
       * @return {boolean} whether it's focused
       * @private
       */
      _getHeaderColumnFocus: function(columnIdx)
      {
        return this._getHeaderColumnState(columnIdx).focused;
      },
      /**
       * Return whether the column header at the index is selected
       * @param {number} columnIdx  column index
       * @return {boolean} whether it's selected
       * @private
       */
      _getHeaderColumnSelection: function(columnIdx)
      {
        return this._getHeaderColumnState(columnIdx).selected;
      },
      /**
       * Return the column selection mode
       * @return {string|null} single, multiple, or null
       * @private
       */
      _getColumnSelectionMode: function()
      {
        var columnSelectionMode = this.options.selectionMode == null ? null : this.options.selectionMode['column'];
        return columnSelectionMode;
      },
      /**
       * Return the state of the column header at a partiocular index
       * @param {number} columnIdx  column index
       * @return {Object} Object which contains booleans focused and selected
       * @private
       */
      _getHeaderColumnState: function(columnIdx)
      {
        var headerColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);

        return {focused: headerColumn.hasClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._FOCUS),
          selected: headerColumn.hasClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED)};
      },
      /**
       * Return the currently pressed keyboard keys
       * @return {Array} Array of keyCodes
       * @private
       */
      _getKeyboardKeys: function()
      {
        if (!this._keyboardKeys)
        {
          this._keyboardKeys = [];
        }

        // reverse the array since we want the keybaord keys to be a LIFO stack
        return this._keyboardKeys.reverse();
      },
      /**
       * Return the last column which was selected (chronologically)
       * @return {number|null} last selected column
       * @private
       */
      _getLastHeaderColumnSelection: function()
      {
        if (this._lastSelectedColumnIdxArray != null &&
          this._lastSelectedColumnIdxArray.length > 0)
        {
          return this._lastSelectedColumnIdxArray[0];
        }
        return null;
      },
      /**
       * Return the last row which was selected (chronologically)
       * @return {number|null} last selected row
       * @private
       */
      _getLastRowSelection: function()
      {
        if (this._lastSelectedRowIdxArray != null &&
          this._lastSelectedRowIdxArray.length > 0)
        {
          return this._lastSelectedRowIdxArray[0];
        }
        return null;
      },
      /**
       * Return an array with row and row indices relative to the table row indices
       * @param {Object} resultObject Object containing data array, key array, and index array
       * @param {number} startIndex start index
       * @return {Array} Array of rows and row index
       * @private
       */
      _getRowIdxRowArray: function(resultObject, startIndex)
      {
        var rowIdxRowArray = [];
        if (resultObject != null)
        {
          var i;
          for (i = 0; i < resultObject['indexes'].length; i++)
          {
            rowIdxRowArray.push({row: {'data': resultObject['data'][i], 'key': resultObject['keys'][i], 'index': resultObject['indexes'][i]}, rowIdx: startIndex + i});
          }
        }
        
        return rowIdxRowArray;
      },
      /**
       * Return the row index for row key. Only loop through displayed rows.
       * @param {Object} rowKey row key
       * @return {number|null} row index
       * @private
       */
      _getRowIdxForRowKey: function(rowKey)
      {
        var tableBodyRows = this._getTableDomUtils().getTableBodyRows();

        if (tableBodyRows != null && tableBodyRows.length > 0)
        {
          var i;
          for (i = 0; i < tableBodyRows.length; i++)
          {
            if (oj.Object.compareValues($(tableBodyRows[i]).data('rowKey'), rowKey))
            {
              return i;
            }
          }
        }
        
        return null;
      },
      /**
       * Return the datasource's row index for the row key.
       * @param {Object} rowKey row key
       * @return {number|null} row index
       * @private
       */
      _getDataSourceRowIndexForRowKey: function(rowKey)
      {
        var tableBodyRows = this._getTableDomUtils().getTableBodyRows();

        if (tableBodyRows != null && tableBodyRows.length > 0)
        {
          var i;
          for (i = 0; i < tableBodyRows.length; i++)
          {
            if (oj.Object.compareValues($(tableBodyRows[i]).data('rowKey'), rowKey))
            {
              var data = this._getData();
              var startIndex = 0;
              if (data instanceof oj.PagingTableDataSource)
              {
                startIndex = data.getStartItemIndex();
              }
              return i + startIndex;
            }
          }
        }
        
        return null;
      },
      /**
       * Return all the row indexes for elements with a particular style class
       * @param {string} styleClass  style class
       * @return {Array} Array of row indexes
       * @private
       */
      _getRowIdxsForElementsWithStyleClass: function(styleClass)
      {
        var elements = this._getTableDomUtils().getTable().find(styleClass);
        var rowIdxs = [];
        if (elements && elements.length > 0)
        {
          var i, j, rowIdx, alreadyAdded;
          for (i = 0; i < elements.length; i++)
          {
            rowIdx = this._getTableDomUtils().getElementRowIdx($(elements.get(i)));

            alreadyAdded = false;
            for (j = 0; j < rowIdxs.length; j++)
            {
              if (rowIdxs[j] == rowIdx)
              {
                alreadyAdded = true;
              }
            }
            if (!alreadyAdded)
            {
              rowIdxs.push(rowIdx);
            }
          }
        }

        return rowIdxs;
      },
      /**
       * Return the row key for datasource's row index.
       * @param {number} rowIndex row index
       * @return {*} row key
       * @private
       */
      _getRowKeyForDataSourceRowIndex: function(rowIndex)
      {
        var tableBodyRows = this._getTableDomUtils().getTableBodyRows();

        if (tableBodyRows != null && tableBodyRows.length > 0)
        {
          var data = this._getData();
          var startIndex = 0;
          if (data instanceof oj.PagingTableDataSource)
          {
            startIndex = data.getStartItemIndex();
          }
          var i;
          for (i = 0; i < tableBodyRows.length; i++)
          {
            if (startIndex + i == rowIndex)
            {
              return $(tableBodyRows[i]).data('rowKey');
            }
          }
        }
        
        return null;
      },
      /**
       * Return the row key for row index.
       * @param {number} rowIdx row index
       * @return {Object|null} row key
       * @private
       */
      _getRowKeyForRowIdx: function(rowIdx)
      {
        var tableBodyRow = this._getTableDomUtils().getTableBodyRow(rowIdx);
        
        if (tableBodyRow != null)
        {
          return tableBodyRow.data('rowKey');
        }
        
        return null;
      },
      /**
       * Return the row renderer
       * @return {Object} renderer
       * @private
       */
      _getRowRenderer: function()
      {
        return this.options['rowRenderer'];
      },
      /**
       * Return whether the row is selected
       * @param {number} rowIdx  row index
       * @return {boolean} whether the row is selected
       * @private
       */
      _getRowSelection: function(rowIdx)
      {
        return this._getTableDomUtils().getTableBodyRow(rowIdx).hasClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
      },
      /**
       * Return the row selection mode
       * @return {string|null} single, multiple, or null
       * @private
       */
      _getRowSelectionMode: function()
      {
        var rowSelectionMode = this.options['selectionMode'] == null ? null : this.options['selectionMode']['row'];
        return rowSelectionMode;
      },
      /**
       * Return the selected column header indexes
       * @return {Array} array of column header indexes
       * @private
       */
      _getSelectedHeaderColumnIdxs: function()
      {
        // selected column headers have the selected css class
        return this._getColumnIdxsForElementsWithStyleClass('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_CELL_CLASS + '.' + oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
      },
      /**
       * Return the selected row indexes
       * @return {Array} array of row indexes
       * @private
       */
      _getSelectedRowIdxs: function()
      {
        // selected rows have the selected css class
        return this._getRowIdxsForElementsWithStyleClass('.' + oj.TableDomUtils.CSS_CLASSES._TABLE_DATA_ROW_CLASS + '.' + oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
      },
      /**
       * Gets the selection
       * @private	 
       */
      _getSelection: function()
      {
        var selectedRowIdxs = this._getSelectedRowIdxs();
        var selectedColumnIdxs = this._getSelectedHeaderColumnIdxs();
        var selectionIdxs = null;
        var rowSelection = true;
        if (selectedRowIdxs != null && selectedRowIdxs.length > 0)
        {
          selectionIdxs = selectedRowIdxs;
        }
        else if (selectedColumnIdxs != null && selectedColumnIdxs.length > 0)
        {
          selectionIdxs = selectedColumnIdxs;
          rowSelection = false;
        }
        else
        {
          return null;
        }

        var rangeArray = [];

        // first count the number of ranges we have by seeing how many
        // non-continguous selections we have
        var rangeCount = 0;
        var previousIdx = null;
        var rangeObj, selectionIndex, selectionIdx, selectionKey;
        var i;
        for (i = 0; i < selectionIdxs.length; i++)
        {
          selectionIdx = selectionIdxs[i];
          if (i == 0)
          {
            rangeObj = {};
            rangeObj['startIndex'] = {};
            rangeObj['endIndex'] = {};
            rangeObj['startKey'] = {};
            rangeObj['endKey'] = {};
            if (rowSelection)
            {
              selectionKey = this._getRowKeyForRowIdx(selectionIdx);
              selectionIndex = this._getDataSourceRowIndexForRowKey(selectionKey);
              rangeObj['startKey']['row'] = selectionKey;
              rangeObj['endKey']['row'] = selectionKey;
              rangeObj['startIndex']['row'] = selectionIndex;
              rangeObj['endIndex']['row'] = selectionIndex;
            }
            else
            {
              rangeObj['startIndex']['column'] = selectionIdx;
              rangeObj['endIndex']['column'] = selectionIdx;
              selectionKey = this._getColumnKeyForColumnIdx(selectionIdx);
              rangeObj['startKey']['column'] = selectionKey;
              rangeObj['endKey']['column'] = selectionKey;
            }
            rangeArray[0] = rangeObj;
          }
          else
          {
            rangeObj = rangeArray[rangeCount];
            if (rowSelection)
            {
              selectionKey = this._getRowKeyForRowIdx(selectionIdx);
              selectionIndex = this._getDataSourceRowIndexForRowKey(selectionKey);
              rangeObj['endKey']['row'] = selectionKey;
              rangeObj['endIndex']['row'] = selectionIndex;
            }
            else
            {
              rangeObj['endIndex']['column'] = selectionIdx;
              selectionKey = this._getColumnKeyForColumnIdx(selectionIdx);
              rangeObj['endKey']['column'] = selectionKey;
            }
            if (selectionIdx != previousIdx + 1)
            {
              if (rowSelection)
              {
                selectionKey = this._getRowKeyForRowIdx(previousIdx);
                selectionIndex = this._getDataSourceRowIndexForRowKey(selectionKey);
                rangeObj['endKey']['row'] = selectionKey;
                rangeObj['endIndex']['row'] = selectionIndex;
                rangeObj = {};
                rangeObj['startIndex'] = {};
                rangeObj['endIndex'] = {};
                rangeObj['startKey'] = {};
                rangeObj['endKey'] = {};
                selectionKey = this._getRowKeyForRowIdx(selectionIdx);
                selectionIndex = this._getDataSourceRowIndexForRowKey(selectionKey);
                rangeObj['startKey']['row'] = selectionKey;
                rangeObj['endKey']['row'] = selectionKey;
                rangeObj['startIndex']['row'] = selectionIndex;
                rangeObj['endIndex']['row'] = selectionIndex;
              }
              else
              {
                rangeObj['endIndex']['column'] = previousIdx;
                selectionKey = this._getColumnKeyForColumnIdx(previousIdx);
                rangeObj['endKey']['column'] = selectionKey;
                rangeObj = {};
                rangeObj['startIndex'] = {};
                rangeObj['endIndex'] = {};
                rangeObj['startKey'] = {};
                rangeObj['endKey'] = {};
                rangeObj['startIndex']['column'] = selectionIdx;
                rangeObj['endIndex']['column'] = selectionIdx;
                selectionKey = this._getColumnKeyForColumnIdx(selectionIdx);
                rangeObj['startKey']['column'] = selectionKey;
                rangeObj['endKey']['column'] = selectionKey;
              }
              rangeCount++;
              rangeArray[rangeCount] = rangeObj;
            }
          }
          previousIdx = selectionIdx;
        }
        return rangeArray;
      },
      /**
       * Return the currnetly sorted column index
       * @return {number|null} column index
       * @private
       */
      _getSortedTableHeaderColumnIdx: function()
      {
        var tableHeaderColumns = this._getTableDomUtils().getTableHeaderColumns();

        var i, sorted;
        for (i = 0; i < tableHeaderColumns.length; i++)
        {
          // sorted column will have the sorted data attr
          sorted = $(tableHeaderColumns[i]).data('sorted');

          if (sorted != null)
          {
            return i;
          }
        }
        return null;
      },
      /**
       * Get tabbable elements within the element
       * @param {jQuery} element  DOM element
       * @return {jQuery|null} jQuery array of DOM elements
       * @private
       */
      _getTabbableElements: function(element)
      {
        var tabbableElements = element.find(':tabbable');

        if (tabbableElements != null && tabbableElements.length > 0)
        {
          return tabbableElements;
        }
        return null;
      },
      /**
       * Return table DOM utils instance
       * @return {Object} instance of table DOM utils
       * @private
       */
      _getTableDomUtils: function()
      {
        if (!this._tableDomUtils)
        {
          this._tableDomUtils = new oj.TableDomUtils(this);
        }
        return this._tableDomUtils;
      },
      /**
       * Handle an ojselect event on a menu item, if sort call the handler on the core.
       * If resize prompt the user with a dialog box
       * @private	 
       */
      _handleContextMenuSelect: function(event, ui)
      {
        var menuItemCommand = ui.item.attr('data-oj-command');
        var headerColumn = this._getTableDomUtils().getFirstAncestor($(this._contextMenuEvent['target']), 'oj-table-column-header-cell');
        var tableBodyCell = this._getTableDomUtils().getFirstAncestor($(this._contextMenuEvent['target']), 'oj-table-data-cell');
        var columnIdx = null;
        
        if (headerColumn != null)
        {
          columnIdx = this._getTableDomUtils().getElementColumnIdx(headerColumn);
        }
        if (tableBodyCell != null)
        {
          columnIdx = this._getTableDomUtils().getElementColumnIdx(tableBodyCell);
        }
        if (menuItemCommand == 'oj-table-sortAsc')
        {
          this._handleSortTableHeaderColumn(columnIdx, true, event);
        }
        else if (menuItemCommand == 'oj-table-sortDsc')
        {
          this._handleSortTableHeaderColumn(columnIdx, false, event);
        }
      },
      /**
       * Callback handler for data error.
       * @param {Object} error 
       * @private
       */
      _handleDataError: function(error)
      {
        this._clearDataWaitingState();
        oj.Logger.error(error);
      },
      /**
       * Callback handler for fetch start in the datasource.
       * @param {Object} event 
       * @private
       */
      _handleDataFetchStart: function(event)
      {
        this._setDataWaitingState();
      },
      /**
       * Callback handler for fetch completed in the datasource. Refresh entire
       * table body DOM and refresh the table dimensions if refresh == true. Hide the Fetching Data... 
       * status message.
       * @param {Object} event
       * @private
       */
      _handleDataFetchEnd: function(event)
      {
        try 
        {
          var self = this;
          this._queueTask(function()
          {
            var offset = 0;
            var data = self._getData();
            
            if (data instanceof oj.PagingTableDataSource)
            {
              // when paging, this contains the page start index. In loadMore
              // mode this is always zero.
              offset = data.getStartItemIndex();
            }
            
            var indexArray = [];
            var i;
            
            for (i = 0; i < event['data'].length; i++)
            {
              // event['startIndex'] contains the offset at which the data should be inserted in the table. In paging mode
              // this is always zero. In loadMore mode it contains an offset.
              // Therefore we have to add both. e.g. in paging mode offset is non-zero while in loadMore event['startIndex'] is non-zero.
              // The indexArray will contain the indexes as contained in the datasource.
              indexArray[i] = i + offset + event['startIndex'];
            }
            
            self._refreshAll({'data': event['data'], 'keys' : event['keys'], 'indexes': indexArray}, event['startIndex']);
          });
        }
        catch (e) 
        {
          oj.Logger.error(e);
        }
        finally 
        {
          this._clearDataWaitingState();
        }
      },
      /**
       * Callback handler for refresh in the datasource. Refresh entire
       * table body DOM and refresh the table dimensions.
       * @param {Object} event 
       * @private
       */
      _handleDataRefresh: function (event)
      {
        try
        {
          var fetchPromise = this._invokeDataFetchRows();
          var self = this;
          this._queueTask(function ()
          {
            self._setCurrentRow(null, null);
            return fetchPromise;
          });
        }
        catch (e) 
        {
          oj.Logger.error(e);
        }
        finally 
        {
          this._clearDataWaitingState();
        }
      },
      /**
       * Callback handler for reset in the datasource. Do an initial fetch
       * @param {Object} event 
       * @private
       */
      _handleDataReset: function (event)
      {
        try
        {
          this._initFetch();
          var self = this;
          this._queueTask(function()
          {
            self._setCurrentRow(null, null);
          });
        }
        catch (e) 
        {
          oj.Logger.error(e);
        }
        finally 
        {
          this._clearDataWaitingState();
        }
      },
      /**
       * Callback handler for rows added into the datasource. Add a new tr and refresh the DOM
       * at the row index and refresh the table dimensions to accomodate the new
       * row
       * @param {Object} event
       * @private
       */
      _handleDataRowAdd: function(event)
      {
        try 
        {
          var data = this._getData();
          var eventData = event['data'];
          var eventIndexes = event['indexes'];
          var eventKeys = event['keys'];
          if (!(eventData instanceof Array))
          {
            eventData = [eventData];
          }
          var startIndex = 0;
          
          if (data instanceof oj.PagingTableDataSource)
          {
            startIndex = data.getStartItemIndex();
          }
          
          var rowArray = [];
          var i;
          for (i = 0; i < eventData.length; i++)
          {
            var rowIdx = eventIndexes[i] - startIndex;
            if (rowIdx !== undefined) 
            {
              var row = {'data': eventData[i], 'key': eventKeys[i], 'index': eventIndexes[i]};

              rowArray.push({row: row, rowIdx: rowIdx});
            }
          }
          if (rowArray.length > 0)
          {
            this._executeTableBodyRowsAdd(rowArray);
          }
        }
        catch (e) 
        {
          oj.Logger.error(e);
        }
        finally 
        {
          this._clearDataWaitingState();
        }
      },
      /**
       * Callback handler for row change in the datasource. Refresh the changed
       * row.
       * @param {Object} event
       * @private
       */
      _handleDataRowChange: function(event)
      {
        try
        {
          var data = this._getData();
          var eventData = event['data'];
          var eventIndexes = event['indexes'];
          var eventKeys = event['keys'];
          if (!(eventData instanceof Array))
          {
            eventData = [eventData];
          }
          var startIndex = 0;
          
          if (data instanceof oj.PagingTableDataSource)
          {
            startIndex = data.getStartItemIndex();
          }
          
          var rowArray = [];
          var i;
          for (i = 0; i < eventData.length; i++)
          {
            var rowIdx = eventIndexes[i] - startIndex;
            if (rowIdx !== undefined) 
            {
              var row = {'data': eventData[i], 'key': eventKeys[i], 'index': eventIndexes[i]};

              rowArray.push({row: row, rowIdx: rowIdx});
            }
          }
          if (rowArray.length > 0)
          {
            this._executeTableBodyRowsChange(rowArray);
          }
        }
        catch (e) 
        {
          oj.Logger.error(e);
        }
        finally 
        {
          this._clearDataWaitingState();
        }
      },
      /**
       * Callback handler for row removed in the datasource. Remove the row DOM from the
       * table body by searching for the matching rowKey. New rows will have null rowKey.
       * After removing the row, refresh all the remaining row indexes since
       * they will have shifted. Lastly, refresh the table dimensions
       * @param {Object} event
       * @private
       */
      _handleDataRowRemove: function(event)
      {
        try
        {
          var data = this._getData();
          var eventData = event['data'];
          var eventIndexes = event['indexes'];
          var eventKeys = event['keys'];
          if (!(eventData instanceof Array))
          {
            eventData = [eventData];
          }
          var startIndex = 0;
          
          if (data instanceof oj.PagingTableDataSource)
          {
            startIndex = data.getStartItemIndex();
          }
          
          var rowArray = [];
          var i;
          for (i = eventData.length - 1; i >= 0; i--)
          {
            var rowIdx = eventIndexes[i] - startIndex;
            if (rowIdx !== undefined) 
            {
              var row = {'data': eventData[i], 'key': eventKeys[i], 'index': eventIndexes[i]};

              rowArray.push({row: row, rowIdx: rowIdx});
            }
          }
          if (rowArray.length > 0)
          {
            this._executeTableBodyRowsRemove(rowArray);
          }
        }
        catch (e) 
        {
          oj.Logger.error(e);
        }
        finally 
        {
          this._clearDataWaitingState();
        }
      },
      /**
       * Callback handler for sort completed in the datasource. Refresh entire
       * table body DOM and refresh the table dimensions. Set row focus to the
       * current row.
       * @param {Object} event 
       * @private
       */
      _handleDataSort: function(event)
      {
        try
        {
          var columnIdx;
          
          if (event != null)
          {
            var i, column, sortField;
            var columns = this._getColumnDefs();

            for (i = 0; i < columns.length; i++)
            {
              column = columns[i];
              sortField = column['sortProperty'] == null ? column['field'] : column['sortProperty'];

              if (event['header'] == sortField)
              {
                columnIdx = i;
                break;
              }
            }
            if (columnIdx != null)
            {
              this._refreshSortTableHeaderColumn(columnIdx, event['direction'] == this._COLUMN_SORT_ORDER._ASCENDING);
            }
          }

          // clear selection if not single selection
          var existingSelection = this['options']['selection'];
          if (existingSelection != null)
          {
            var clearSelection = false;

            if (existingSelection.length > 1)
            {
              clearSelection = true;
            }
            else if (existingSelection[0] != null)
            {
              var startIndex = existingSelection[0]['startIndex'];
              var endIndex = existingSelection[0]['endIndex'];

              if (!oj.Object.compareValues(startIndex, endIndex) && endIndex != null)
              {
                clearSelection = true;
              }
            }
            if (clearSelection)
            {
              this._setSelection(null);
              this.option('selection', null, {'_context': {writeback: true, internalSet: true}});
            }
          }
          // set the current row
          this._setCurrentRow(this.options['currentRow'], null);
          var self = this;
          this._queueTask(function()
          {
            return self._invokeDataFetchRows(null);
          }).then(function()
          {
            if (columnIdx != null)
            {
              setTimeout(function(){self._scrollColumnIntoViewport(columnIdx)}, 0);
            }
          });
        }
        catch (e) 
        {
          oj.Logger.error(e);
        }
        finally 
        {
          this._clearDataWaitingState();
        }
      },
      /**
       * Handler for Left/Right keydown.
       * @param {Object} event
       * @private
       */
      _handleKeydownLeftRight: function(event)
      {
        // pressing left/right navigates the column headers
        var focusedHeaderColumnIdx = this._getFocusedHeaderColumnIdx();
        var columns = this._getColumnDefs();

        if (focusedHeaderColumnIdx != null)
        {
          var newFocusedHeaderColumnIdx = focusedHeaderColumnIdx;

          if (this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_LEFT))
          {
            newFocusedHeaderColumnIdx = focusedHeaderColumnIdx > 0 ? focusedHeaderColumnIdx - 1 : focusedHeaderColumnIdx;
          }
          else if (this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_RIGHT))
          {
            newFocusedHeaderColumnIdx = focusedHeaderColumnIdx < columns.length - 1 ? focusedHeaderColumnIdx + 1 : focusedHeaderColumnIdx;
          }

          if (newFocusedHeaderColumnIdx != focusedHeaderColumnIdx)
          {
            this._setHeaderColumnFocus(newFocusedHeaderColumnIdx, true, false, null);

            if (event[this._KEYBOARD_CODES._KEYBOARD_MODIFIER_SHIFT])
            {
              // if shift is also pressed then we need to select too
              var newFocusedHeaderColumnSelection = this._getHeaderColumnSelection(newFocusedHeaderColumnIdx);
              // we may be clearing or setting the selection
              this._setHeaderColumnSelection(newFocusedHeaderColumnIdx, !newFocusedHeaderColumnSelection, null, event, true);
              // if we are clearing the selection, then clear the previous column too.
              if (newFocusedHeaderColumnSelection)
              {
                if (this._getHeaderColumnSelection(focusedHeaderColumnIdx))
                {
                  this._setHeaderColumnSelection(focusedHeaderColumnIdx, false, null, event, true);
                }
              }
            }
          }
        }
      },
      /**
       * Handler for Tab keydown.
       * @param {Object} event
       * @private
       */
      _handleKeydownTab: function(event)
      {
        // if Tab is pressed while a row has focus then we 
        // want to Tab within that row and then go to the 
        // next row until Esc is pressed
        var tabHandled = false;
        var focusedRowIdx = this._getFocusedRowIdx();

        if (focusedRowIdx != null && this._isTableNavigationMode())
        {
          var currentFocusElement = document.activeElement;
          var tableBody = this._getTableDomUtils().getTableBody();
          
          if ($.contains(tableBody[0], currentFocusElement))
          {
            // if already focused on an element in the body, then
            // don't do anything
            return;
          }
          
          var tabbableElementsInBody = this._getTabbableElements(tableBody);
          // only bother if there are any tabbable elements
          if (tabbableElementsInBody != null)
          {
            tabHandled = true;
            
            if (!event[this._KEYBOARD_CODES._KEYBOARD_MODIFIER_SHIFT])
            {
              var tableBodyRow = this._getTableDomUtils().getTableBodyRow(focusedRowIdx);
              var tabbableElementsInRow = this._getTabbableElements(tableBodyRow);

              if (tabbableElementsInRow != null)
              {
                $(tabbableElementsInRow[0]).focus();
              }
              else
              {
                // if there are no tabbable elements
                // in the row then focus on the first 
                // tabbable element in the body
                $(tabbableElementsInBody[0]).focus();
              }
              event.preventDefault();
              event.stopPropagation();
            }
          }
        }
        
        if (!tabHandled)
        {
          // tab out of the component to the next tabbable
          // element on the page
          var table = this._getTableDomUtils().getTable();
          var tabbableElementsInDocument = this._getTabbableElements($(document));
          var tabbableElementsInDocumentCount = tabbableElementsInDocument.length;
          var tabbableElementsInTable = this._getTabbableElements(table);
          var tabbableElementsInTableCount = tabbableElementsInTable != null ? tabbableElementsInTable.length : 0;
          var tableTabIndex = tabbableElementsInDocument.index(this._getTableDomUtils().getTable());
          if (!event[this._KEYBOARD_CODES._KEYBOARD_MODIFIER_SHIFT])
          {
            if (tableTabIndex == tabbableElementsInDocumentCount - 1)
            {
              // Table is the last element. Let the browser handle the tab.
              return;
            }
            else
            {
              $(tabbableElementsInDocument[tableTabIndex + tabbableElementsInTableCount + 1]).focus();
            }
          }
          else if (tableTabIndex >= 0)
          {
            if (tableTabIndex == 0)
            {
              // Table is the first element. Let the browser handle the tab.
              return;
            }
            else
            {
              $(tabbableElementsInDocument[tableTabIndex - 1]).focus();
            }
          }
          else
          {
            return;
          }
          event.preventDefault();
          event.stopPropagation();
        }
        // we need to remove Tab on keydown because we may not
        // get a keyup for it if focus moves
        // outside of table
        this._removeKeyboardKey(event.keyCode);
      },
      /**
       * Handler for Up/Down keydown.
       * @param {Object} event
       * @private
       */
      _handleKeydownUpDown: function(event)
      {
        var focusedRowIdx = this._getFocusedRowIdx();
        var focusedHeaderColumnIdx = this._getFocusedHeaderColumnIdx();

        if (focusedRowIdx != null)
        {
          // if row is focused then up/down navigates the rows
          var tableBodyRows = this._getTableDomUtils().getTableBodyRows();
          var rowCount = tableBodyRows != null ? tableBodyRows.length : 0;
          var newFocusedRowIdx = focusedRowIdx;

          if (this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_UP))
          {
            if (focusedRowIdx > 0)
            {
              newFocusedRowIdx = focusedRowIdx - 1;
            }
            else
            {
              newFocusedRowIdx = focusedRowIdx;
            }
          }
          else if (this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_DOWN))
          {
            newFocusedRowIdx = focusedRowIdx < rowCount - 1 ? focusedRowIdx + 1 : focusedRowIdx;
          }

          if (newFocusedRowIdx != focusedRowIdx)
          {
            var focused = this._setRowFocus(newFocusedRowIdx, true, true, null, event);
            
            if (!focused)
            {
              return;
            }

            if (event[this._KEYBOARD_CODES._KEYBOARD_MODIFIER_SHIFT])
            {
              // if shift is also pressed then we need to select too
              var newFocusedRowSelection = this._getRowSelection(newFocusedRowIdx);
              // we may be clearing or setting the selection
              this._setRowSelection(newFocusedRowIdx, !newFocusedRowSelection, null, event, true);
              // if we are clearing the selection, then clear the previous row too.
              if (newFocusedRowSelection)
              {
                if (this._getRowSelection(focusedRowIdx))
                {
                  this._setRowSelection(focusedRowIdx, false, null, event, true);
                }
              }
            }
          }
          // if user is on the first row and presses up the focus on the first column header
          else if (newFocusedRowIdx == focusedRowIdx &&
            focusedRowIdx == 0 &&
            this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_UP))
          {
            this._setHeaderColumnFocus(0, true, false, null);
          }
        }
        // if user is on a column header and pressed down then focus on the first row
        else if (focusedHeaderColumnIdx != null &&
          this._isKeyboardKeyPressed(this._KEYBOARD_CODES._KEYBOARD_CODE_DOWN))
        {
          this._setRowFocus(0, true, true, null, event);
        }
      },
      /**
       * Handler for End keyup.
       * @param {Object} event
       * @private
       */
      _handleKeyupEnd: function(event)
      {
        // pressing End focuses on last column
        var focusedColumnIdx = this._getFocusedHeaderColumnIdx();

        if (focusedColumnIdx != null &&
          focusedColumnIdx != this._getColumnDefs().length - 1)
        {
          this._setHeaderColumnFocus(this._getColumnDefs().length - 1, true, false, null);
        }
        else
        {
          var focusedRowIdx = this._getFocusedRowIdx();
          var tableBodyRows = this._getTableDomUtils().getTableBodyRows();
          var rowCount = tableBodyRows != null ? tableBodyRows.length : 0;

          if (focusedRowIdx != null && focusedRowIdx != rowCount - 1 && rowCount > 0)
          {
            this._setRowFocus(rowCount - 1, true, true, null, event);
          }
        }
      },
      /**
       * Handler for Enter keyup.
       * @param {Object} event
       * @private
       */
      _handleKeyupEnter: function(event)
      {
        // pressing enter does sort on the focused column header
        var focusedColumnIdx = this._getFocusedHeaderColumnIdx();

        if (focusedColumnIdx != null && this._getColumnDefs()[focusedColumnIdx]['sortable'] == this._OPTION_ENABLED)
        {
          var tableHeaderColumn = this._getTableDomUtils().getTableHeaderColumn(focusedColumnIdx);
          var sorted = tableHeaderColumn.data('sorted');
          // if not already sorted then sort ascending. If already sorted
          // ascending then do descending sort and vice versa.
          if (sorted == null || sorted == this._COLUMN_SORT_ORDER._DESCENDING)
          {
            this._handleSortTableHeaderColumn(focusedColumnIdx, true, event);
          }
          else
          {
            this._handleSortTableHeaderColumn(focusedColumnIdx, false, event);
          }
        }
      },
      /**
       * Handler for Esc keyup.
       * @param {Object} event
       * @private
       */
      _handleKeyupEsc: function(event)
      {
        // pressing Esc always returns focus back to the table.
        // This is for when users are tabbing through focuable
        // elements and need to get back to general table nav
        event.preventDefault();
        event.stopPropagation();
        this._getTableDomUtils().getTable().focus();
        this._setTableNavigationMode(false);
      },
      /**
       * Handler for Home keyup.
       * @param {Object} event
       * @private
       */
      _handleKeyupHome: function(event)
      {
        // pressing Home focuses on first column
        var focusedColumnIdx = this._getFocusedHeaderColumnIdx();

        if (focusedColumnIdx != null && focusedColumnIdx != 0)
        {
          this._setHeaderColumnFocus(0, true, false, null);
        }
        else
        {
          var focusedRowIdx = this._getFocusedRowIdx();

          if (focusedRowIdx != null && focusedRowIdx != 0)
          {
            this._setRowFocus(0, true, true, null, event);
          }
        }
      },
      /**
       * Handler for Spacebar keyup.
       * @param {Object} event
       * @private
       */
      _handleKeyupSpacebar: function(event)
      {
        // pressing spacebar selects the focused row/column
        var focusedRowIdx = this._getFocusedRowIdx();

        if (focusedRowIdx != null)
        {
          this._setRowSelection(focusedRowIdx, !this._getRowSelection(focusedRowIdx), null, event, true);
        }
        else
        {
          var focusedHeaderColumnIdx = this._getFocusedHeaderColumnIdx();
          if (focusedHeaderColumnIdx != null)
          {
            this._clearSelectedRows();
            this._setHeaderColumnSelection(focusedHeaderColumnIdx, !this._getHeaderColumnSelection(focusedHeaderColumnIdx), null, event, true);
          }
        }
      },
      /**
       * Callback handler max fetch count.
       * @private
       */
      _handleScrollerMaxRowCount: function()
      {
        // TODO: use inline messaging framwork when ready
        var errSummary = this._LOGGER_MSG._ERR_DOM_SCROLLER_MAX_COUNT_SUMMARY;
        var errDetail = this._LOGGER_MSG._ERR_DOM_SCROLLER_MAX_COUNT_DETAIL;
        this._showInlineMessage(errSummary, errDetail, oj.Message.SEVERITY_LEVEL['WARNING']);
      },
     /**
       * Handle scrollLeft on scroller
       * @private	 
       */
      _handleScrollerScrollLeft: function(scrollLeft, maxScrollLeft)
      {
        if (this._GetReadingDirection() === "rtl")
        {
          scrollLeft = Math.abs(scrollLeft);
          if (this._getTableDomUtils()._isWebkit())
          {
            scrollLeft = maxScrollLeft - scrollLeft;
          }
        }

        var tableHeader = this._getTableDomUtils().getTableHeader();
        var tableFooter = this._getTableDomUtils().getTableFooter();

        if (!this._getTableDomUtils().isDivScroller())
        {
          var tableHeaderRow = this._getTableDomUtils().getTableHeaderRow();
          if (tableHeaderRow)
          {
            if (this._GetReadingDirection() === "rtl")
            {
              tableHeaderRow.css(oj.TableDomUtils.CSS_PROP._RIGHT, '-' + scrollLeft + oj.TableDomUtils.CSS_VAL._PX);
            }
            else
            {
              tableHeaderRow.css(oj.TableDomUtils.CSS_PROP._LEFT, '-' + scrollLeft + oj.TableDomUtils.CSS_VAL._PX);
            }
          }

          var tableFooterRow = this._getTableDomUtils().getTableFooterRow();
          if (tableFooterRow)
          {
            if (this._GetReadingDirection() === "rtl")
            {
              tableFooterRow.css(oj.TableDomUtils.CSS_PROP._RIGHT, '-' + scrollLeft + oj.TableDomUtils.CSS_VAL._PX);
            }
            else
            {
              tableFooterRow.css(oj.TableDomUtils.CSS_PROP._LEFT, '-' + scrollLeft + oj.TableDomUtils.CSS_VAL._PX);
            }
          }
        }
        else
        {
          if (tableHeader)
          {
            if (this._GetReadingDirection() === "rtl")
            {
              tableHeader.css(oj.TableDomUtils.CSS_PROP._RIGHT, '-' + scrollLeft + oj.TableDomUtils.CSS_VAL._PX);
            }
            else
            {
              tableHeader.css(oj.TableDomUtils.CSS_PROP._LEFT, '-' + scrollLeft + oj.TableDomUtils.CSS_VAL._PX);
            }
          }
          if (tableFooter)
          {
            if (this._GetReadingDirection() === "rtl")
            {
              tableFooter.css(oj.TableDomUtils.CSS_PROP._RIGHT, '-' + scrollLeft + oj.TableDomUtils.CSS_VAL._PX);
            }
            else
            {
              tableFooter.css(oj.TableDomUtils.CSS_PROP._LEFT, '-' + scrollLeft + oj.TableDomUtils.CSS_VAL._PX);
            }
          }
        }
      },
      /**
       * Handler for column sort
       * @param {number} columnIdx  column index
       * @param {boolean} ascending  sort order ascending
       * @param {Object} event
       * @private
       */
      _handleSortTableHeaderColumn: function(columnIdx, ascending, event)
      {
        // clear the sorted indicator on any other column
        this._clearSortedHeaderColumn(columnIdx);
        // get the column metadata
        var column = this._getColumnDefs()[columnIdx];
        // get which field to sort on
        var sortField = column['sortProperty'] == null ? column['field'] : column['sortProperty'];
        // invoke sort on the data
        this._invokeDataSort(sortField, ascending, event);
        this._sortColumn = column;
        this._refreshSortTableHeaderColumn(columnIdx, ascending);
      },
      /**
       * Hide the inline message.
       * @private
       */
      _hideInlineMessage: function()
      {
        var inlineMessage = this._getTableDomUtils().getTableInlineMessage();
        if (inlineMessage.css(oj.TableDomUtils.CSS_PROP._DISPLAY) != oj.TableDomUtils.CSS_VAL._NONE)
        {
          var inlineMessageHeight = inlineMessage.outerHeight();
          var tableContainer = this._getTableDomUtils().getTableContainer();
          var tableContainerBorderBottom = parseInt(tableContainer.css(oj.TableDomUtils.CSS_PROP._BORDER_BOTTOM_WIDTH), 10);
          var tableContainerMarginBottom = parseInt(tableContainer.css(oj.TableDomUtils.CSS_PROP._MARGIN_BOTTOM), 10);
          tableContainerMarginBottom = tableContainerMarginBottom - tableContainerBorderBottom - inlineMessageHeight;
          tableContainer.css(oj.TableDomUtils.CSS_PROP._MARGIN_BOTTOM, tableContainerMarginBottom + oj.TableDomUtils.CSS_VAL._PX);
          tableContainer.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._WARNING);
          inlineMessage.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._WARNING);
          inlineMessage.css(oj.TableDomUtils.CSS_PROP._BOTTOM, '');
          inlineMessage.css(oj.TableDomUtils.CSS_PROP._DISPLAY, oj.TableDomUtils.CSS_VAL._NONE);
        }
      },
      /**
       * Hide the 'No data to display.' message.
       * @private
       */
      _hideNoDataMessage: function()
      {
        var noDataRow = this._getTableDomUtils().getTableNoDataRow();
        if (noDataRow != null) {
          noDataRow.remove();
        }
      },
      /**
       * Hide the Fetching Data... status message.
       * @private
       */
      _hideStatusMessage: function()
      {
        var statusMessage = this._getTableDomUtils().getTableStatusMessage();
        statusMessage.css(oj.TableDomUtils.CSS_PROP._DISPLAY, oj.TableDomUtils.CSS_VAL._NONE);
      },
      /**
       * Hide the column header sort link
       * @param {number} columnIdx  column index
       * @param {boolean} ascending  sort order ascending
       * @private
       */
      _hideTableHeaderColumnSortLink: function(columnIdx, ascending)
      {
        // check if the column is sortable. If not, then there won't be any sort links
        if (this._getColumnDefs()[columnIdx]['sortable'] == this._OPTION_ENABLED)
        {
          var tableHeaderColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);
          // check if the column is currently sorted
          var sorted = tableHeaderColumn.data('sorted');

          // we should only hide the ascending sort link if the column is not sorted or
          // is sorted by descending order
          if (ascending && (sorted == null || sorted == this._COLUMN_SORT_ORDER._DESCENDING))
          {
            var headerColumnAscLink = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_ASC_LINK_CLASS);
            headerColumnAscLink.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
            headerColumnAscLink.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._ENABLED);
            headerColumnAscLink.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
          }
          // we should only hide the descending sort link if the column is not sorted or
          // is sorted by ascending order
          else if (!ascending && (sorted == null || sorted == this._COLUMN_SORT_ORDER._ASCENDING))
          {
            var headerColumnDscLink = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_DSC_LINK_CLASS);
            headerColumnDscLink.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
            headerColumnDscLink.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._ENABLED);
            headerColumnDscLink.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
          }
        }
      },
      /**
       * Do an initial fetch
       * @private
       */
      _initFetch: function()
      {
        var self = this;
        var data = this._getData();
        // do an initial fetch if a TableDataSource
        // paging control should do the fetches for PagingTableDataSource
        if (data != null && (data instanceof oj.TableDataSource) && !(data instanceof oj.PagingTableDataSource))
        {
          // reset the scrollTop when we do an initial fetch
          this._getTableDomUtils().getScroller()[0].scrollTop = 0;
          
          if (this._isLoadMoreOnScroll())
          {
            this._queueTask(function()
            {
              return self._invokeDataFetchRows(0, {'fetchType': 'init', 'pageSize' : self.options['scrollPolicyOptions']['fetchSize']});
            });
          }
          else
          {
            this._queueTask(function()
            {
              return self._invokeDataFetchRows(0, {'fetchType': 'init'});
            });
          }
        }
        else if (data == null)
        {
          this._queueTask(function()
          {
            return Promise.resolve();
          });
        }
      },
      /**
       * Fetch rows
       * @param {number|null} startIndex start index
       * @param {Object} options options for the fetch
       * @return {Promise} Promise resolves when done.
       * @private
       */
      _invokeDataFetchRows: function(startIndex, options)
      {
        options = options || {};
        options['startIndex'] = startIndex;
        options['silent'] = true;
        var initFetch = options['fetchType'] == 'init'? true : false;
        var data = this._getData();
        var self = this;
        return new Promise(function(resolve, reject)
        {
          if (data != null)
          {
            self._setDataWaitingState();
            data.fetch(options).then(function(result)
            {
              if (result != null)
              {
                if (result['data'] != null)
                {
                  var offset = 0;
                  
                  if (data instanceof oj.PagingTableDataSource)
                  {
                    offset = data.getStartItemIndex();
                  }

                  var indexArray = [];
                  var i;

                  for (i = 0; i < result['data'].length; i++)
                  {
                    indexArray[i] = i + offset + result['startIndex'];
                  }
                  
                  self._refreshAll({'data': result['data'], 'keys' : result['keys'], 'indexes': indexArray}, result['startIndex'], initFetch, initFetch);
                }
              }
              self._clearDataWaitingState();
              resolve(null);
            }, function(error)
            {
               // TODO inline messaging framework
               self._clearDataWaitingState();
               reject(error);
            });
          }
          else
          {
            resolve(null);
          }
        });
      },
      /**
       * Invoke sort on a field. This function is called when a user clicks the 
       * column header sort links
       * @param {string} sortField  field name
       * @param {boolean} ascending  sort order ascending
       * @param {Object} event
       * @private
       */
      _invokeDataSort: function(sortField, ascending, event)
      {
        var data = this._getData();
        // if no data then bail
        if (!data)
        {
          return null;
        }

        // show the Fetching Data... message
        this._showStatusMessage();
        
        var sortCriteria = {};
        sortCriteria['key'] = sortField;

        // the sort function on the datasource takes comparators
        if (ascending)
        {
          sortCriteria['direction'] = this._COLUMN_SORT_ORDER._ASCENDING;
        }
        else
        {
          sortCriteria['direction'] = this._COLUMN_SORT_ORDER._DESCENDING;
        }
        this._trigger('sort', event, {'header': sortCriteria['key'], 'direction': sortCriteria['direction']});
        data.sort(sortCriteria);
      },
      /**
       * Whether the columns have been updated
       * @return {boolean} true or false
       * @private
       */
      _isColumnMetadataUpdated: function()
      {
        if (this._columnDefArray != null)
        {
          var columnsMetadata = this._getColumnMetadata();
          if (this._columnDefArray.length != columnsMetadata.length)
          {
            return true;
          }
          else
          {
            var i, prop;
            for (i = 0; i < columnsMetadata.length; i++)
            {
              for (prop in columnsMetadata[i]) {
                if (columnsMetadata[i].hasOwnProperty(prop)) {
                  if (columnsMetadata[i][prop] != this._columnDefArray[i][prop])
                  {
                    return true;
                  }
                }
              }
            }
          }
          return false;
        }
        return true;
      },
      /**
       * Is keybaord key pressed
       * @param {number} keyCode  KeyCode of the keyboard key.
       * @return {boolean} true or false
       * @private
       */
      _isKeyboardKeyPressed: function(keyCode)
      {
        var keyboardKeys = this._getKeyboardKeys();
        var i;
        for (i = 0; i < keyboardKeys.length; i++)
        {
          if (keyboardKeys[i] == keyCode)
          {
            return true;
          }
        }
        return false;
      },
      /**
       * Is loadMoreOnScroll
       * @return {boolean} true or false
       * @private
       */
      _isLoadMoreOnScroll: function()
      {
        return this.options['scrollPolicy'] == this._OPTION_SCROLL_POLICY._LOADMORE_ON_SCROLL ? true: false;
      },
      /**
       * Return whether the node is editable or clickable
       * @param {jQuery} node  Node
       * @return {boolean} true or false
       * @private	 
       */
      _isNodeEditableOrClickable: function(node)
      {
        var nodeName;
        var table = this._getTableDomUtils().getTable();

        while (null != node && node[0] != table[0] &&
          (nodeName = node.prop("nodeName")) != "TD" && nodeName != "TH")
        {
          // If the node is a text node, move up the hierarchy to only operate on elements
          // (on at least the mobile platforms, the node may be a text node)
          if (node[0].nodeType == 3) // 3 is Node.TEXT_NODE
          {
            node = node[0].parentNode;
            continue;
          }

          var tabIndex = node.attr('tabIndex');

          if (tabIndex != null && tabIndex >= 0)
          {
            return true;
          }
          else if (nodeName.match(/^INPUT|SELECT|OPTION|BUTTON|^A\b|TEXTAREA/))
          {
            // ignore elements with tabIndex == -1
            if (tabIndex != -1)
            {
              return true;
            }
          }
          node = node.parentNode;
        }
        return false;
      },
      /**
       * Returns whether the table is footerless
       * @return {boolean} true or false
       * @private
       */
      _isTableFooterless: function()
      {
        var columns = this._getColumnDefs();
        var i, footerRenderer;
        
        for (i = 0; i < columns.length; i++)
        {
          footerRenderer = this._getColumnRenderer(i, 'footer');
          if (footerRenderer != null)
          {
            return false
          }
        }
        return true;
      },
      /**
       * Returns whether the table is headerless
       * @return {boolean} true or false
       * @private
       */
      _isTableHeaderless: function()
      {
        var columns = this._getColumnDefs();

        var i, j;
        for (i = 0; i < columns.length; i++)
        {
          if (columns[i]['headerText'] != null ||
            columns[i]['headerStyle'] != null ||
            (columns[i]['sortable'] != null &&
              columns[i]['sortable'] != this._OPTION_NONE) ||
            columns[i]['sortProperty'] != null ||
            columns[i]['headerRenderer'] != null)
          {
            return false;
          }
        }
        
        return true;
      },
      /**
       * Returns whether the table header columns were rendered
       * @return {boolean} true or false
       * @private
       */
      _isTableHeaderColumnsRendered: function()
      {
        return this._renderedTableHeaderColumns == true;
      },
      /**
       * Return whether the component is in table navigation mode
       * @return {boolean} true or false
       * @private
       */
      _isTableNavigationMode: function()
      {
        return this._tableNavMode;
      },
      /**
       * Returns whether the table refresh is needed based on option change
       * @param {string} key option key
       * @param {Object} value option value
       * @return {boolean} true or false
       * @private
       */
      _isTableRefreshNeeded: function(key, value)
      {
        var currentOptions = this._cachedOptions;
        var refresh = false;
        
        if (key == 'contextMenu' && value == '#' + this._getTableDomUtils().getTable().attr('id') + '_contextmenu')
        {
          refresh = false;
        }
        else if (key != 'selection' && key != 'currentRow' && !oj.Object.compareValues(value, currentOptions[key]))
        {
          refresh = true;
        }
        
        this._cachedOptions = $.extend(true, {}, this.options);
        
        return refresh;
      },
      /**
       * Returns whether any of the table columns are sortable
       * @return {boolean} true or false
       * @private
       */
      _isTableSortable: function()
      {
        var columns = this._getColumnDefs();

        var i;
        for (i = 0; i < columns.length; i++)
        {
          if (columns[i]['sortable'] != null &&
              columns[i]['sortable'] != this._OPTION_NONE)
          {
            return true;
          }
        }
        
        return false;
      },
      /**
       * @param {Object} resultObject Object containing data array, key array, and index array
       * @param {number} startIndex start index
       * @param {boolean} resetScrollTop reset the scrollTop
       * @param {boolean} resetScrollLeft reset the scrollLeft
       * @private
       */
      _refreshAll: function(resultObject, startIndex, resetScrollTop, resetScrollLeft)
      {
        if (this._isColumnMetadataUpdated() ||
            (!this._isTableHeaderColumnsRendered() &&
            !this._isTableHeaderless()))
        {
          this._clearCachedMetadata();
          this._refreshTableHeader();
          
          // see if we need to clear the sort. If the column we sorted on is no
          // longer there then clear it.
          if (this._sortColumn != null)
          {
            var i, column;
            var foundColumn = false;
            var columns = this._getColumnDefs();
            if (columns != null)
            {
              for (i = 0; i < columns.length; i++)
              {
                column = columns[i];
                if (oj.Object.compareValues(column, this._sortColumn))
                {
                  foundColumn = true;
                  break;
                }
              }
              if (!foundColumn)
              {
                this._getData().sort(null);
              }
            }
          }
        }
        this._refreshTableFooter();
        this._refreshTableBody(resultObject, startIndex, resetScrollTop, resetScrollLeft);
      },
      /**
       * Handler for column sort
       * @param {number} columnIdx  column index
       * @param {boolean} ascending  sort order ascending
       * @private
       */
      _refreshSortTableHeaderColumn: function(columnIdx, ascending)
      {
        // clear the sorted indicator on any other column
        this._clearSortedHeaderColumn(columnIdx);
        // get the column header DOM element
        var tableHeaderColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);
        var sorted = tableHeaderColumn.data('sorted');
        
        if (ascending && sorted != this._COLUMN_SORT_ORDER._ASCENDING)
        {
          // store sort order on the DOM element
          tableHeaderColumn.data('sorted', this._COLUMN_SORT_ORDER._ASCENDING);
          var headerColumnAscLink = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_ASC_LINK_CLASS);
          headerColumnAscLink.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._ENABLED);
          headerColumnAscLink.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
          var headerColumnAsc = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_ASC_CLASS);
          headerColumnAsc.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
          var headerColumnDsc = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_DSC_CLASS);
          headerColumnDsc.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
          this._hideTableHeaderColumnSortLink(columnIdx, !ascending);
        }
        else if (!ascending && sorted != this._COLUMN_SORT_ORDER._DESCENDING)
        {
          // store sort order on the DOM element
          tableHeaderColumn.data('sorted', this._COLUMN_SORT_ORDER._DESCENDING);
          var headerColumnDscLink = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_DSC_LINK_CLASS);
          headerColumnDscLink.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._ENABLED);
          headerColumnDscLink.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
          var headerColumnDsc = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_DSC_CLASS);
          headerColumnDsc.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
          var headerColumnAsc = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_ASC_CLASS);
          headerColumnAsc.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
        }
      },
      /**
       * Refresh the entire table body with data from the datasource
       * @param {Object} resultObject Object containing data array, key array, and index array
       * @param {number} startIndex start index
       * @param {boolean} resetScrollTop reset the scrollTop
       * @param {boolean} resetScrollLeft reset the scrollLeft
       * @private
       */
      _refreshTableBody: function(resultObject, startIndex, resetScrollTop, resetScrollLeft)
      {
        var tableBody = this._getTableDomUtils().getTableBody();

        if (tableBody == null)
        {
          return;
        }

        var rows = this._getRowIdxRowArray(resultObject, startIndex);
        var tableBodyRows;

        if (startIndex == 0)
        {
          oj.Components.subtreeDetached(tableBody[0]);
          tableBody.empty();
        }
        else
        {
          tableBodyRows = tableBody.children();
          if (tableBodyRows != null && tableBodyRows.length > 0)
          {
            var i;
            for (i = tableBodyRows.length - 1; i >= startIndex; i--)
            {
              this._getTableDomUtils().removeTableBodyRow(i);
            }
          }
        }
        this._getTableDomUtils().clearCachedDomRowData();
        this._hideNoDataMessage();
        tableBodyRows = tableBody.children();

        // if no data then bail
        if (rows.length == 0 && (tableBodyRows == null || tableBodyRows.length == 0))
        {
          this._showNoDataMessage();
        }
        else
        {
          var tableBodyDocFrag = $(document.createDocumentFragment());
          var i, row, rowIdx, tableBodyRow;
          for (i = 0; i < rows.length; i++)
          {
            row = rows[i].row;
            rowIdx = rows[i].rowIdx;
            if (row != null)
            { 
              tableBodyRow = this._getTableDomUtils().createTableBodyRow(rowIdx, row['key']);
              this._getTableDomUtils().styleTableBodyRow(tableBodyRow, true);
              this._getTableDomUtils().insertTableBodyRow(rowIdx, tableBodyRow, row, tableBodyDocFrag);
              this._refreshTableBodyRow(rowIdx, row, tableBodyRow, tableBodyDocFrag, startIndex, true);
            }
          }
          tableBody.append(tableBodyDocFrag); //@HTMLUpdateOK

          // Re-size the table before calling subtree attached. subtreeAttached is a
          // potentially expensive call so we want table to be laid out before it.
          this._getTableDomUtils().refreshTableDimensions(null, null, resetScrollTop, resetScrollLeft);
          oj.Components.subtreeAttached(tableBody[0]);
        }
      },
      /**
       * Refresh the row at a particular index with the row data
       * @param {number} rowIdx  row index relative to the start of the table
       * @param {Object} row  row and key object
       * @param {Object} tableBodyRow tr element
       * @param {Object} docFrag  document fragment
       * @param {number} docFragStartIdx  document fragment row start index
       * @param {boolean} isNew is new row
       * @private
       */
      _refreshTableBodyRow: function(rowIdx, row, tableBodyRow, docFrag, docFragStartIdx, isNew)
      {
        var options = this.options;
        var rowRenderer = this._getRowRenderer();
        var columns = this._getColumnDefs();
        var tableBody = this._getTableDomUtils().getTableBody();

        if (isNaN(rowIdx) || rowIdx < 0)
        {
          // validate rowIdx value
          oj.Logger.error('Error: Invalid rowIdx value: ' + rowIdx);
        }

        if (tableBodyRow == null)
        {
          // check if we already have a <tr> element at that index
          tableBodyRow = this._getTableDomUtils().getTableBodyRow(rowIdx);
          if (!tableBodyRow)
          {
            // if not return
            return;
          }
          else
          {
            tableBodyRow.empty();
            isNew = true;
            this._getTableDomUtils().createTableBodyCellAccSelect(rowIdx, row['key'], tableBodyRow);
          }
        }

        this._hideNoDataMessage();
        
        // check if a row renderer was defined
        if (rowRenderer)
        {
          var rowContext = this._getTableDomUtils().getRendererContextObject(row, tableBodyRow[0]);
          var rowContent = rowRenderer({'rowContext': rowContext, 
                                        'row': $.extend({}, row['data'])});

          if (rowContent != null)
          {
            // if the renderer returned a value then we set it as the content
            // for the row
            tableBodyRow.append(rowContent); //@HTMLUpdateOK
          }
          else
          {
            // if the renderer didn't return a value then the existing
            // row was manipulated. So get it and set the required
            // attributes just in case it was replaced or the attributes
            // got removed
            if (docFrag == null)
            {
              tableBodyRow = $(tableBody.children()[rowIdx]);
            }
            else
            {
              docFragStartIdx = docFragStartIdx == null ? 0 : docFragStartIdx;
              tableBodyRow = $(docFrag.children()[rowIdx - docFragStartIdx]);
            }
            this._getTableDomUtils().clearCachedDomRowData();
            this._getTableDomUtils().setTableBodyRowAttributes(rowIdx, row, tableBodyRow);
            this._getTableDomUtils().styleTableBodyRow(tableBodyRow, false);
          }
          this._getTableDomUtils().createTableBodyCellAccSelect(rowIdx, row['key'], tableBodyRow);

          // set the cell attributes and styling. Skip the 1st one
          // because it's the acc row select td
          var tableBodyCells = tableBodyRow.children('td');
          var i;
          for (i = 1; i < tableBodyCells.length; i++)
          {
            var tableBodyCell = $(tableBodyCells[i]);
            this._getTableDomUtils().setTableBodyCellAttributes(rowIdx, row['key'], i - 1, tableBodyCell);
            this._getTableDomUtils().styleTableBodyCell(i - 1, tableBodyCell, false);
          }
        }
        else
        {
          this._getTableDomUtils().setTableBodyRowAttributes(rowIdx, row, tableBodyRow);
          
          var j, column, cellRenderer;
          for (j = 0; j < columns.length; j++)
          {
            column = columns[j];
            cellRenderer = this._getColumnRenderer(j, 'cell');
            // set the cells in the inserted row with values from the row
            this._getTableDomUtils().setTableBodyCell(rowIdx, j, tableBodyRow, row, cellRenderer, isNew);
          }
        }
      },
      /**
       * Refresh the table footer
       * @private
       */
      _refreshTableFooter: function()
      {
        var columns = this._getColumnDefs();
        var tableFooter = this._getTableDomUtils().getTableFooter();

        if (!tableFooter)
        {
          if (this._isTableFooterless())
          {
            return;
          }
          else
          {
            // metadata could have been updated to add column headers
            tableFooter = this._getTableDomUtils().createTableFooter();
            this._getTableDomUtils().styleTableFooter(tableFooter);
          }
        }

        var tableFooterRow = this._getTableDomUtils().getTableFooterRow();
        // remove all the existing footer cells
        tableFooterRow.empty();

        if (columns && columns.length > 0)
        {
          this._getTableDomUtils().createTableFooterAccSelect(tableFooterRow);
          
          var i, column, footerRenderer, footerCell, footerCellContent;
          for (i = 0; i < columns.length; i++)
          {
            column = columns[i];
            footerRenderer = this._getColumnRenderer(i, 'footer');
            footerCell = this._getTableDomUtils().createTableFooterCell(i, this._getColumnSelectionMode());
            this._getTableDomUtils().styleTableFooterCell(i, footerCell);
            this._getTableDomUtils().insertTableFooterCell(i, footerCell);

            if (footerRenderer)
            {
              // if footerRenderer is defined then call that
              footerCellContent = footerRenderer({'footerContext': this._getTableDomUtils().getRendererContextObject(null, footerCell[0]),
                                                  'columnIndex': i});
              
              if (footerCellContent != null)
              {
                // if the renderer returned a value then we set it as the content
                // for the footer cell
                footerCell.empty();
                footerCell.append(footerCellContent); //@HTMLUpdateOK
              }
              else
              {
                // if the renderer didn't return a value then the existing
                // footer cell was manipulated. So get it and set the required
                // attributes just in case it was replaced or the attributes
                // got removed
                footerCell = $(tableFooterRow.children(':not(.' + oj.TableDomUtils.CSS_CLASSES._HIDDEN_CONTENT_ACC_CLASS + ')')[i]);
                this._getTableDomUtils().styleTableFooterCell(i, footerCell, this._getColumnSelectionMode());
              }
            }
          }
        }
      },
      /**
       * Refresh the table header
       * @private
       */
      _refreshTableHeader: function()
      {
        var columns = this._getColumnDefs();
        var tableHeader = this._getTableDomUtils().getTableHeader();

        if (!tableHeader)
        {
          if (this._isTableHeaderless())
          {
            return;
          }
          else
          {
            // metadata could have been updated to add column headers
            tableHeader = this._getTableDomUtils().createTableHeader();
            this._getTableDomUtils().styleTableHeader(tableHeader);
          }
        }

        var tableHeaderRow = this._getTableDomUtils().getTableHeaderRow();
        // remove all the existing column headers
        tableHeaderRow.empty();

        if (columns && columns.length > 0)
        {
          var tableHeaderAccSelectRowColumn = this._getTableDomUtils().createTableHeaderAccSelectRowColumn();
          tableHeaderRow.append(tableHeaderAccSelectRowColumn); //@HTMLUpdateOK

          var i, j, column, headerRenderer, headerColumn, headerColumnContent, headerContext, context;
          for (i = 0; i < columns.length; i++)
          {
            column = columns[i];
            headerRenderer = this._getColumnRenderer(i, 'header');
            headerColumn = this._getTableDomUtils().createTableHeaderColumn(i, this._getColumnSelectionMode());
            
            if (headerRenderer)
            {
              // if headerRenderer is defined then call that
              headerContext = this._getTableDomUtils().getRendererContextObject(null, headerColumn[0]);
              context = {'headerContext': headerContext, 
                         'columnIndex': i,
                         'data': column['headerText']};
              
              if (column.sortable == oj.TableDomUtils._OPTION_ENABLED)
              {
                // add the sortable icon renderer
                context['columnHeaderSortableIconRenderer'] = function(options, delegateRenderer)
                {
                  oj.TableRendererUtils.columnHeaderSortableIconRenderer(this, options, delegateRenderer);
                }
              }
              else
              {
                context['columnHeaderDefaultRenderer'] = function(options, delegateRenderer)
                {
                  oj.TableRendererUtils.columnHeaderDefaultRenderer(this, options, delegateRenderer);
                }
              }
              headerColumnContent = headerRenderer(context);
              
              if (headerColumnContent != null)
              {
                // if the renderer returned a value then we set it as the content
                // for the headerColumn
                headerColumn.empty();
                headerColumn.append(headerColumnContent); //@HTMLUpdateOK
              }
              else
              {
                // if the renderer didn't return a value then the existing
                // headerColumn was manipulated. So get it and set the required
                // attributes just in case it was replaced or the attributes
                // got removed
                headerColumn = $(tableHeaderRow.children(':not(' + '.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_ACC_SELECT_ROW_CLASS + ')')[i]);
                this._getTableDomUtils().setTableHeaderColumnAttributes(i, headerColumn);
                this._getTableDomUtils().styleTableHeaderColumn(i, headerColumn, this._getColumnSelectionMode(), false);
              }
            }

            // set the acc column selection checkbox
            this._getTableDomUtils().createTableHeaderColumnAccSelect(i, this._getColumnSelectionMode());
          }
          this._renderedTableHeaderColumns = true;
        }
      },
      /**
       * Refresh the status message
       * @private
       */
      _refreshTableStatusMessage: function()
      {
        var tableStatusMessage = this._getTableDomUtils().getTableStatusMessage();
        
        if (tableStatusMessage != null)
        {
          tableStatusMessage.remove();
        }
        this._getTableDomUtils().createTableStatusMessage();
      },
      /**
       * Register the events which will be published by the table component.
       * @private
       */
      _registerCustomEvents: function()
      {
        var jqEvent = (/** @type {{special: Object}} */($.event));
        var jqEventSpecial = jqEvent['special'];
        // ojtablebeforecurrentrow handlers will be passed an object which contains the
        // old and new current row
        jqEventSpecial['ojtablebeforecurrentrow'] = {
          /**
           * Handle event
           * @param {{handleObj: {handler: {apply: Function}}}} event
           * @private
           */
          handle: function(event) {
            var handleObj = event['handleObj'];
            return handleObj['handler'].apply(this, [event, arguments[1]]);
          }
        };
        // ojtablesort handlers will be passed an object which contains the
        // header and direction
        jqEventSpecial['ojtablesort'] = {
          /**
           * Handle event
           * @param {{handleObj: {handler: {apply: Function}}}} event
           * @private
           */
          handle: function(event) {
            var handleObj = event['handleObj'];
            return handleObj['handler'].apply(this, [event, arguments[1]]);
          }
        };
      },
      /**
       * Register event listeners which need to be registered datasource. 
       * @private
       */
      _registerDataSourceEventListeners: function()
      {
        // register the listeners on the datasource
        var data = this._getData();
        if (data != null)
        {
          this._unregisterDataSourceEventListeners();
          
          this._dataSourceEventHandlers = [];
          this._dataSourceEventHandlers.push({'eventType': oj.TableDataSource.EventType['REQUEST'], 'eventHandler': this._handleDataFetchStart.bind(this)});
          this._dataSourceEventHandlers.push({'eventType': oj.TableDataSource.EventType['SYNC'], 'eventHandler': this._handleDataFetchEnd.bind(this)});
          this._dataSourceEventHandlers.push({'eventType': oj.TableDataSource.EventType['SORT'], 'eventHandler': this._handleDataSort.bind(this)});
          this._dataSourceEventHandlers.push({'eventType': oj.TableDataSource.EventType['ADD'], 'eventHandler': this._handleDataRowAdd.bind(this)});
          this._dataSourceEventHandlers.push({'eventType': oj.TableDataSource.EventType['REMOVE'], 'eventHandler': this._handleDataRowRemove.bind(this)});
          this._dataSourceEventHandlers.push({'eventType': oj.TableDataSource.EventType['CHANGE'], 'eventHandler': this._handleDataRowChange.bind(this)});
          this._dataSourceEventHandlers.push({'eventType': oj.TableDataSource.EventType['REFRESH'], 'eventHandler': this._handleDataRefresh.bind(this)});
          this._dataSourceEventHandlers.push({'eventType': oj.TableDataSource.EventType['RESET'], 'eventHandler': this._handleDataReset.bind(this)});
          this._dataSourceEventHandlers.push({'eventType': oj.TableDataSource.EventType['ERROR'], 'eventHandler': this._handleDataError.bind(this)});

          var i;
          var ev;
          for (i = 0; i < this._dataSourceEventHandlers.length; i++) {
            ev = data.on(this._dataSourceEventHandlers[i]['eventType'], this._dataSourceEventHandlers[i]['eventHandler']);
            if (ev) {
                this._dataSourceEventHandlers[i]['eventHandler'] = ev;
            }
          }
        }
      },
      /**
       * Register event listeners which need to be registered directly on
       * the DOM element.
       * @private
       */
      _registerDomEventListeners: function()
      {
        if (this._getTableDomUtils().getScroller() != null)
        {
          // if width or height is defined then we can have scrollbars so register scroll event listeners
          this._getTableDomUtils().getScroller().scroll((function(event) {
            var scrollLeft = $(event.target).scrollLeft();
            var maxScrollLeft = $(event.target)[0].scrollWidth - $(event.target)[0].clientWidth;

            this._handleScrollerScrollLeft(scrollLeft, maxScrollLeft);
          }).bind(this));
        }
      },
      /**
       * Register the DOM Scroller. 
       * @private
       */
      _registerDomScroller: function()
      {
        var self = this;
        this._domScrollerMaxCountFunc = function(result)
        {
          if (result != null)
          {
            if (result['maxCountLimit'])
            {
              self._handleScrollerMaxRowCount();
            }
          }
        };
        this._domScroller = new oj.DomScroller(this._getTableDomUtils().getScroller(), this._data, {'fetchSize' : this.options['scrollPolicyOptions']['fetchSize'], 'maxCount' : this.options['scrollPolicyOptions']['maxCount'], 'success': this._domScrollerMaxCountFunc});
      },
      /**
       * Register event listeners for resize the container DOM element.
       * @param {jQuery} element  DOM element
       * @private
       */
      _registerResizeListener: function(element)
      {         
        if (!this._isResizeListenerAdded)
        {
          var self = this;
          oj.DomUtils.addResizeListener(element[0], function(width, height)
                                                    {
                                                      var tableContainerHeight = self._getTableDomUtils().getTableContainer().outerHeight();
                                                      var tableContainerWidth = self._getTableDomUtils().getTableContainer().outerWidth();
                                                      self._getTableDomUtils().refreshTableDimensions(tableContainerWidth, tableContainerHeight);
                                                    }, 50);
          this._isResizeListenerAdded = true;
        }
      },
      /**
       * Remove a keyCode from our internal list of pressed keys. This is done on keyup.
       * @private
       */
      _removeKeyboardKey: function(keyCode)
      {
        var keyboardKeys = this._getKeyboardKeys();
        var i;
        for (i = 0; i < keyboardKeys.length; i++)
        {
          if (keyboardKeys[i] == keyCode)
          {
            keyboardKeys.splice(i, 1);
          }
        }
      },

      /**
       * Scroll column into viewport
       * @param {number} columnIdx  row index
       * @private
       */
      _scrollColumnIntoViewport: function(columnIdx)
      {
        var isRTL = (this._GetReadingDirection() === "rtl");
        var tableBody = this._getTableDomUtils().getTableBody();
        var tableHeaderColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);

        if (!tableHeaderColumn)
        {
          return;
        }

        var scrollbarWidth = this._getTableDomUtils().getScrollbarWidth();
        var headerColumnRect = tableHeaderColumn.get(0).getBoundingClientRect();
        var tableBodyRect = tableBody.get(0).getBoundingClientRect();

        var scrolledLeft = false;
        
        if (isRTL)
        {
          if (headerColumnRect.left < tableBodyRect.left + scrollbarWidth)
          {
            var scrollLeftDiff = tableBodyRect.left - headerColumnRect.left + scrollbarWidth;
            if (!this._getTableDomUtils()._isIE())
            {
              scrollLeftDiff = -1 * scrollLeftDiff;
            }
            tableBody.scrollLeft(tableBody.scrollLeft() + scrollLeftDiff);
            scrolledLeft = true;
          }

          if (headerColumnRect.right > tableBodyRect.right && !scrolledLeft)
          {
            var scrollLeftDiff = headerColumnRect.right - tableBodyRect.right;
            if (!this._getTableDomUtils()._isIE())
            {
              scrollLeftDiff = -1 * scrollLeftDiff;
            }
            tableBody.scrollLeft(tableBody.scrollLeft() - scrollLeftDiff);
          }
        }
        else
        {
          if (headerColumnRect.left < tableBodyRect.left)
          {
            var scrollLeftDiff = tableBodyRect.left - headerColumnRect.left;
            tableBody.scrollLeft(tableBody.scrollLeft() - scrollLeftDiff);
            scrolledLeft = true;
          }

          if (headerColumnRect.right > tableBodyRect.right - scrollbarWidth && !scrolledLeft)
          {
            var scrollLeftDiff = headerColumnRect.right - tableBodyRect.right + scrollbarWidth;
            tableBody.scrollLeft(tableBody.scrollLeft() + scrollLeftDiff);
          }
        }
      },
      /**
       * Scroll row into viewport
       * @param {number} rowIdx  row index
       * @private
       */
      _scrollRowIntoViewport: function(rowIdx)
      {

        var tableBodyRow = this._getTableDomUtils().getTableBodyRow(rowIdx);
        var scrollbarHeight = this._getTableDomUtils().getScrollbarHeight();
        var rowRect = tableBodyRow.get(0).getBoundingClientRect();
        var scrollingElement = this._getTableDomUtils().getScroller();
        var scrollingElementRect = scrollingElement.get(0).getBoundingClientRect();

        var scrolledDown = false;
        if (rowRect.bottom > scrollingElementRect.bottom - scrollbarHeight)
        {
          var scrollTopDiff = rowRect.bottom - scrollingElementRect.bottom + scrollbarHeight;
          scrollingElement.scrollTop(scrollingElement.scrollTop() + scrollTopDiff);
          scrolledDown = true;
        }

        if (rowRect.top < scrollingElementRect.top && !scrolledDown)
        {
          var scrollTopDiff = scrollingElementRect.top - rowRect.top;
          scrollingElement.scrollTop(scrollingElement.scrollTop() - scrollTopDiff);
        }

      },
      /**
       * Update the current row. If called with null then resets the currentRow.
       * If index/key argument is specified then sets the current row. A beforecurrentrow 
       * event is fired before the current row is changed. If that event results in
       * an error then the current row will not be changed.
       * @param {Object} currentRow current row
       * @param {Object} event
       * @param {Object} optionChange whether it was invoked through an optionChange call
       * @return {boolean} whether setting the current row was successful
       * @throws {Error}
       * @private
       */
      _setCurrentRow: function(currentRow, event, optionChange)
      {
        var existingCurrentRow = this._currentRow;
        var tableBodyRow, existingCurrentRowIndex, existingCurrentRowKey, existingCurrentRowIdx;
        var updateCurrentRow = true;
        
        if (currentRow == null)
        {
          if (existingCurrentRow != null)
          {
            try
            {
              updateCurrentRow = this._trigger('beforecurrentrow', event, {'currentRow': null, 'previousCurrentRow': this._currentRow});
            }
            catch (err)
            {
              // caught an error. Do not change current row
              var errSummary = this._LOGGER_MSG._ERR_PRECURRENTROW_ERROR_SUMMARY;
              var errDetail = oj.Translations.applyParameters(this._LOGGER_MSG._ERR_PRECURRENTROW_ERROR_DETAIL, {'error': err.toString()});
              oj.Logger.info(errSummary + '\n' + errDetail);
              // do not update the currentRow to the new value if an exception was caught
              return false;
            }

            if (!updateCurrentRow)
            {
              // do not update the currentRow to the new value if a listener returned false
              return false;
            }

            this._currentRow = null;
            this.option('currentRow', null,  {'_context': {writeback: true, originalEvent:event, internalSet: true}});
            
            if (event == null)
            {
              this._setRowFocus(-1, true, false, null, event);
            }

            existingCurrentRowIndex = existingCurrentRow['rowIndex'];
            existingCurrentRowKey = this._getRowKeyForDataSourceRowIndex(existingCurrentRowIndex);
            existingCurrentRowIdx = this._getRowIdxForRowKey(existingCurrentRowKey);
            tableBodyRow = this._getTableDomUtils().getTableBodyRow(existingCurrentRowIdx);

            if (tableBodyRow != null)
            {
              tableBodyRow.removeClass(oj.TableDomUtils.CSS_CLASSES._TABLE_DATA_CURRENT_ROW_CLASS);
            }
          }

          return true;
        }
        var data = this._getData();
        var rowIndex = currentRow['rowIndex'];
        var rowIdx;
        var rowKey = currentRow['rowKey'];
        if (rowKey == null)
        {
          rowKey = this._getRowKeyForDataSourceRowIndex(rowIndex);
        }
        if (rowIndex == null)
        {
          rowIndex = this._getDataSourceRowIndexForRowKey(rowKey);
        }
        rowIdx = this._getRowIdxForRowKey(rowKey);
        currentRow = {'rowIndex': rowIndex, 'rowKey': rowKey};
          
        if (rowIdx != -1 && 
            (!data || 
            data.totalSize() == 0 ||
            rowIdx < -1 ||
            rowIdx === null ||
            rowKey === null))
        {
          var errSummary = this._LOGGER_MSG._ERR_CURRENTROW_UNAVAILABLE_INDEX_SUMMARY;
          var errDetail = oj.Translations.applyParameters(this._LOGGER_MSG._ERR_CURRENTROW_UNAVAILABLE_INDEX_DETAIL, {'rowIdx': rowIdx});
          if (optionChange)
          {
            // Only throw an Error if the current row was set through option change
            // so that the caller can be notified of the invalid row.
            throw new Error(errSummary + '\n' + errDetail);
          }
          else
          {
            oj.Logger.info(errSummary + '\n' + errDetail);
          }
          // do not update the currentRow
          return false;
        }
        
        var currentFocusedRowIdx = this._getFocusedRowIdx();
        var currentRowChanged = !oj.Object.compareValues(this._currentRow, currentRow);
        
        if (currentRowChanged)
        {
          try
          {
            updateCurrentRow = this._trigger('beforecurrentrow', event, {'currentRow': {'rowIndex': rowIndex, 'rowKey': rowKey}, 'previousCurrentRow': this._currentRow});
          }
          catch (err)
          {
            // caught an error. Do not change current row
            var errSummary = this._LOGGER_MSG._ERR_PRECURRENTROW_ERROR_SUMMARY;
            var errDetail = oj.Translations.applyParameters(this._LOGGER_MSG._ERR_PRECURRENTROW_ERROR_DETAIL, {'error': err.toString()});
            oj.Logger.info(errSummary + '\n' + errDetail);
            // do not update the currentRow to the new value if an exception was caught
            return false;
          }
          
          if (!updateCurrentRow)
          {
            // do not update the currentRow to the new value if a listener returned false
            return false;
          }
          
          this._currentRow = {'rowIndex': rowIndex, 'rowKey': rowKey};
          this.option('currentRow', this._currentRow, {'_context': {writeback: true, originalEvent:event, internalSet: true}});
          tableBodyRow = this._getTableDomUtils().getTableBodyRow(rowIdx);

          if (tableBodyRow != null)
          {
            tableBodyRow.addClass(oj.TableDomUtils.CSS_CLASSES._TABLE_DATA_CURRENT_ROW_CLASS);
          }
          if (existingCurrentRow != null)
          {
            existingCurrentRowIndex = existingCurrentRow['rowIndex'];
            existingCurrentRowKey = this._getRowKeyForDataSourceRowIndex(existingCurrentRowIndex);
            existingCurrentRowIdx = this._getRowIdxForRowKey(existingCurrentRowKey);
            tableBodyRow = this._getTableDomUtils().getTableBodyRow(existingCurrentRowIdx);

            if (tableBodyRow != null)
            {
              tableBodyRow.removeClass(oj.TableDomUtils.CSS_CLASSES._TABLE_DATA_CURRENT_ROW_CLASS);
            }
          }
        }
        
        if (currentRowChanged || currentFocusedRowIdx != currentRow['rowIndex'])
        {
          if (event == null)
          {
            this._setRowFocus(rowIdx, true, false, null, event);
          }
        }
        return true;
      },
      /**
       * Set waiting state and show the Fetching Data... status message.
       * @private
       */
      _setDataWaitingState: function()
      {
        this._showStatusMessage();
        this._hideNoDataMessage();
        this._dataFetching = true;
      },
      /**
       * Set focus on column header
       * @param {number} columnIdx  column index
       * @param {boolean} focused  whether it's focused
       * @param {boolean} clearSelectedRows  whether to clear the selected rows
       * @param {Object} event
       * @private
       */
      _setHeaderColumnFocus: function(columnIdx, focused, clearSelectedRows, event)
      {
        var element = null;
        
        if (event != null)
        {
          element = event.currentTarget;
        }
        
        if (focused)
        {
          var focusedHeaderColumnIdx = this._getFocusedHeaderColumnIdx();
          if (focusedHeaderColumnIdx != null && focusedHeaderColumnIdx != columnIdx)
          {
            this._setHeaderColumnFocus(focusedHeaderColumnIdx, false, false, event);
          }
          // clear focused row
          this._clearFocusedRow(true);
          // clear selected rows
          if (clearSelectedRows)
          {
            this._clearSelectedRows();
          }
          // scroll column into view
          this._scrollColumnIntoViewport(columnIdx);
          this._activeColumnIndex = columnIdx;
          
        }
        this._setHeaderColumnState(columnIdx, {focused: focused}, element);
      },
      /**
       * Set selection on column header
       * @param {number} columnIdx  column index
       * @param {boolean} selected  whether it's focused
       * @param {jQuery} element  DOM element which triggered the column header selection
       * @param {Object} event
       * @param {boolean} updateSelection  whether to update the selection
       * @private
       */
      _setHeaderColumnSelection: function(columnIdx, selected, element, event, updateSelection)
      {
        if (this._getColumnSelectionMode() == this._OPTION_SELECTION_MODES._SINGLE ||
          this._getColumnSelectionMode() == this._OPTION_SELECTION_MODES._MULTIPLE)
        {
          if (isNaN(columnIdx) || columnIdx < 0)
          {
            // validate value
            oj.Logger.error('Error: Invalid column selection value: ' + columnIdx);
          }

          // if we have single selection then clear any existing selections
          if (this._getColumnSelectionMode() == this._OPTION_SELECTION_MODES._SINGLE && selected)
          {
            this._clearSelectedHeaderColumns();
          }
          this._setHeaderColumnState(columnIdx, {selected: selected}, element, event);
          // save it
          this._setLastHeaderColumnSelection(columnIdx, selected);
          
          // update the acc checkbox
          var accSelectionColumn = this._getTableDomUtils().getTableHeaderColumnAccSelect(columnIdx);
          var accSelectCheckbox = $(accSelectionColumn.children('.' + oj.TableDomUtils.CSS_CLASSES._CHECKBOX_ACC_SELECT_COLUMN_CLASS)[0]);
          accSelectCheckbox.prop('checked', selected);
          
          if (updateSelection)
          {
            var selection = this._getSelection();
            var existingSelection = this['options']['selection'];
            this.option('selection', selection, {'_context': {writeback: true, internalSet: true}});
          }
        }
      },
      /**
       * Set the state of the column header. e.g., focused, selected, etc.
       * @param {number} columnIdx  column index
       * @param {Object} state  Object which contains whether it's focused or selected
       * @param {jQuery} element  DOM element which triggered the column header state
       * @param {Object} event
       * @private
       */
      _setHeaderColumnState: function(columnIdx, state, element, event)
      {
        var headerColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);

        if (!headerColumn)
        {
          return;
        }

        var focused = state.focused;
        var selected = state.selected;

        if (selected != null)
        {
          var headerColumnSelected = headerColumn.hasClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);

          if (headerColumnSelected != selected)
          {
            if (!selected)
            {
              headerColumn.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
            }
            else
            {
              headerColumn.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
            }
          }
        }
        if (focused != null)
        {
          if (!focused)
          {
            headerColumn.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._FOCUS);
            this._hideTableHeaderColumnSortLink(columnIdx, true);
            this._hideTableHeaderColumnSortLink(columnIdx, false);
          }
          else
          {
            headerColumn.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._FOCUS);
            this._showTableHeaderColumnSortLink(columnIdx);
          }
        }
        this._updateHeaderColumnCellsClass(columnIdx);
      },
      /**
       * Set the last column which was selected (chronologically)
       * @param {number} columnIdx  column index
       * @param {boolean} selected  whether it's selected
       * @private
       */
      _setLastHeaderColumnSelection: function(columnIdx, selected)
      {
        if (!this._lastSelectedColumnIdxArray)
        {
          this._lastSelectedColumnIdxArray = [];
        }

        var i;
        for (i = 0; i < this._lastSelectedColumnIdxArray.length; i++)
        {
          if (this._lastSelectedColumnIdxArray[i] == columnIdx)
          {
            this._lastSelectedColumnIdxArray.splice(i, 1);
            break;
          }
        }

        if (selected)
        {
          this._lastSelectedColumnIdxArray.push(columnIdx);
        }
      },
      /**
       * Set the last row which was selected (chronologically)
       * @param {number} rowIdx  row index
       * @param {boolean} selected  whether it's selected
       * @private
       */
      _setLastRowSelection: function(rowIdx, selected)
      {
        if (!this._lastSelectedRowIdxArray)
        {
          this._lastSelectedRowIdxArray = [];
        }

        for (var i = 0; i < this._lastSelectedRowIdxArray.length; i++)
        {
          if (this._lastSelectedRowIdxArray[i] == rowIdx)
          {
            this._lastSelectedRowIdxArray.splice(i, 1);
            break;
          }
        }

        if (selected)
        {
          this._lastSelectedRowIdxArray.push(rowIdx);
        }
      },
      /**
       * Set focus on row
       * @param {number} rowIdx  row index
       * @param {boolean} focused  whether it's focused
       * @param {boolean} updateCurrentRow  whether to update the currentRow
       * @param {jQuery} element  DOM element which triggered the row focus
       * @param {Object} event
       * @return {boolean} whether setting the row focus was successful
       * @private
       */
      _setRowFocus: function(rowIdx, focused, updateCurrentRow, element, event)
      {
        if (rowIdx == -1)
        {
          var focusedRowIdx = this._getFocusedRowIdx();
          if (focusedRowIdx != null)
          {
            this._setRowFocus(focusedRowIdx, false, updateCurrentRow, null, null);
          }
          if (updateCurrentRow)
          {
            var updateRowFocus = this._setCurrentRow(null, event);

            if (!updateRowFocus)
            {
              return false;
            }
          }
          return true;
        }
        var tableBodyRow = this._getTableDomUtils().getTableBodyRow(rowIdx);
        
        if (!tableBodyRow)
        {
          return false;
        }

        if (focused)
        {
          var focusedRowIdx = this._getFocusedRowIdx();
          if (focusedRowIdx != null && focusedRowIdx != rowIdx)
          {
            this._setRowFocus(focusedRowIdx, false, updateCurrentRow, element, null);
          }
          
          if (updateCurrentRow)
          {
            var rowKey = this._getRowKeyForRowIdx(rowIdx);
            var updateRowFocus = this._setCurrentRow({'rowKey': rowKey}, event);

            if (!updateRowFocus)
            {
              return false;
            }
          }
        
          tableBodyRow.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._FOCUS);
          this._scrollRowIntoViewport(rowIdx);
          // clear any hover on the row
          this._updateRowCellsClass(rowIdx, {focused: true, hover: false});
          // clear any focused column header
          this._clearFocusedHeaderColumn();
          // clear any selected column header
          this._clearSelectedHeaderColumns();
          // set to table navigation mode
          this._setTableNavigationMode(true);
        }
        else
        {
          tableBodyRow.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._FOCUS);
        }
        // update focus style for the cells
        this._updateRowCellsClass(rowIdx, {focused: focused});
        
        return true;
      },
      /**
       * Set selection on row
       * @param {number} rowIdx  column index
       * @param {boolean} selected  whether it's selected
       * @param {jQuery} element  DOM element which triggered the row selection
       * @param {Object} event
       * @param {boolean} updateSelection  whether to update the selection
       * @private
       */
      _setRowSelection: function(rowIdx, selected, element, event, updateSelection)
      {
        if (this._getRowSelectionMode() == this._OPTION_SELECTION_MODES._SINGLE ||
          this._getRowSelectionMode() == this._OPTION_SELECTION_MODES._MULTIPLE)
        {
          if (isNaN(rowIdx) || rowIdx < 0)
          {
            // validate value
            oj.Logger.error('Error: Invalid row selection value: ' + rowIdx);
          }

          // if we have single selection then clear any existing selections
          if (this._getRowSelectionMode() == this._OPTION_SELECTION_MODES._SINGLE && selected)
          {
            this._clearSelectedRows();
          }
          var tableBodyRow = this._getTableDomUtils().getTableBodyRow(rowIdx);
          
          if (tableBodyRow != null)
          {
            var selectionChanged = false;
            var rowSelected = tableBodyRow.hasClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);

            if (rowSelected != selected)
            {
              if (!selected)
              {
                tableBodyRow.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
              }
              else
              {
                tableBodyRow.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
              }
              selectionChanged = true;
            }

            if (selectionChanged)
            {
              // if selection was set then we want to override
              // the default style precedence
              if (selected)
              {
                this._updateRowCellsClass(rowIdx, {hover: false, focused: false, selected: true});
              }
              else
              {
                this._updateRowCellsClass(rowIdx, {selected: false});
              }
            }
            // save it
            this._setLastRowSelection(rowIdx, selected);

            // update the acc checkbox
            var accSelectionCell = this._getTableDomUtils().getTableBodyCellAccSelect(tableBodyRow);
            var accSelectCheckbox = $(accSelectionCell.children('.' + oj.TableDomUtils.CSS_CLASSES._CHECKBOX_ACC_SELECT_ROW_CLASS)[0]);
            accSelectCheckbox.prop('checked', selected);

            if (updateSelection)
            {
              var selection = this._getSelection();
              this.option('selection', selection, {'_context': {writeback: true, internalSet: true}});
            }
          }
        }
      },
      /**
       * Sets selection
       * @param {Object} selection
       * @private	 
       */
      _setSelection: function(selection)
      { 
        if (selection == null)
        {
          this._clearSelectedRows();
          this._clearSelectedHeaderColumns();
          return;
        }

        // we need to set the selection
        var i, j, rangeObj, startRowKey, endRowKey, startRowIndex, endRowIndex, startRowIdx, endRowIdx, startColumnIdx, endColumnIdx;
        for (i = 0; i < selection.length; i++)
        {
          rangeObj = selection[i];

          if ((rangeObj['startKey'] == null && rangeObj['startIndex'] == null) || 
              (rangeObj['endKey'] == null && rangeObj['endIndex'] == null))
          {
            oj.Logger.error('Error: Invalid range object in selection. Both start and end objects must be specified');
            return null;
          }
          
          startRowKey = null;
          endRowKey = null; 
          startRowIndex = null;
          endRowIndex = null;
          startRowIdx = null;
          endRowIdx = null;
          startColumnIdx = null;
          endColumnIdx = null;

          // if keys are specified, we get the index from the key
          if (rangeObj['startKey'] != null && rangeObj['startKey']['row'] != null)
          {
            startRowIndex = this._getDataSourceRowIndexForRowKey(rangeObj['startKey']['row']);
          }
          if (rangeObj['endKey'] != null && rangeObj['endKey']['row'] != null)
          {
            endRowIndex = this._getDataSourceRowIndexForRowKey(rangeObj['endKey']['row']);
          }
          if (rangeObj['startKey'] != null && rangeObj['startKey']['column'] != null)
          {
            startColumnIdx = this._getColumnIdxForColumnKey(rangeObj['startKey']['column']);
          }
          if (rangeObj['endKey'] != null && rangeObj['endKey']['column'] != null)
          {
            endColumnIdx = this._getColumnIdxForColumnKey(rangeObj['endKey']['column']);
          }

          if (startRowIndex == null && rangeObj['startIndex'] != null)
          {
            startRowIndex = rangeObj['startIndex']['row'];
          }
          if (endRowIndex == null && rangeObj['endIndex'] != null)
          {
            endRowIndex = rangeObj['endIndex']['row'];
          }
          if (startColumnIdx == null && rangeObj['startIndex'] != null)
          {
            startColumnIdx = rangeObj['startIndex']['column'];
          }
          if (endColumnIdx == null && rangeObj['endIndex'] != null)
          {
            endColumnIdx = rangeObj['endIndex']['column'];
          }

          if (startRowIndex != null && endRowIndex != null && startColumnIdx != null && endColumnIdx != null)
          {
            oj.Logger.error('Error: Invalid range object in selection - Can only support row or column selection. Not both');
            return null;
          }
          if (startRowIndex != null && startRowIndex >= 0 && endRowIndex != null && endRowIndex >= 0)
          {
            // this is a row based selection
            startRowKey = this._getRowKeyForDataSourceRowIndex(startRowIndex);
            endRowKey = this._getRowKeyForDataSourceRowIndex(endRowIndex);
            startRowIdx = this._getRowIdxForRowKey(startRowKey);
            endRowIdx = this._getRowIdxForRowKey(endRowKey);
            for (j = startRowIdx; j <= endRowIdx; j++)
            {
              try
              {
                this._setRowSelection(j, true, null, null, false);
              }
              catch (e)
              {
                oj.Logger.error('Error: ' + e);
              }
            }
          }
          else if (startColumnIdx != null && endColumnIdx != null)
          {
            // this is a column based selection
            for (j = startColumnIdx; j <= endColumnIdx; j++)
            {
              try
              {
                this._setHeaderColumnSelection(j, true, null, null, false);
              }
              catch (e)
              {
                oj.Logger.error('Error: ' + e);
              }
            }
          }
          else
          {
            oj.Logger.error('Error: Invalid range object in selection - \n start row: ' + startRowIndex + '\n' + 'end row: ' + endRowIndex+ '\n' + 'start column: ' + startColumnIdx + '\n' + 'end column: ' + endColumnIdx);
            return null;
          }
        }
      },
      /**
       * Set whether the component is in table navigation mode
       * @param {boolean} value true or false
       * @private
       */
      _setTableNavigationMode: function(value)
      {
        this._tableNavMode = value;
      },
      /**
       * Show the inline message.
       * @param {string} summary
       * @param {string} detail
       * @param {number} severityLevel
       * @private
       */
      _showInlineMessage: function(summary, detail, severityLevel)
      {
        this._getTableDomUtils().setTableInlineMessage(summary, detail, severityLevel);
        var inlineMessage = this._getTableDomUtils().getTableInlineMessage();
        
        if (inlineMessage.css(oj.TableDomUtils.CSS_PROP._DISPLAY) == oj.TableDomUtils.CSS_VAL._NONE)
        {
          inlineMessage.css(oj.TableDomUtils.CSS_PROP._DISPLAY, oj.TableDomUtils.CSS_VAL._BLOCK);
          // set the table container margin to add space to display the message
          var inlineMessagePaddingLeft = parseInt(inlineMessage.css(oj.TableDomUtils.CSS_PROP._PADDING_LEFT), 10);
          var inlineMessagePaddingRight = parseInt(inlineMessage.css(oj.TableDomUtils.CSS_PROP._PADDING_RIGHT), 10);
          var tableContainer = this._getTableDomUtils().getTableContainer();
          if (severityLevel == oj.Message.SEVERITY_LEVEL['WARNING'])
          {
            tableContainer.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._WARNING);
            inlineMessage.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._WARNING);
          }
          var tableContainerWidth = tableContainer.width();
          inlineMessage.css(oj.TableDomUtils.CSS_PROP._WIDTH, tableContainerWidth - inlineMessagePaddingLeft - inlineMessagePaddingRight + oj.TableDomUtils.CSS_VAL._PX);
          var inlineMessageHeight = inlineMessage.outerHeight();
          var tableContainerBorderBottom = parseInt(tableContainer.css(oj.TableDomUtils.CSS_PROP._BORDER_BOTTOM_WIDTH), 10);
          var tableContainerBorderLeft = parseInt(tableContainer.css(oj.TableDomUtils.CSS_PROP._BORDER_LEFT_WIDTH), 10);
          var tableContainerMarginBottom = parseInt(tableContainer.css(oj.TableDomUtils.CSS_PROP._MARGIN_BOTTOM), 10);
          tableContainerMarginBottom = tableContainerMarginBottom + tableContainerBorderBottom + inlineMessageHeight;
          tableContainer.css(oj.TableDomUtils.CSS_PROP._MARGIN_BOTTOM, tableContainerMarginBottom + oj.TableDomUtils.CSS_VAL._PX);
          inlineMessage.css(oj.TableDomUtils.CSS_PROP._BOTTOM, -1 * (tableContainerMarginBottom + tableContainerBorderBottom) + oj.TableDomUtils.CSS_VAL._PX);
          inlineMessage.css(oj.TableDomUtils.CSS_PROP._LEFT, -1 * tableContainerBorderLeft + oj.TableDomUtils.CSS_VAL._PX);
        }
      },
      /**
       * Show the 'No data to display.' message.
       * @private
       */
      _showNoDataMessage: function()
      {
        this._getTableDomUtils().createTableNoDataRow(this._getColumnDefs().length);
      },
      /**
       * Show the Fetching Data... status message.
       * @private
       */
      _showStatusMessage: function()
      {
        var statusMessage = this._getTableDomUtils().getTableStatusMessage();
        statusMessage.css(oj.TableDomUtils.CSS_PROP._DISPLAY, oj.TableDomUtils.CSS_VAL._INLINE);
        this._getTableDomUtils().refreshTableStatusPosition();
      },
      /**
       * Show the column header sort link
       * @param {number} columnIdx  column index
       * @private
       */
      _showTableHeaderColumnSortLink: function(columnIdx)
      {
        if (this._getColumnDefs()[columnIdx]['sortable'] == this._OPTION_ENABLED)
        {
          var tableHeaderColumn = this._getTableDomUtils().getTableHeaderColumn(columnIdx);

          if (!tableHeaderColumn)
          {
            return;
          }
          
          // check if the column is currently sorted
          var sorted = tableHeaderColumn.data('sorted');

          // we should only show the ascending sort link if the column is not sorted
          if (sorted == null)
          {
            var headerColumnAscLink = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_ASC_LINK_CLASS);
            headerColumnAscLink.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._ENABLED);
            headerColumnAscLink.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
            var headerColumnAsc = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_ASC_CLASS);
            headerColumnAsc.removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
            var headerColumnDsc = tableHeaderColumn.find('.' + oj.TableDomUtils.CSS_CLASSES._COLUMN_HEADER_DSC_CLASS);
            headerColumnDsc.addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._DISABLED);
          }
        }
      },
      /**
       * Unregister event listeners which are registered on datasource. 
       * @private
       */
      _unregisterDataSourceEventListeners: function()
      {
        var data = this._getData();
        // unregister the listeners on the datasource
        if (this._dataSourceEventHandlers != null && data != null)
        {
          var i;
          for (i = 0; i < this._dataSourceEventHandlers.length; i++)
            data.off(this._dataSourceEventHandlers[i]['eventType'], this._dataSourceEventHandlers[i]['eventHandler']);
        }
      },
      /**
       * Update the css class from all the cells in a column according to column state
       * @param {number} columnIdx  column index
       * @param {boolean} blur  true or false
       * @private
       */
      _updateHeaderColumnCellsClass: function(columnIdx, blur)
      {
        var state = this._getHeaderColumnState(columnIdx);
        var selected = state.selected;
        var selectedRowIdxs = this._getSelectedRowIdxs();
        var tableBodyRows = this._getTableDomUtils().getTableBodyRows();
        if (tableBodyRows != null && tableBodyRows.length > 0)
        {
          var i, j, tableBodyCell, rowSelected;
          for (i = 0; i < tableBodyRows.length; i++)
          {
            tableBodyCell = this._getTableDomUtils().getTableBodyCell(i, columnIdx);
            if (!selected)
            {
              rowSelected = false;
              for (j = 0; j < selectedRowIdxs.length; j++)
              {
                if (i == selectedRowIdxs[j])
                {
                  rowSelected = true;
                  break;
                }
              }
              if (!rowSelected)
              {
                $(tableBodyCell).removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
              }
            }
            else
            {
              $(tableBodyCell).addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
            }
          }
        }
      },
      /**
       * Update the css class from all the cells in a row according to row state
       * @param {number} rowIdx  row index
       * @param {Object} state  row state
       * @param {boolean} blur  true or false
       * @private
       */
      _updateRowCellsClass: function(rowIdx, state, blur)
      {
        var tableBodyCells = this._getTableDomUtils().getTableBodyCells(rowIdx);
        var focused = state.focused;
        var selected = state.selected;
        var hover = state.hover;

        if (!tableBodyCells)
        {
          return;
        }

        if (hover != null)
        {
          var i;
          for (i = 0; i < tableBodyCells.length; i++)
          {
            if (!hover)
            {
              $(tableBodyCells[i]).removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._HOVER);
            }
            else
            {
              $(tableBodyCells[i]).addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._HOVER);
            }
          }
        }
        if (focused != null)
        {
          var i;
          for (i = 0; i < tableBodyCells.length; i++)
          {
            if (!focused)
            {
              $(tableBodyCells[i]).removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._FOCUS);
            }
            else
            {
              $(tableBodyCells[i]).addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._FOCUS);
            }
          }
        }
        if (selected != null)
        {
          var i;
          for (i = 0; i < tableBodyCells.length; i++)
          {
            if (!selected)
            {
              $(tableBodyCells[i]).removeClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
            }
            else
            {
              $(tableBodyCells[i]).addClass(oj.TableDomUtils.MARKER_STYLE_CLASSES._SELECTED);
            }
          }
        }
      },
      _setFinalTask: function(task)
      {
        this._finalTask = (task ? task.bind(this) : undefined);
      },

      _queueTask: function(task)
      {
        var self = this;
        if (!this._pendingTasks)
        {
          this._taskCount = 0;
          this._pendingTasks = Promise.resolve();
        }
        this._taskCount++;
        this._pendingTasks = this._pendingTasks
          .then(task.bind(self))
          .then(function(value)
        {
          self._taskCount--;
          if (self._taskCount == 0)
          {
            self._pendingTasks = undefined;
            if (self._finalTask)
            {
              self._finalTask();
            }
            self._trigger("ready");
          }
          return value;
        },
        function(error)
        {
          self._taskCount--;
          if (self._taskCount == 0)
          {
            self._pendingTasks = undefined;
            oj.Logger.error(error);
          }
          return Promise.reject(error);
        });

        return this._pendingTasks;
      }

      /* Later when needed
      _whenReady: function()
      {
        if (this._pendingTasks)
        {
          return this._pendingTasks;
        }
        return Promise.resolve();
      }
      */
    /**** end internal functions ****/
    }
    //////////////////     FRAGMENTS    //////////////////
    /**
     * <table class="keyboard-table">
     *   <thead>
     *     <tr>
     *       <th>Target</th>
     *       <th>Gesture</th>
     *       <th>Action</th>
     *     </tr>
     *   </thead>
     *   <tbody>
     *     <tr>
     *       <td rowspan="2">Cell</td>
     *       <td><kbd>Tap</kbd></td>
     *       <td>Focus on the row. If <code class="prettyprint">selectionMode</code> for rows is enabled, selects the row as well.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Press & Hold</kbd></td>
     *       <td>Display context menu</td>
     *     </tr>
     *     
     *     <tr>
     *       <td rowspan="2">Column Header</td>
     *       <td><kbd>Tap</kbd></td>
     *       <td>Focus on the header. If <code class="prettyprint">selectionMode</code> for columns is enabled, selects the column as well.</td>
     *     </tr>      
     *     <tr>
     *       <td><kbd>Press & Hold</kbd></td>
     *       <td>Display context menu</td>
     *     </tr>
     *   </tbody>
     * </table>
     *
     * @ojfragment touchDoc - Used in touch section of classdesc, and standalone gesture doc
     * @memberof oj.ojTable
     */    
    
    /**
     * <table class="keyboard-table">
     *   <thead>
     *     <tr>
     *       <th>Target</th>
     *       <th>Key</th>
     *       <th>Action</th>
     *     </tr>
     *   </thead>
     *   <tbody>
     *     <tr>
     *       <td rowspan="11">Cell</td>
     *       <td><kbd>Tab</kbd></td>
     *       <td>Move focus to next focusable element in row.
     *           <br>If focus is on the last focusable element in the row, move focus to first focusable element in next row.
     *           <br>If focus is on the last focusable element in the last row, move focus to next focusable element on the page (outside table).
     *       </td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Shift+Tab</kbd></td>
     *       <td>Move focus to previous focusable element in row.
     *           <br>If focus is on the first focusable element in the row, move focus to last focusable element in previous row.
     *           <br>If focus is on the first focusable element in the first row, move focus to previous focusable element on the page (outside table).
     *       </td>
     *     </tr>
     *     <tr>
     *       <td><kbd>DownArrow</kbd></td>
     *       <td>Move focus to the next row.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Shift+DownArrow</kbd></td>
     *       <td>Select and move focus to the next row.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>UpArrow</kbd></td>
     *       <td>Move focus to the previous row. If at the first row then move to the column header.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Shift+UpArrow</kbd></td>
     *       <td>Select and move focus to the previous row.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>LeftArrow</kbd></td>
     *       <td>Do nothing.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>RightArrow</kbd></td>
     *       <td>Do nothing.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Home</kbd></td>
     *       <td>Move focus to first row.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>End</kbd></td>
     *       <td>Move focus to last row.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Space</kbd></td>
     *       <td>Select row.</td>
     *     </tr>
     *     <tr>
     *       <td rowspan="11">Column Header</td>
     *       <td><kbd>Tab</kbd></td>
     *       <td>Navigate to next focusable element on page (outside table).</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Shift+Tab</kbd></td>
     *       <td>Navigate to previous focusable element on page (outside table).</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>DownArrow</kbd></td>
     *       <td>Move focus to the first row.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>UpArrow</kbd></td>
     *       <td>Do nothing.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>LeftArrow</kbd></td>
     *       <td>Move focus to previous column header.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Shift+LeftArrow</kbd></td>
     *       <td>Select and move focus to previous column header.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>RightArrow</kbd></td>
     *       <td>Move focus to next column header.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Shift+RightArrow</kbd></td>
     *       <td>Select and move focus to next column header.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Home</kbd></td>
     *       <td>Move focus to first column header.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>End</kbd></td>
     *       <td>Move focus to last column header.</td>
     *     </tr>
     *     <tr>
     *       <td><kbd>Space</kbd></td>
     *       <td>Select column.</td>
     *     </tr>
     *   </tbody>
     * </table>
     * @ojfragment keyboardDoc - Used in keyboard section of classdesc, and standalone gesture doc
     * @memberof oj.ojTable
     */
    
    //////////////////     SUB-IDS     //////////////////

    /**
     * <p>Sub-ID for the ojTable component's cells.  See the <a href="#getNodeBySubId">getNodeBySubId</a> 
     * method for details.</p>
     * To lookup a cell the locator object should have the following:
     * <ul>
     * <li><b>subId</b>: 'oj-table-cell'</li>
     * <li><b>rowIndex</b>: the zero based absolute row index</li>
     * <li><b>columnIndex</b>: the zero based absolute column index</li>
     * </ul>       
     * 
     * @ojsubid
     * @member
     * @name oj-table-cell
     * @memberof oj.ojTable
     * @instance
     * 
     * @example <caption>Get the cell at row index 1 and column index 2:</caption>
     * var node = $( ".selector" ).ojTable( "getNodeBySubId", {'subId': 'oj-table-cell', 'rowIndex': 1, 'columnIndex': 2} );
     */

    /**
     * <p>Sub-ID for the ojTable component's headers.  See the <a href="#getNodeBySubId">getNodeBySubId</a> 
     * method for details.</p>
     * 
     *  To lookup a header the locator object should have the following:
     * <ul>
     * <li><b>subId</b>: 'oj-table-header'</li>
     * <li><b>index</b>: the zero based absolute column index.</li>
     * </ul>
     * 
     * @ojsubid
     * @member
     * @name oj-table-header
     * @memberof oj.ojTable
     * @instance
     * 
     * @example <caption>Get the header at the specified location:</caption>
     * var node = $( ".selector" ).ojTable( "getNodeBySubId", {'subId': 'oj-table-header', 'index':0} );
     */
    
    /**
     * <p>Sub-ID for the ojTable component's sort ascending icon in column headers.  See the <a href="#getNodeBySubId">getNodeBySubId</a> 
     * method for details.</p>
     * 
     * To lookup a sort icon the locator object should have the following:
     * <ul>
     * <li><b>subId</b>: 'oj-table-sort-ascending'</li>
     * <li><b>index</b>: the zero based absolute column index</li>
     * </ul>
     * 
     * @ojsubid
     * @member
     * @name oj-table-sort-ascending
     * @memberof oj.ojTable
     * @instance
     * 
     * @example <caption>Get the sort ascending icon from the header at the specified location:</caption>
     * var node = $( ".selector" ).ojTable( "getNodeBySubId", {'subId': 'oj-table-sort-ascending', 'index':0} );
     */ 
    
    /**
     * <p>Sub-ID for the ojTable component's sort descending icon in column headers.  See the <a href="#getNodeBySubId">getNodeBySubId</a> 
     * method for details.</p>
     * 
     * To lookup a sort icon the locator object should have the following:
     * <ul>
     * <li><b>subId</b>: 'oj-table-sort-descending'</li>
     * <li><b>index</b>: the zero based absolute column index</li>
     * </ul>
     * 
     * @ojsubid
     * @member
     * @name oj-table-sort-descending
     * @memberof oj.ojTable
     * @instance
     * 
     * @example <caption>Get the sort descending icon from the header at the specified location:</caption>
     * var node = $( ".selector" ).ojTable( "getNodeBySubId", {'subId': 'oj-table-sort-descending', 'index':0} );
     */
    
    /**
     * <p>Sub-ID for the ojTable component's footers.  See the <a href="#getNodeBySubId">getNodeBySubId</a> 
     * method for details.</p>
     * 
     *  To lookup a footer the locator object should have the following:
     * <ul>
     * <li><b>subId</b>: 'oj-table-footer'</li>
     * <li><b>index</b>: the zero based absolute column index.</li>
     * </ul>
     * 
     * @ojsubid
     * @member
     * @name oj-table-footer
     * @memberof oj.ojTable
     * @instance
     * 
     * @example <caption>Get the header at the specified location:</caption>
     * var node = $( ".selector" ).ojTable( "getNodeBySubId", {'subId': 'oj-table-footer', 'index':0} );
     */
  )
}());