enumerated-type
Advanced tools
Comparing version 0.1.0 to 0.2.0
184
index.js
@@ -1,2 +0,2 @@ | ||
'use strict'; | ||
// 'use strict'; | ||
@@ -13,2 +13,106 @@ const isFunction = require("lodash.isfunction"); | ||
/** | ||
* @return {string} | ||
*/ | ||
function enumValueToString() { | ||
return `[Symbol: Symbol(${this.name}) ${JSON.stringify(this)}]`; | ||
} | ||
function enumValueFunc(enumInst, enumName, props) { | ||
const enumValue = function (name, value) { | ||
// Object.setPrototypeOf(this, props); | ||
Object.defineProperty(this, 'name', { | ||
value: name, | ||
writable: false, | ||
enumerable: true, | ||
}); | ||
if (typeof value === "object") { | ||
const symbol = Symbol(name); | ||
Object.defineProperty(this, '_value', { | ||
value: symbol, | ||
writable: false, | ||
enumerable: false, | ||
}); | ||
// // copy value props to enumValue | ||
const valueKeys = Object.keys(value); | ||
let j = valueKeys.length; | ||
while (j--) { | ||
const key = valueKeys[j]; | ||
const valueElement = value[key]; | ||
if (isFunction(valueElement) && valueElement.length === 0) { | ||
Object.defineProperty(this, key, { | ||
get: valueElement, | ||
}); | ||
} else { | ||
this[key] = isFunction(valueElement) ? valueElement.bind(this) : valueElement; | ||
} | ||
} | ||
} else { | ||
Object.defineProperty(this, '_value', { | ||
value: value, | ||
writable: false, | ||
enumerable: false, | ||
}); | ||
} | ||
Object.freeze(this); | ||
}; | ||
const propsProto = Object.create(props ? props : null); | ||
// convert functions without arguments to getters | ||
const propKeys = Object.keys(props); | ||
let i = propKeys.length; | ||
while (i--) { | ||
const propKey = propKeys[i]; | ||
const propValue = propsProto[propKey]; | ||
if (isFunction(propValue) && propValue.length === 0) { | ||
Object.defineProperty(propsProto.__proto__, propKey, { | ||
get: propValue, | ||
}); | ||
} | ||
} | ||
propsProto.enum = enumInst; | ||
propsProto.name = enumName; | ||
propsProto[Symbol.toPrimitive] = function (hint) { | ||
if (hint === 'number') return this.enum.values.indexOf(this); | ||
return this._value; | ||
}; | ||
Object.defineProperty(propsProto, 'index', { | ||
get: function () { | ||
return this.enum.values.indexOf(this); | ||
}, | ||
enumerable: true, | ||
}); | ||
Object.defineProperty(propsProto, 'next', { | ||
get: function () { | ||
const values = this.enum.values; | ||
const index = values.indexOf(this); | ||
return index >= 0 && index + 1 < values.length ? values[index + 1] : UNDEFINED; | ||
}, | ||
enumerable: true, | ||
}); | ||
Object.defineProperty(propsProto, 'previous', { | ||
get: function () { | ||
const values = this.enum.values; | ||
const index = values.indexOf(this); | ||
return index >= 1 && index < values.length ? values[index - 1] : UNDEFINED; | ||
}, | ||
enumerable: true, | ||
}); | ||
Object.freeze(propsProto); | ||
enumValue.prototype = propsProto; | ||
return enumValue; | ||
} | ||
function Enum(enumName, keyName, values, props) { | ||
@@ -26,9 +130,9 @@ if (keyName !== UNDEFINED && (typeof keyName !== "string" || keyName === '')) { | ||
props = Object.create(props ? props : null); | ||
props.constructor = Symbol; | ||
props.enum = this; | ||
const enumValueConstructor = enumValueFunc(this, enumName, props); | ||
const names = Object.keys(values); | ||
const keyNameValues = {}; | ||
const iMax = names.length; | ||
let objectValues = 0; | ||
let nonObjectValues = 0; | ||
let functionValues = 0; | ||
for (let i = 0; i < iMax; i++) { | ||
@@ -38,12 +142,4 @@ const name = names[i]; | ||
const symbol = Symbol(name); | ||
const enumValue = Object(symbol); | ||
enumValue.__proto__ = props; | ||
const enumValue = new enumValueConstructor(name, value); | ||
Object.defineProperty(enumValue, 'name', { | ||
value: name, | ||
writable: false, | ||
enumerable: true, | ||
}); | ||
if (keyName !== UNDEFINED) { | ||
@@ -58,15 +154,13 @@ if (value[keyName] === UNDEFINED) { | ||
keyNameValues[value[keyName]] = enumValue; | ||
} | ||
} else { | ||
if (value === UNDEFINED) { | ||
throw `IllegalArgument, value of each enum value cannot be undefined`; | ||
} | ||
// copy value props to enumValue | ||
const valueKeys = Object.keys(value); | ||
let j = valueKeys.length; | ||
while (j--) { | ||
let key = valueKeys[j]; | ||
const valueElement = value[key]; | ||
enumValue[key] = isFunction(valueElement) ? valueElement.bind(enumValue) : valueElement; | ||
if (keyNameValues[value] !== UNDEFINED) { | ||
throw `IllegalArgument, enum with value of '${value}' is already defined by ${keyNameValues[value]._name}`; | ||
} | ||
keyNameValues[value] = enumValue; | ||
} | ||
Object.freeze(enumValue); | ||
Object.defineProperty(this, name, { | ||
@@ -78,6 +172,26 @@ value: enumValue, | ||
if (typeof value === 'object') { | ||
if (isFunction(value)) { | ||
functionValues++; | ||
} else { | ||
objectValues++; | ||
} | ||
} else { | ||
nonObjectValues++; | ||
} | ||
enumValues.push(enumValue); | ||
} | ||
enumValues.sort((a, b) => a[keyName] > b[keyName] ? 1 : -1); | ||
if (objectValues !== 0 && nonObjectValues !== 0) { | ||
// inconsistent value types | ||
throw `IllegalArgument, enum values must all be objects or all non-objects, got ${objectValues} object values, ${nonObjectValues} non-object values`; | ||
} | ||
if (keyName !== UNDEFINED) { | ||
enumValues.sort((a, b) => a[keyName] > b[keyName] ? 1 : -1); | ||
} else { | ||
enumValues.sort((a, b) => a._value > b._value ? 1 : -1); | ||
} | ||
Object.freeze(enumValues); | ||
@@ -109,2 +223,18 @@ | ||
}); | ||
} else { | ||
const enumKeys = enumValues.map(value => value._value); | ||
Object.defineProperty(this, 'keys', { | ||
value: enumKeys, | ||
writable: false, | ||
enumerable: false, | ||
}); | ||
// define property with value to return matching enumValue to the key | ||
Object.defineProperty(this, 'value', { | ||
value: function (key) { | ||
return forEach.call(enumValues, (enumValue) => { | ||
if (enumValue._value === key) return BREAK(enumValue); | ||
}); | ||
}, | ||
}); | ||
} | ||
@@ -140,6 +270,6 @@ | ||
Enum.prototype[Symbol.toStringTag] = function toString() { | ||
return this.name; | ||
Enum.prototype.toString = function toString() { | ||
return '[object Enum(' + this.name + ')]'; | ||
}; | ||
module.exports = Enum; |
{ | ||
"name": "enumerated-type", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "enum type for javascript", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
137
README.md
@@ -21,5 +21,140 @@ # enumerated-type | ||
Use `new Enum(enumName, keyPropName, values, commonProperties)` to define an enum for given | ||
values. The property values of the `values` object can be objects/arrays/functions or | ||
non-objects. If | ||
```javascript | ||
const Enum = require("enumerated-type"); | ||
const StepType = new Enum("StepType", "stepTypeId", { | ||
duty: { stepTypeId: 1, isDuty: true, }, | ||
variable: { stepTypeId: 2, isVariable: true, }, | ||
optional: { stepTypeId: 3, isOptional: true, }, | ||
xor: { stepTypeId: 4, isGateway: true, isXor: true, }, | ||
and: { stepTypeId: 5, isGateway: true, isAnd: true, }, | ||
combineXor: { stepTypeId: 6, isGateway: true, isXor: true, isCombined: true, }, | ||
combineAnd: { stepTypeId: 7, isGateway: true, isAnd: true, isCombined: true, }, | ||
}, { | ||
isDuty: false, | ||
isVariable: false, | ||
isOptional: false, | ||
isGateway: false, | ||
isXor: false, | ||
isAnd: false, | ||
isCombined: false, | ||
}); | ||
``` | ||
`commonProperties` are used for each enum value for its prototype, therefore they provide a | ||
default value when the enum value does not define the property directly: | ||
```javascript | ||
const values = []; | ||
for (let value of StepType) { | ||
values.push({ | ||
isAnd: value.isAnd, | ||
isCombined: value.isCombined, | ||
isDuty: value.isDuty, | ||
isGateway: value.isGateway, | ||
isOptional: value.isOptional, | ||
isVariable: value.isVariable, | ||
isXor: value.isXor, | ||
name: value.name, | ||
stepTypeId: value.stepTypeId, | ||
}); | ||
} | ||
expect(values).toEqual([ | ||
{ "isAnd": false, "isCombined": false, "isDuty": true, "isGateway": false, "isOptional": false, "isVariable": false, "isXor": false, "name": "duty", "stepTypeId": 1, }, | ||
{ "isAnd": false, "isCombined": false, "isDuty": false, "isGateway": false, "isOptional": false, "isVariable": true, "isXor": false, "name": "variable", "stepTypeId": 2, }, | ||
{ "isAnd": false, "isCombined": false, "isDuty": false, "isGateway": false, "isOptional": true, "isVariable": false, "isXor": false, "name": "optional", "stepTypeId": 3, }, | ||
{ "isAnd": false, "isCombined": false, "isDuty": false, "isGateway": true, "isOptional": false, "isVariable": false, "isXor": true, "name": "xor", "stepTypeId": 4, }, | ||
{ "isAnd": true, "isCombined": false, "isDuty": false, "isGateway": true, "isOptional": false, "isVariable": false, "isXor": false, "name": "and", "stepTypeId": 5, }, | ||
{ "isAnd": false, "isCombined": true, "isDuty": false, "isGateway": true, "isOptional": false, "isVariable": false, "isXor": true, "name": "combineXor", "stepTypeId": 6, }, | ||
{ "isAnd": true, "isCombined": true, "isDuty": false, "isGateway": true, "isOptional": false, "isVariable": false, "isXor": false, "name": "combineAnd", "stepTypeId": 7, }, | ||
]); | ||
``` | ||
Use enum values as property names: | ||
```javascript | ||
let obj = {}; | ||
obj['duty'] = "++duty"; | ||
obj[StepType.duty] = "--duty"; | ||
obj[StepType.variable] = "--variable"; | ||
obj[StepType.optional] = "--optional"; | ||
obj[StepType.xor] = "--xor"; | ||
obj[StepType.and] = "--and"; | ||
obj[StepType.combineXor] = "--combineXor"; | ||
obj[StepType.combineAnd] = "--combineAnd"; | ||
expect(obj.duty).toEqual('++duty'); | ||
let values = []; | ||
StepType.forEach(value => { | ||
values.push(obj[value]); | ||
}); | ||
expect(values).toEqual([ | ||
"--duty", | ||
"--variable", | ||
"--optional", | ||
"--xor", | ||
"--and", | ||
"--combineXor", | ||
"--combineAnd", | ||
]); | ||
``` | ||
### Enum Instance Properties | ||
* `.name` : name of the enum passed to constructor | ||
* `.values` : array of enum values sorted by `keyPropName` if provided or by value if non-object | ||
values. | ||
* `.keys` : array of enum key properties if keyPropName was passed to constructor, otherwise | ||
array of enum values which were not objects | ||
* `[keyPropName](key)`: function taking key and returning enum value whose `keyPropName` equals | ||
the value of the key. Only defined if `keyPropName` is defined and is a string. Convenience | ||
method for converting key property value to an enum value. | ||
* `.value(key)`: function taking the a value and returning enum value whose value equals the | ||
key. Only defined if `keyPropName` is not defined. | ||
* `.forEach`: function (callback, defaultResult) taking a callback function(value, index, | ||
values) and optional default result if callback does not return `BREAK(result)` or | ||
`RETURN(result)`. see [for-each-break] | ||
* `.map`: function (callback) taking a callback function(value, index, values) and returning | ||
array of values returned my callback, unless callback returns `BREAK(result)` or | ||
`RETURN(result)`. see [for-each-break] | ||
* `.filter`: function (callback) taking a callback function(value, index, values) and returning | ||
array of values for which callback result tested true, unless callback returns `BREAK(result)` | ||
or `RETURN(result)`. see [for-each-break] | ||
* `[Symbol.iterator]`: iterator over enum values | ||
* `[Symbol.hasInstance]`: handles `instanceof` and returns true when left hand side is an enum | ||
value of this enum instance. | ||
* `.toString`: returns `[object Enum(enumName)]` | ||
### Enum Value Instance Properties | ||
In addition to enum value properties passed for the value each enum value has the following | ||
additional properties: | ||
* `.enum`: parent enum instance. | ||
* `.name`: name of the enum value (property name in values object passed to `Enum()` | ||
constructor. | ||
* `._value`: Symbol(name) symbol instance for the property name of the enum value. | ||
* `.index`: the enum value's index within enum instances values array. | ||
* `.next`: enum value after this enum or `undefined` if last enum value. | ||
* `.previous`: enum value before this enum or `undefined` if first enum value. | ||
* `[Symbol.toPrimitive]`: returns `_value` property for `string` or `default` hint, for `number` | ||
returns the enum value's index within enum instances values array. | ||
Functions passed in as property values for enum value properties or `commonProperties` will have | ||
`this` set to the enum value instance. Any functions which take no arguments (ie. | ||
function.length === 0) will be converted to getters of the enum value instance and should not | ||
use the function call syntax. | ||
## License | ||
MIT, see [LICENSE.md](http://github.com/vsch/enumerated-type/blob/master/LICENSE.md) for details. | ||
MIT, see [LICENSE.md](http://github.com/vsch/enumerated-type/blob/master/LICENSE.md) for | ||
details. | ||
[for-each-break]: http://github.com/vsch/for-each-break/blob/master/README.md |
17047
6
229
160