Copyright © 2023 Gerd Wagner (CC BY-NC)
You may run this example app from our server.
Published 2023-06-19
This tutorial explains how enumerations and enumeration attributes are handled in the Object Event Modeling JavaScript framework OEMjs, which allows low-code app development based on business object, business event and business activity classes defined with property ranges and other property constraints.
The enumeration tutorial is Part 2 of our series of four tutorials about model-based development of front-end web applications with OEMjs. It shows how to build a web app where business object classes have enumeration attributes.
The other parts of the tutorial are:
Part 1: Building a minimal app.
Part 3: Managing (both unidirectional and bidirectional) associations, such as the associations between books and publishers, assigning a publisher to a book (and books to a publisher), and between books and authors, assigning authors to a book (and books to an author).
Part 4: Handling subtype (inheritance) relationships between object types.
In OEMjs, enumerations can be defined in the following way:
1 2 3 4 5 6 7 | const BookCategoryEL = new eNUMERATION("BookCategoryEL", ["novel","biography","textbook","other"]); const LanguageEL = new eNUMERATION("LanguageEL", {"en":"English","de":"German","fr":"French","es":"Spanish", "it":"Italian","pt":"Portugese","gr":"Greek","pl":"Polish"}); const PublicationFormEL = new eNUMERATION("PublicationFormEL", ["hardcover","paperback","ePub","PDF"]); |
Notice that a simple enumeration (like "BookCategoryEL") is defined with a name and a list of labels for enumeration literals, while a code list enumeration (like "LanguageEL") is defined with a name and a code list map consisting of a number of code-label pairs such as "en":"English".
Enumerations can be used for defining the range of attributes in the following way:
1 2 3 4 5 6 7 8 9 | Book.properties = { ... "category": {range: BookCategoryEL, label:"Category"}, "publicationForms": {range: PublicationFormEL, label:"Publication forms", minCard: 1, maxCard: Infinity}, "originalLanguage": {range: LanguageEL, label:"Original language"}, "otherLanguages": {range: LanguageEL, label:"Other languages", maxCard: Infinity} } |
Based on the property definitions of the Book.mjs module file, a default user interface (UI) is generated for each of the data management operations Create, Retrieve, Update and Delete (CRUD).
For the data management operation Retrieve, the following UI is created, showing all book records retrieved from the app's database with columns showing their enumeration attribute values:
Notice that the enumeration attributes Category and Original language are single-valued, while the enumeration attributes Publication forms and Other languages are multi-valued.
For the data management operation Update, the following UI is created:
In all application domains, there are string-valued attributes with a fixed list of possible string values. These attributes are called enumeration attributes, and the fixed value lists defining their possible string values are called enumerations. For instance, when we have to manage data about people, we often need to include information about their gender. The possible values of a gender
attribute may be restricted to one of the enumeration labels "male","female" and "undetermined", or to one of the enumeration codes "M", "F" and "U". Whenever we deal with codes, we also need to have their corresponding labels, at least in a legend explaining the meaning of each code.
Instead of using the enumeration string values as the internal values of an enumeration attribute, it is preferable to use a simplified internal representation for them, such as the positive integers 1, 2, 3, etc., which enumerate the possible values. However, since these integers do not reveal their meaning (which is indicated by the enumeration label) in program code, for readability we rather use special constants, called enumeration literals, such as MALE
or M
, prefixed by the name of the enumeration like in this.gender = GenderEL.MALE
. Notice that we follow the convention that the names of enumeration literals are written all upper case, and that we also use the convention to suffix the name of an enumeration datatype with "EL" standing for "enumeration literal" (such that we can recognize from the name GenderEL
that each instance of this datatype is a "gender enumeration literal").
There are also enumerations having records as their instances, such that one of the record fields provides the name of the enumeration literals. An example of such an enumeration is the following list of units of measurement:
Units of Measurement | ||
---|---|---|
Unit Symbol | Unit Name | Dimension |
m | meter | length |
kg | kilogram | mass |
g | gram | mass |
s | second | time |
ms | milisecond | time |
Notice that since both the "Unit Symbol" and the "Unit Name" fields are unique, either of them could be used for the name of the enumeration literals.
In summary, we can distinguish between the following three forms of enumerations:
simple enumerations define a list of self-explanatory enumeration labels;
code lists define a list of code/label pairs.
record enumerations consist of a list of records, so they are defined like classes with simple attributes defining the record fields.
These three forms of enumerations are discussed in more detail below.
Notice that, since enumerations are used as the range of enumeration attributes, they are considered to be datatypes.
Enumerations may have further features. For instance, we may want to be able to define a new enumeration by extending an existing enumeration. In programming languages and in other computational languages, enumerations are implemented with different features in different ways. See also the Wikipedia article on enumerations.
A simple enumeration defines a fixed list of self-explanatory enumeration labels, like in the example of a GenderEL
enumeration shown in the following UML class diagram:
Since the labels of a simple enumeration are being used, in capitalized form, as the names of the corresponding enumeration literals (GenderEL.MALE
, GenderEL.FEMALE
, etc.), we may also list the (all upper case) enumeration literals in the UML enumeration datatype, instead of the corresponding (lower or mixed case) enumeration labels.
A code list is an enumeration that defines a fixed list of code/label pairs. Unfortunately, the UML concept of an enumeration datatype does not support the distinction between codes as enumeration literals and their labels. For defining both codes and labels in a UML class diagram in the form of an enumeration datatype, we may use the attribute compartment of the data type rectangle and use the codes as attribute names defining the enumeration literals, and set their initial values to the corresponding label. This approach results in a visual representation as in the following diagram:
In the case of a code list, we can use both the codes or the labels as the names of enumeration literals, but using the codes seems preferable for brevity (GenderEL.M
, GenderEL.F
, etc.). For displaying the value of an enumeration attribute, it's an option to show not only the label, but also the code, like "male (M)", provided that there is sufficient space. If space is an issue, only the code can be shown.
A record enumeration defines a record type with a unique field designated to provide the enumeration literals, and a fixed list of records of that type. In general, a record type is defined by a set of field definitions (in the form of primitive datatype attributes), such that one of the unique fields is defined to be the enumeration literal field, and a set of operation definitions.
Unfortunately, record enumerations, as the most general form of an enumeration datatype, are not supported by the current version of UML (2.5) where the general form of an enumeration is defined as a special kind of datatype (with optional field and operation definitions) having an additional list of unique strings as enumeration literals (shown in a fourth compartment). The UML definition does neither allow designating one of the unique fields as the enumeration literal field, nor does it allow populating an enumeration with records.
Consequently, for showing a record enumeration in a UML class diagram, we need to find a workaround. For instance, if our modeling tool allows adding a drawing, we could draw a rectangle with four compartments, such that the first three of them correspond to the name, properties and operations compartments of a datatype rectangle, and the fourth one is a table with the names of properties/fields defined in the second compartment as column headers, as shown in the following figure.
UnitEL | ||
---|---|---|
unitSymbol: String unitName: String dimension: String | ||
Unit Symbol | Unit Name | Dimension |
m | meter | length |
kg | kilogram | mass |
g | gram | mass |
s | second | time |
ms | millisecond | time |
There may be cases of enumerations that need to be extensible, that is, it must be possible to extend their list of enumeration values (labels or code/label pairs) by adding a new one. This can be expressed in a class diagram by appending an ellipsis to the list of enumeration values, as shown in Figure 2-1. An example of an extensible enumeration.
Since enumeration values are internally represented by enumeration literals, which are normally stored as plain positive integers in a database, a new enumeration value can only be added at the end of the value list such that it can be assigned a new index integer without re-assigning the indexes of other enumeration values. Otherwise, the mapping of enumeration indexes to corresponding enumeration values would not be preserved.
Alternatively, if new enumeration values have to be inserted in-between other enumeration values, and their indexes re-assigned, this implies that
enumeration indexes are plain sequence numbers and do no longer identify an enumeration value;
the value of an enumeration literal can no longer be an enumeration index, but rather has to be an identifying string: preferably the enumeration code in the case of a code list, or the enumeration label, otherwise.
An enumeration attribute is an attribute that has an enumeration as its range.
In the user interface, an output field for an enumeration attribute would display the enumeration label, rather than its internal value, the corresponding enumeration index.
For allowing user input to an enumeration attribute, we can use the UI concept of a (drop-down) selection list, which may be implemented with an HTML select
element, such that the enumeration labels would be used as the text content of its option
elements, while the enumeration indexes would be used as their values. We have to distinguish between single-valued and multi-valued enumeration attributes. In the case of a single-valued enumeration attribute, we use a standard select
element. In the case of a multi-valued enumeration attribute, we use a multi-select
element with the HTML attribute setting multiple="multiple"
.
In the case of using a single-select
element for an optional enumeration attribute, we need to include in its options an element like "---" for indicating that nothing has been selected. Then, the UI page for the CRUD use case "Create" shows "---" as the initially selected option.
For both cases, an example is shown in Figure 2-2. A single and a multiple select
element with no selected option. While the single select
element for "Original language" shows the initially selected option "---" denoting "nothing selected", the multiple select
element "Other available languages" shows a small window displaying four of the options that can be selected.
For usability, the multiple selection list can only be implemented with an HTML select
element, if the number of enumeration literals does not exceed a certain threshold (like 20), which depends on the number of options the user can see on the screen without scrolling.
For user input for a single-valued enumeration attribute, a radio button group can be used instead of a single selection list, if the number of enumeration literals is sufficiently small (say, not larger than 7). A radio button group is implemented with an HTML fieldset
element acting as a container of labeled input
elements of type "radio", all having the same name, which is normally equal to the name of the represented enumeration attribute.
A checkbox group
For user input for a multi-valued enumeration attribute, a checkbox group can be used instead of a multiple selection list, if the number of enumeration literals is sufficiently small (say, not larger than 7). A checkbox group is implemented with an HTML fieldset
element acting as a container of labeled input
elements of type "checkbox", all having the same name, which is normally equal to the name of the represented enumeration attribute.
Defining enumerations is directly supported in information modeling languages (such as in UML Class Diagrams), in data schema languages (such as in XML Schema, but not in SQL) , and in many programming languages (such as in C++ and Java, but not in JavaScript).
Unfortunately, standard SQL does not support enumerations. Some DBMS, such as MySQL and Postgres, provide their own extensions of SQL column definitions in the CREATE TABLE statement allowing to define enumeration-valued columns.
A MySQL enumeration is specified as a list of enumeration labels with the keyword ENUM
within a column definition, like so:
CREATE TABLE people ( name VARCHAR(40), gender ENUM('MALE', 'FEMALE', 'UNDETERMINED') );
A Postgres enumeration is specified as a special user-defined type that can be used in column definitions:
CREATE TYPE GenderEL AS ENUM ('MALE', 'FEMALE', 'UNDETERMINED'); CREATE TABLE people ( name text, gender GenderEL )
In XML Schema, an enumeration datatype can be defined as a simple type restricting the primitive type xs:string
in the following way:
<xs:simpleType name="BookCategoryEL"> <xs:restriction base="xs:string"> <xs:enumeration value="NOVEL"/> <xs:enumeration value="BIOGRAPHY"/> <xs:enumeration value="TEXTBOOK"/> <xs:enumeration value="OTHER"/> </xs:restriction> </xs:simpleType>
In JavaScript, we can define an enumeration as a special JS object having a property for each enumeration literal such that the property's name is the enumeration literal's name (the enumeration label or code in upper case) and its value is the corresponding enumeration index. One approach for implementing this is using the Object.defineProperties
method:
var BookCategoryEL = null; Object.defineProperties( BookCategoryEL, { NOVEL: {value: 1, writable: false}, BIOGRAPHY: {value: 2, writable: false}, TEXTBOOK: {value: 3, writable: false}, OTHER: {value: 4, writable: false}, MAX: {value: 4, writable: false}, labels: {value:["novel","biography","textbook","other"], writable: false} });
This definition allows using the enumeration literals BookCategoryEL.NOVEL
, BookCategoryEL.BIOGRAPHY
etc., standing for the enumeration indexes 1, 2 , 3 and 4, in program statements. Notice how this definition takes care of the requirement that enumeration literals like BookCategoryEL.NOVEL
are constants, the value of which cannot be changed during program execution. This is achieved with the help of the property descriptor writable: false
in the Object.defineProperties
statement.
We can also use a more generic approach and define a meta-class Enumeration
for creating enumerations in the form of special JS objects:
function Enumeration( enumLabels) { var i=0, LBL=""; this.MAX = enumLabels.length; this.labels = enumLabels; // generate the enum literals as capitalized keys/properties for (i=1; i AAAAAAA enumLabels.length; i++) { LBL = enumLabels[i-1].toUpperCase(); this[LBL] = i; } // prevent any runtime change to the enumeration Object.freeze( this); };
Using this Enumeration
class allows to define a new enumeration in the following way:
var BookCategoryEL = new Enumeration(["novel","biography","textbook","other"])
Having an enumeration like BookCategoryEL
, we can then check if an enumeration attribute like category
has an admissible value by testing if its value is not smaller than 1 and not greater than BookCategoryEL.MAX
. Also, the label can be retrieved in the following way:
formEl.category.value = BookCategoryEL.labels[this.category - 1];
As an example, we consider the following model class Book
with the enumeration attribute category
:
function Book( slots) { this.isbn = ""; // string this.title = ""; // string this.category = 0; // number (BookCategoryEL) if (arguments.length > 0) { this.setIsbn( slots.isbn); this.setTitle( slots.title); this.setCategory( slots.category); } };
For validating input values for the enumeration attribute category
, we can use the following check function:
Book.checkCategory = function (c) { if (!c) { return new MandatoryValueConstraintViolation( "A category must be provided!"); } else if (!Number.isInteger(c) || c < 1 || c > BookCategoryEL.MAX) { return new RangeConstraintViolation( "The category must be a positive integer " + "not greater than "+ BookCategoryEL.MAX +" !"); } else { return new NoConstraintViolation(); } };
Notice how the range constraint defined by the enumeration BookCategoryEL
is checked: it is tested if the input value c
is a positive integer and if it is not greater than BookCategoryEL.MAX
.
We again consider the simple data management problem that we have considered before. So, again, the purpose of our app is to manage information about books. But now we have four additional enumeration attributes, as shown in the UML class diagram in Figure A-1. An information design model for the object type Book
below:
the single-valued mandatory attribute originalLanguage
with the enumeration datatype LanguageEL
as its range,
the multi-valued optional attribute otherAvailableLanguages
with range LanguageEL
,
the single-valued mandatory attribute category
with range BookCategoryEL
the multi-valued mandatory attribute publicationForms
with range PublicationFormEL
Notice that the attributes otherAvailableLanguages
and publicationForms
are multivalued, as indicated by their multiplicity expressions [*] and [1..*]. This means that the possible values of these attributes are sets of enumeration literals, such as the set {ePub, PDF}, which can be represented in JavaScript as a corresponding array list of enumeration literals, [PublicationFormEL.EPUB, PublicationFormEL.PDF]
.
The meaning of the design model and its enumeration attributes can be illustrated by a sample data population:
ISBN | Title | Original language | Other languages | Category | Publication forms |
---|---|---|---|---|---|
0553345842 | The Mind's I | English (en) | de, es, fr | novel | paperback, ePub, PDF |
1463794762 | The Critique of Pure Reason | German (de) | de, es, fr, pt, ru | other | paperback, PDF |
1928565379 | The Critique of Practical Reason | German (de) | de, es, fr, pt, ru | other | paperback |
0465030793 | I Am A Strange Loop | English (en) | es | textbook | hardcover, ePub |