/**
* 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} );
*/
)
}());
Source: src/main/javascript/oracle/oj/ojtable/ojtable.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01