baja/coll/tableMixIn.js

/**
 * @copyright 2015 Tridium, Inc. All Rights Reserved.
 * @author Gareth Johnson
 */

/**
 * Defines {@link baja.coll.tableMixIn}.
 * @module baja/coll/tableMixIn
 */
define([ "bajaScript/bson",
        "bajaScript/baja/obj/Simple",
        "bajaScript/baja/obj/Facets",
        "bajaScript/baja/coll/TableCursor",
        "bajaScript/baja/coll/collUtil",
        "bajaScript/baja/comm/Callback",
        "bajaPromises" ],
        function (
        baja,
        Simple,
        Facets,
        TableCursor,
        collUtil,
        Callback,
        bajaPromises) {
  
  "use strict";

  const { objectify, strictArg } = baja;

  /**
   * A Mix-In for objects that are tables.
   * 
   * @mixin
   * @alias baja.coll.tableMixIn
   */
  var tableMixIn = {

    /**
     * Returns an array of Table Columns.
     *
     * @see baja.coll.Table#getCol
     *
     * @returns {Array.<module:baja/coll/Table.TableColumn>} an array of columns
     */
    getColumns: function () {
      var columns = [],
          cols =  this.$tableData.cols,
          i;
      
      for (i = 0; i < cols.length; ++i) {
        columns.push(this.getCol(cols[i].n));
      }
      
      return columns;
    },
    
    /**
     * Returns a column object for the given column name.
     *
     * @param {String|Number} column the column name or index.
     * @returns {module:baja/coll/Table.TableColumn} the table column or null 
     * if the column can't be found.
     */
    getCol: function (column) {
      strictArg(column);
      
      var to = typeof column,
          cols = this.$tableData.cols,
          data,
          i;
      
      if (to === "number") {
        data = cols[column];
      } else if (to === "string") {
        for (i = 0; i < cols.length; ++i) {
          if (cols[i].n === column) {
            data = cols[i]; 
            break;
          }
        }
      }
      
      // If there's no data then return null at this point
      if (!data) {
        return null;
      }
      
      /**
       * Table Column.
       * @class module:baja/coll/Table.TableColumn
       * @see baja.coll.Table
       */
      return /** @lends module:baja/coll/Table.TableColumn.prototype */ { 
        /**
         * Return the column name.
         * 
         * @returns {String}
         */
        getName: function getName() {
          return data.n;
        },
        
        /**
         * Return the column display name.
         * 
         * @returns {String}
         */
        getDisplayName: function getDisplayName() {
          return data.dn;
        },
        
        /**
         * Return the column Type.
         * 
         * @returns {Type}
         */
        getType: function getType() {
          return baja.lt(data.t);
        },
        
        /**
         * Return the column flags.
         * 
         * @returns {Number}
         */
        getFlags: function getFlags() {
          return data.f;
        },
        
        /**
         * the column facets
         * 
         * @returns {baja.Facets}
         */
        getFacets: function getFacets() {
          return Facets.DEFAULT.decodeFromString(data.x, baja.Simple.$unsafeDecode);
        }
      };
    },

    /**
     * Returns a promise that's used to access a Component that contains all the 
     * table's configuration data. In the case of a History, the Component will
     * be a 'history:HistoryConfig' Component. For other types of query, this
     * may change in the future.
     *
     * @param {Object} [obj] optional object literal parameters.
     * @param {Function} [obj.ok] (Deprecated: use Promise) the ok callback.
     * This function is called once the parameters have been resolved.
     * @param {Function} [obj.fail] (Deprecated: use Promise) the fail callback.
     * This function is called once the parameters have failed to resolve.
     * @returns {Promise.<baja.Component>} promise that resolves to a config
     * Component.
     */
    toConfig: function (obj) {
      obj = obj || {};

      var that = this,
          cb = new Callback(obj.ok, obj.fail);

      if (!that.$config) {
        if (that.$tableData.config) {
          baja.bson.importUnknownTypes(that.$tableData.config,
            function ok() { 
              that.$config = baja.bson.decodeValue(that.$tableData.config,
                                                   baja.$serverDecodeContext);
              cb.ok(that.$config);
            }, 
            function fail(err) {
              cb.fail(err);
            }
          );
        } else {
          that.$config = new baja.Component();
          cb.ok(that.$config);
        }
      } else {
        cb.ok(that.$config);
      }

      return cb.promise();
    },

    /**
     * Return true if the table has a configuration component.
     * 
     * @returns {Boolean} true if a config component is available.
     */
    hasConfig: function () {
      return !!this.$tableData.config;
    },

    /**
     * Iterate through a Table.
     * 
     * Please note, this may retrieve data asynchronously.
     * 
     * A callback function is passed in to retrieve the Cursor.
     * 
     * @see module:baja/coll/TableCursor
     * 
     * @param {Object|Function} obj the object literal that specifies the
     * method's arguments.
     * @param {Function} [obj.ok] (Deprecated: use Promise) called when the
     * cursor has been created with the cursor as an argument.
     * @param {Function} [obj.fail] (Deprecated: use Promise) called if the
     * cursor fails to be retrieved. An error is passed in as the first
     * argument.
     * @param {baja.comm.Batch} [obj.batch] if specified, the operation will be
     * batched into this object.
     * @param {Function} [obj.before] called just before the Cursor is about to
     * be iterated through.
     * @param {Function} [obj.after] called just after the Cursor has finished
     * iterating.
     * @param {Number} [obj.offset=0] Specifies the row number to start encoding
     * the result-set from.
     * @param {Number} [obj.limit=10] Specifies the maximum number of rows that
     * can be encoded.
     * @returns {Promise} a promise that will be resolved once the cursor has
     * been retrieved.
     * 
     * @example
     *   myTable.cursor({
     *     each: function () {
     *       // Called for each item in the Cursor...
     *       var dataFromCursor = this.get();
     *     }
     *   })
     *     .then(function (cursor) {
     *       // Called once we have the Cursor
     *     })
     *     .catch(function (err) {
     *       // Called if any errors in getting data
     *     });
     */
    cursor: function (obj) {
      obj = objectify(obj, "each");
      
      var cb = new Callback(obj.ok, obj.fail, obj.batch),
          that = this;
      
      // Add an intermediate callback to create the Cursor Object and pass it back
      cb.addOk(function (ok, fail, resp) {     
        function createCursor(cursorValues) {
          const cursor = new TableCursor(that, cursorValues);
      
          if (typeof obj.before === "function") {
            cursor.before(obj.before); 
          }
          
          if (typeof obj.after === "function") {
            cursor.after(obj.after); 
          }
          
          // Please note, if defined, this will trigger an iteration
          if (typeof obj.each === "function") {
            cursor.each(obj.each); 
          }

          return cursor;
        }

        bajaPromises.all(resp.map((bson) => baja.bson.decodeAsync(bson, baja.$serverDecodeContext)))
          .then((cursorValues) => ok(createCursor(cursorValues)), fail);
      });
      
      obj.limit = obj.limit || 10;
      obj.offset = obj.offset || 0;
      
      // $cursorBsonArray might get stashed away by resolving a cursor-based ORD, in Ord.js. this
      // allows the ORD resolution to return the table data in the same network call, rather than
      // doing the resolution and cursor in two separate calls.
      if (obj.$cursorBsonArray) {
        cb.ok(obj.$cursorBsonArray);
      } else {
        // Make a network call for the Cursor data
        baja.comm.cursor(this.$tableData.req, cb, obj);
      }
      
      return cb.promise();
    }
  };
  
  /**
   * Mix-in the table methods onto the given Object.
   *
   * @private
   *
   * @param obj
   */
  return function mixin(obj) {
    obj.getColumns = tableMixIn.getColumns;
    obj.getCol = tableMixIn.getCol;
    obj.toConfig = tableMixIn.toConfig;
    obj.hasConfig = tableMixIn.hasConfig;
    obj.cursor = tableMixIn.cursor;
  };
});