Introduction
I love TypeScript. It brings the surity of static typing to the dynamo of the JavaScript language in a way that helps to support large complex projects. One of the best things it has is the enumeration. We all know the enumeration and the benefits of using an enumeration are well known. However one thing that TypeScript does not support out of the box is the concept of a flagged enumeration. This is useful in many circumstances and a desired tool in the TypeScript belt. The following examples and code is an implementation based on how C# handles Flagged enumerations.
Background
TypeScript defines and enumeration simply as you would expect.
enum Example {
Alpha,
Beta,
Cappa,
}
This simple definition produces this output.
var Example;
(function (Example) {
Example[Example["Alpha"] = 0] = "Alpha";
Example[Example["Beta"] = 1] = "Beta";
Example[Example["Cappa"] = 2] = "Cappa";
})(Example || (Example = {}));
This construct allows you to determine the enum value quickly and easily and is useful in switch statements and code readability. However what this does not do is easily allow you to turn this into a flagged enumeration with bitwise operations and comparison.
With the use of a bitwise shift operator we can turn this simple enum into a flagged enum
enum Example {
Alpha = 1 << 0,
Beta = 1 << 1,
Cappa = 1 << 2,
}
However this is only the start of what we need to get full functionality and usability out of the enumeration. It does not support intersects, to strings, contains, add, subtract or equal operators easily or smoothly.
Turning this into a comprehensive enumeration takes a little extra work.
The Code
The following code nugget enables you to turn an enum into a flagged enum class with a single line of code.
export module FlaggedEnum {
"use strict";
export interface IFlaggedEnumGenerator {
(_enum: any, _max: number): IFlaggedEnum;
}
export interface IFlaggedEnum {
(val: IFlaggedEnum): void;
(val: number): void;
(val: string): void;
toArray(): IFlaggedEnum[];
contains(val: IFlaggedEnum): boolean;
contains(val: number): boolean;
contains(val: string): boolean;
add(val: IFlaggedEnum): IFlaggedEnum;
add(val: number): IFlaggedEnum;
add(val: string): IFlaggedEnum;
remove(val: IFlaggedEnum): IFlaggedEnum;
remove(val: number): IFlaggedEnum;
remove(val: string): IFlaggedEnum;
intersect(val: IFlaggedEnum): IFlaggedEnum;
intersect(val: number): IFlaggedEnum;
intersect(val: string): IFlaggedEnum;
equals(val: IFlaggedEnum): boolean;
equals(val: number): boolean;
equals(val: string): boolean;
}
export var create: IFlaggedEnumGenerator = function (_enum: any, _max: number): IFlaggedEnum {
var base: any = _enum,
max: number = _max;
var Base: IFlaggedEnum = <any>function (val: any): void {
if (typeof (val) === "string") {
val = base[val];
}
this.value = val + 0;
};
var proto: any = Base.prototype;
proto.valueOf = function (): number { return <number>this.value; };
proto.toString = function (): string {
var list: string[] = [];
for (var i: number = 1; i < max; i = i << 1) {
if ((this.value & i) !== 0) {
list.push(base[i]);
}
}
return list.toString();
};
proto.toArray = function (): IFlaggedEnum[] {
var list: IFlaggedEnum[] = [];
for (var i: number = 1; i < max; i = i << 1) {
if ((this.value & i) !== 0) {
list.push(new Base(i));
}
}
return list;
};
proto.contains = function (val: any): boolean {
if (typeof (val) === "string") {
val = base[val];
}
return (this.value & val) === (val + 0);
};
proto.add = function (val: any): IFlaggedEnum {
if (typeof (val) === "string") {
val = base[val];
}
return new Base(this.value | val);
};
proto.remove = function (val: any): IFlaggedEnum {
if (typeof (val) === "string") {
val = this.base[val];
}
return new Base((this.value ^ val) & this.value);
};
proto.intersect = function (val: any): IFlaggedEnum {
if (typeof (val) === "string") {
val = base[val];
}
var final: number = 0;
for (var i: number = 1; i < max; i = (i << 1)) {
if ((this.value & i) !== 0 && (val & i) !== 0) {
final += i;
}
}
return new Base(final);
};
proto.equals = function (val: any): boolean {
if (typeof (val) === "string") {
val = base[val];
}
return this.value === (val + 0);
};
return Base;
};
}
Using the code
Using this module is simple. It is designed to create a class that that you can interact with and manipulate in place of your enum. So starting with our enum Example, we would create a flagged enum Example class like this.
var FlaggedExample: IFlaggedEnum = FlaggedEnum.create(Example, 1 << 2);
var example = new FlaggedExample(3);
Now we have a class that we can instantiate and variable that we can manipulate. The class definition is only needed one time for every enum type you want to convert into a Flagged Enum. Instances of the class can accept strings, enum and number values interchangeably in all their operations.
example.add(4);
example.remove("Alpha");
example.contains(Example.Alpha);
example.contains("Beta");
example.equals(6);
example.intersect(new FlaggedExample(5));
example.toArray();
example.valueOf();
example.toString();
example + 0;
example + Example.Alpha;
And thats it for this walk through. Go forth and flag.
History
The repository for this code is maintained @ Bitbucket.org