Source: src/main/javascript/oracle/oj/ojtable/FlattenedTreeTableDataSource.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

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

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

/*jslint browser: true,devel:true*/
/**
 * @export
 * @class oj.FlattenedTreeTableDataSource
 * @extends oj.TableDataSource
 * @classdesc Object representing data used by the rowexpander component
 * @param {Object} data
 * @param {Object|null} options Array of options for the TreeTableDataSource
 * @constructor
 */
oj.FlattenedTreeTableDataSource = function(data, options)
{
  // Initialize
  options = options || {};

  if (!(data instanceof oj.FlattenedTreeDataSource))
  {
    var errSummary = oj.TableDataSource._LOGGER_MSG['_ERR_DATA_INVALID_TYPE_SUMMARY'];
    var errDetail = oj.TableDataSource._LOGGER_MSG['_ERR_DATA_INVALID_TYPE_DETAIL'];
    throw new Error(errSummary + '\n' + errDetail);
  }

  this._data = data;
  this._eventHandlers = [];
  this._startIndex = 0;
  this._nodeSetList = [];
  
  // override the fetchSize with -1 if not already specified.
  if (this._data.getOption('fetchSize') == null)
  {
    this._data.getFetchSize = function()
    {
      return -1;
    };
  }
  var self = this;
  // override the insert/removeRows function
  this._data.insertRows = function(insertAtIndex, insertAtKey, nodeSet)
  {
    var i, row, rowIdx, rowKey;
    var rowArray = [];
    var keyArray = [];
    var indexArray = [];
    for (i = 0; i < nodeSet.getCount(); i++)
    {
      row = nodeSet.getData(i);
      rowKey = nodeSet.getMetadata(i)['key'];
      rowIdx = insertAtIndex + i;
      self._nodeSetList[rowIdx] = {};
      self._nodeSetList[rowIdx]['nodeSet'] = nodeSet;
      self._nodeSetList[rowIdx]['startIndex'] = insertAtIndex;
      rowArray.push(row);
      keyArray.push(rowKey);
      indexArray.push(rowIdx);
      self._rows['data'].splice(rowIdx, 0, row);
      self._rows['keys'].splice(rowIdx, 0, rowKey);
      self._rows['indexes'].splice(rowIdx, 0, rowIdx);
    }
    if (!self._pageSize)
    {
      // only publish an add event if we are not paged. If we are paged then we re-fetch later anyway
      oj.TableDataSource.superclass.handleEvent.call(self, oj.TableDataSource.EventType['ADD'], {'data': rowArray, 'keys': keyArray, 'indexes': indexArray});
    }
    self._realignRowIndices();
    
    if (self._pageSize)
    {
      setTimeout(function(){
        self.fetch();
      }, 0);
    }
  };
  this._data.removeRows = function(rowKeys)
  {
    var i, row, rowIdx;
    var rowArray = [];
    var keyArray = [];
    var indexArray = [];
    for (i = 0; i < rowKeys.length; i++)
    {
      // indices shift down as we remove
      rowIdx = rowKeys[i].index - i;
      // just create a dummy row for deletion
      rowArray.push('');
      keyArray.push('');
      indexArray.push(rowIdx);
      self._rows['data'].splice(rowIdx, 1);
      self._rows['keys'].splice(rowIdx, 1);
      self._rows['indexes'].splice(rowIdx, 1);
    }
    if (!self._pageSize)
    {
      // only publish an remove event if we are not paged. If we are paged then we re-fetch later anyway
      oj.TableDataSource.superclass.handleEvent.call(self, oj.TableDataSource.EventType['REMOVE'], {'data': rowArray, 'keys': keyArray, 'indexes': indexArray});
    }
    self._realignRowIndices();
    
    if (self._pageSize)
    {
      setTimeout(function(){
        self.fetch();
      }, 0);
    }
  };
  
  this.Init();
  
  if ((options != null && (options['startFetch'] == 'enabled' || options['startFetch'] == null))
    || options == null)
  {
    this._startFetchEnabled = true;
  }
};

// Subclass from oj.DataSource 
oj.Object.createSubclass(oj.FlattenedTreeTableDataSource, oj.TableDataSource, "oj.FlattenedTreeTableDataSource");

/**
 * Initializes the instance.
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.Init = function()
{
  oj.FlattenedTreeTableDataSource.superclass.Init.call(this);
};

/**
 * Determines whether this FlattenedTreeTableDataSource supports certain feature.
 * @override
 * @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
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.getCapability = function(feature)
{
    return 'full';
};

/**
 * Retrieves the underlying DataSource.
 * @return {Object} the underlying oj.DataSource.
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.getWrappedDataSource = function()
{
    return this._data;
};

/**
 * Fetch the row data.
 * @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 to a compound object which contains an array of row data objects, an array of ids, and the startIndex triggering done when complete.<p>
 *         The structure of the resolved compound object is:<p>
 * <table>
 * <tbody>
 * <tr><td><b>data</b></td><td>An array of raw row data</td></tr>
 * <tr><td><b>keys</b></td><td>An array of key values for the rows</td></tr>
 * <tr><td><b>startIndex</b></td><td>The startIndex for the returned set of rows</td></tr>
 * </tbody>
 * </table>  
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.fetch = function(options)
{
  options = options || {};
  var self = this;
  var fetchType = options['fetchType'];
  
  if (fetchType == 'init' && !this._startFetchEnabled)
  {
      return Promise.resolve();
  }

  return self._fetchInternal(options);
};




/**** start delegated functions ****/

/**
 * Return the row data found at the given index.
 * 
 * @param {number} index Index for which to return the row data. 
 * @param {Object=} options Options to control the at.
 * @return {Promise} Promise resolves to a compound object which has the structure below. If the index is out of range, Promise resolves to null.<p>
 * <table>
 * <tbody>
 * <tr><td><b>data</b></td><td>The raw row data</td></tr>
 * <tr><td><b>index</b></td><td>The index for the row</td></tr>
 * <tr><td><b>key</b></td><td>The key value for the row</td></tr>
 * </tbody>
 * </table>
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.at = function(index, options)
{
  var row;

  if (index < 0 || index >= this._rows.length)
  {
    row = null;
  }
  else
  {
    row = {'data': this._rows['data'][index], 'index': index, 'key': this._rows['keys'][index]};
  }

  return new Promise(function(resolve, reject) {
    resolve(row);
  });
};

/**
 * Collapse the specified row.
 * @param {Object} rowKey the key of the row to collapse
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.collapse = function(rowKey)
{
  this._data.collapse(rowKey);
};


/**
 * Expand the specified row.
 * @param {Object} rowKey the key of the row to expand
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.expand = function(rowKey)
{
  this._data.expand(rowKey);
};

/**
 * Return the first row data whose id value is the given id
 * @param {string} id ID for which to return the row data, if found. 
 * @param {Object=} options Options to control the get.
 * @return {Promise} Promise which resolves to a compound object which has the structure below where the id matches the given id. If none are found, resolves to null.<p>
 * <table>
 * <tbody>
 * <tr><td><b>data</b></td><td>The raw row data</td></tr>
 * <tr><td><b>index</b></td><td>The index for the row</td></tr>
 * <tr><td><b>key</b></td><td>The key value for the row</td></tr>
 * </tbody>
 * </table>
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.get = function(id, options)
{
  oj.Assert.failedInAbstractFunction();
  return null;
};

/**
 * Attach an event handler to the datasource
 * 
 * @param {string} eventType eventType supported by the datasource
 * @param {function(Object)} eventHandler event handler function
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.on = function(eventType, eventHandler)
{
  if (eventType == 'expand' ||
      eventType == 'collapse')
  {
    // expand/collapse listeners should be passed through to the FlattenedTreeDatasource
    this._data.on(eventType, eventHandler);
  }
  else
  {
    oj.FlattenedTreeTableDataSource.superclass.on.call(this, eventType, eventHandler);
  }
};

/**
 * Detach an event handler from the datasource
 * 
 * @param {string} eventType eventType supported by the datasource
 * @param {function(Object)} eventHandler event handler function
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.off = function(eventType, eventHandler)
{
  if (eventType == 'expand' ||
      eventType == 'collapse')
  {
    // expand/collapse listeners should be passed through to the FlattenedTreeDatasource
    this._data.off(eventType, eventHandler);
  }
  else
  {
    oj.FlattenedTreeTableDataSource.superclass.off.call(this, eventType, eventHandler);
  }
};

/**
 * Performs a sort on the data source.
 * @override
 * @param {Object} criteria the sort criteria.
 * @param {Object} criteria.key The key that identifies which field to sort
 * @param {string} criteria.direction the sort direction, valid values are "ascending", "descending", "none" (default)
 * @return {Promise} promise object triggering done when complete.
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.sort = function(criteria)
{
  if (criteria == null)
  {
      return Promise.resolve();
  }
  
  var self = this;
  criteria['axis'] = 'column';
  return new Promise(function(resolve, reject) 
  {
    self._data.getWrappedDataSource().sort(criteria,
      {"success": function(nodeSet)
        {
          setTimeout(function()
          {
            self._data.refresh();
            self._rows = null;
            var result = {'header': criteria['key'], 'direction': criteria['direction']};
            oj.TableDataSource.superclass.handleEvent.call(self, oj.TableDataSource.EventType['SORT'], result);
            resolve(result);
          }, 0);
        }.bind(this),
        "error": function(status)
        {
          //this._handleFetchRowsError(status, {'start': rowStart, 'count': rowCount}, callbacks, callbackObjects);
          reject(status);
        }.bind(this)
      });
  });
};

/**
 * Return the total size of data available, including server side if not local.
 * @override
 * @returns {number} total size of data
 * @export
 * @expose
 * @memberof! oj.FlattenedTreeTableDataSource
 * @instance
 */
oj.FlattenedTreeTableDataSource.prototype.totalSize = function()
{
  return -1;
};

/**** end delegated functions ****/

oj.FlattenedTreeTableDataSource.prototype._getMetadata = function(index)
{
    
  return this._nodeSetList[index]['nodeSet'].getMetadata(index - this._nodeSetList[index]['startIndex']);
}

oj.FlattenedTreeTableDataSource.prototype._fetchInternal = function(options)
{
  options = options || {};
  this._startFetch(options);
  this._startIndex = options['startIndex'] == null ? this._startIndex : options['startIndex'];
  
  // if pageSize is not specified then fetch everything
  var rangeCount = Number.MAX_VALUE;
  
  this._pageSize = options['pageSize'] == null ? this._pageSize : options['pageSize'];
  if (this._pageSize != null)
  {
    rangeCount = this._pageSize;
  }
  
  var startIndex = this._startIndex;
  if (this._rows != null)
  {
    if (this._pageSize != null)
    {
      var endIndex = this._rows['data'].length - 1;

      if (this._startIndex + this._pageSize - 1 <= endIndex)
      {
        endIndex = oj.FlattenedTreeTableDataSource._getEndIndex(this._rows, this._startIndex, this._pageSize);
        var rowArray = [];
        var keyArray = [];
        var i;
        for (i = this._startIndex; i <= endIndex; i++)
        {
          rowArray[i - this._startIndex] = this._rows['data'][i];
          keyArray[i - this._startIndex] = '';
        }
        var result = {'data': rowArray, 'keys': keyArray, 'startIndex': this._startIndex};
        this._endFetch(options, result, null);
        return Promise.resolve(result);
      }
      else if (this._startIndex <= endIndex)
      {
        startIndex = endIndex + 1;
      }
    }
    else
    {
      // we are fetching everything again so clear out the existing cached rows
      this._data.refresh();
      this._rows = null;
    }
  }
  
  var rangeOption = {'start': startIndex, 'count':  rangeCount};
  var self = this;
  return new Promise(function(resolve, reject) 
  {
    self._data.fetchRows(rangeOption,
    {
      "success": function(nodeSet)
      {
        self._handleFetchRowsSuccess(nodeSet);
        options['refresh'] = true;
        var endIndex = oj.FlattenedTreeTableDataSource._getEndIndex(self._rows, self._startIndex, self._pageSize);
        var rowArray = [];
        var keyArray = [];
        var i;
        for (i = self._startIndex; i <= endIndex; i++)
        {
          rowArray[i - self._startIndex] = self._rows['data'][i];
          keyArray[i - self._startIndex] = nodeSet.getMetadata(i)['key'];
        }
        var result = {'data': rowArray, 'keys': keyArray, 'startIndex': self._startIndex};
        self._endFetch(options, result, null);
        resolve(result);
      }.bind(this),
      "error": function(error)
      {
        self._endFetch(options, null, error);
        reject(error);
      }.bind(this)
    });
  });
};

oj.FlattenedTreeTableDataSource.prototype._handleFetchRowsSuccess = function(nodeSet)
{
  var i, rowIdx;
  for (i = 0; i < nodeSet.getCount(); i++)
  {
    rowIdx = this._startIndex + i;
    this._nodeSetList[rowIdx] = {};
    this._nodeSetList[rowIdx]['nodeSet'] = nodeSet;
    this._nodeSetList[rowIdx]['startIndex'] = this._startIndex;
  }

  if (!this._rows)
  {
    this._rows = {};
    this._rows['data'] = [];
    this._rows['keys'] = [];
    this._rows['indexes'] = [];
  }
  oj.FlattenedTreeTableDataSource._getRowArray(nodeSet, this, this._rows);
};

/**
 * Indicate starting fetch
 * @param {Object} options
 * @private
 */
oj.FlattenedTreeTableDataSource.prototype._startFetch = function(options)
{
  if (!options['silent'])
  {
    oj.TableDataSource.superclass.handleEvent.call(this, oj.TableDataSource.EventType['REQUEST'], {'startIndex' : options['startIndex']});
  }
};

/**
 * Indicate ending fetch
 * @param {Object} options
 * @param {Object} result Result object
 * @param {Object} error Error
 * @private
 */
oj.FlattenedTreeTableDataSource.prototype._endFetch = function(options, result, error)
{
  if (error != null)
  {
    oj.TableDataSource.superclass.handleEvent.call(this, oj.TableDataSource.EventType['ERROR'], error);
  }
  else
  {
    if (!options['silent'])
    {
      oj.TableDataSource.superclass.handleEvent.call(this, oj.TableDataSource.EventType['SYNC'],  result);
    }
  }
};

oj.FlattenedTreeTableDataSource._getEndIndex = function(rows, startIndex, pageSize)
{
  var endIndex = rows['data'].length - 1;
  
  if (pageSize > 0)
  {
    endIndex = startIndex + pageSize - 1;
    endIndex = endIndex > rows['data'].length - 1 ? rows['data'].length - 1 : endIndex;
  }
  
  return endIndex;
};

oj.FlattenedTreeTableDataSource._getRowArray = function(nodeSet, rowSet, rows)
{
  var endIndex = nodeSet.getCount() - 1;
  var startIndex = nodeSet.getStart();

  var i;
  for (i = startIndex; i <= endIndex; i++)
  {
    var row = nodeSet.getData(i);
    rows['data'][i] = row;
    rows['keys'][i] = '';
    rows['indexes'][i] = i;
  }
};

// Realign all the indices of the rows (after sort for example)
oj.FlattenedTreeTableDataSource.prototype._realignRowIndices = function()
{
  for (var i = 0; i < this._rows['data'].length; i++)
  {
    this._rows['indexes'][i] = i;
  }
};