Low Code App Development with OEMjs
The Object Event Modeling JavaScript Framework OEMjs supports
- enumerations (since JS does not support them);
- business object, event and activity classes (and class hierarchies) with semantic meta-data (e.g., for declarative constraint validation);
- unidirectional and bidirectional associations;
- storage adapters that facilitate switching from one storage technology (such as IndexedDB) to another one (such as Google's Cloud Firestore);
- view models for declarative user interface definitions;
- 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:
- "String", "NonEmptyString", "Identifier", "Email", "URL", "PhoneNumber"
- "Integer", "PositiveInteger", "NonNegativeInteger", "AutoNumber"
- "Decimal", "Number", "Percent", "ClosedUnitInterval", "OpenUnitInterval"
- "Boolean"
- "Date", "DateTime"
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);