-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
226 lines (212 loc) · 14 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="UTF-8"/>
<title>The OEMjs Business App Framework</title>
<meta name="viewport" content="width=device-width, initial-scale = 1.0" />
<meta name="description"
content="The Object Event Modeling JavaScript (OEMjs) framework allows low-code app development, based on business object, business event and business activity classes defined with property ranges and other property constraints. OEMjs allows building MVC apps quickly without boilerplate code for UIs, constraint validation and data storage management."/>
<!-- Facebook Open Graph annotations -->
<meta property="og:site_name" content="OEMjs"/>
<meta property="og:title" content="The OEMjs Business App Framework"/>
<meta property="og:locale" content="en" />
<meta property="og:description"
content="The Object Event Modeling JavaScript framework OEMjs allows low-code app development, based on business object, business event and business activity classes defined with property ranges and other property constraints. OEMjs allows building MVC apps quickly without boilerplate code for UIs, constraint validation and data storage management."/>
<meta property="og:url" content="https://gwagner57.github.io/OEMjs/"/>
<meta property="og:image" content=""/> <!-- recommended 1200 x 630 -->
<meta name="twitter:card" content="summary_large_image"/>
<!--
<link rel="icon" type="image/png" href="img/favicon32px.png" sizes="32x32"/>
<link rel="icon" type="image/png" href="img/favicon16px.png" sizes="16x16"/>
-->
<link rel="stylesheet" type="text/css" href="docs/css/normalize.css" />
<link rel="stylesheet" type="text/css" href="docs/css/prio-menu.css" />
<link rel="stylesheet" type="text/css" href="docs/css/dpmn.css" />
</head>
<body>
<header>
<div id="navigation" class="priority-nav">
<nav id="menu">
<ul id="menu-closed">
<li><a href="./index.html">Home</a></li>
<li><a href="https://github.com/gwagner57/OEMjs">GitHub Repo</a></li>
<li><a href="./tutorials">Tutorials</a></li>
<!--
<li><a href="./faq.html" title="Frequently Asked Questions">FAQ</a></li>
-->
<li><a href="#menu-closed">×</a></li>
<li><a href="#menu">☰</a></li>
</ul>
</nav>
</div>
<div id="share"><button id="share-this" title="Share this"><svg width="20" height="20" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(0 -552.36)">
<path d="m380.24 566.95a62.704 62.704 0 0 0 -62.704 62.704 62.704 62.704 0 0 0 1.956 15.302l-173.93 100.42a62.704 62.704 0 0 0 -43.011 -17.184 62.704 62.704 0 0 0 -62.704 62.704 62.704 62.704 0 0 0 62.704 62.704 62.704 62.704 0 0 0 43.05 -17.163l173.91 100.41a62.704 62.704 0 0 0 -1.9735 15.291 62.704 62.704 0 0 0 62.704 62.704 62.704 62.704 0 0 0 62.704 -62.704 62.704 62.704 0 0 0 -62.704 -62.704 62.704 62.704 0 0 0 -42.42 16.61l-174.32-100.66a62.704 62.704 0 0 0 1.7706 -14.493 62.704 62.704 0 0 0 -1.774 -14.493l174.35-100.66a62.704 62.704 0 0 0 42.406 16.621 62.704 62.704 0 0 0 62.704 -62.704 62.704 62.704 0 0 0 -62.704 -62.704z" fill="black" /></g></svg> Share</button>
<ul class='links'>
<li class='share facebook' title="Share on Facebook"><svg aria-hidden="true" class="icon-social" xmlns="http://www.w3.org/2000/svg"><symbol id="social-facebook" viewBox="0 0 18 18"><path d="M15.7,1.5H2.3c-0.5,0-0.8,0.4-0.8,0.8v13.3c0,0.5,0.4,0.8,0.8,0.8h7.2v-5.8h-2V8.4h2V6.8c0-1.9,1.2-3,2.9-3 c0.8,0,1.5,0.1,1.7,0.1v2l-1.2,0c-0.9,0-1.1,0.4-1.1,1.1v1.4h2.2l-0.3,2.3h-1.9v5.8h3.8c0.5,0,0.8-0.4,0.8-0.8V2.3 C16.5,1.9,16.1,1.5,15.7,1.5z"></path></symbol><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#social-facebook"/></svg></li>
<li class='share twitter' title="Share on Twitter"><svg aria-hidden="true" class="icon-social" xmlns="http://www.w3.org/2000/svg"><symbol id="social-twitter" viewBox="0 0 18 18"><path d="M16.5,4.3c-0.6,0.2-1.1,0.4-1.8,0.5c0.6-0.4,1.1-1,1.4-1.7c-0.6,0.4-1.3,0.6-2,0.8c-0.6-0.6-1.4-1-2.2-1 c-1.7,0-3.1,1.4-3.1,3.1c0,0.2,0,0.5,0.1,0.7C6.3,6.5,4.1,5.3,2.5,3.4C2.3,3.9,2.1,4.4,2.1,5c0,1.1,0.5,2,1.4,2.6 c-0.5,0-1-0.2-1.4-0.4c0,0,0,0,0,0c0,1.5,1.1,2.8,2.5,3.1c-0.3,0.1-0.5,0.1-0.8,0.1c-0.2,0-0.4,0-0.6-0.1c0.4,1.2,1.5,2.1,2.9,2.2 c-1.1,0.8-2.4,1.3-3.8,1.3c-0.2,0-0.5,0-0.7,0c1.4,0.9,3,1.4,4.7,1.4c5.7,0,8.8-4.7,8.8-8.9c0-0.1,0-0.3,0-0.4 C15.6,5.5,16.1,4.9,16.5,4.3"></path></symbol><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#social-twitter"/></svg></li>
<li class='share linkedin' title="Share on LinkedIn"><svg aria-hidden="true" class="icon-social" xmlns="http://www.w3.org/2000/svg"><symbol id="social-linkedin" viewBox="0 0 18 18"><path d="M15.4,1.5H2.6C2,1.5,1.5,2,1.5,2.6v12.8c0,0.6,0.5,1.1,1.1,1.1h12.8c0.6,0,1.1-0.5,1.1-1.1V2.6C16.5,2,16,1.5,15.4,1.5z M3.8,7.1H6v7.2H3.8V7.1z M4.9,6.1c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3c0.7,0,1.3,0.6,1.3,1.3C6.2,5.6,5.6,6.1,4.9,6.1z M14.5,14.3h-2.3v-3.5c0-0.8,0-1.9-1.2-1.9c-1.2,0-1.4,0.9-1.4,1.8v3.5H7.4V7.1h2.2v1h0c0.3-0.6,1-1.2,2.1-1.2 c2.3,0,2.7,1.5,2.7,3.4V14.3z"></path></symbol><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#social-linkedin"/></svg></li>
<li class='share email' title="Share with Email"><a aria-label="Share with Email" href="" target="_blank"><svg aria-hidden="true" class="icon-social" xmlns="http://www.w3.org/2000/svg"><symbol id="social-mail" viewBox="0 0 18 18"><path d="M9,8.2L3,4.5h12L9,8.2z M15,13.5H3V6l6,3.8L15,6V13.5z M15,3H3C2.2,3,1.5,3.7,1.5,4.5l0,9C1.5,14.3,2.2,15,3,15 h12c0.8,0,1.5-0.7,1.5-1.5v-9C16.5,3.7,15.8,3,15,3z"></path></symbol><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#social-mail"/></svg></a></li>
</ul></div>
</header>
<main>
<h1>Low Code App Development with OEMjs</h1>
<p>The Object Event Modeling JavaScript Framework OEMjs supports</p>
<ol>
<li>enumerations (since JS does not support them);</li>
<li>business object, event and activity classes (and class hierarchies) with semantic meta-data (e.g., for declarative constraint validation);</li>
<li>unidirectional and bidirectional associations;</li>
<li>storage adapters that facilitate switching from one storage technology (such as IndexedDB) to another one (such as Google's Cloud Firestore);</li>
<li>view models for declarative user interface definitions;</li>
<li>model-based generation of CRUD user interfaces and activity-based user interfaces.</li>
</ol>
<details><summary>Example 1: <a href="apps/minimal/">Minimal CRUD App</a> + <a href="tutorials/introductory/">Tutorial</a></summary>
<p>The purpose of this app is to manage book data. It consists of two JS module files, <kbd>app.mjs</kbd> and <kbd>Book.mjs</kbd>, with together 70 lines of code, only.</p>
</details>
<details><summary>Example 2: <a href="apps/enumerations/">Enumeration App</a> + <a href="tutorials/enumerations/">Tutorial</a></summary>
<p>The purpose of this app is to show the use of enumeration attributes. It consists of two JS module files, <kbd>app.mjs</kbd> and <kbd>Book.mjs</kbd>, with together 70 lines of code, only.</p>
</details>
<details><summary>Example 3: <a href="apps/books-authors-publishers/">A CRUD App with 3 Associated Classes</a></summary>
<p>The purpose of this app is to manage data about books and their authors and publishers. It consists of four files,
<kbd>app.mjs</kbd>, <kbd>Book.mjs</kbd>, <kbd>Author.mjs</kbd> and <kbd>Publisher.mjs</kbd>, 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.</p>
</details>
<details><summary>Example 4: <a href="apps/public-library/">Public Library App</a> with Activity User Interfaces</summary>
<p>This app shows the use of business activities in addition to business objects, which is at the heart of Object Event Modeling (OEM).</p>
</details>
<h2>Use Case 1: Enumerations</h2>
<details><summary>Handling Enumerations and Enumeration Attributes</summary>
<h3>Defining an Enumeration</h3>
<pre>var WeatherStateEL = new eNUMERATION ("WeatherStateEL", ["sunny", "partly cloudy", "cloudy", "cloudy with rain", "rainy"]);</pre>
<h3>Using an Enumeration as the Range of an Attribute</h3>
<pre>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"}
}</pre>
<h3>Using Enumeration Literals</h3>
<p>Recall that <em>enumeration literals</em> are constants that stand for a positive integer (the <em>enumeration index</em>).</p>
<p>For instance, the enum literal <code>WeatherStateEL.SUNNY</code> stands for the enum index 1. In program code, we
do not use the enum index, but rather the enum literal. For instance,</p>
<pre>var theWeather = new Weather({
weatherState: WeatherStateEL.SUNNY, // do not use the enum index value 1
temperature: 30
})</pre>
<h3>Looping over an Enumeration</h3>
<p>We loop over the enumeration <code>WeatherStateEL</code> with a <code>for</code> loop counting from 1
to <code>WeatherStateEL.MAX</code>:</p>
<pre>for (let weatherState = 1; weatherState <= WeatherStateEL.MAX; weatherState++) {
switch (weatherState) {
case WeatherStateEL.SUNNY:
...
break;
case WeatherStateEL.PARTLY_CLOUDY:
...
break;
}
}
</pre>
</details>
<h2>Use Case 2: Constraint Valdiation</h2>
<details><summary>Defining Constraints in the Model and Checking Them in the View</summary>
<p>OEMjs allows defining property constraints in a model class:</p>
<pre>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}
}</pre>
<p>Suitable range constraints can be defined by using one of the following range keywords:</p>
<ul>
<li>"String", "NonEmptyString", "Identifier", "Email", "URL", "PhoneNumber"</li>
<li>"Integer", "PositiveInteger", "NonNegativeInteger", "AutoNumber"</li>
<li>"Decimal", "Number", "Percent", "ClosedUnitInterval", "OpenUnitInterval"</li>
<li>"Boolean"</li>
<li>"Date", "DateTime"</li>
</ul>
<p>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 <code>add</code> and <code>update</code> methods of a storage manager, using the generic validation method
<code>dt.check</code>, as shown in the following example:</p>
<pre>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);
});
});</pre>
</details>
<h2>Use Case 3: Storage Adapters</h2>
<details><summary>Flexible Data Storage Management with Storage Adapters</summary>
<p>OEMjs comes with a sTORAGEmANAGER class and two storage adapters for using <code>IndexedDB</code> or <code>Google FireStore</code>.</p>
<p>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 <code>Book</code>:</p>
<pre>const storageAdapter = {name:"IndexedDB", dbName:"Test"};
const storageManager = new sTORAGEmANAGER( storageAdapter);
storageManager.retrieveAll( Book).then( list);
</pre>
</details>
</main>
<footer>
<hr/>
<p>© 2023 G. Wagner (<a href="https://creativecommons.org/licenses/by-nc/4.0/">CC BY-NC</a>), Brandenburg University of Technology, Germany</p>
</footer>
<script>
var shareButton = document.getElementById("share-this"),
shareLinkList = document.querySelector("ul.links"),
canonicalUrlLinkElem = document.querySelector('link[rel=canonical]'),
pageUrl = canonicalUrlLinkElem ? canonicalUrlLinkElem.href : document.URL,
title = document.querySelector("meta[property='og:title']").getAttribute("content"),
description = document.querySelector("meta[name='description']").getAttribute("content");
function setShareLinks() {
var pageUrl = document.URL,
emailLinkEl = document.querySelector(".share.email > a");
var descrUrlComp = encodeURIComponent( description);
document.querySelector(".share.facebook").addEventListener("click", function () {
url = "https://www.facebook.com/sharer.php?u=" + pageUrl;
window.open(url,"NewWindow","");
});
document.querySelector(".share.twitter").addEventListener("click", function () {
url = "https://twitter.com/intent/tweet?url=" + pageUrl + "&text=" + descrUrlComp;
window.open(url,"NewWindow","");
});
document.querySelector(".share.linkedin").addEventListener("click", function () {
url = "https://www.linkedin.com/shareArticle?mini=true&url=" + pageUrl;
window.open(url,"NewWindow","");
});
emailLinkEl.href = "mailto:?subject=" + title + "&body=" + description + "%0A%0A" + pageUrl;
};
if (navigator.share) {
shareButton.style.display = "inline"
shareButton.addEventListener('click', function () {
navigator.share({
title: title,
url: pageUrl,
text: description
}).then( function () {
console.log("Thanks for sharing!");
})
.catch( console.error);
});
} else {
shareLinkList.style.display = "inline-block";
setShareLinks();
}
</script></body>
</html>