baja/coll/QueryResult.js

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

/**
 * Defines {@link baja.coll.QueryResult}.
 * @module baja/coll/QueryResult
 */
define([ "bajaScript/bson",
        "bajaScript/baja/obj/Simple",
        "bajaScript/baja/coll/collUtil",
        "bajaScript/baja/coll/QueryCursor",
        "bajaScript/baja/comm/Callback",
        "bajaScript/baja/tag/entityDecoder",
        "bajaPromises" ],
        function (
        baja,
        Simple,
        collUtil,
        QueryCursor,
        Callback,
        entityDecoder,
        bajaPromises) {
  
  "use strict";
  
  var subclass = baja.subclass,
      callSuper = baja.callSuper,
      
      objectify = baja.objectify,
      isObjEmpty = collUtil.isObjEmpty;
  
  /**
   * Represents a `baja:QueryResult` in BajaScript.
   * 
   * A QueryResult can be resolved from a NEQL query.
   *
   * @class
   * @alias baja.coll.QueryResult
   */  
  var QueryResult = function QueryResult(queryData) {
    callSuper(QueryResult, this, arguments);
    this.$queryData = queryData;
  };
  
  subclass(QueryResult, Simple); 
  
  /**
   * Default `QueryResult` instance.
   * @type {baja.coll.QueryResult}
   */
  QueryResult.DEFAULT = new QueryResult({});
  
  /**
   * Make a `QueryResult`.
   * 
   * @private
   *
   * @param {Object} queryData
   * @returns {baja.coll.QueryResult} the QueryResult.
   */
  QueryResult.make = function (queryData) {
    return isObjEmpty(queryData) ? QueryResult.DEFAULT : new QueryResult(queryData);
  };
  
  /**
   * Make a `QueryResult`.
   * 
   * @private
   *
   * @param {Object} queryData
   * @returns {baja.coll.QueryResult} the QueryResult
   */
  QueryResult.prototype.make = function (queryData) {
    return QueryResult.make(queryData);
  };
  
  /**
   * Decode a `QueryResult` from a `String`.
   * 
   * @private
   * 
   * @param {String} str
   * @returns {baja.coll.QueryResult}
   */
  QueryResult.prototype.decodeFromString = function (str) {
    return QueryResult.make(JSON.parse(str));
  };
  
  /**
   * Encode the `QueryResult` to a `String`.
   *
   * @private
   *
   * @returns {String}
   */
  QueryResult.prototype.encodeToString = function () {
    return JSON.stringify(this.$queryData);
  };
  
  /**
   * Return the Query string for the result.
   * 
   * @returns {String} The Query.
   */
  QueryResult.prototype.getQuery = function () {
    return this.$queryData.query;
  };

  /**
   * Return the Scope ORD for the result.
   * 
   * @returns {baja.Ord} The Scope ORD.
   */
  QueryResult.prototype.getScopeOrd = function () {
    return baja.Ord.make(this.$queryData.scope);
  };


  /**
   * Iterate through a QueryResult's Entities.
   * 
   * Please note, this may retrieve data asynchronously.
   * 
   * A callback function is passed in to retrieve the Cursor.
   * 
   * @see module:baja/coll/QueryCursor
   * 
   * @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
   *   myQueryResult.cursor({
   *     each: function () {
   *       // Called for each Entity 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
   *     });
   */
  QueryResult.prototype.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) {   
      var i,
          cursor;

      // Decode Entities
      var promises = [];
      for (i = 0; i < resp.length; ++i) {
        promises[i] = entityDecoder.decodeEntity(resp[i]);
      }

      return bajaPromises.all(promises)
        .then(function (entities) {
          // Create a Cursor Object and pass it into the Handler
          cursor = new QueryCursor(that, entities);

          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 ok(cursor);
        });
    });
    
    obj.limit = obj.limit || 10;
    obj.offset = obj.offset || 0;
    
    // If '$data' is specified then use this for the first Cursor iteration. This is an optimization made
    // by BajaScript's ORD architecture. This enables an ORD to be queried and then iterated through at the same time.
    if (obj.$data) {
      cb.ok(obj.$data);
    } else {
      
      // Make a a network call for the Cursor data
      baja.comm.cursor(this.$queryData.req, cb, obj);
    }
    
    return cb.promise();
  }; 
  
  return QueryResult;
});