baja/comp/SlotCursor.js

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

/**
 * Defines `SlotCursor` (not exposed on `baja` namespace).
 * @module baja/comp/SlotCursor
 */
define([ "bajaScript/sys",
        "bajaScript/baja/sys/structures/FilterCursor" ], function (
         baja,
         FilterCursor) {
  
  "use strict";
  
  var subclass = baja.subclass,
      callSuper = baja.callSuper;
  
  /** 
   * A `Cursor` used for `Slot` iteration.
   *
   * @class
   * @alias module:baja/comp/SlotCursor
   * @extends baja.FilterCursor
   * 
   * @see baja.Complex#getSlots
   */
  var SlotCursor = function SlotCursor() {
    callSuper(SlotCursor, this, arguments);
  };
  
  subclass(SlotCursor, FilterCursor);
  
  /**
   * If the `Slot` is a `Property`, return its value (otherwise return null).
   *
   * @returns a Property value.
   */
  SlotCursor.prototype.getValue = function () {
    var slot = this.get();
    return slot !== null && slot.isProperty() ? this.$context.get(slot) : null;
  };
  
  /**
   * If the `Slot` is a `Property`, return its display `String` (otherwise return null).
   *
   * @returns {String} display String.
   */
  SlotCursor.prototype.getDisplay = function () {
    var slot = this.get();
    return slot !== null && slot.isProperty() ? this.$context.getDisplay(slot) : null;
  };
  
  /**
   * Return the first `Property` value in the cursor (regardless of iterative state).
   *
   * @returns {baja.Value} first `Property` value found in the Cursor (or null if nothing found).
   */
  SlotCursor.prototype.firstValue = function () {
    var slot = this.first();
    return slot !== null && slot.isProperty() ? this.$context.get(slot) : null;
  };
  
  /**
   * Return the first `Property` display String in the cursor (regardless of iterative state).
   *
   * @returns {String} first `Property` display String found in the Cursor (or null if nothing found).
   */
  SlotCursor.prototype.firstDisplay = function () {
    var slot = this.first();
    return slot !== null && slot.isProperty() ? this.$context.getDisplay(slot) : null;
  };
  
  /**
   * Return the last `Property` value in the cursor (regardless of iterative state).
   *
   * @returns {baja.Value} first Property value found in the Cursor (or null if nothing found).
   */
  SlotCursor.prototype.lastValue = function () {
    var slot = this.last();
    return slot !== null && slot.isProperty() ? this.$context.get(slot) : null;
  };
  
  /**
   * Return the last `Property` display `String` in the cursor (regardless of iterative state).
   *
   * @returns {String} last `Property` display `String` found in the Cursor (or null if nothing found).
   */
  SlotCursor.prototype.lastDisplay = function () {
    var slot = this.last();
    return slot !== null && slot.isProperty() ? this.$context.getDisplay(slot) : null;
  };
  
  /**
   * Iterate through the Cursor and call 'each' on every `Property` Slot and 
   * get its value.
   * 
   * When the function is called, 'this' refers to the associated `Complex` 
   * and the argument is the value of the `Property`.
   * 
   * @param {Function} func function called on every iteration with the 
   * argument being a `Property`'s value.
   */
  SlotCursor.prototype.eachValue = function (func) {
    return this.each(function (slot, i) {
      if (slot.isProperty()) {
        return func.call(this, this.get(slot), i);
      }
      
      // Return false so nothing stops iterating
      return false;
    });
  };
  
  /**
   * Iterate through the Cursor and call 'each' on every `Property` Slot and 
   * get its display `String`.
   * 
   * When the function is called, 'this' refers to the associated Complex and the argument
   * is the display String.
   * 
   * @param {Function} func function called on every iteration with the argument being a Property's display String
   */
  SlotCursor.prototype.eachDisplay = function (func) {
    return this.each(function (slot, i) {
      if (slot.isProperty()) {
        return func.call(this, this.getDisplay(slot), i);
      }
      
      // Return false so nothing stops iterating
      return false;
    });
  };
  
  /**
   * Return an array of `Property` values (regardless of iterative state).
   * This will call `get()` on each slot in the cursor, so please see the
   * `get()` documentation for performance notes.
   *
   * @returns {Array.<baja.Value>}
   * @see baja.Complex#get
   */  
  SlotCursor.prototype.toValueArray = function () {
    var slots = this.toArray(),
        values = [],
        i;
    
    for (i = 0; i < slots.length; ++i) {
      if (slots[i].isProperty()) {
        values.push(this.$context.get(slots[i]));
      }
    }
            
    return values;
  };
  
  /**
   * Return an array of `Property` display `String`s (regardless of iterative state).
   *
   * @returns {Array.<String>}
   */  
  SlotCursor.prototype.toDisplayArray = function () {
    var slots = this.toArray(),
        displays = [],
        i;
    
    for (i = 0; i < slots.length; ++i) {
      if (slots[i].isProperty()) {
        displays.push(this.$context.getDisplay(slots[i]));
      }
    }
            
    return displays;
  };
  
  /**
   * Return an Object Map of `Property` names with their corresponding 
   * values (regardless of iterative state).
   *
   * @returns {Object}
   */  
  SlotCursor.prototype.toValueMap = function () {
    var slots = this.toArray(),
        map = {},
        s,
        i;
    
    for (i = 0; i < slots.length; ++i) {
      s = slots[i];
      if (s.isProperty()) {
        map[s.getName()] = this.$context.get(s);
      }
    }
            
    return map;
  };
  
  /**
   * Return an Object Map of `Property` names with their corresponding display 
   * `String`s (regardless of iterative state).
   *   
   * @returns {Object}
   */  
  SlotCursor.prototype.toDisplayMap = function () {
    var slots = this.toArray(),
        map = {},
        s,
        i;
    
    for (i = 0; i < slots.length; ++i) {
      s = slots[i];
      if (s.isProperty()) {
        map[s.getName()] = this.$context.getDisplay(s);
      }
    }
            
    return map;
  };
    
  function slotCursorFrozen(slot) {
    return slot.isFrozen();
  }
   
  /**
   * Adds a filter to the Cursor for frozen Slots.
   * 
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.frozen = function () {
    this.filter(slotCursorFrozen);
    return this;
  };
  
  function slotCursorDynamic(slot) {
    return !slot.isFrozen();
  }
  
  /**
   * Adds a filter to the Cursor for dynamic Slots.
   * 
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.dynamic = function () {
    this.filter(slotCursorDynamic);
    return this;
  };
  
  function slotCursorProperties(slot) {
    return slot.isProperty();
  }
  
  /**
   * Adds a filter to the Cursor for Properties.
   * 
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.properties = function () {
    this.filter(slotCursorProperties);
    return this;
  };
  
  function slotCursorActions(slot) {
    return slot.isAction();
  }
  
  /**
   * Adds a filter to the Cursor for Actions.
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.actions = function () {
    this.filter(slotCursorActions);
    return this;
  };
  
  function slotCursorTopics(slot) {
    return slot.isTopic();
  }
  
  /**
   * Adds a filter to the Cursor for Topics.
   * 
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.topics = function () {
    this.filter(slotCursorTopics);
    return this;
  };

  /**
   * Adds a filter for Property values that match the TypeSpec via {@link Type#is}.
   *
   * This method can take a variable number of TypeSpecs. If a variable
   * number of TypeSpecs are specified then a slot will be filtered through
   * if any of the TypeSpecs match (logical OR).
   *
   * @see Type#is
   *
   * @param {Type|String} typeSpec the TypeSpec to test against.
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.is = function (typeSpec) {
    var typeSpecs = Array.prototype.slice.call(arguments);
    const that = this;

    this.filter(function (slot) {
      if (slot.isProperty()) {
        var t = slot.getType(),
          a = that.$context && that.$context.get(slot),
          i;

        for (i = 0; i < typeSpecs.length; ++i) {
          if (t.is(typeSpecs[i]) || (a && a.getType().is(typeSpecs[i]))) {
            return true;
          }
        }
      }
      return false;
    });

    return this;
  };
  
  function slotCursorIsValue(slot) {
    return slot.isProperty() && slot.getType().isValue();
  }
  
  /**
   * Adds a filter for Property values that are of Type `baja:Value` 
   * ({@link Type#isValue}).
   * 
   * @see Type#isValue
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.isValue = function () {
    this.filter(slotCursorIsValue);
    return this;
  };
  
  function slotCursorIsSimple(slot) {
    return slot.isProperty() && slot.getType().isSimple();
  }
  
  /**
   * Adds a filter for Property values that are of Type `baja:Simple` 
   * ({@link Type#isSimple}).
   * 
   * @see Type#isSimple
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.isSimple = function () {
    this.filter(slotCursorIsSimple);
    return this;
  };
  
  function slotCursorIsNumber(slot) {
    return slot.isProperty() && slot.getType().isNumber();
  }
  
  /**
   * Adds a filter for Property values that are of Type `baja:Number` 
   * ({@link Type#isNumber}).
   * 
   * @see Type#isNumber
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.isNumber = function () {
    this.filter(slotCursorIsNumber);
    return this;
  };
  
  function slotCursorIsComplex(slot) {
    return slot.isProperty() && slot.getType().isComplex();
  }
  
  /**
   * Adds a filter for Property values that are of Type `baja:Complex`
   * ({@link Type#isComplex}).
   * 
   * @see Type#isComplex
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.isComplex = function () {
    this.filter(slotCursorIsComplex);
    return this;
  };
  
  function slotCursorIsComponent(slot) {
    return slot.isProperty() && slot.getType().isComponent();
  }
  
  /**
   * Adds a filter for Property values that are of Type `baja:Component` 
   * ({@link Type#isComponent}).
   * 
   * @see Type#isComponent
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.isComponent = function () {
    this.filter(slotCursorIsComponent);
    return this;
  };
  
  function slotCursorIsStruct(slot) {
    return slot.isProperty() && slot.getType().isStruct();
  }
  
  /**
   * Adds a filter for Property values that are of Type `baja:Struct` 
   * ({@link Type#isStruct}).
   * 
   * @see Type#isStruct
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.isStruct = function () {
    this.filter(slotCursorIsStruct);
    return this;
  };
  
  /**
   * Adds a filter for Properties whose Type matches via equals. 
   * 
   * This method can take a variable number of TypeSpecs. If a variable number 
   * of TypeSpecs are specified then a slot will be filtered through if any of 
   * the TypeSpecs match (logical OR).
   *
   * @param {Type|String|Array} typeSpec the TypeSpec to test against. 
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.equalType = function (typeSpec) {
    var typeSpecs = Array.prototype.slice.call(arguments),
        i;
    
    // Ensure we have the Types we're interested in
    for (i = 0; i < typeSpecs.length; ++i) {
      typeSpec = typeSpecs[i];
      typeSpecs[i] = typeof typeSpec === "string" ? baja.lt(typeSpec) : typeSpec;
    }
  
    this.filter(function (slot) {
      if (slot.isProperty()) {
        var t = slot.getType(),
            i;
        
        for (i = 0; i < typeSpecs.length; ++i) {
          if (t.equals(typeSpecs[i])) {
            return true;
          }
        }
      }
      return false;
    });
    
    return this;
  };
  
  /**
   * Adds a filter for Property values that match via equals. 
   * 
   * This method can take a variable number of values. If a variable number 
   * of values are specified then a slot will be filtered through if any of 
   * the values match (logical OR).
   *
   * @param value the value to be used for equals.
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.equalValue = function (value) {
    var values = Array.prototype.slice.call(arguments);
    
    this.filter(function (slot) {
      if (slot.isProperty()) {
        var v = this.get(slot),
            i;
        
        for (i = 0; i < values.length; ++i) {
          if (v.equals(values[i])) {
            return true;
          }
        }
      }
      return false;
    });
    
    return this;
  };
  
  /**
   * Adds a filter for Property values that match via equivalent. 
   * 
   * This method can take a variable number of values. If a variable number of 
   * values are specified then a slot will be filtered through if any of the 
   * values match (logical OR).
   *
   * @param value the value to be used for equivalent.
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.equivalent = function (value) {
    var values = Array.prototype.slice.call(arguments); 
    
    this.filter(function (slot) {
      if (slot.isProperty()) {
        var v = this.get(slot),
            i;
        
        for (i = 0; i < values.length; ++i) {
          if (v.equivalent(values[i])) {
            return true;
          }
        }
      }
      return false;
    });
    
    return this;
  };
  
  /**
   * Adds a filter for Slots that match the given Slot name.
   *
   * @param {String|RegExp} slotName a String or Regular Expression for 
   * matching Slots via name.
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.slotName = function (slotName) {
    if (typeof slotName === "string") {
      // String Comparison
      this.filter(function (slot) {
        return slot.getName().equals(slotName.toString());
      });
    } else {
      // RegEx test
      this.filter(function (slot) {
        return slotName.test(slot.getName());
      });
    }
    return this;
  };
    
  /**
   * Adds a filter for Slots that match the requested Slot Flags.
   *
   * @see baja.Flags
   *
   * @param {Number} flags the Slot flags to be tested for.
   *
   * @returns {module:baja/comp/SlotCursor} itself.
   */
  SlotCursor.prototype.flags = function (flags) {
    this.filter(function (slot) {
      return (this.getFlags(slot) & flags);
    });
    return this;
  };
  
  return SlotCursor;
});