Flagged Enum

A factory to create a handy flagged enum

by KooiInc MeHere

CSS

body {
  font: normal 14px/17px calibri, verdana, arial;
  margin: 2rem;
  a[target] {
    text-decoration: none;
    font-weight: bold;
    &:hover {
      text-decoration: underline;
    }
    &:before {
      content: "↗";
      display: inline-block;
      margin-right: 3px;
    }
  }
}

JavaScript

/*
* Notes: 
* The proxy handler enables case insensitive property queries
* BigInt is used to enable bitflags with length > 52
* jsFiddle linter does not know BigInt literals ([number]n)
*/
demo();

// ---
function EnumFactory() {
  const proxyfy = {
    construct(target, args) { 
      const caseInsensitiveHandler = { 
          get(target, key) {
          return target[key.toUpperCase()] || target[key];	
        } 
      };
      const proxified = new Proxy(new target(...args), caseInsensitiveHandler ); 
      return Object.freeze(proxified);
    },
  }
  const ProxiedEnumCtor = new Proxy(EnumCtor, proxyfy);
  const throwIf = (assertion = false, message = `Unspecified error`, ErrorType = Error) => 
      assertion && (() => { throw new ErrorType(message); })();
  const hasFlag = (val, sub) => {
    throwIf(!val || !sub, "valueIn: missing parameters", RangeError);
    const andVal = (sub & val);
    return andVal !== BigInt(0) && andVal === val;
  };

  function EnumCtor(values) {
    throwIf(values.constructor !== Array || 
    		values.length < 2 || 
        values.filter( v => v.constructor !== String ).length > 0,
      `EnumFactory: expected Array of at least 2 strings`, TypeError);
    const base = BigInt(1);
    this.NONE = BigInt(0);
    values.forEach( (v, i) => this[v.toUpperCase()] = base<<BigInt(i) );
  }

  EnumCtor.prototype = {
    get keys() { return Object.keys(this).slice(1); },
    get allKeys() { return Object.keys(this); },
    subset(sub) {
      const arrayValues = this.keys;
      return new ProxiedEnumCtor(
        [...sub.toString(2)].reverse()
          .reduce( (acc, v, i) => ( +v < 1 ? acc : [...acc, arrayValues[i]] ), [] )
      );
    },
    getLabel(enumValue) {
      const tryLabel = Object.entries(this).find( value => value[1] === enumValue ).shift();
      return !enumValue || !tryLabel ? 
        "getLabel: no value parameter or value not in enum" :
        tryLabel;
   ...