This cool JS snippet implements enumerations for JS:
function makeEnum(idField, indexField) { idField = idField || 'id'; indexField = indexField || 'index'; var enumeration = []; // Standalone object is useful for Underscore collection method usage, // for example: // _.keys(MyEnum.dict); var dict = {}; enumeration.dict = dict; // This method can be used as a callback of map method: // var objects = ids.map(MyEnum.get); enumeration.get = function(id) { return enumeration[id]; }; // Registers a new enumeration item. enumeration.register = function(instance) { instance[indexField] = enumeration.length; enumeration[instance[idField]] = instance; dict[instance[idField]] = instance; enumeration.push(instance); return enumeration; }; // Maps enumeration as dictionary: // MyEnum.mapDict(function(value, key) { return ...; }); // // This is a shorthand for the next Underscore expression: // _.chain(MyEnum.dict).map(function(value, key) { // return [key, ...]; // }).object().value(); enumeration.mapDict = function(iteratee, context) { var result = {}; for (var id in dict) { if (dict.hasOwnProperty(id)) { result[id] = iteratee.call(context || this, dict[id], id, dict); } } return result; }; return enumeration; }
This code is inspired by TypeScript enumeration implementation, however, it is not limited to two-way string:index mapping. Enumeration item can be arbitrary object, not a plain string. This makes them more powerful.
Essentially, enumeration is an array of all items. Therefore, all methods of Array class are applicable to enumeration: <tt>forEach</tt>, <tt>filter</tt>, <tt>map</tt> etc. In addition, makeEnum function extends the enumeration array with the next properties/methods:
- [id], .get(id) – get enumeration item by id
- .dict – pure dictionary from item id to an item
- .register – registers a new item
- .mapDict – helper mapping method (see description above)
Here's enumeration example:
// Define enumeration item class. function Outcome(id, color) { this.id = id; // String, enumeration key this.color = color; // String this.cls = id.toLowerCase().replace(/_/g, '-'); // String, CSS class this.name = $translate.instant('OUTCOME.' + id); // String this.index = 0; // Integer, auto-assigned by enumeration } // We can inherit it from something else and add methods. Outcome.prototype = Object.create(Model.prototype); Outcome.prototype.decorate = function(element) { element.css('color', this.color); }; // Create enumeration and register the items. var OutcomeEnum = makeEnum() .register(new Outcome('ON_SCHEDULE', 'green')) .register(new Outcome('ATTENTION', 'yellow')) .register(new Outcome('BEHIND_SCHEDULE', 'red')); // Add some static methods to enumeration. OutcomeEnum.compute = function(dueDate, estimatedDate) { var timeDiff = dueDate.getTime() - estimatedDate.getTime(); if (timeDiff === 0) { return OutcomeEnum.ATTENTION; } else if (timeDiff > 0) { return OutcomeEnum.ON_SCHEDULE; } else { return OutcomeEnum.BEHIND_SCHEDULE; } };
Let's review various use cases:
// Parse outcome from JSON. var outcome = OutcomeEnum[json.outcome]; // Parse outcome array from JSON. var outcomes = json.outcomes.map(OutcomeEnum.get); // Sort records by outcome. records.sort(function(x, y) { return x.outcome.index - y.outcome.index; }); // Compute outcome by parameters. var outcome = OutcomeEnum.compute(new Date(2016, 2, 1), new Date(2016, 1, 1)); // Iterate through outcome array. OutcomeEnum.forEach(function(outcome) { console.log(outcome.id, ' outcome has ', outcome.color, ' color'); }); // Even in AngularJS repeater. <div ng-repeat="outcome in OutcomeEnum"> {{outcome.name}} outcome has {{outcome.color}} color </div> // Compare outcomes. if (outcome === OutcomeEnum.ATTENTION) { console.log('Attention!'); } // Map outcome dictionary. var anticipatedOutcomes = [OutcomeEnum.ON_SCHEDULE, OutcomeEnum.ATTENTION]; function isOutcomeAnticipated(outcome) { return anticipatedOutcomes.indexOf(outcome) !== -1; } var outcomeAnticipated = OutcomeEnum.mapDict(isOutcomeAnticipated); console.log(outcomeAnticipated); /* Output: { "ON_SCHEDULE": true, "ATTENTION": true, "BEHIND_SCHEDULE": false } */
We use these JavaScript enumerations every day and benefit a lot from their intuitive and simple API. This is a good example of how a small chunk of well-thought code can make the life easier.