baja/nav/NavContainer.js

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

/**
 * Defines {@link baja.NavContainer}.
 * @module baja/nav/NavContainer
 */
define([
  "bajaScript/sys",
  "bajaScript/baja/obj/Icon",
  "bajaScript/baja/obj/Object",
  "bajaScript/baja/comm/Callback" ], function (
  baja,
  Icon,
  BObject,
  Callback) {
  
  "use strict";
  
  var subclass = baja.subclass,
      callSuper = baja.callSuper,
      objectify = baja.objectify,
      emptyObj = {};
  
  /**
   * `NavContainer` is a generic `NavNode`.
   *
   * @class
   * @alias baja.NavContainer
   * @extends baja.Object
   */
  var NavContainer = function NavContainer(obj) {
    var that = this;
    callSuper(NavContainer, that, arguments); 

    that.$navKids = [];
    that.$update(obj);
  };
  
  subclass(NavContainer, BObject);
  
  /**
   * Return the type spec of object this nav node navigates to.
   * 
   * @return {String} The nav type spec.
   */
  NavContainer.prototype.getNavTypeSpec = function () {
    return this.$navTypeSpec;
  };

  /**
   * Return the Nav Name.
   *
   * @returns {String}
   */
  NavContainer.prototype.getNavName = function () {
    return this.$navName;
  };
  
  /**
   * Return the Nav Display Name.
   * 
   * @returns {String}
   */
  NavContainer.prototype.getNavDisplayName = function () {
    return this.$navDisplayName;
  };
  
  /**
   * Return the Nav Description.
   *
   * @returns {String}
   */
  NavContainer.prototype.getNavDescription = function () {
    return this.$navDescription;
  };
  
  /**
   * Return the Nav ORD.
   *
   * If passing `sessionAware`, the returned ORD may return session information
   * when BajaScript is running in Workbench (most commonly including the IP
   * address and fox|foxs schemes). Note that a session-aware ORD is not safe
   * to pass directly up to a station for resolution as in an RPC or servlet
   * call, as a station won't know how to open a FOX session to itself.
   *
   * @param {object} [params]
   * @param {boolean} [params.sessionAware] include session information if
   * available
   * @returns {baja.Ord}
   * @example
   * <caption>When running in Workbench, connected to a station at ip:1.2.3.4,
   * session information can be retrieved.</caption>
   * console.log(services.getNavOrd());
   * // local:|station:|slot:/Services
   * console.log(services.getNavOrd({ sessionAware: true });
   * // ip:1.2.3.4|fox:|station:|slot:/Services
   *
   * @example
   * <caption>When running in a browser, session information is never present.
   * </caption>
   * console.log(services.getNavOrd());
   * // local:|station:|slot:/Services
   * console.log(services.getNavOrd({ sessionAware: true });
   * // local:|station:|slot:/Services
   */
  NavContainer.prototype.getNavOrd = function (params) {
    var sessionAware = baja.objectify(params).sessionAware;
    if (sessionAware) {
      if (!this.$sessionNavOrd) {
        this.$sessionNavOrd = baja.Ord.make(this.$sessionNavOrdStr);
      }
      return this.$sessionNavOrd;
    } else {
      if (!this.$navOrd) {
        this.$navOrd = baja.Ord.make(this.$navOrdStr);
      }
      return this.$navOrd;
    }
  };
  
  /**
   * Return the Nav Parent (or null if there's no parent).
   *
   * @returns {baja.NavContainer}
   */
  NavContainer.prototype.getNavParent = function () {
    return this.$navParent || null;
  };
      
  /**
   * Access the Nav Children.
   *
   * @param {Object} obj the Object Literal for the method's arguments.
   * @param {Function} [obj.ok] (Deprecated: use Promise) called when we have
   * the Nav Children. An array of Nav Children is passed as an argument into
   * this function.
   * @param {Function} [obj.fail] (Deprecated: use Promise) called if the
   * function fails to complete.
   * @returns {Promise.<Array.<baja.NavNode>>} a promise that will be resolved
   * once the nav children have been retrieved.
   *
   * @example
   *   container.getNavChildren()
   *     .then(function (kids) {
   *       baja.outln('retrieved nav children: ' + kids.join());
   *     })
   *     .catch(function (err) {
   *       baja.error('failed to retrieve nav children: ' + err);
   *     });
   */
  NavContainer.prototype.getNavChildren = function (obj) {
    obj = objectify(obj, "ok");
    var cb = new Callback(obj.ok);
    cb.ok(this.$navKids);
    return cb.promise();
  };
  
  /**
   * Return the Nav Icon for this node.
   *
   * @returns {baja.Icon}
   */
  NavContainer.prototype.getNavIcon = function () {
    if (!this.$navIcon) {
      this.$navIcon = Icon.make(this.$navIconStr);
    }
    return this.$navIcon;
  };

  /**
   * 
   * @since Niagara 4.14
   * @returns {baja.Permissions}
   * @throws {Error} if this node is not a BIProtected
   */
  NavContainer.prototype.getPermissions = function () {
    if (!this.$permissionsStr) {
      throw new Error(this.getNavOrd() + ": Cannot determine permissions on this node." +
        "\nResolve and get permissions on the target instead.");
    }
    return baja.Permissions.make(this.$permissionsStr);
  };
  
  /**
   * Add a child node to this container.
   * 
   * Please note, this is a private method and should only be used by Tridium developers.
   *
   * @private
   *
   * @param node
   * @returns node
   */
  NavContainer.prototype.$addChildNode = function (node) {
    node.$navParent = this;
    this.$navKids.push(node);
    return node;
  };

  /**
   * Remove a child node from this container. If found and removed, the node is
   * returned. If not found then null is returned.
   * 
   * Please note, this is a private method and should only be used by Tridium developers.
   *
   * @private
   *
   * @param {string} name
   * @returns node
   */
  NavContainer.prototype.$removeChildNode = function (name) {
    var i,
        navKids = this.$navKids,
        oldNode = null;

    for (i = 0; i < navKids.length; ++i) {
      if (navKids[i].getNavName() === name) {
        oldNode = navKids[i];
        oldNode.$navParent = null;
        navKids.splice(i, 1);
        break;
      }
    }

    return oldNode;
  };

  /**
   * Remove all the children from the container.
   * 
   * Please note, this is a private method and should only be used by Tridium developers.
   *
   * @private
   *
   * @returns An array of the child nodes that were removed.
   */
  NavContainer.prototype.$removeAllChildNodes = function () {
    var i,
        navKids = this.$navKids;

    for (i = 0; i < navKids.length; ++i) {
      navKids[i].$navParent = null;
    }
    this.$navKids = [];
    return navKids;
  };

  /**
   * Update the node with the new nav node information. This will not reset any children.
   * 
   * @private
   * 
   * @param {Object} [obj] An object literal that contains the updated information.
   */
  NavContainer.prototype.$update = function (obj) {
    obj = obj || emptyObj;
    var that = this;
  
    that.$navName = obj.navName || "";
    that.$navDisplayName = (obj.displayName || obj.navName) || "";
    that.$navDescription = obj.description || that.$navDisplayName;
    that.$navOrdStr = obj.ord || "";
    that.$navOrd = null;
    that.$sessionNavOrdStr = obj.sessionOrd || that.$navOrdStr;
    that.$sessionNavOrd = null;
    that.$navIconStr = obj.icon || "";
    that.$navIcon = "";
    that.$navTypeSpec = obj.typeSpec || "baja:INavNode";
    that.$permissionsStr = obj.hasOwnProperty('permissions') ? obj.permissions : null;
  };

  /**
   * Updates and returns the child node.
   *
   * @private
   *
   * @param {String} name The name of the child to find.
   * @param obj The object to update the nav node with.
   * @returns The updated child node or null if it couldn't be found.
   */
  NavContainer.prototype.$updateChildNode = function (name, obj) {
    var i,
        navKids = this.$navKids,
        node = null;

    for (i = 0; i < navKids.length; ++i) {
      if (navKids[i].getNavName() === name) {
        node = navKids[i];
        node.$update(obj);
        break;
      }
    }

    return node;
  };

  /**
   * NavNodes do not always fire renamed events. Use this method to make BajaScript nav event
   * handlers behave as though this one did.
   *
   * @private
   * @param {string} newName
   * @param {object} [cx]
   * @since Niagara 4.14
   */
  NavContainer.prototype.$syntheticSetName = function (newName, cx = {}) {
    const oldName = this.$navName;
    this.$navName = newName;
    const parent = this.getNavParent();
    if (parent) {
      baja.nav.fireHandlers('renamed', (err) => { throw err; }, baja.nav,
        parent.getNavOrd(), this, oldName, cx);
    }
  };

  /**
   * Set the new nav display name, and trigger a "renamed" event on the same nav name. This should
   * trigger event consumers to repaint this nav node with the new display name as needed.
   *
   * @private
   * @param {string} newName
   * @param {object} [cx]
   * @since Niagara 4.14
   */
  NavContainer.prototype.$syntheticSetDisplayName = function (newName, cx) {
    this.$navDisplayName = newName;
    this.$syntheticSetName(this.getNavName(), cx);
  };
  
  return NavContainer;
});