4. The Meta-Class Enumeration

We define an Enumeration meta-class, which supports both simple enumerations and code lists (but not record enumerations). While a simple enumeration is defined by a list of labels in the form of a JS array as the constructor argument such that the labels are used for the names of the enumeration literals, a code list is defined as a special kind of key-value map in the form of a JS object as the constructor argument such that the codes are used for the names of the enumeration literals. Consequently, the constructor needs to test if the invocation argument is a JS array or not. The following first part of the code shows how simple enumerations are created:

function Enumeration( enumArg) {
  if (Array.isArray( enumArg)) {
    // a simple enumeration defined by a list of labels
    if (!enumArg.every( function (l) {
            return (typeof l === "string"); })) {
      throw new ConstraintViolation(
        "A list of enumeration labels must be an array of strings!");          
    }
    this.labels = enumArg;
    this.enumLitNames = this.labels;
    this.codeList = null;
  } else if (...) {
    ... // a code list defined by a code/label map
  }
  this.MAX = this.enumLitNames.length;
  // generate the enumeration literals by capitalizing/normalizing
  for (let i=1; i <= this.enumLitNames.length; i++) {
    // replace " " and "-" with "_"
    const lbl = this.enumLitNames[i-1].replace(/( |-)/g, "_");
    // convert to array of words, capitalize them, and re-convert
    const LBL = lbl.split("_").map( lblPart => lblPart.toUpperCase()).join("_");
    // assign enumeration index
    this[LBL] = i;
  }
  Object.freeze( this);
};

After setting the MAX property of the newly created enumeration, the enumeration literals are created in a loop as further properties of the newly created enumeration such that the property name is the normalized label string and the value is the index, or sequence number, starting with 1. Notice that a label string like "text book" or "text-book" is normalized to the enumeration literal name "TEXT_BOOK", following a widely used convention for constant names. Finally, by invoking Object.freeze on the newly created enumeration, all its properties become 'unwritable' (or read-only).

The following second part of the code shows how code list enumerations are created:

function Enumeration( enumArg) {
  if (Array.isArray( enumArg)) {  // a simple enumeration
    ...
  } else if (typeof enumArg === "object" && 
             Object.keys( enumArg).length > 0) {
    // a code list defined by a code/label map
    if (!Object.keys( enumArg).every( function (code) {
            return (typeof enumArg[code] === "string"); })) {
      throw new OtherConstraintViolation(
          "All values of a code/label map must be strings!");          
    }
    this.codeList = enumArg;
    // use the codes as the names of enumeration literals
    this.enumLitNames = Object.keys( this.codeList);
    this.labels = this.enumLitNames.map( 
        c => `${enumArg[c]} (${c})`);
  }
  ...  
};

Notice that the code list labels in this.labels are extended by appending their codes in parenthesis.