Source: src/main/javascript/oracle/oj/ojdatagrid/ArrayDataGridDataSource.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

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

/**
 * An array based implementation of the DataGridDataSource.
 * @param {Array|Object} data the data in the form of array or observable array.
 * @param {Object=} options the options specific to this DataGridDataSource.
 * @param {Array=} options.columns an array of columns to return as column headers.
 * @param {string=} options.rowHeader the key to the header designated as the row header.
 * @export
 * @constructor
 * @extends oj.DataGridDataSource
 */
oj.ArrayDataGridDataSource = function(data, options)
{
    var errSummary, errDetail;
    if (!(data instanceof Array) &&
            (typeof (data) != 'function' &&
                    typeof (data.subscribe) != 'function'))
    {
        // we only support Array or ko.observableArray. To
        // check for observableArray, we can't do instanceof check because it's
        // a function. So we just check if it contains a subscribe function.
        errSummary = '_ERR_DATA_INVALID_TYPE_SUMMARY';
        errDetail = '_ERR_DATA_INVALID_TYPE_DETAIL';
        throw new Error(errSummary + '\n' + errDetail);
    }
    if (options != null)
    {
        this.rowHeaderKey = options['rowHeader'];
        this.columns = options['columns'];
    }

    oj.ArrayDataGridDataSource.superclass.constructor.call(this, data);
};

// Subclass from oj.DataGridDataSource
oj.Object.createSubclass(oj.ArrayDataGridDataSource, oj.DataGridDataSource, "oj.ArrayDataGridDataSource");

/**
 * Initial the array based data source.
 * @export
 */
oj.ArrayDataGridDataSource.prototype.Init = function()
{
    // suck out the column definition from data
    if (this.columns == null)
    {
        this.columns = this._getColumnsForScaffolding(this.getDataArray());
    }
    this._initializeRowKeys();

    //if the data is an observable array subscribe to array change notifications
    if (typeof (this.data) == 'function')
    {
        this.data['subscribe'](this._subscribe.bind(this), null, 'arrayChange');
    }

    // call super
    oj.ArrayDataGridDataSource.superclass.Init.call(this);
};

/**
 * Initialize the generated row keys.
 * @private
 */
oj.ArrayDataGridDataSource.prototype._initializeRowKeys = function()
{
    var data;
    data = this.getDataArray();
    for (this.lastKey = 0; this.lastKey < data.length; this.lastKey += 1)
    {
        //inject the row key into the object
        data[this.lastKey]['ojKey'] = this.lastKey.toString();
    }
};

/**
 * Get the column headers from the data, if it is an array of arrays with no row header key set,
 * gets the column number as the column header.
 * @param {Object} data the data to extract the column information.
 * @return {Array} the columns extracted from the data.
 * @private
 */
oj.ArrayDataGridDataSource.prototype._getColumnsForScaffolding = function(data)
{
    var propertyName, columns;
    if ((typeof data.length !== 'number') || data.length === 0)
    {
        return [];
    }

    columns = [];
    for (propertyName in data[0])
    {
        if (data[0].hasOwnProperty(propertyName))
        {
            if (!(this.rowHeaderKey != undefined && propertyName == this.rowHeaderKey))
            {
                columns.push(propertyName);
            }
        }
    }

    return columns;
};

/**
 * Returns the total number of rows or columns.  If the value return is not >= 0 then it is automatically assumed
 * that the total count is unknown.
 * @param {string} axis the axis in which we inquire for the total count.  Valid values are "row" and "column".
 * @return {number} the total number of rows/columns.
 * @export
 */
oj.ArrayDataGridDataSource.prototype.getCount = function(axis)
{
    if (axis === "row")
    {
        return this._size();
    }

    if (axis === "column")
    {
        return this.columns.length;
    }

    return 0;
};

/**
 * Retrieve the data for the header of a specified index.
 * @param {string} axis the axis of the header.  Valid values are "row" and "column".
 * @param {number} index the index in which to get the data.
 * @private
 */
oj.ArrayDataGridDataSource.prototype._getHeaderData = function(axis, index)
{
    var data;
    if (axis === 'row')
    {
        data = this.getDataArray();
        // if row header is specified
        if (this.rowHeaderKey != undefined)
        {
            return data[index][this.rowHeaderKey];
        }
        else if (data.length > 0 && data[0] instanceof Array)
        {
            // generate default row header for two dimensional array
            if (this._getRowKeyByIndex(index) === undefined)
            {
                return index.toString();
            }
            else
            {
                return this._getRowKeyByIndex(index);
            }
        }
        else
        {
            return null;
        }
    }
    else if (axis === 'column')
    {
        return this.columns[index];
    }
};

/**
 * Retrieve the metadata for the header of a specified index.
 * @param {string} axis the axis of the header.  Valid values are "row" and "column".
 * @param {number} index the index in which to get the metadata.
 * @private
 */
oj.ArrayDataGridDataSource.prototype._getHeaderMetadata = function(axis, index)
{
    if (axis === 'row')
    {
        if (this.rowHeaderKey != undefined)
        {
            return {'key': this._getRowKeyByIndex(index)};
        }
    }

    return {'key': this._getHeaderData(axis, index)};
};

/**
 * Fetch a range of headers from the data source.
 * @param {Object} headerRange information about the header range, it must contain the following properties:
 *        axis, start, count.
 * @param {string} headerRange.axis the axis of the header that are fetched.  Valid values are "row" and "column".
 * @param {number} headerRange.start the start index of the range in which the header data are fetched.
 * @param {number} headerRange.count the size of the range in which the header data are fetched.  
 * @param {Object} callbacks the callbacks to be invoke when fetch headers operation is completed.  The valid callback
 *        types are "success" and "error".
 * @param {function(HeaderSet)} callbacks.success the callback to invoke when fetch headers completed successfully.
 * @param {function({status: Object})} callbacks.error the callback to invoke when fetch cells failed.
 * @param {Object=} callbackObjects the object in which the callback function is invoked on.  This is optional.  
 *        You can specify the callback object for each callbacks using the "success" and "error" keys.
 * @export
 */
oj.ArrayDataGridDataSource.prototype.fetchHeaders = function(headerRange, callbacks, callbackObjects)
{
    var axis, start, count, end, headerSet, data;

    axis = headerRange.axis;
    start = headerRange.start;
    count = headerRange.count;

    oj.Assert.assert(axis === 'row' || axis === 'column');
    oj.Assert.assert(start < this.getCount(axis));
    oj.Assert.assert(count > 0);

    start = Math.max(0, start);
    if (axis === "column")
    {
        end = Math.min(this.columns.length, start + count);
    }
    else
    {
        data = this.getDataArray();
        // check if no row header is available
        if (this.rowHeaderKey === undefined && !(data.length > 0 && data[0] instanceof Array))
        {
            // header count = 0
            end = start;
        }
        else
        {
            end = Math.min(data.length, start + count);
        }
    }
    headerSet = new oj.ArrayHeaderSet(start, end, axis, this);

    if (callbacks != null && callbacks['success'] != null)
    {
        // make sure callbackObjects is not null
        if (callbackObjects == null)
        {
            callbackObjects = {};
        }
        callbacks['success'].call(callbackObjects['success'], headerSet, headerRange);
    }
};

/**
 * Retrieve the data for the cell of a specified indexes.
 * @param {number} row the row index in which to get the data.
 * @param {number} column the column index in which to get the data.
 * @private
 */
oj.ArrayDataGridDataSource.prototype._getCellData = function(row, column)
{
    var col = this.columns[column];
    return this.getDataArray()[row][col];
};

/**
 * Retrieve the metadata for the cell of a specified indexes.
 * @param {number} row the row index in which to get the data.
 * @param {number} column the column index in which to get the data.
 * @private
 */
oj.ArrayDataGridDataSource.prototype._getCellMetadata = function(row, column)
{
    var keys = {"row": this._getRowKeyByIndex(row), "column": this.columns[column]};
    return {"keys": keys};
};

/**
 * Fetch a range of cells from the data source.
 * @param {Array.<Object>} cellRanges Information about the cell range.  A cell range is defined by an array 
 *        of range info for each axis, where each range contains three properties: axis, start, count.
 * @param {string} cellRanges.axis the axis associated with this range where cells are fetched.  Valid 
 *        values are "row" and "column".
 * @param {number} cellRanges.start the start index of the range for this axis in which the cells are fetched.
 * @param {number} cellRanges.count the size of the range for this axis in which the cells are fetched. 
 * @param {Object} callbacks the callbacks to be invoke when fetch cells operation is completed.  The valid callback
 *        types are "success" and "error".
 * @param {function(CellSet)} callbacks.success the callback to invoke when fetch cells completed successfully.
 * @param {function({status: Object})} callbacks.error the callback to invoke when fetch cells failed.
 * @param {Object=} callbackObjects the object in which the callback function is invoked on.  This is optional.  
 *        You can specify the callback object for each callbacks using the "success" and "error" keys.
 * @export
 */
oj.ArrayDataGridDataSource.prototype.fetchCells = function(cellRanges, callbacks, callbackObjects)
{
    var i, cellRange, rowStart, rowEnd, cellSet, colStart, colEnd;

    // extract the start and end row/column info from cellRanges (there should only be two, one for each axis)
    for (i = 0; i < cellRanges.length; i += 1)
    {
        cellRange = cellRanges[i];
        oj.Assert.assert(cellRange['axis'] === 'row' || cellRange['axis'] === 'column');
        oj.Assert.assert(cellRange['start'] < this.getCount(cellRange['axis']));
        oj.Assert.assert(cellRange['count'] > 0);
        if (cellRange['axis'] === "row")
        {
            rowStart = cellRange['start'];
            rowEnd = Math.min(this._size(), rowStart + cellRange['count']);
        }
        else if (cellRange['axis'] === "column")
        {
            colStart = cellRange['start'];
            colEnd = Math.min(this.columns.length, colStart + cellRange['count']);
        }
    }

    // check for errors
    if (rowEnd === undefined || colEnd === undefined)
    {
        if (callbacks != null && callbacks['error'] != null)
        {
            // make sure callbackObjects is not null
            if (callbackObjects == null)
            {
                callbackObjects = {};
            }
            callbacks['error'].call(callbackObjects['error']);
        }
        return;
    }

    cellSet = new oj.ArrayCellSet(rowStart, rowEnd, colStart, colEnd, this);

    if (callbacks != null && callbacks['success'] != null)
    {
        // make sure callbackObjects is not null
        if (callbackObjects == null)
        {
            callbackObjects = {};
        }
        callbacks['success'].call(callbackObjects['success'], cellSet, cellRanges);
    }
};

/**
 * Returns the keys based on the indexes. 
 * @param {Object} indexes the index for each axis
 * @param {string|number|null} indexes.row the index for the row axis
 * @param {string|number|null} indexes.column the index for the column axis
 * @return {Promise} a Promise object which upon resolution will pass in an object containing the keys for each axis
 * @export
 */
oj.ArrayDataGridDataSource.prototype.keys = function(indexes)
{
    var rowIndex = indexes['row'], columnIndex = indexes['column'];
    return new Promise(function(resolve, reject) {
        resolve({"row": this._getRowKeyByIndex(rowIndex), "column": this.columns[columnIndex]});
    }.bind(this));
};

/**
 * Returns the row and column index based on the keys. In a paging case returns the 
 * index on the page, not the absolute index in the array.
 * @param {Object} keys the key for each axis
 * @param {string|number|null} keys.row the key for the row axis
 * @param {string|number|null} keys.column the key for the column axis
 * @return {Promise} a promise object containing the index for each axis
 * @export
 */
oj.ArrayDataGridDataSource.prototype.indexes = function(keys)
{
    var rowKey = keys['row'], columnKey = keys['column'];
    return new Promise(function(resolve, reject) {
        resolve({"row": this._getRowIndexByKey(rowKey), "column": this.columns.indexOf(columnKey)});
    }.bind(this));
};

/**
 * Performs a sort on the data source.
 * @param {Object} criteria the sort criteria. 
 * @param {string} criteria.axis The axis in which the sort is performed, valid values are "row", "column"
 * @param {Object} criteria.key The key that identifies which header to sort
 * @param {string} criteria.direction the sort direction, valid values are "ascending", "descending", "none" (default)
 * @param {Object} callbacks the callbacks to be invoke upon completion of the sort operation.  The callback
 *        properties are "success" and "error".
 * @param {function()} callbacks.success the callback to invoke when the sort completed successfully.  
 * @param {function({status: Object})} callbacks.error the callback to invoke when sort failed.
 * @param {Object=} callbackObjects the object in which the callback function is invoked on.  This is optional.  
 *        You can specify the callback object for each callbacks using the "success" and "error" properties.
 * @export
 */
oj.ArrayDataGridDataSource.prototype.sort = function(criteria, callbacks, callbackObjects)
{
    var sortArray = [], newColumns = [], i, headerIndex, axis, headerKey, direction;

    // make sure callbackObjects is non null
    if (callbacks != null && callbackObjects == null)
    {
        callbackObjects = {};
    }

    // reset sort order if no criteria is specified
    if (criteria == null)
    {
        this._resetSortOrder(callbacks, callbackObjects);
        return;
    }

    axis = criteria['axis'];
    headerKey = criteria['key'];
    direction = criteria['direction'];

    if (axis === 'column')
    {
        // keep a copy of the original unsorted array.  Both array and observable array have slice method.
        if (this.origData == undefined)
        {
            this.origData = this.data.slice();        
        }
        this.getDataArray().sort(this._naturalSort(direction, headerKey));

        if (callbacks != null && callbacks['success'] != null)
        {
            callbacks['success'].call(callbackObjects['success']);
        }
    }
    else if (axis === 'row')
    {
        headerIndex = this._getRowIndexByKey(headerKey);
        //rebuild the array to sort on
        for (i = 0; i < this.columns.length; i += 1)
        {
            sortArray[i] = this.getDataArray()[headerIndex][this.columns[i]];
        }

        //sort the given array with no headerKye specified
        sortArray.sort(this._naturalSort(direction));

        //reorder the columns property
        for (i = 0; i < this.columns.length; i += 1)
        {
            newColumns[i] = this.columns[sortArray.indexOf(this.getDataArray()[headerIndex][this.columns[i]])];
        }

        // keep a copy of the original column order.
        this.origColumns = this.columns;
        this.columns = newColumns;
        if (callbacks != null && callbacks['success'] != null)
        {
            callbacks['success'].call(callbackObjects['success']);
        }
    }
    else
    {
        if (callbacks !== null && callbacks['error'] != null)
        {
            callbacks['error'].call(callbackObjects['error'], "Invalid axis value");
        }
    }
};

/**
 * Reset the sort order of the data.
 * @param {Object} callbacks the callbacks to be invoke upon completion of the sort operation.  The callback
 *        properties are "success" and "error".
 * @param {function()} callbacks.success the callback to invoke when the sort completed successfully.  
 * @param {function({status: Object})} callbacks.error the callback to invoke when sort failed.
 * @param {Object=} callbackObjects the object in which the callback function is invoked on.  This is optional.  
 *        You can specify the callback object for each callbacks using the "success" and "error" properties.
 * @private
 */
oj.ArrayDataGridDataSource.prototype._resetSortOrder = function(callbacks, callbackObjects)
{
    // reset data to the unsorted array
    if (this.origData != null)
    {
        this.data = this.origData;
    }

    // reset column order if row header was sorted before
    if (this.origColumns != null)
    {
        this.columns = this.origColumns;
    }

    if (callbacks != null && callbacks['success'] != null)
    {
        callbacks['success'].call(callbackObjects['success']);
    }
};

/**
 * Determines whether this DataGridDataSource supports certain feature.
 * @param {string} feature the feature in which its capabilities is inquired.  Currently the only valid feature is "sort".
 * @return {string|null} the name of the feature.  For sort, the valid return values are: "full", "none".  Returns null if the
 *         feature is not recognized.
 * @export
 */
oj.ArrayDataGridDataSource.prototype.getCapability = function(feature)
{
    if (feature === 'sort')
    {
        // array based data source supports column sorting only
        return 'column';
    }
    if (feature === 'move')
    {
        return 'row';
    }
    return null;
};

/**
 * Get a comparator fuicntion for natural sorting of objects
 * @param {string} direction ascending, descending 
 * @param {string|number=} key the key or index to perform the sort on
 * @returns {function(Object, Object)|undefined} a comapartor function, dependent on direction
 * @private
 */
oj.ArrayDataGridDataSource.prototype._naturalSort = function(direction, key)
{
    if (direction === 'ascending')
    {
        return function(a, b)
        {
            var as, bs;
            //Get the values the array we're sorting
            if (key != undefined)
            {
                //if the sorting item is an array it will be indexed with strings of ints and needs
                //to be accessed using ints not strings
                if (a instanceof Array)
                {
                    a = a[parseInt(key, 10)];
                    b = b[parseInt(key, 10)];
                }
                else
                {
                    a = a[key];
                    b = b[key];
                }
            }
            //Strings of numbers return false, so we can compare strings of numebers with numbers                
            as = isNaN(a);
            bs = isNaN(b);
            //If they are strings, check to see if they are dates, if they are, turn the string to a sortable date formatted string           
            if (a instanceof Date) {
                a = a.toISOString();
                as = true;
            }
            if (b instanceof Date) {
                b = b.toISOString();
                bs = true;
            }
            //both are string
            if (as && bs)
            {
                return a < b ? -1 : a === b ? 0 : 1;
            }
            //only a is a string
            if (as)
            {
                return 1;
            }
            //only b is a string
            if (bs)
            {
                return -1;
            }
            //both are numbers
            return a - b;
        };
    }
    if (direction === 'descending')
    {
        return function(a, b)
        {
            var as, bs;
            if (key != undefined)
            {
                //if the sorting item is an array it will be indexed with strings of ints and needs
                //to be accessed using ints not strings                
                if (a instanceof Array)
                {
                    a = a[parseInt(key, 10)];
                    b = b[parseInt(key, 10)];
                }
                else
                {
                    a = a[key];
                    b = b[key];
                }
            }
            as = isNaN(a);
            bs = isNaN(b);
            if (a instanceof Date) {
                a = a.toISOString();
                as = true;
            }
            if (b instanceof Date) {
                b = b.toISOString();
                bs = true;
            }
            if (as && bs)
            {
                return a > b ? -1 : a === b ? 0 : 1;
            }
            if (as)
            {
                return -1;
            }
            if (bs)
            {
                return 1;
            }
            return b - a;
        };
    }

    // only if direction is not recognized
    return;
};

/**
 * Moves a row from one location to another.
 * @param {Object} moveKey the key of the row to move
 * @param {Object} atKey the key of the reference row which combined with position are used to determine 
 *        the destination of where the row should moved to.
 * @param {string} position The position of the moved row relative to the reference row.  
 *        Valid values are: "before", "after" 
 * @param {function()} callbacks.success the callback to invoke when the move completed successfully.  
 * @param {function({status: Object})} callbacks.error the callback to invoke when move failed.
 * @param {Object=} callbackObjects the object in which the callback function is invoked on.  This is optional.  
 *        You can specify the callback object for each callbacks using the "success" and "error" properties.
 * @export
 */
oj.ArrayDataGridDataSource.prototype.move = function(moveKey, atKey, position, callbacks, callbackObjects)
{
    var moveKeyIndex, moveData, atKeyIndex, event, data;

    //remove the data from the array, but hold on to it
    moveKeyIndex = this._getRowIndexByKey(moveKey);
    moveData = this.data.splice(moveKeyIndex, 1)[0];

    //fire the delete event to the datagrid
    if (this.data instanceof Array)
    {
        event = this._getModelEvent('delete', moveKey, null, moveKeyIndex, -1, true);
        this.handleEvent("change", event);
    }

    //add the stored data back into the array
    if (atKey === null)
    {
        this.data.push(moveData);
    }
    else
    {
        atKeyIndex = this._getRowIndexByKey(atKey);
        this.data.splice(atKeyIndex, 0, moveData);
    }

    //fire the insert event to the datagrid
    if (this.data instanceof Array)
    {
        event = this._getModelEvent('insert', moveKey, null, atKeyIndex, -1);        
        this.handleEvent("change", event);
    }

    // if we keep track of original data, we'll need to update it
    if (this.origData != null)
    {
       // note that once a row is moved then the current sort order is the new unsorted order
       this.origData = this.data.slice();
    }
};

/**
 * Gets the data array, if the data property is a function call it, else return data
 * @return {Object|Array} the array of the data
 * @private
 */
oj.ArrayDataGridDataSource.prototype.getDataArray = function()
{
    if (typeof (this.data) === 'function')
    {
        return this.data();
    }
    return this.data;
};

/**
 * Gets the row index of a given row key
 * @param {string|number|Object|null} key the key to get row index of
 * @return {number} the index with a certain key, -1 if the key doesn't exist
 * @private
 */
oj.ArrayDataGridDataSource.prototype._getRowIndexByKey = function(key)
{
    var i, data = this.getDataArray();
    for (i = 0; i < data.length; i++)
    {
        if (data[i]['ojKey'] === key)
        {
            return i;
        }
    }
    return -1;
};

/**
 * Gets the row key stored at a given index
 * @param {number} index the index to get row key of
 * @return {string|number|null} the key at index, null if the index doesn't exist
 * @private
 */
oj.ArrayDataGridDataSource.prototype._getRowKeyByIndex = function(index)
{
    var data = this.getDataArray();
    if (data[index])
    {
        return data[index]['ojKey'];
    }
    return null;
};

/**
 * Returns an Object for an event 
 * @param {string} operation the operation done on the model
 * @param {Object|string|number|null} rowKey the key for the row axis
 * @param {Object|string|number|null} columnKey the key for the column axis
 * @param {number=} rowIndex the index for the row axis
 * @param {number=} columnIndex the index for the column axis
 * @param {boolean=} silent should the event be silent
 * @return {Object} an object containing the the source, operation, and keys of the event
 * @private
 */
oj.ArrayDataGridDataSource.prototype._getModelEvent = function(operation, rowKey, columnKey, rowIndex, columnIndex, silent)
{
    var event = {};
    event['source'] = this;
    event['operation'] = operation;
    event['keys'] = {'row': rowKey, 'column': columnKey};
    event['indexes'] = {'row': rowIndex, 'column': columnIndex};
    event['silent'] = silent;
    return event;
};

/**
 * Subscribe to knockout events
 * @param {Array} changes an array of change objects fired by an observable array
 * @private
 */
oj.ArrayDataGridDataSource.prototype._subscribe = function(changes)
{
    var i, rowData, rowKey, rowIndex, added = false, move = false, keys = [], indexes = [], event, beforeDelCount = 0, change;

    // first loop though the changes, 
    for (i = 0; i < changes.length; i++)
    {
        change = changes[i];
        // if a model was moved using a reverseAll or a sort, just refresh the grid
        if (change['moved'] !== undefined)
        {
            move = true;
            event = this._getModelEvent('refresh', null, null);        
            this.handleEvent("change", event);
            break;
        }
        
        // check if there were any adds, this way the delete will know to be fired silently
        if (change['status'] === 'added')
        {
            added = true;
        }
    }
    
    //if we moved a model we just refreshed
    if (!move)
    {        
        //loop through changes looking for deletes
        for (i = 0; i < changes.length; i++)
        {
            change = changes[i];
            if (change['status'] === 'deleted')
            {
                rowData = change['value'];
                rowIndex = change['index'];
                rowKey = rowData['ojKey'];  
                
                //collect the deletes to do in one batch delete
                keys.push({'row': rowKey, 'column': -1});
                indexes.push({'row': rowIndex, 'column': -1});
            }
        }
        
        // batch delete all deletes
        if (keys.length > 0)
        {
            event = {'source': this, 'operation': 'delete', 'keys': keys, 'indexes': indexes, 'silent': added};
            this.handleEvent("change", event);
        }
        
        //loop through changes looking for adds
        for (i = 0; i < changes.length; i++)
        {
            change = changes[i];
            if (change['status'] === 'added')
            {
                rowData = change['value'];   
                rowIndex = change['index'];               
                //if no key add inject one into the add object based on the last assigned key          
                if (rowData['ojKey'] == null)
                {
                    rowData['ojKey'] = this.lastKey.toString();
                    this.lastKey++;
                }
                //add at the given index and remove from the end of the page silently
                rowKey = rowData['ojKey'];
                event = this._getModelEvent('insert', rowKey, null, rowIndex, -1);                            
                this.handleEvent("change", event);
            }
        }
    }    
    
    // if we keep track of original data, we'll need to update it
    if (this.origData != null)
    {
       // note that once the observable array is updated then the current sort order is the new unsorted order
       this.origData = this.data.slice();
    }
};

/**
 * Get the length of the collection. -1 if an initial fetch has not been
 * done yet. Default to the size of the collection. If pageSize is set then
 * limit it.
 * @returns {number} length of the collection
 * @private
 */
oj.ArrayDataGridDataSource.prototype._size = function()
{
    return this.getDataArray()['length'];
};

//////// testing methods to get properties /////////
/**
 * Gets the rowHeaderKey property.  This is an internal method for testing and should not be used by application.
 * @return {string|null} the row header key
 * @export
 * @ignore
 */
oj.ArrayDataGridDataSource.prototype.getRowHeaderKey = function()
{
    return this.rowHeaderKey;
};

/**
 * Gets the columns property.  This is an internal method for testing and should not be used by application.
 * @return {Array|null} the keys of the column headers
 * @export
 * @ignore
 */
oj.ArrayDataGridDataSource.prototype.getColumns = function()
{
    return this.columns;
};

/**
 * Gets the data property.  This is an internal method for testing and should not be used by application.
 * @return {Array|Object|null} the underlying array data.
 * @export
 * @ignore
 */
oj.ArrayDataGridDataSource.prototype.getData = function()
{
    return this.data;
};