/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* Defines {@link baja.DynamicEnum}.
* @module baja/obj/DynamicEnum
*/
define([ "lex!",
"bajaScript/sys",
"bajaScript/baja/obj/Enum",
"bajaScript/baja/obj/EnumRange",
"bajaScript/baja/obj/objUtil",
"bajaPromises" ], function (
lex,
baja,
Enum,
EnumRange,
objUtil,
Promise) {
"use strict";
var subclass = baja.subclass,
callSuper = baja.callSuper,
objectify = baja.objectify,
bajaDef = baja.def,
bajaHasType = baja.hasType,
strictArg = baja.strictArg,
cacheDecode = objUtil.cacheDecode,
cacheEncode = objUtil.cacheEncode;
/**
* Represents a `baja:DynamicEnum` in BajaScript.
*
* `DynamicEnum` stores an ordinal state variable as
* a `Number`. An instance of `EnumRange` may be used to specify the range.
*
* When creating a `Simple`, always use the `make()` method instead of
* creating a new Object.
*
* @class
* @alias baja.DynamicEnum
* @extends baja.Enum
*/
var DynamicEnum = function DynamicEnum(ordinal, range) {
callSuper(DynamicEnum, this, arguments);
this.$ordinal = strictArg(ordinal, Number);
this.$range = strictArg(range || baja.EnumRange.DEFAULT, baja.EnumRange);
};
subclass(DynamicEnum, Enum);
/**
* Make a `DynamicEnum`.
*
* @param {Object|Number} [obj] the Object Literal for the method's arguments or an ordinal.
* @param {Number} [obj.ordinal] the ordinal for the enum. If omitted it
* defaults to the first ordinal in the range or 0 if the range has no
* ordinals.
* @param {baja.EnumRange} [obj.range] the range for the enum.
* @param {baja.DynamicEnum|baja.FrozenEnum|Boolean|String} [obj.en] if defined, this enum will be used for the ordinal and range.
* As well as an enum, this can also be a TypeSpec
* String (moduleName:typeName) for a FrozenEnum.
* @returns {baja.DynamicEnum} the DynamicEnum.
*
* @example
* //An ordinal or an Object Literal can be used for the method's arguments...
* var de1 = baja.DynamicEnum.make(0); // Just with an ordinal
*
* //... or with an Object Literal...
*
* var de2 = baja.DynamicEnum.make({ordinal: 0, range: enumRange});
*
* //...or create from another enumeration...
*
* var de3 = baja.DynamicEnum.make({en: anEnum});
*/
DynamicEnum.make = function (obj) {
obj = objectify(obj, "ordinal");
var ordinal = 0,
range = bajaDef(obj.range, EnumRange.DEFAULT),
en;
if (obj.ordinal !== undefined) {
ordinal = obj.ordinal;
} else if (range.getOrdinals().length > 0) {
ordinal = range.getOrdinals()[0];
}
// Create from another enumeration...
if (obj.en !== undefined) {
en = obj.en;
if (!bajaHasType(en)) {
throw new Error("Invalid Enum Argument");
}
if (en.getType().is("baja:DynamicEnum")) {
return en;
}
// If a type spec is passed in then resolve it to the enum instance
if (typeof en === "string") {
en = baja.$(en);
}
if (en.getType().isFrozenEnum() || en.getType().is("baja:Boolean")) {
ordinal = en.getOrdinal();
range = en.getRange();
} else {
throw new Error("Argument must be an Enum");
}
}
// Check for default
if (ordinal === 0 && range === EnumRange.DEFAULT) {
return DynamicEnum.DEFAULT;
}
return new DynamicEnum(ordinal, range);
};
/**
* Make a DynamicEnum.
*
* @param {Object|Number} [obj] the Object Literal for the method's arguments or an ordinal.
* @param {Number} [obj.ordinal] the ordinal for the enum. If omitted it
* defaults to the first ordinal in the range or 0 if the range has no
* ordinals.
* @param {baja.DynamicEnum|baja.FrozenEnum|Boolean|String} [obj.en] if defined, this enum will be used for the ordinal and range.
* As well as an enum, this can also be a TypeSpec
* String (moduleName:typeName) for a FrozenEnum.
* @returns {baja.DynamicEnum} the DynamicEnum.
*
* @example
* // An ordinal or an Object Literal can be used for the method's arguments...
* var de1 = baja.$("baja:DynamicEnum").make(0); // Just with an ordinal
*
* //... or with an Object Literal...
*
* var de2 = baja.$("baja:DynamicEnum").make({ordinal: 0, range: enumRange});
*
* //...or create from another enumeration...
*
* var de3 = baja.$("baja:DynamicEnum").make({en: anEnum});
*/
DynamicEnum.prototype.make = function (obj) {
return DynamicEnum.make.apply(DynamicEnum, arguments);
};
/**
* Decode a `DynamicEnum` from a `String`.
*
* @param {String} str
* @param {Object} [params]
* @param {Boolean} [params.unsafe=false] if set to true, this will allow
* decodeFromString to continue. If not, decodeFromString will throw an error. This flag is for
* internal bajaScript use only. All external implementations should use decodeAsync instead.
* @returns {baja.DynamicEnum}
*/
DynamicEnum.prototype.decodeFromString = function (str, { unsafe = false } = {}) {
if (!unsafe) { throw new Error('DynamicEnum#decodeAsync should be called instead to ensure all types are loaded for the decode'); }
const [ o, r ] = str.split('@');
const defaultRange = EnumRange.DEFAULT;
const range = r ? defaultRange.decodeFromString(r, { unsafe }) : defaultRange;
return this.make({ ordinal: parseInt(o), range });
};
/**
* Asynchronously decode a `DynamicEnum` from a string, including importing
* any type spec encoded in the range.
* @param {string} str
* @param {baja.comm.Batch} [batch]
* @returns {Promise.<string>}
*/
DynamicEnum.prototype.decodeAsync = function (str, batch) {
const [ o, r ] = str.split('@');
const defaultRange = EnumRange.DEFAULT;
return Promise.resolve(r ? defaultRange.decodeAsync(r, batch) : defaultRange)
.then((range) => this.make({ ordinal: parseInt(o), range }));
};
DynamicEnum.prototype.decodeFromString = cacheDecode(DynamicEnum.prototype.decodeFromString);
/**
* Encode a `DynamicEnum` to a `String`.
*
* @returns {String}
*/
DynamicEnum.prototype.encodeToString = function () {
var s = "";
s += this.$ordinal;
if (this.$range !== baja.EnumRange.DEFAULT) {
s += "@" + this.$range.encodeToString();
}
return s;
};
DynamicEnum.prototype.encodeToString = cacheEncode(DynamicEnum.prototype.encodeToString);
/**
* Default DynamicEnum instance.
* @type {baja.DynamicEnum}
*/
DynamicEnum.DEFAULT = new DynamicEnum(0, EnumRange.DEFAULT);
/**
* Return the data type symbol.
*
* @returns {String} the data type symbol.
*/
DynamicEnum.prototype.getDataTypeSymbol = function () {
return "e";
};
/**
* Return whether the enum is active or not.
*
* @returns {Boolean} true if active.
*/
DynamicEnum.prototype.isActive = function () {
return this.$ordinal !== 0;
};
/**
* Return the ordinal.
*
* @returns {Number} the ordinal.
*/
DynamicEnum.prototype.getOrdinal = function () {
return this.$ordinal;
};
/**
* Return the range.
*
* @returns {baja.EnumRange} the enum range.
*/
DynamicEnum.prototype.getRange = function () {
return this.$range;
};
/**
* Return the tag for the ordinal.
*
* @returns {String} the tag.
*/
DynamicEnum.prototype.getTag = function () {
return this.$range.getTag(this.$ordinal);
};
/**
* Return the String representation of the DynamicEnum.
*
* @param {Object} [cx]
* @param {baja.EnumRange} [cx.range] range to use when formatting the string.
* If not provided, the range configured on the DynamicEnum directly will be
* used.
* @returns {String|Promise.<String>} a string representation of this
* DynamicEnum (Promise if context given; String if no context given)
*/
DynamicEnum.prototype.toString = function (cx) {
var ordinal = this.$ordinal;
if (!cx) {
return this.$range.getDisplayTag(ordinal);
} else {
return toEnumString(this, cx.range || this.getRange(), ordinal);
}
};
function toEnumString(en, range, ordinal) {
if (!range || !range.isOrdinal(ordinal)) {
return Promise.resolve(String(en));
}
var lexicon = range.getOptions().get('lexicon'),
got = range.get(ordinal),
tag = got.getTag();
if (lexicon) {
return lex.module(lexicon)
.then(function (lex) {
const tagKey = baja.SlotPath.unescape(tag);
let lexValue = lex.get(tag);
if (!lexValue && tagKey !== tag) {
lexValue = lex.get({ key: tagKey, def: tagKey });
}
return lexValue || tagKey;
})
.catch(function () { //module not found
return String(got);
});
} else {
return Promise.resolve(String(got));
}
}
/**
* Get the enum for the specified tag or ordinal.
*
* This method is used to access an enum based upon a tag or ordinal.
*
* @param {String|Number|baja.Simple} arg a tag or ordinal (any `baja:Number`
* type).
* @returns {baja.DynamicEnum} the enum for the tag or ordinal.
* @throws {Error} if the tag or ordinal is invalid.
* @since Niagara 4.6
*/
DynamicEnum.prototype.get = function (arg) {
const range = this.getRange();
const enumFromRange = range.get(arg);
if (enumFromRange instanceof DynamicEnum) {
return enumFromRange;
}
return DynamicEnum.make({ ordinal: enumFromRange.getOrdinal(), range });
};
/**
* Equals comparison via tag or ordinal.
*
* @param {String|Number|baja.DynamicEnum} arg the enum, tag or ordinal used for comparison.
* @returns {Boolean} true if equal.
*/
DynamicEnum.prototype.is = function (arg) {
var tof = typeof arg;
if (tof === "number") {
return arg === this.$ordinal;
}
if (tof === "string") {
return arg === this.getTag();
}
return this.equals(arg);
};
return DynamicEnum;
});