/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* Defines {@link baja.Complex}.
* @module baja/comp/Complex
*/
define([
"bajaScript/comm",
"bajaScript/baja/comp/Flags",
"bajaScript/baja/comp/Property",
"bajaScript/baja/comp/SlotCursor",
"bajaScript/baja/obj/Value",
"bajaScript/baja/comm/Callback",
"bajaPromises" ], function (
baja,
Flags,
Property,
SlotCursor,
Value,
Callback,
Promise) {
"use strict";
var subclass = baja.subclass,
callSuper = baja.callSuper,
strictArg = baja.strictArg,
objectify = baja.objectify,
bajaDef = baja.def,
COPYING_CONTEXT = { type: "copying" },
DEFAULT_ON_CLONE = Flags.DEFAULT_ON_CLONE,
REMOVE_ON_CLONE = Flags.REMOVE_ON_CLONE;
function applyObjToComplex(clx, obj) {
var slotName,
value,
oldValue,
complexIsComponent = isComponent(clx);
for (slotName in obj) {
if (obj.hasOwnProperty(slotName)) {
value = obj[slotName];
if (clx.has(slotName)) {
// If value is a Object Literal then recursively apply it
if (value && value.constructor === Object) {
applyObjToComplex(clx.get(slotName), value);
} else {
clx.set({
slot: slotName,
value: value
});
}
} else if (complexIsComponent) {
// If value is an Object Literal then recursively apply it
if (value && value.constructor === Object) {
oldValue = value;
value = new baja.Component();
applyObjToComplex(value, oldValue);
}
clx.add({
slot: slotName,
value: value
});
}
}
}
}
function getOwnProperty(obj, key) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
return obj[key];
}
}
/**
* `Complex` is the `Value` which is defined by one or more
* property slots. `Complex` is never used directly, rather
* it is the base class for `Struct` and `Component`.
*
* Since `Complex` relates to a abstract `Type`, this Constructor should
* never be directly used to create a new object.
*
* Even when subclassing `Struct` or `Component`, as in a Type Extension, it
* may not be safe to call the constructor directly because frozen slots may
* not be applied. The best way to obtain an instance of a `Complex` is by
* using `baja.$()`.
*
* @see baja.Struct
* @see baja.Component
* @see baja.$
*
* @class
* @alias baja.Complex
* @extends baja.Value
*/
var Complex = function Complex() {
callSuper(Complex, this, arguments);
this.$map = new baja.OrderedMap();
this.$parent = null;
this.$propInParent = null;
};
subclass(Complex, Value);
/**
* Called once the frozen Slots have been loaded onto the Complex.
*
* @param {Object} [arg]
*
* @private
*/
Complex.prototype.contractCommitted = function (arg) {
// If this is a complex and there was an argument then attempt
// to set (of add if a Component) Properties...
if (arg && arg.constructor === Object) {
applyObjToComplex(this, arg);
}
};
/**
* Return the name of the `Component`.
*
* The name is taken from the parent `Component`'s Property for this
* `Component` instance.
*
* @returns {String} name (null if not mounted).
*/
Complex.prototype.getName = function () {
return this.$propInParent === null ? null : this.$propInParent.getName();
};
/**
* Return a display name.
*
* If a Slot is defined as an argument, the display name for the slot will
* be returned. If no Slot is defined, the display name of the `Complex`
* will be returned.
*
* @param {baja.Slot|String} [slot] the Slot or Slot name.
*
* @returns {String} the display name (or null if none available).
*/
Complex.prototype.getDisplayName = function (slot) {
var s,
nameMap,
nameMapDisp,
entry;
// If no Slot defined then get the display name of the Complex
if (slot === undefined) {
return this.getPropertyInParent() === null ? null : this.getParent().getDisplayName(this.getPropertyInParent());
}
slot = this.getSlot(slot);
// Bail if this slot doesn't exist
if (slot === null) {
return null;
}
// See if a BNameMap is being used.
nameMap = this.get("displayNames");
if (nameMap && nameMap.getType().getTypeSpec().equals("baja:NameMap")) {
// BOX does a special encoding for name maps by predecoding their format values. This saves us
// having to do anything for decoding since we want to access the display names synchronously.
if (!nameMap.$decodedMap) {
nameMapDisp = this.getDisplay("displayNames");
if (nameMapDisp) {
nameMap.$decodedMap = JSON.parse(nameMapDisp);
}
}
if (nameMap.$decodedMap) {
entry = getOwnProperty(nameMap.$decodedMap, slot.getName());
if (entry) {
return entry;
}
} else {
entry = nameMap.get(slot.getName());
if (entry) {
// If we've found an entry then used this for the Slot's display name
return entry.toString();
}
}
}
// This should be ok but just double check to ensure we have a display string
s = slot.$getDisplayName();
if (typeof s !== "string") {
s = "";
}
// If there is no display name then default to unescaping the slot name
if (s === "") {
s = baja.SlotPath.unescape(slot.getName());
}
return s;
};
/**
* Return a display string.
*
* If a Slot argument is defined, the display value for the Slot will be
* returned. If a Slot argument is not defined, the display value for the
* Complex will be returned.
*
* @param {baja.Property|String} [slot] the Slot or Slot name.
* @param {object} [cx] as of Niagara 4.10, you can pass a context as the
* second argument to use for string formatting. When a context is passed,
* a Promise will be returned to be resolved with the formatted string.
*
* @returns {String|null|Promise.<String|null>} display (or null if none available).
*
* @example
* <caption>Printing display values of slots</caption>
*
* // myPoint has a Property named out...
* baja.outln('The display string of the out Property: ' + myPoint.getDisplay('out'));
*
* // or use string formatting:
* return myPoint.getDisplay('out', { precision: 5 })
* .then(function (str) { baja.outln('formatted display string: ' + str); });
*/
Complex.prototype.getDisplay = function (slot, cx) {
var display = null;
if (slot === undefined) {
slot = this.getPropertyInParent();
if (cx && hasCustomToString(this)) {
display = this.toString(cx);
} else {
display = slot && slot.$getDisplay();
}
return cx ? Promise.resolve(display) : display;
}
slot = this.getSlot(slot);
if (cx) {
if (slot) {
if (slot.isProperty()) {
var value = this.get(slot);
if ((value.getType().isSimple() && !(value instanceof baja.DefaultSimple)) ||
(isComplex(value) && hasCustomToString(value))) {
display = value.toString(cx);
} else {
display = slot.$getDisplay();
}
} else {
return Promise.reject(new Error('"' + slot + '" is not a Property'));
}
}
return Promise.resolve(display);
} else {
if (!slot) {
return null;
}
if (!slot.isProperty()) {
throw new Error('"' + slot + '" is not a Property');
}
return slot.$getDisplay();
}
};
/**
* Return the `String` representation.
*
* @returns {String}
*/
Complex.prototype.toString = function () {
var str = this.getDisplay();
return typeof str === "string" ? str : this.getType().toString();
};
/**
* Utilize the slot facets, context, and slot to resolve the proper toString.
* @param {baja.Property|String} slot
* @param {object} [cx] the context
* @return {Promise.<String>}
* @throws {Error} if the slot name isn't a Property.
* @since Niagara 4.11
*/
Complex.prototype.propertyValueToString = function (slot, cx) {
var slotResult = this.getSlot(slot);
if (!slotResult || !slotResult.isProperty()) {
throw new Error('"' + slot + '" is not a Property');
}
cx = Object.assign(this.getFacets(slotResult).toObject(), cx);
return this.get(slot).toString(cx);
};
/**
* Return the parent.
*
* @returns {baja.Complex} parent
*/
Complex.prototype.getParent = function () {
return this.$parent;
};
/**
* Get the nearest ancestor of this object which is
* an instance of `Component`. If this object is itself
* a `Component`, then return `this`. Return
* null if this object doesn't exist under a `Component`.
*
* @since Niagara 4.10
* @returns {baja.Component | null}
*/
Complex.prototype.getParentComponent = function () {
var complex = this;
while (complex && !isComponent(complex)) {
complex = complex.getParent();
}
return complex;
};
/**
* @param {baja.Complex|*} comp the descendent complex to test
* @returns {boolean} if this Complex is an ancestor of the given Complex (but not the same
* instance). Returns false if the given parameter is not actually a Complex.
* @since Niagara 4.12
*/
Complex.prototype.isAncestorOf = function (comp) {
if (!isComplex(comp)) {
return false;
}
let ancestor = comp;
while ((ancestor = ancestor.getParent())) {
if (ancestor === this) { return true; }
}
return false;
};
/**
* Return the `Property` in the parent.
*
* @returns {baja.Property} the `Property` in the parent (null if not mounted).
*/
Complex.prototype.getPropertyInParent = function () {
return this.$propInParent;
};
/**
* Return the `Slot`.
*
* This is useful method to ensure you have the `Slot` instance instead of the
* Slot name String. If a `Slot` is passed in, it will simply be checked and
* returned.
*
* @param {baja.Slot|String} slot the `Slot` or Slot name.
* @returns {baja.Slot} the `Slot` for the `Component` (or null if the
* `Slot` doesn't exist).
*/
Complex.prototype.getSlot = function (slot) {
if (typeof slot === "string") {
return this.$map.get(slot);
}
strictArg(slot, baja.Slot);
return this.$map.get(slot.getName());
};
/**
* Return a `Cursor` for accessing a `Complex`'s Slots.
*
* Please see {@link module:baja/comp/SlotCursor} for useful builder methods.
*
* @param {Function} [filter] function to filter out the Slots we're not interested in.
* The filter function will be passed each `Slot` to see if it should be
* be included. The function must return false to filter out a value and true
* to keep it.
*
* @returns {module:baja/comp/SlotCursor} a `Cursor` for iterating through the
* `Complex`'s Slots.
* @see baja.Complex#get
*
* @example
* // A Cursor for Dynamic Properties
* var frozenPropCursor = myComp.getSlots().dynamic().properties();
*
* // A Cursor for Frozen Actions
* var frozenPropCursor = myComp.getSlots().frozen().actions();
*
* // An Array of Control Points
* var valArray = myComp.getSlots().properties().is("control:ControlPoint").toValueArray();
*
* // An Array of Action Slots
* var actionArray = myComp.getSlots().actions().toArray();
*
* // An Object Map of slot name/value pairs
* var map = myComp.getSlots().properties().toMap();
*
* // The very first dynamic Property
* var firstProp = myComp.getSlots().dynamic().properties().first();
*
* // The very last dynamic Property
* var lastProp = myComp.getSlots().dynamic().properties().last();
*
* // The very first dynamic Property value
* var firstVal = myComp.getSlots().dynamic().properties().firstValue();
*
* // The very first dynamic Property value
* var lastVal = myComp.getSlots().dynamic().properties().lastValue();
*
* // All the Slots that start with the name 'foo'
* var slotNameCursor = myComp.getSlots().slotName(/^foo/);
*
* // Use a custom Cursor to find all of the Slots that have a particular facets key/value
* var custom = myComp.getSlots(function (slot) {
* return slot.isProperty() && (this.getFacets(slot).get("myKey", "def") === "foo");
* });
*
* // Same as above
* var custom2 = myComp.getSlots().filter(function (slot) {
* return slot.isProperty() && (this.getFacets(slot).get("myKey", "def") === "foo");
* });
*
* // All Slots marked summary on the Component
* var summarySlotCursor = myComp.getSlots().flags(baja.Flags.SUMMARY);
*
* // Call function for each Property that's a ControlPoint
* myComp.getSlots().is("control:ControlPoint").each(function (slot) {
* baja.outln("The Nav ORD for the ControlPoint: " + this.get(slot).getNavOrd();
* });
*
* @example
* <caption>You may see better performance if you can filter on slots
* instead of on values.<caption>
*
* // slower
* var links = component.getSlots().toValueArray()
* .filter(function (value) { return value.getType().is('baja:Link'); });
*
* // faster - this avoids unnecessarily decoding every non-Link slot
* var links = component.getSlots().is('baja:Link').toValueArray();
*/
Complex.prototype.getSlots = function (filter) {
var cursor = this.$map.getCursor(this, SlotCursor);
if (filter) {
cursor.filter(filter);
}
return cursor;
};
/**
* Return `Flags` for a slot or for the `Complex`'s parent Property.
*
* If no arguments are provided and the `Complex` has a parent, the
* flags for the parent's `Property` will be returned.
*
* @see baja.Flags
*
* @param {baja.Slot|String} [slot] `Slot` or Slot name.
* @returns {Number} the flags for the `Slot` or the parent's `Property` flags.
*/
Complex.prototype.getFlags = function (slot) {
// If no arguments are specified then attempt to get parent properly slot Flags
if (arguments.length === 0) {
if (this.$parent === null || this.$propInParent === null) {
throw new Error("Complex has no parent");
}
return this.$parent.getFlags(this.$propInParent);
}
var mySlot = this.getSlot(slot);
if (mySlot === null) {
throw new Error("Slot doesn't exist: " + slot);
}
return mySlot.getFlags();
};
/**
* Return a `Property`'s value.
*
* Note that when an instance of a `Complex` is created, auto-generated
* accessors are created to make accessing a frozen `Property`'s value
* convenient (see example).
*
* Performance notes:
*
* BajaScript performs "lazy decoding" on Complexes - this means that after a
* Complex is retrieved from the station, its slot values are kept in their
* raw JSON encoding until they are actually needed (by calling `get()`). This
* helps performance by not spending CPU time decoding values that won't be
* used. It follows that calling `getSlot(slot)` will be much faster than
* calling `get(slot)` for the first time, so consider filtering on slots
* rather than on values, if possible, in performance-critical situations. See
* example under `getSlots()`.
*
* @param {baja.Property|String} prop the Property or Property name.
* @returns {baja.Value} the value for the Property (null if the Property doesn't exist).
* @see baja.Complex#getSlots
*
* @example
* <caption>
* Note that when an instance of a Complex is created,
* auto-generated setters are created to make setting a frozen Property's
* value convenient. The auto-generated setter is in the format of
* <code>set(first letter is capitalized)SlotName(...)</code>.
* </caption>
*
* // myPoint has a frozen Property named out...
* var val = myPoint.getOut();
* val = myPoint.get('out'); //equivalent
*/
Complex.prototype.get = function (prop) {
prop = this.getSlot(prop);
if (prop === null) {
return null;
}
return prop.$getValue();
};
/**
* Return true if the `Slot` exists.
*
* @param {baja.Property|String} prop the Property or Property name
* @returns {Boolean}
*/
Complex.prototype.has = function (prop) {
return this.getSlot(prop) !== null;
};
/**
* Return the result of `valueOf` on the specified Property's value.
* If `valueOf` is not available then the `Property`'s value is returned.
*
* @see baja.Complex#get
*
* @param {baja.Property|String} prop the `Property` or Property name.
* @returns the `valueOf` for the `Property`'s value or the `Property`'s
* value (null if the `Property` doesn't exist).
*/
Complex.prototype.getValueOf = function (prop) {
var v = this.get(prop);
if (v !== null && typeof v.valueOf === "function") {
return v.valueOf();
}
return v;
};
function syncStruct(fromVal, toVal) {
fromVal.getSlots().properties().each(function (fromProp) {
var toProp = toVal.getSlot(fromProp.getName());
// Sync value display and slot display name
fromProp.$setDisplay(toProp.$getDisplay());
fromProp.$setDisplayName(toProp.$getDisplayName());
if (fromProp.getType().isStruct()) {
// If another struct then sync appropriately
syncStruct(fromProp.$getValue(), toProp.$getValue());
} else {
// If a simple then directly set the value
fromProp.$setValue(toProp.$getValue());
}
});
}
/**
* Set a `Property`'s value.
*
* If the Complex is mounted, this will **asynchronously** set the Property's
* value on the Server.
*
* @name baja.Complex#set
* @function
*
* @param {Object} obj the object literal for the method's arguments.
* @param {baja.Property|String} obj.slot the `Property` or Property name
* the value will be set on.
* @param obj.value the value being set (Type must extend `baja:Value`).
* @param {Function} [obj.ok] (Deprecated: use Promise) the ok function
* callback. Called once the network call has succeeded on the Server.
* @param {Function} [obj.fail] (Deprecated: use Promise) the fail function
* callback. Called if this method has an error.
* @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be
* batched into this object.
* @param [obj.cx] the Context (used internally by BajaScript).
* @returns {Promise} a promise that will be resolved once the value has been
* set on the Complex. If the complex is mounted, will be resolved once the
* value has been saved to the server.
*
* @example
* <caption>
* An object literal is used to specify the method's arguments.
* </caption>
* myObj.set({
* slot: "outsideAirTemp",
* value: 23.5,
* batch: myBatch // if defined, any network calls will be batched into this object (optional)
* })
* .then(function () {
* baja.outln('value has been set on the server');
* })
* .catch(function (err) {
* baja.error('value failed to set on the server: ' + err);
* });
*
* @example
* <caption>
* Note that when an instance of a Complex is created,
* auto-generated setters are created to make setting a frozen Property's
* value convenient. The auto-generated setter is in the format of
* <code>set(first letter is capitalized)SlotName(...)</code>.
* </caption>
*
* // myPoint has a Property named outsideAirTemp...
* myObj.setOutsideAirTemp(23.5);
*
* // ... or via an Object Literal if more arguments are needed...
*
* myObj.setOutsideAirTemp({ value: 23.5, batch: myBatch })
* .then(function () {
* baja.outln('value has been set on the server');
* });
*/
Complex.prototype.set = function (obj) {
obj = objectify(obj);
var cb = new Callback(obj.ok, obj.fail, obj.batch),
prop = obj.slot,
val = obj.value,
propType,
valType,
isClx,
cx = bajaDef(obj.cx, null),
serverDecode = cx && cx.serverDecode,
commit = cx && cx.commit,
syncStructVals = cx && cx.syncStructVals,
comp = this; // Ensure 'this' is the Component in the ok and fail callback...
// Find the top level Component
while (comp !== null && !isComponent(comp)) {
comp = comp.getParent();
}
cb.addOk(function (ok, fail, resp) {
if (comp !== null) {
ok.call(comp, resp);
} else {
ok(resp);
}
});
cb.addFail(function (ok, fail, err) {
if (comp !== null) {
fail.call(comp, err);
} else {
fail(err);
}
});
try {
prop = this.getSlot(prop);
// If decoding from the Server then short circuit some of this
if (!serverDecode) {
// Validate arguments
strictArg(prop, Property);
strictArg(val);
if (prop === null) {
throw new Error("Could not find Property: " + obj.slot);
}
if (!baja.hasType(val)) {
throw new Error("Can only set BValue Types as Component Properties");
}
propType = prop.getType();
valType = val.getType();
if (valType.isAbstract()) {
throw new Error("Cannot set value in Complex to Abstract Type: " + valType);
}
// NCCB-20264 If val and prop types are both numbers, don't do this type coercion
// unless the slot is frozen
// If it's a dynamic slot, change the type of the slot
if (prop.isFrozen()) {
if (valType.isNumber() && propType.isNumber() &&
!valType.equals(propType) &&
!propType.isAbstract()) {
// Recreate the number with the correct boxed type if the type spec differs
val = propType.getInstance().constructor.make(val.valueOf());
}
}
if (!valType.isValue()) {
throw new Error("Cannot set non Value Types as Properties in a Complex");
}
if (val === this) {
throw new Error("Illegal argument: this === value");
}
}
if (cx) {
if (typeof cx.displayName === "string") {
prop.$setDisplayName(cx.displayName);
}
if (typeof cx.display === "string") {
prop.$setDisplay(cx.display);
}
}
if (val.equals(prop.$getValue())) {
// TODO: May need to check for mounted on Components here
cb.ok();
return cb.promise();
}
// Return if this set is trapped. If the set is trapped then the set operation will
// be proxied off to a remote Space elsewhere...
if (!commit && this.$fw("modifyTrap", [ prop ], val, cb, cx)) {
return cb.promise();
}
// Unparent
isClx = isComplex(val);
if (isClx) {
if (val.getParent()) {
throw new Error("Complex already parented: " + val.getType());
}
val.$parent = null;
val.$propInParent = null;
}
// If this is the same Struct from a Server decode then attempt to sync it
// rather than completely replace it...
if (syncStructVals && val.getType().isStruct() && val.getType().equals(prop.getType())) {
syncStruct(/*from*/prop.$getValue(), /*to*/val);
} else {
// Set new Property value
prop.$setValue(val);
// Parent
if (isClx) {
val.$parent = this;
val.$propInParent = prop;
// If we have a Component then attempt to mount it
if (isComponent(val) && this.isMounted()) {
this.$space.$fw("mount", val);
}
}
}
// Invoke modified event (this will bubble up to a Component for the changed callback etc).
this.$fw("modified", prop, cx);
// TODO: Modified a link. Need to set up Knobs?
cb.ok();
} catch (err) {
cb.fail(err);
}
return cb.promise();
};
/**
* Load all of the Slots on the `Complex`.
*
* @param {Object} [obj] the object literal for the method's arguments.
* @param {Function} [obj.ok] (Deprecated: use Promise) the ok function
* callback. Called once network call has succeeded on the Server.
* @param {Function} [obj.fail] (Deprecated: use Promise) the fail function
* callback. Called if this method has an error.
* @param {baja.comm.Batch} [obj.batch] if defined, any network calls will be
* batched into this object.
* @returns {Promise} a promise to be resolved when the slots have been
* loaded.
*
* @see baja.Component#loadSlots
*/
Complex.prototype.loadSlots = function (obj) {
if (obj && obj.ok && typeof obj.ok === "function") {
obj.ok.call(this);
}
return Promise.resolve();
};
/**
* Return the `Facets` for a Slot.
*
* If no arguments are provided and the `Complex` has a parent, the
* facets for the parent's Property will be returned.
*
* @param {baja.Slot|String} [slot] the `Slot` or Slot name.
* @returns {baja.Facets} the `Facets` for the `Slot` (or null if Slot not
* found) or the parent's Property facets.
*/
Complex.prototype.getFacets = function (slot) {
// If no arguments are specified then attempt to get parent properly slot Flags
if (arguments.length === 0) {
if (this.$parent !== null && this.$propInParent !== null) {
return this.$parent.getFacets(this.$propInParent);
}
return null;
}
const mySlot = this.getSlot(slot);
if (!mySlot) { return null; }
const slotFacets = mySlot.getFacets();
if (mySlot.isFrozen()) {
const [ closestComponent, slotPath ] = findClosestComponent(this);
if (closestComponent) {
const facetsMap = closestComponent.get('slotFacets_');
if (baja.hasType(facetsMap, 'baja:FacetsMap')) {
return baja.Facets.make(facetsMap.get(slotPath.concat(slot).join('/')), slotFacets);
}
}
}
return slotFacets;
};
/**
* Compare if all of this object's properties are equal to the specified object.
*
* @param {baja.Complex} obj
* @returns {Boolean} true if this object is equivalent to the specified object.
*/
Complex.prototype.equivalent = function (obj) {
if (!baja.hasType(obj)) {
return false;
}
if (!obj.getType().equals(this.getType())) {
return false;
}
if (this.$map.getSize() !== obj.$map.getSize()) {
return false;
}
// Compare flags and names for all slots.
let slots = obj.getSlots(),
s,
val;
while (slots.next()) {
s = slots.get();
// Always check flags
if (!this.getFlags(s.getName()).equals(obj.getFlags(s))) {
return false;
}
if (s.isProperty()) {
val = obj.get(s);
if (val === null) {
return false;
}
// Compare Property values
if (!val.equivalent(this.get(s.getName()))) {
return false;
}
}
// Ensure they're the same order in the SlotMap
if (this.$map.getIndex(s.getName()) !== slots.getIndex()) {
return false;
}
}
return true;
};
/**
* Create a clone of this `Complex`.
*
* If the `exact` argument is true and this `Complex` is a `Component` then
* the `defaultOnClone` and `removeOnClone` flags will be ignored.
*
* @param {Boolean} [exact=false] flag to indicate whether to create an exact copy
* @returns {baja.Complex} cloned Complex.
*/
Complex.prototype.newCopy = function (exact) {
var newInstance = this.getType().getInstance(),
props = this.getSlots().properties(),
p,
val;
while (props.next()) {
p = props.get();
// If default on clone and it's not an exact copy then skip this Property
if (!exact && (DEFAULT_ON_CLONE & p.getFlags()) === DEFAULT_ON_CLONE) {
continue;
}
// Make a copy of the Property value
val = this.get(p).newCopy(exact);
if (p.isFrozen()) {
newInstance.set({
"slot": p.getName(),
"value": val,
"cx": COPYING_CONTEXT
});
} else {
// If remove on clone and it's not an exact copy then skip copying this Property
if (!exact && (REMOVE_ON_CLONE & p.getFlags()) === REMOVE_ON_CLONE) {
continue;
}
// TODO: Skip BLinks, dynamic slots added in constructor and slots with removeOnClone set
newInstance.add({
"slot": p.getName(),
"value": val,
"flags": p.getFlags(),
"facets": p.getFacets(),
"cx": COPYING_CONTEXT
});
}
// Copy of flags
newInstance.getSlot(p.getName()).$setFlags(p.getFlags());
}
return newInstance;
};
/**
* Internal framework method.
*
* This method should only be used by Tridium developers. It follows
* the same design pattern as Niagara's Component 'fw' method.
*
* @private
*/
Complex.prototype.$fw = function (x, a, b, c, d) {
if (x === "modified") {
if (this.$parent !== null) {
this.$parent.$fw(x, this.$propInParent, b, c, d);
}
} else if (x === "modifyTrap") {
if (this.$parent !== null) {
a.push(this.$propInParent);
return this.$parent.$fw(x, a, b, c, d);
}
return false;
}
};
/**
* Return true if there is an overridden toString method.
*
* @inner
* @param {baja.Complex} complex
* @return {boolean}
*/
function hasCustomToString(complex) {
return complex.toString !== Complex.prototype.toString;
}
/**
* @param {baja.Complex} complex
* @returns {Array} first element is the closest `baja.Component` (which may
* be the given complex if it itself is a component), null if the complex is
* not rooted somewhere under a component. Second element is an array of slots
* leading from the closest component to the given complex (empty array if
* they are the same instance).
*/
function findClosestComponent(complex) {
const slotPath = [];
let closestComponent = complex;
while (closestComponent && !isComponent(closestComponent)) {
slotPath.splice(0, 0, closestComponent.getName());
closestComponent = closestComponent.getParent();
}
return [ closestComponent, slotPath ];
}
function isComponent(comp) {
return baja.hasType(comp, 'baja:Component');
}
function isComplex(comp) {
return baja.hasType(comp, 'baja:Complex');
}
return Complex;
});