dragdrop/NavNodeEnvelope.js

/**
 * @copyright 2015 Tridium, Inc. All Rights Reserved.
 * @author Logan Byam
 */

/**
 * @module bajaux/dragdrop/NavNodeEnvelope
 */
define([ 'baja!',
        'baja!baja:INavNode',
        'jquery',
        'Promise',
        'underscore',
        'bajaux/dragdrop/Envelope',
        'nmodule/js/rc/asyncUtils/asyncUtils' ], function (
         baja,
         types,
         $,
         Promise,
         _,
         Envelope,
         asyncUtils) {

  'use strict';

  var doRequire = asyncUtils.doRequire;

  var getNavNode = _.once(function () {
    return doRequire('bajaScript/baja/nav/NavNode');
  });


////////////////////////////////////////////////////////////////
// Support functions
////////////////////////////////////////////////////////////////

  function getTypeSpecs(obj) {
    var kids = obj.kids || [],
        kidSpecs = kids.map(getTypeSpecs);
    return [ obj.typeSpec ].concat(kidSpecs);
  }

  /**
   * Extract an array of all type specs from the array of JSON objects.
   * 
   * @inner
   * @param {Array.<Object>} json
   * @returns {Array.<String>}
   */
  function getAllTypeSpecs(json) {
    return _.uniq(_.flatten(json.map(getTypeSpecs)));
  }
  
  function toJson(navNode) {
    var kids = navNode.getSlots && 
          navNode.getSlots().is('baja:INavNode').toValueArray(),
        ord = navNode.getNavOrd();

    return {
      ord: ord && String(ord),
      name: navNode.getNavName(),
      displayName: navNode.getNavDisplayName(),
      icon: navNode.getNavIcon().encodeToString(),
      description: navNode.getNavDescription(),
      typeSpec: navNode.getNavTypeSpec(),
      kids: kids ? kids.map(toJson) : []
    };
  }

  function toNavNode(obj) {
    var typeSpec = obj.typeSpec,
        type = baja.lt(obj.typeSpec);
    
    if (!type || !type.is('baja:INavNode')) {
      throw new Error('only INavNodes supported, ' + typeSpec + ' found');
    }

    return Promise.all([
      getNavNode(),
      Promise.all((obj.kids || []).map(toNavNode))
    ])
      .then(([ NavNode, kids ]) => {
        const navNode = new NavNode({
          navName: obj.name,
          displayName: obj.displayName,
          description: obj.description,
          ord: String(obj.ord),
          icon: obj.icon,
          typeSpec
        });

        kids.forEach((kid) => { kid.getNavParent = () => navNode; });
        navNode.getType = () => type;
        navNode.getNavChildren = () => Promise.resolve(kids);

        return navNode;
      });
  }



////////////////////////////////////////////////////////////////
// NavNodeEnvelope
////////////////////////////////////////////////////////////////
  
  /**
   * Envelope for transforming raw JSON into NavNode instances, or vice versa.
   * 
   * @class
   * @extends module:bajaux/dragdrop/Envelope
   * @alias module:bajaux/dragdrop/NavNodeEnvelope
   * @param {Array.<Object>|Array.<baja.NavNode>} arr either an array of raw
   * JSON to be converted to NavNodes, or an array of NavNodes to be converted
   * to JSON
   * @throws {Error} if a non-Array given
   */
  var NavNodeEnvelope = function NavNodeEnvelope(arr) {
    Envelope.apply(this, arguments);

    if (!Array.isArray(arr)) {
      throw new Error('array required');
    }

    var navNodes,
        json;

    //TODO: need an "isArrayOf" or similar
    if (baja.hasType(arr[0], 'baja:INavNode')) {
      navNodes = arr;
    } else {
      json = arr;
    }

    this.$navNodes = navNodes;
    this.$json = json;
  };
  NavNodeEnvelope.prototype = Object.create(Envelope.prototype);
  NavNodeEnvelope.prototype.constructor = NavNodeEnvelope;

  /**
   * @returns {string} `niagara/navnodes`
   */
  NavNodeEnvelope.prototype.getMimeType = function () {
    return 'niagara/navnodes';
  };

  /**
   * Get the JSON representations of the envelope's NavNodes:
   * 
   *     [{
   *       "name": "navName",
   *       "displayName": "navDisplayName",
   *       "description": "navDescription",
   *       "icon": "navIcon",
   *       "ord": "navOrd",
   *       "typeSpec": "typeSpec",
   *       "kids": [ \/* child nav node JSON objects *\/ ]
   *     }]
   *     
   * @returns {Promise.<Array.<object>>} promise to be resolved with an array of raw
   * JSON objects
   */
  NavNodeEnvelope.prototype.toJson = function () {
    return Promise.resolve(
      this.$json || (this.$json = this.$navNodes.map(toJson))
    );
  };

  /**
   * Get the actual NavNodes represented by this envelope.
   * 
   * @returns {Promise.<Array.<baja.NavNode>>} promise to be resolved with an array of
   * NavNode instances
   */
  NavNodeEnvelope.prototype.toValues = function () {
    var that = this,
        json = that.$json,
        navNodes = that.$navNodes;

    if (navNodes) {
      return Promise.resolve(navNodes);
    }

    return baja.importTypes(getAllTypeSpecs(json))
      .then(function () {
        return Promise.all(_.map(json, toNavNode));
      })
      .then(function (navNodes) {
        return (that.$navNodes = navNodes);
      });
  };

  return NavNodeEnvelope;
});