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

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/**
 * Copyright (c) 2014, Oracle and/or its affiliates.
 * All rights reserved.
 */
/**
 * @export
 * @class oj.PagingDataGridDataSource
 * @classdesc Object representing data used by the paging component
 * @param {oj.DataGridDataSource|null} dataSource
 * @param {Object|null} options Array of options for the PagingControlDataSource
 * @extends oj.DataGridDataSource
 * @implements oj.PagingModel
 * @constructor
 */
oj.PagingDataGridDataSource = function(dataSource, options)
{
    // Initialize
    if (!(dataSource instanceof oj.DataGridDataSource))
    {
        // we only support Array, oj.Collection, 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.
        throw new oj.Message('Not a datagridatasource', 'Not a datagridatasource', oj.Message.SEVERITY_LEVEL['ERROR']);
    }
    this.dataSource = dataSource;
    this._startIndex = 0;
    this.Init();
};

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

/**
 * Initializes the instance.
 * @export
 */
oj.PagingDataGridDataSource.prototype.Init = function()
{
    oj.PagingDataGridDataSource.superclass.Init.call(this);
    this._registerEventListeners();
};

/**
 * Register event handlers on the underlying datasource.
 * @private
 */
oj.PagingDataGridDataSource.prototype._registerEventListeners = function()
{
    this.dataSource.on("change", this._handleChange.bind(this));
};

/**
 * Get the current page
 * @return {number} The current page
 * @export
 */
oj.PagingDataGridDataSource.prototype.getPage = function()
{
  return this._page;
};

/**
 * Set the current page
 * @param {number} value The current page
 * @param {Object=} options Options<p>
 *                  pageSize: The page size.<p>
 * @export
 */
oj.PagingDataGridDataSource.prototype.setPage = function(value, options)
{
  options = options || {};
  value = parseInt(value, 10);
  
  try 
  {
    oj.PagingDataGridDataSource.superclass.handleEvent.call(this, oj.PagingModel.EventType['BEFOREPAGE'], {'page' : value, 'previousPage' : this._page});
  }
  catch (err)
  {
    return Promise.reject(null);
  }
  
  this._pageSize = options['pageSize'] != null ? options['pageSize'] : this._pageSize;
  options['startIndex'] = value * this._pageSize;
  var previousPage = this._page;
  this._page = value;
  this._startIndex = options['startIndex'];
  var self = this;
  
  return new Promise(function(resolve, reject)
  {
    self._fetchInternal(options).then(function(result)
    {
      resolve(null);
    }, function (error)
    {
      // restore old page
      self._page = previousPage;
      self._startIndex = self._page * self._pageSize;
      reject(null);  
    });
  });
};

/**
 * Calls fetch on the datasource with paging options.
 * @private
 */
oj.PagingDataGridDataSource.prototype._fetchInternal = function(options)
{
    this._initialized = true;
    this._startIndex = options['startIndex'];
    
    var self = this;
    return new Promise(function(resolve, reject) {
        self.handleEvent("change", {'operation': 'sync', 'pageSize': self._pageSize});  
        resolve(undefined);
    });
};

/**
 * Calls fetch on the datasource with paging options.
 * @param {Object=} options Options to control fetch
 * @param {number} options.startIndex The index at which to start fetching records.
 * @param {boolean} options.silent If set, do not fire a sync event.
 * @return {Promise} Promise object resolves when done
 * @export
 * @expose
 * @memberof! oj.PagingDataGridDataSource
 * @instance
 */
oj.PagingDataGridDataSource.prototype.fetch = function(options)
{
    this._pageSize = options['pageSize'] + options['startIndex'];
    options['startIndex'] = 0;
    return this._fetchInternal(options);
};

/**
 * Get the current page start index
 * @return {number} The current page start index
 * @export
 */
oj.PagingDataGridDataSource.prototype.getStartItemIndex = function()
{
  return this._startIndex;
};

/**
 * Get the current page end index
 * @return {number} The current page end index
 * @export
 */
oj.PagingDataGridDataSource.prototype.getEndItemIndex = function()
{
  return this._endIndex;
};

/**
 * Get the page count
 * @return {number} The total number of pages
 * @export
 */
oj.PagingDataGridDataSource.prototype.getPageCount = function()
{
  var totalSize = this.totalSize();
  return totalSize == -1 ? -1 : Math.ceil(totalSize / this._pageSize);
};

/**
 * Handle data grid change events
 * @param {Object} options the options associated with the oj.DataGridDataSource event.
 * @private
 */
oj.PagingDataGridDataSource.prototype._handleChange = function(options) {
    var operation;
    operation = options['operation'];
    switch(operation)
    {
        case 'refresh':
            this._startIndex = 0;
            // pass the refresh event through to the data grid and the paging control
            this.handleEvent("change", {'operation': 'sync', 'pageSize': this._pageSize});  
            this.handleEvent(oj.PagingTableDataSource.EventType['REFRESH'], null);
            break;
        case 'reset':
            // the paging control will set a new startIndex and kick off a fecth here
            this.handleEvent(oj.PagingTableDataSource.EventType['RESET'], null);
            break;
        case 'insert':
            this.handleEvent(oj.PagingTableDataSource.EventType['ADD'], {'index':options['indexes']['row']});
            break;
        case 'delete':
            this.handleEvent(oj.PagingTableDataSource.EventType['REMOVE'], null);
            break;
        case 'update':
            options['indexes']['row'] = options['indexes']['row'] - this._startIndex >= 0 ? options['indexes']['row'] - this._startIndex : -1;
            this.handleEvent("change", options);
            break;
        default:
            this.handleEvent('change', options);
            this.handleEvent(oj.PagingTableDataSource.EventType['SYNC'], null);
    }
};

/**** start delegated functions ****/
/**
 * 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. In the case of paging returns the total number of rows/colums on the page.
 * @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.PagingDataGridDataSource.prototype.getCount = function(axis)
{
    return this.dataSource.getCount(axis);
};

/**
 * Returns whether the total count returned in getCount function is an actual or an estimate.
 * @param {string} axis the axis in which we inquire whether the total count is an estimate.  Valid values are 
 *        "row" and "column".
 * @return {string} "actual" if the count returned in getCount function is the actual count, "estimate" if the 
 *         count returned in getCount function is an estimate.  The default value is "actual".
 * @export
 */
oj.PagingDataGridDataSource.prototype.getCountPrecision = function(axis)
{
    return this.dataSource.getCountPrecision(axis);
};

/**
 * 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.PagingDataGridDataSource.prototype.fetchHeaders = function(headerRange, callbacks, callbackObjects)
{
    var headerSet;
    if (this._initialized == null)
    {
        headerSet = new oj.ArrayHeaderSet(0, 0, headerRange.axis, null);
        if (callbacks != null && callbacks['success'])
        {
            callbacks['success'].call(callbackObjects['success'], headerSet, headerRange);
        }
    }
    else
    {
        if (headerRange['axis'] === 'row')
        {
            headerRange['start'] += this._startIndex;            
            if (headerRange['start'] + headerRange['count'] > this._startIndex + this._pageSize)
            {
                headerRange['count'] = this._pageSize - headerRange['start'];
            }
            this._pendingRowHeaderCallback = {'callbacks': callbacks, 'callbackObjects': callbackObjects}
            this.dataSource.fetchHeaders(headerRange, {success: this._handleRowHeaderFetchSuccess.bind(this), error: this._handleRowHeaderFetchError.bind(this)}, callbackObjects);
        }
        else
        {
            this.dataSource.fetchHeaders(headerRange, callbacks, callbackObjects);
        }
    }
};

/**
 * Handle row headers fetch success by adjusting startIndex back to 0 and passing a PagingHeaderSet
 * @param {Object} headerSet a cellSet object
 * @param {Object} headerRange
 * @private
 */
oj.PagingDataGridDataSource.prototype._handleRowHeaderFetchSuccess = function(headerSet, headerRange)
{
    var pagingHeaderSet;
    headerRange['start'] -= this._startIndex;
    pagingHeaderSet = new oj.PagingHeaderSet(headerSet, this._startIndex)
    this._pendingRowHeaderCallback['callbacks']['success'].call(this._pendingRowHeaderCallback['callbackObjects']['success'], pagingHeaderSet, headerRange);
    this._pendingRowHeaderCallback = null;
};

/**
 * Handle row header fetch error
 * @param {Object} error error
 * @private
 */
oj.PagingDataGridDataSource.prototype._handleRowHeaderFetchError = function(error)
{
    this._pendingRowHeaderCallback['callbacks']['error'].call(this._pendingRowHeaderCallback['callbackObjects']['error'], error);
    this._pendingRowHeaderCallback = null;
};

/**
 * 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.PagingDataGridDataSource.prototype.fetchCells = function(cellRanges, callbacks, callbackObjects)
{
    var cellSet, i;
    if (this._initialized == null)
    {
        cellSet = new oj.ArrayCellSet(0, 0, 0, 0, null);
        if (callbacks != null && callbacks['success'])
        {
            callbacks['success'].call(callbackObjects['success'], cellSet, cellRanges);
        }
    }
    else
    {
        for (i=0; i<cellRanges.length; i+=1)
        {
            if (cellRanges[i]['axis'] === 'row')
            {
                cellRanges[i]['start'] += this._startIndex;
                if (cellRanges[i]['start'] + cellRanges[i]['count'] > this._startIndex + this._pageSize)
                {
                    cellRanges[i]['count'] = this._pageSize - cellRanges[i]['start'];
                }
            }        
        }
        this._pendingCellCallback = {'callbacks': callbacks, 'callbackObjects': callbackObjects}
        this.dataSource.fetchCells(cellRanges, {success: this._handleCellsFetchSuccess.bind(this), error: this._handleCellsFetchError.bind(this)}, callbackObjects);
    }
};

/**
 * Handle cell fetch success by adjusting the row startIndex and passing the PagingCellSet
 * @param {Object} cellSet a cellSet object
 * @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.
 * @private
 */
oj.PagingDataGridDataSource.prototype._handleCellsFetchSuccess = function(cellSet, cellRanges)
{
    var i, pagedCellSet;
    for (i=0; i<cellRanges.length; i+=1)
    {
        if (cellRanges[i]['axis'] === 'row')
        {
            cellRanges[i]['start'] -= this._startIndex;
        }        
    }
    pagedCellSet = new oj.PagingCellSet(cellSet, this._startIndex)
    this._pendingCellCallback['callbacks']['success'].call(this._pendingCellCallback['callbackObjects']['success'], pagedCellSet, cellRanges);
    this._pendingCellCallback = null;
   
    // tell PC fetchEnd
    this._endIndex = this._startIndex + cellSet.getCount('row') - 1;
    this.handleEvent('sync', {'data': new Array(cellSet.getCount('row')), 'startIndex': this._startIndex});
};

/**
 * Handle a cell fetch error
 * @param {Object} error error
 * @private
 */
oj.PagingDataGridDataSource.prototype._handleCellsFetchError = function(error)
{
    this._pendingCellCallback['callbacks']['error'].call(this._pendingCellCallback['callbackObjects']['error'], error);
    this._pendingCellCallback = null;
};

/**
 * Returns the keys based on the indexes. 
 * @param {Object} indexes the index for each axis
 * @param {Object} indexes.row the index for the row axis
 * @param {Object} indexes.column the index for the column axis
 * @return {Object.<Object, Object>} an object containing the keys for each axis
 * @export
 */
oj.PagingDataGridDataSource.prototype.keys = function(indexes)
{
    var pagedIndexes = {'column': indexes['column'], 'row': indexes['row'] + this._startIndex};
    return this.dataSource.keys(pagedIndexes);
};

/**
 * Returns the row and column index based on the keys.
 * @param {Object} keys the key for each axis
 * @param {Object} keys.row the key for the row axis
 * @param {Object} keys.column the key for the column axis
 * @return {Object.<number, number>} indexes an object containing the index for each axis
 * @export
 */
oj.PagingDataGridDataSource.prototype.indexes = function(keys)
{
    var indexes = this.dataSource.indexes(keys);
    if (indexes['row'] != -1)
    {
        indexes['row'] -= this._startIndex;
    }
    return indexes;
};

/**
 * 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.PagingDataGridDataSource.prototype.getCapability = function(feature)
{
    return this.dataSource.getCapability(feature);
};

/**
 * @export
 * Return the size of the data locally in the dataSource. -1 if an initial fetch has not been
 * done yet.
 * @returns {number} size of data
 * @expose
 * @memberof! oj.PagingDataGridDataSource
 * @instance
 */
oj.PagingDataGridDataSource.prototype.size = function()
{
    var count;
    if (this._initialized == null)
    {
        return -1;
    }
    count = this.dataSource.getCount('row')
    if (this.dataSource.getCount('row') > this._pageSize)
    {
        return this._pageSize;
    }
    return count;
};

/**
 * 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.PagingDataGridDataSource.prototype.sort = function(criteria, callbacks, callbackObjects)
{
    this.dataSource.sort(criteria, callbacks, callbackObjects);
};

/**
 * @export
 * Return the total size of data available, including server side if not local.
 * @returns {number} total size of data
 * @expose
 * @memberof! oj.PagingDataGridDataSource
 * @instance
 */
oj.PagingDataGridDataSource.prototype.totalSize = function()
{
    if (this._initialized == null)
    {
        return -1;
    }
    return this.dataSource.getCount('row');
};

/**
 * Move a model to a new index in the collection, if atKey is null adds to the end
 * @param {Object} moveKey the key of the model that should be moved
 * @param {Object} atKey the key of the model that the moved model should be inserted before or after
 * @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.PagingDataGridDataSource.prototype.move = function(moveKey, atKey, position, callbacks, callbackObjects)
{
    this.dataSource.move(moveKey, atKey, position, callbacks, callbackObjects);
};
/**** end delegated functions ****/