Low Code App Development with OEMjs

The Object Event Modeling JavaScript Framework OEMjs supports

  1. enumerations (since JS does not support them);
  2. business object, event and activity classes (and class hierarchies) with semantic meta-data (e.g., for declarative constraint validation);
  3. unidirectional and bidirectional associations;
  4. storage adapters that facilitate switching from one storage technology (such as IndexedDB) to another one (such as Google's Cloud Firestore);
  5. view models for declarative user interface definitions;
  6. model-based generation of CRUD user interfaces and activity-based user interfaces.
Example 1: Minimal CRUD App + Tutorial

The purpose of this app is to manage book data. It consists of two JS module files, app.mjs and Book.mjs, with together 70 lines of code, only.

Example 2: Enumeration App + Tutorial

The purpose of this app is to show the use of enumeration attributes. It consists of two JS module files, app.mjs and Book.mjs, with together 70 lines of code, only.

Example 3: A CRUD App with 3 Associated Classes

The purpose of this app is to manage data about books and their authors and publishers. It consists of four files, app.mjs, Book.mjs, Author.mjs and Publisher.mjs, with together 105 lines of code, only. The app manages a many-to-many unidrectional association between books and authors, and a many-to-one unidrectional association between books and publishers.

Example 4: Public Library App with Activity User Interfaces

This app shows the use of business activities in addition to business objects, which is at the heart of Object Event Modeling (OEM).

Use Case 1: Enumerations

Handling Enumerations and Enumeration Attributes

Defining an Enumeration

var WeatherStateEL = new eNUMERATION ("WeatherStateEL", ["sunny", "partly cloudy", "cloudy", "cloudy with rain", "rainy"]);

Using an Enumeration as the Range of an Attribute

class Weather extends bUSINESSoBJECT {
  constructor (ws, t) {
    this.weatherState = ws;
    this.temperature = t;
  }
}
Weather.properties = {
  "weatherState": {range: WeatherStateEL, label: "Weather conditions"},
  "temperature": {range: "Decimal", label: "Temperature"}
}

Using Enumeration Literals

Recall that enumeration literals are constants that stand for a positive integer (the enumeration index).

For instance, the enum literal WeatherStateEL.SUNNY stands for the enum index 1. In program code, we do not use the enum index, but rather the enum literal. For instance,

var theWeather = new Weather({
  weatherState: WeatherStateEL.SUNNY, // do not use the enum index value 1
  temperature: 30
})

Looping over an Enumeration

We loop over the enumeration WeatherStateEL with a for loop counting from 1 to WeatherStateEL.MAX:

for (let weatherState = 1; weatherState <= WeatherStateEL.MAX; weatherState++) {
  switch (weatherState) {
  case WeatherStateEL.SUNNY: 
    ...
    break;
  case WeatherStateEL.PARTLY_CLOUDY: 
    ...
    break;
  }
}

Use Case 2: Constraint Valdiation

Defining Constraints in the Model and Checking Them in the View

OEMjs allows defining property constraints in a model class:

class Book extends bUSINESSoBJECT {
  constructor ({isbn, title, year, edition}) {
    super( isbn); 
    this.title = title;
    this.year = year;
    if (edition) this.edition = edition;
  }
}
Book.properties = {
  "isbn": {range:"NonEmptyString", isIdAttribute: true, label:"ISBN", pattern:/\b\d{9}(\d|X)\b/,
        patternMessage:"The ISBN must be a 10-digit string or a 9-digit string followed by 'X'!"},
  "title": {range:"NonEmptyString", min: 2, max: 50}, 
  "year": {range:"Integer", min: 1459, max: util.nextYear()},
  "edition": {range:"PositiveInteger", optional: true}
}

Suitable range constraints can be defined by using one of the following range keywords:

The constraints defined for a property in a business object class can be checked on input/change and before submit in an HTML form and, in addition, before commit in the add and update methods of a storage manager, using the generic validation method dt.check, as shown in the following example:

const formEl = document.querySelector("#Book-Create > form");
// loop over Book.properties and add event listeners for validation on input
for (const propName of Object.keys( Book.properties)) {
  const propDef = Book.properties[propName];
  formEl[propName].addEventListener("input", function () {
    var errMsg = dt.check( propName, propDef, formEl[propName].value).message;
    formEl[propName].setCustomValidity( errMsg);
  });
});

Use Case 3: Storage Adapters

Flexible Data Storage Management with Storage Adapters

OEMjs comes with a sTORAGEmANAGER class and two storage adapters for using IndexedDB or Google FireStore.

A storage manager works like a wrapper of the methods of an adapter. The storage manager methods invoke corresponding methods of its adapter. The following code example shows how to use a storage manager for invoking a data retrieval operation on a model class Book:

const storageAdapter = {name:"IndexedDB", dbName:"Test"};
const storageManager = new sTORAGEmANAGER( storageAdapter);
storageManager.retrieveAll( Book).then( list);