@seanox/aspect-js
Version:
full stack JavaScript framework for SPAs incl. reactivity rendering, mvc / mvvm, models, expression language, datasource, virtual paths, unit test and some more
1,335 lines (1,007 loc) • 45.9 kB
Markdown
[Motivation](motivation.md) | [Inhalt](README.md#einf-hrung) | [Expression Language](expression.md)
- - -
# Einführung
## Was ist Seanox aspect-js?
Geprägt durch die guten Erfahrungen mit JSF (Java Server Faces) in Bezug
auf Funktion und einfache Integration ins Markup, entstand die Idee für
eine ähnliche client-seitige Full-Stack Lösung mit minimalistischem
und leichtgewichtigem Ansatz zur Implementierung von Single-Page-Applications
(SPAs).
Seanox aspect-js greift dazu den deklarativen Ansatz von HTML auf und erweitert
ihn um Expression Language, reaktives Rendering, zusätzliche Attribute,
Model-View-Controller, View-Model-Binding, Resource-Bundle (i18n/l10n),
NoSQL-Datasource, Testumgebung und vieles mehr.
# Merkmale
* Einfache Integration in Markup und JavaScript (sauberer Code)
kombinierbar mit anderen JavaScript-Frameworks, wenn diese nicht dasselbe tun
und eine andere Syntax verwenden
* Leichtgewichtige Implementierung
erfordert keine zusätzlichen Frameworks
* Komponentenbasierte Architektur
* Namespaces und Domain-Konzept
zur besseren Strukturierung von Komponenten, Modulen und Geschäftslogik
* Modularisierung (unterstützt Importe zur Laufzeit)
Komponentenkonzept für das intelligente/automatische Laden von Ressourcen
* Event handling
* Expression Language
Meta-Sprach-Erweiterung mit voller JavaScript-Unterstützung
* Reaktives Rendering
das Rendering reagiert auf Änderungen in Datenobjekten und löst ein
partielles Rendering der Konsumenten aus
* Markup-Rendering
unterstützt: condition, custom tags, events, filter, interval, iterate,
rendering, resources messages, validation, ...
* Markup-Härtung
erschwert die Manipulation der Attribute im Markup
nicht sichtbare Komponenten werden aus dem DOM entfernt und erst bei
Verwendung eingesetzt
* Model-View-Controller (MVC) / Model-View-ViewModel (MVVM)
unterstützt View-Model-Binding und Ereignisse
* Sitemap zur Organisation der Darstellung in Pages, Faces und Facets
unterstützt virtuelle Pfade und Berechtigungskonzepte
* Resource-Bundle / Resource-Messages
Internationalisierung (i18n), Lokalisierung (l10n) und Text-Auslagerung
* NoSQL-DataSource auf Basis von XML
leichtgewichtiges Datenmanagement für Aggregation / Projektion / Transformation
* Micro Frontends
Platform und Framework zur Implementierung von Micro-Frontends
* Testumgebung
für automatisierte Unit- und Integration-Tests
* ...
## Inhalt
* [Erste Schritte](#erste-schritte)
* [Arbeitsbereich](#arbeitsbereich)
* [Expression Language](#expression-language)
* [Attribute](#attribute)
* [output](#output)
* [import](#import)
* [condition](#condition)
* [interval](#interval)
* [iterate](#iterate)
* [id](#id)
* [composite](#composite)
* [strict](#strict)
* [events](#events)
* [validate](#validate)
* [message](#message)
* [notification](#notification)
* [render](#render)
* [release](#release)
* [DataSource](#datasource)
* [Resource-Bundle (Messages/i18n/l10n)](#resource-bundle-messages-i18n-l10n)
* [Model-View-Controller](#model-view-controller)
* [Model](#model)
* [View](#view)
* [Controller](#controller)
* [View-Model-Binding](#view-model-binding)
* [Composite](#composite)
* [Binding](#binding)
* [Dock / Undock](#dock--undock)
* [Synchronization](#synchronization)
* [Validation](#validation)
* [Events](#events)
* [SiteMap](#sitemap)
* [Page](#page)
* [Face](#face)
* [Facet](#facet)
* [Face-Flow](#face-flow)
* [Navigation](#navigation)
* [Berechtigungskonzept](#berechtigungskonzept)
* [Virtual Paths](#virtual-paths)
* [Komponenten](#komponenten)
* [Scripting](#scripting)
* [Reaktives Rendering](#reaktives-rendering)
* [API-Erweiterungen](#api-erweiterungen)
* [Ereignisse](#ereignisse)
* [Test](#test)
* [Testfall](#testfall)
* [Szenario](#szenario)
* [Suite](#suite)
* [Assert](#assert)
* [Konfiguration](#konfiguration)
* [Monitoring](#monitoring)
* [Control](#control)
* [Events](#events-1)
## Erste Schritte
Das Framework besteht aus einer JavaScript-Datei, die über die URL eines
Release-Channels eingebunden oder als Release heruntergeladen wird.
Release-Channel stellen kontinuierlich die neuesten finalen Hauptversionen zur
Verfügung. So ist Seanox aspect-js immer auf dem neuesten Stand.
Jedes Release besteht aus verschiedenen Versionen für unterschiedliche
Einsatzzwecke. Diese Versionen gibt es zudem immer in zwei Varianten. Der
Standard ist die komprimierte Version für den produktiven Einsatz. Optional
ist auch die unkomprimierte und dokumentierte max-Variante für Entwicklung
und Fehleranalyse verfügbar.
Zu Beginn erstelle eine HTML-Datei, z.B. _index.html_ und füge Seanox
apect-js ein.
```html
<!-- development version, includes helpful comments -->
<script src="https://cdn.jsdelivr.net/npm/@seanox/aspect-js/release/aspect-js-max.js"></script>
```
oder
```html
<!-- production and minimized version -->
<script src="https://cdn.jsdelivr.net/npm/@seanox/aspect-js/release/aspect-js.js"></script>
```
__Das Framework ist zur Implementierung modularer und komponentenbasierter
Single-Page-Applications entwickelt worden. Durch das automatische Laden von
Ressourcen, Modulen und Daten ist für die Verwendung ein Web-Server
erforderlich.__
## Arbeitsbereich
Seanox aspect-js agiert ausschliesslich im HTML-Element `BODY`, welches selbst
mit einbezogen wird.
## Expression Language
Expressions bzw. die Expression Language (EL) ist ein einfacher Zugang zum
clientseitigen JavaScript und damit zu den Modellen und Komponenten im Seanox
aspect-js. Expressions unterstützen das komplette JavaScript-API, das mit
zusätzlichen Schlüsselwörtern angereichert ist, womit sich auch
die zahlreichen arithmetischen und logischen Operatoren verwenden lassen.
Die Expression Language kann ab dem HTML-Element `BODY` im kompletten Markup als
Freitext, sowie in allen Attributen verwendet werden. Ausgenommen sind die
HTML-Elemente `STYLE` und `SCRIPT`, deren Inhalt von der Expression Language
nicht unterstützt wird. Bei der Verwendung als Freitext wird als Ausgabe
reiner Text (plain text) erzeugt. Das Hinzufügen von Markup, insbesondere
HTML-Code, ist so nicht möglich und wird nur mit den Attributen `output`
und `import` unterstützt.
```html
<body lang="{{DataSource.locale}}">
<p>
Today is {{new Date().toDateString()}}
and it's {{new Date().toLocaleTimeString()}} o'clock.
</p>
</body>
```
Expressions werden durch den Renderer interpretiert, der nach dem Laden der
Seite startet. Somit können Expressions beim Laden der Seite sichtbar sein.
Hier empfiehlt sich die Verwendung der Attribute [output](markup.md#output) und
[import](markup.md#import), welche eine direkte Ausgabe in das innere HTML vom
Element bewirken.
```html
<p output="Today is {{new Date().toDateString()}}
and it's {{new Date().toLocaleTimeString()}} o'clock.">
</p>
```
Oder die Verwendung vom Attribut [release](markup.md#release), welches
HTML-Elemente und deren Inhalte erst nach dem Rendern sichtbar macht.
```html
<p release>
Today is {{new Date().toDateString()}}
and it's {{new Date().toLocaleTimeString()}} o'clock.
</p>
```
Expressions können zur Laufzeit globale Variablen erzeugen und nutzen.
```html
{{now:new Date()}}
<p>
Today is {{now.toDateString()}}
and it's {{now.toLocaleTimeString()}} o'clock.
</p>
```
[Mehr erfahren](expression.md)
## Attribute
Der deklarative Ansatz wird in Seanox aspect-js mit Attributen umgesetzt, die
sich ab dem HTML-Element `BODY`, welches mit eingeschlossen ist, in allen
HTML-Elementen verwenden und kombinieren lassen. Die Werte der Attribute
können statisch oder mit Verwendung der Expression-Language dynamisch sein.
Enthält ein Attribut eine Expression, wird der Wert durch den Renderer mit
jeder Auffrischung (Renderzyklus) auf Basis der initialen Expression
aktualisiert.
[Mehr erfahren](markup.md#attribute)
### output
Setzt beim HTML-Element den Wert oder das Ergebnis seines Ausdrucks als inneres
HTML ein. Das Verhalten ist vergleichbar mit dem Attribut [import](#import), nur
erfolgt mit jedem Renderzyklus eine aktualisierte Ausgabe. Als Wert werden Text,
ein oder mehrere Elemente als NodeList bzw. Array, sowie absolute oder relative
URLs zu einer entfernten Ressource und auch die [DataSource-URL (locator)](
datasource.md#locator) für transformierte Inhalte aus der [DataSource](
datasource.md) unterstützt.
Das output-Attribut lässt sich mit dem condition-Attribut kombinieren und
wird dann erst ausgeführt, wenn die Bedingung `true` ist.
```html
<p output="Today is {{new Date().toDateString()}}
and it's {{new Date().toLocaleTimeString()}} o'clock.">
</p>
<p output="xml:/example/content">
loading resource...
</p>
<p output="xml:/example/data xslt:/example/style">
loading resource...
</p>
```
[Mehr erfahren](markup.md#output)
### import
Lädt den Inhalt für das HTML-Element zur Laufzeit und fügt diesen
als inneres HTML ein. Das Verhalten ist vergleichbar mit dem Attribut [output](
#output), nur erfolgt der Import einmalig und das import-Attribut wird nach
dem erfolgreichen Laden entfernt. Als Wert werden ein oder mehrere Elemente als
NodeList bzw. Array, sowie absolute oder relative URLs zu einer entfernten
Ressource und auch die [DataSource-URL (locator)](datasource.md#locator)
für transformierte Inhalte aus der [DataSource](datasource.md)
unterstützt.
Das import-Attribut lässt sich mit dem condition-Attribut kombinieren und
wird dann erst ausgeführt, wenn die Bedingung `true` ist.
```html
<p import="Today is {{new Date().toDateString()}}
and it's {{new Date().toLocaleTimeString()}} o'clock.">
</p>
<p import="xml:/example/content">
loading resource...
</p>
<p import="xml:/example/data xslt:/example/style">
loading resource...
</p>
<p import="https://raw.githubusercontent.com/seanox/aspect-js/master/test/resources/import_c.htmlx">
loading resource...
</p>
```
[Mehr erfahren](markup.md#import)
### condition
Als Bedingung legt das Attribut fest, ob ein Element im DOM enthalten bleibt.
Der als Wert angegebene Ausdruck muss explizit `true` zurückliefern, damit
das Element erhalten bleibt. Bei abweichenden Rückgabewerten wird das
Element temporär aus dem DOM entfernt und lässt sich später durch
das Auffrischen des __Eltern-Elements__ wieder einfügen, wenn der Ausdruck
`true` zurückliefert.
```html
<article condition="{{Model.visible}}">
...
</article>
```
Die Verwendung vom condition-Attribut in Verbindung mit eingebettetem JavaScript
ist als Composite-JavaScript möglich.
```html
<script type="composite/javascript" condition="{{Model.visible}}">
...
</script>
```
[Mehr erfahren](markup.md#condition)
### interval
Aktiviert eine intervallgesteuerte Auffrischung des HTML-Elements, ohne dass die
Auffrischung aktiv angestossen werden muss. Das Intervall nutzt das innere HTML
als Vorlage, aus der mit jedem Intervallzyklus aktueller Inhalt erzeugt und
eingefügt wird. Das Attribut erwartet als Wert Millisekunden, die auch als
Expression formuliert werden können, wobei ungültige Werte eine
Konsolenausgabe verursachen. Die Verarbeitung erfolgt nebenläufig bzw.
asynchron aber nicht parallel. Die Verarbeitung wird nach der vorgegebenen Zeit
starten, wenn eine zuvor begonnene JavaScript-Prozedur beendet wurde. Daher ist
das Intervall als zeitnah, nicht aber als exakt zu verstehen. Das Intervall
beginnt mit dem Auffrischen automatisch und endet wenn:
- das Element nicht mehr im DOM existiert
- das condition-Attribut verwendet wird, dass nicht `true` ist
```html
<p interval="1000">
{{new Date().toLocaleTimeString()}}
</p>
```
Mit der Kombination von Intervall und Variablen-Expression ist die Umsetzung
eines permanenten Zählers sehr einfach.
```html
{{counter:0}}
<p interval="1000">
{{counter:parseInt(counter) +1}}
{{counter}}
</p>
```
Die Verwendung vom interval-Attribut in Verbindung mit eingebettetem JavaScript
ist als Composite-JavaScript möglich.
```html
<script type="composite/javascript" interval="1000">
console.log(new Date().toLocaleTimeString());
</script>
```
[Mehr erfahren](markup.md#interval)
### iterate
Die iterative Ausgabe basiert auf Listen, Aufzählungen und Arrays. Wird ein
HTML-Element als iterativ deklariert, wird das innerer HTML als Vorlage
verwendet, aus der mit jedem Iterationszyklus aktueller Inhalt erzeugt und als
inneres HTML eingefügt wird. Als Wert wird für das Attribut ein
[Variablen-Ausdruck](expression.md#variable-expression) erwartet, zu dem ein
Meta-Objekt erstellt wird, was in der Vorlage den Zugriff auf die Iteration
ermöglicht. So erzeugt der Variablen-Ausdruck `iterate={{
tempA:Model.list}}` das Meta-Objekt `tempA = {item, index, data}`.
```javascript
const Model = {
months: ["Spring", "Summer", "Autumn", "Winter"]
};
```
```html
<select iterate={{months:Model.months}}>
<option value="{{months.index}}">
{{months.item}}
</option>
</select>
```
[Mehr erfahren](markup.md#iterate)
### id
Die ID (Bezeichner) hat in Seanox aspect-js eine elementare Bedeutung. Sie
bildet die Grundlage für das [View-Model-Binding](
sitemap.md#view-model-binding) und wird von der [SiteMap](sitemap.md#sitemap)
für [Faces](sitemap.md#face) und [Facets](sitemap.md#facet) im [Face-Flow](
sitemap.md#face-flow) und somit als Ziel für virtuelle Pfade verwendet.
Wie bei allen Attributen ist hier die Expression-Language anwendbar, mit der
Besonderheit, dass Änderungen zur Laufzeit keine Auswirkungen haben, da das
Attribut bzw. der Wert für das View-Model-Binding nur initial verarbeitet
wird, solange das HTML-Element im DOM existiert.
[Mehr erfahren](markup.md#id)
### composite
Kennzeichnet im Markup ein Element als [Composite](composite.md). Composites
sind essenzielle Bestandteile, die zwingend einen Bezeichner (ID / Composite-ID)
benötigen.
```html
<article id="example" composite>
...
</article>
```
Composites sind auch die Grundlage für das [View-Model-Binding](
mvc.md#view-model-binding), was die Verbindung von HTML-Elementen im Markup
(View) mit korrespondierenden JavaScript-Objekten (Models) umfasst. Models sind
statische JavaScript-Objekte, die vergleichbar mit managed Beans und DTOs (Data
Transfer Objects) Daten, Zustände und Funktionen für die View
bereitstellen. Die View als Präsentationsfläche und
Benutzer-Schnittstelle für Interaktionen und das Model sind primär
entkoppelt. Für den MVVM (Model-View-ViewModel) Ansatz, als Erweiterung zum
MVC ([Model-View-Controller](mvc.md#model-view-controller)), verbindet der
Controller Views und Models auf Basis der Composite-IDs bidirektional, womit
keine manuelle Implementierung und Deklaration von Ereignissen, Interaktion oder
Synchronisation erforderlich ist.
Von der [SiteMap](sitemap.md#sitemap) werden Composites als [Faces](
sitemap.md#face) zur primären Projektion von JavaScript-Objekten
(Models) genutzt, womit sich diese als Ziele für virtuelle Pfade im
[Face-Flow](sitemap.md#face-flow) verwenden lassen, was direkten Einfluss auf
die Sichtbarkeit der Composites hat. Bei aktiver SiteMap lassen sich Composites
mit dem Attribut [static](#static) kennzeichnen, womit ein Composite als Face
unabhängig von virtuellen Pfaden permanent sichtbar ist.
Details zur Verwendung von Composites / modularen Komponente werden in den
Abschnitten [Composites](composite.md) und [Model-View-Controller](mvc.md)
beschrieben.
[Mehr erfahren](markup.md#composite)
### strict
Das Attribut ist mit den Attributen [composite](#composite) und [validate](
#validate) kombinierbar.
In Kombination mit dem Attribut [composite](#composite) legt es fest, dass beim
Laden der Ressourcen (JS, CSS, HTML) zu einer Komponente, der Dateiname in der
originalen Schreibweise verwendet wird. Das Standardverhalten ohne das Attribut
[strict](#strict) verwendet die Composite-Id mit einem Kleinbuchstaben am
Anfang.
[Mehr erfahren](markup.md#strict)
### events
Bindet ein oder mehrere [Ereignisse](https://www.w3.org/TR/DOM-Level-3-Events)
an ein HTML-Element. Das ermöglicht die ereignisgesteuerte Synchronisation
von HTML-Elementen mit korrespondierenden JavaScript-Objekten (Models), sowie
die Validierung der zu synchronisierenden Daten (mehr dazu im Abschnitt
[validate](#validate)) und das ereignisgesteuerte Ansteuern und Auffrischen
anderer HTML-Elemente (mehr dazu im Abschnitt [render](#render)).
```html
<span id="output1">{{#text1.value}}</span>
<input id="text1" type="text"
events="input change" render="#output1"/>
```
Wie bei allen Attributen ist hier die Expression-Language anwendbar, mit der
Besonderheit, dass Änderungen zur Laufzeit keine Auswirkungen haben, da das
Attribut bzw. der Wert für das View-Model-Binding nur initial verarbeitet
wird, solange das HTML-Element im DOM existiert.
[Mehr erfahren](markup.md#events)
### validate
Das Attribut `validate` erfordert die Kombination mit dem Attribut `events`.
Zusammen definieren und steuern sie die Synchronisation zwischen dem Markup
eines Composites und dem korrespondierenden JavaScript-Objekt (Model), wo eine
gleichnamige Eigenschaft als Ziel für die Synchronisation vorhanden sein
muss.
Die Validierung funktioniert dabei zweistufig und nutzt zu Beginn die Standard
HTML5-Validierung. Kann diese keine Abweichungen vom erwarteten Ergebnis
ermitteln oder wurde keine HTML5-Validierung festgelegt, wird die Validierung
vom JavaScript-Objekt aufgerufen, wenn das Modell eine entsprechende
validate-Methode `boolean validate(element, value)` bereitstellt und das zu
validierende Element in einem Composite eingebettet ist.
```html
<form id="Model" composite>
<input id="text1" type="text" placeholder="e-mail address"
pattern="^\w+([\w\.\-]*\w)*@\w+([\w\.\-]*\w{2,})$"
validate events="input change" render="#Model"/>
<input type="submit" value="submit" validate events="click"/>
</form>
```
Die Synchronisation und das Standard-Verhalten (action) vom Browser werden durch
die Validierung direkt beeinflusst, die dazu vier Zustände als
Rückgabewert nutzen kann: `true`, `not true`, `text`, `undefined/void`.
[Mehr erfahren](markup.md#validate)
### message
Message ist ein optionaler Bestandteil der [Validierung](#validate) und wird zur
Text- bzw. Fehler-Ausgabe im Fall einer unbestätigten Validierung verwendet.
Das Attribut erfordert die Kombination mit den Attributen [validate](#validate)
und [events](#events).
```html
<form id="Model" composite>
<input id="text1" type="text" placeholder="e-mail address"
pattern="^\w+([\w\.\-]*\w)*@\w+([\w\.\-]*\w{2,})$"
validate message="Valid e-mail address required"
events="input change" render="#Model"/>
<input type="submit" value="submit" validate events="click"/>
</form>
```
```html
<form id="Model" composite>
<input id="text1" type="text" placeholder="e-mail address"
pattern="^\w+([\w\.\-]*\w)*@\w+([\w\.\-]*\w{2,})$"
validate message="{{Messages['Model.text1.validation.message']}}"
events="input change" render="#Model"/>
<input type="submit" value="submit" validate events="click"/>
</form>
```
[Mehr erfahren](markup.md#message)
### notification
Notification ist ein optionaler Bestandteil der [Validierung](#validate) und
legt fest, dass der Inhalt vom message-Attribut als Info-Box (Browser-Feature)
am entsprechenden Element angezeigt wird, wenn die Validierung nicht erfolgreich
ist. Das Attribut erfordert die Kombination mit den Attributen [validate](
#validate), [events](#events) und [message](#message).
```html
<form id="Model" composite>
<input id="text1" type="text" placeholder="e-mail address"
pattern="^\w+([\w\.\-]*\w)*@\w+([\w\.\-]*\w{2,})$"
validate message="Valid e-mail address required" notification
events="input change" render="#Model"/>
<input type="submit" value="submit" validate events="click"/>
</form>
```
[Mehr erfahren](markup.md#notification)
### render
Das Attribut erfordert die Kombination mit dem Attribut [events](#events).
Zusammen definieren sie, welche Ziele mit welchen auftretenden [Ereignissen](
https://www.w3.org/TR/DOM-Level-3-Events) vom Renderer aufgefrischt werden.
Als Wert werden ein oder mehrere durch Leerzeichen getrennte CSS- bzw.
Query-Selectoren erwartet, welche die Ziele festlegen.
```html
<span id="output1">{{#text1.value}}</span>
<input id="text1" type="text"
events="input change" render="#output1"/>
```
__Alternativ kann auch das [reaktive Rendering](reactive.md) verwendet werden,
wo Änderungen in den Datenobjekten eine partielle Aktualisierung der View
ausl&aouml;sen.__
[Mehr erfahren](markup.md#render)
### release
Inverser Indikator dafür, dass ein Element gerendert wurde. Der Renderer
entfernt dieses Attribut, wenn ein HTML-Element gerendert wurde. Dieser Effekt
kann für CSS verwendet werden, um HTML-Elemente nur im gerenderten Zustand
anzuzeigen. Eine entsprechende CSS-Regel wird dem HEAD automatisch mit dem Laden
der Seite hinzugefügt.
```html
<span release>{{'Show me after rendering.'}}</span>
```
[Mehr erfahren](markup.md#release)
## DataSource
DataSource ist ein NoSQL-Ansatz zur Datenspeicherung auf Basis von XML-Daten in
Kombination mit mehrsprachiger Datentrennung, optionaler Aggregation und
Transformation. Es ist eine Kombination von Ansätzen einer
read-only-Datenbank und einem CMS.
Die DataSource basiert auf statischen Daten. Daher verwendet die Implementierung
einen Cache, um den Netzwerkzugriff zu minimieren.
Die Daten werden per XPath abgefragt. Das Ergebnis kann verkettet, aggregiert
und per XSLT transformiert werden.
[Mehr erfahren](datasource.md#datasource)
## Resource-Bundle (Messages/i18n/l10n)
(Resource)Messages ist eine statische Erweiterung der [DataSource](
datasource.md) für Internationalisierung (i18n), Lokalisierung (l10n)
sowie für Mandanten bezogene Texte. Die Implementierung basiert auf einer
Menge von Schlüssel-Wert-Paaren in Form von Label-Elementen, die in der
Datei `locales.xml` im DataSource-Verzeichnis definiert werden.
[Mehr erfahren](message.md)
## Model-View-Controller
Der Model-View-Controller (MVC) ist ein Entwurfsmuster zur Trennung von
Interaktion, Daten und Darstellung. Hier muss zwischen I/O-Controller und
Applikations-Controller unterschieden werden. Das reine MVC-Entwurfsmuster meint
den I/O-Controller zur Übermittlung der Interaktionen. Da dies von
Betriebssystem und Browser übernommen wird, bezieht sich Seanox aspect-js
vordergründig auf den Applikations-Controller.
```
+------------------------------------------+--------------+-----------------------+
| View | Controller | Model |
+------------------------------------------+--------------+-----------------------+
| Markup | Composite | JavaScript |
| | Reactive | |
+------------------------------------------+--------------+-----------------------+
| <form id="model" composite> | aspect-js | const model = { |
| <input id="message" events="input"/> | | message: "", |
| <button id="submit"/> | | submit: { |
| </form> | | onClick() { |
| | | } |
| | | } |
| | | } |
+------------------------------------------+--------------+-----------------------+
```
[Mehr erfahren](mvc.md)
### Model
Models sind statische JavaScript-Objekte, die vergleichbar mit managed Beans und
DTOs (Data Transfer Objects) Daten, Zustände und Funktionen für die
View bereitstellen. Als Singletons/Facades/Delegates können sie weitere
Komponenten und Abstraktionen nutzen, selbst Geschäftslogik enthalten und
sind ein Bindeglied zwischen View und Middleware.
[Mehr erfahren](mvc.md#model)
### View
Die View, welche durch das Markup repräsentiert wird, ist ausschliesslich
für die Darstellung bzw. Projektion von Modellen verantwortlich. Wobei
Projektion eine gute Beschreibung ist, da die Art der Darstellung eines Modells
nicht eingeschränkt ist.
[Mehr erfahren](mvc.md#view)
### Controller
Der (Applikations-)Controller steuert Abläufe innerhalb einer Applikation
(Face-Flow) und übernimmt mit dem View-Model-Binding den Datenfluss
zwischen View und Model, wobei hier auch von MVVM (Model-View-ViewModel) und
MVCS (Model-View-Controller-Service) gesprochen werden kann.
[Mehr erfahren](mvc.md#controller)
### View-Model-Binding
Das View-Model-Binding übernimmt die bidirektionale Verknüpfung der
HTML-Elemente der View mit den Modellen als statische JavaScript-Objekte und
organisiert so den Datenfluss, kommuniziert Ereignisse sowie Zustände und
bindet Funktionen an.
[Mehr erfahren](mvc.md#view-model-binding)
#### Composite
Die Grundlage für das View-Model-Binding bilden Composites, was funktional
eigenständige Komponenten sind, die sich aus Markup, CSS und JavaScript
sowie optional aus weiteren Ressourcen zusammensetzen. Die Bindung alle
Bestandteile erfolgt über die auch als Composite-ID bezeichnete ID vom
HTML-Konstrukt, dem namensgleichen Model im JavaScript sowie der zum HTML
korrespondierenden ID im CSS.
```html
<!DOCTYPE HTML>
<html>
<head>
<script src="aspect-js.js"></script>
</head>
<body>
<div id="example" composite></div>
</bod>
</html>
```
```javascript
const example = {
...
}
```
```css
#example {
...
}
```
Die Composite-ID ist somit ein eindeutiger Bezeichner innerhalb der Anwendung.
Sie ist eine Zeichenfolge, die aus Buchstaben, Zahlen und Unterstrichen besteht,
mindestens ein Zeichen lang ist, mit einem Unterstrich oder einem Buchstaben
beginnt und sich durch die Kombination der Attribute `id` und `composite` bei
einem HTML-Element bildet.
Composites oder besser deren Composite-ID, definieren den Punkt im globalen
Objektbaum, wo sich das korrespondierende JavaScript-Model befindet. Alle von
einem Composite eingeschlossenen HTML-Elemente mit einer ID werden dann im
korrespondierenden JavaScript-Model als Zweige reflektiert, wenn diese im Model
entsprechend implementiert sind.
```javascript
const model = {
message: "Hello",
submit: {
...
}
};
```
```html
<html>
<body>
<form id="model" composite>
<input type="text" id="message"/>
<input type="submit" id="submit"/>
...
</form>
</body>
</html>
```
[Mehr erfahren](mvc.md#composite)
#### Binding
Beim View-Model-Binding geht es um die Verbindung von Markup/HTML (View) mit dem
entsprechenden JavaScript-Objekt (Model). Das Binding leitet Interaktionen und
Statusänderungen der View an das Model weiter und stellt eine
Schnittstelle für Middleware-Funktionen und Services für die View bereit.
Womit keine manuelle Implementierung von Ereignissen, Synchronisation und
Interaktion zwischen View und Anwendungslogik erforderlich ist.
```javascript
const model = {
message: "Hello",
submit: {
onClick(event) {
...
}
}
};
```
```html
<html>
<body>
<form id="model" composite>
<input type="text" id="message" value="{{model.message}}" events="change"/>
<input type="submit" id="submit"/>
...
</form>
</body>
</html>
```
[Mehr erfahren](mvc.md#binding)
#### Dock / Undock
Wird ein Composite im DOM verwendet/eingefügt, wird das entsprechende
JavaScript-Objekt (Model) angedockt/verknüpft und beim Entfernen aus dem
DOM abgedockt/entknüpft. In beiden Fällen kann das Modell optional
geeignete Methoden implementieren. Die Methode `dock` wird vor dem Rendern, vor
dem Einfügen des Composites in das DOM oder nach dem Laden der Seite beim
ersten Rendern ausgeführt und kann zur Vorbereitung der Darstellung
verwendet werden. Die Methode `undock` wird ausgeführt, nachdem das
Composite aus dem DOM entfernt wurde und kann zur Nachbereitung bzw. Bereinigung
der Darstellung verwendet werden.
```javascript
const model = {
dock() {
...
},
undock() {
...
}
};
```
```html
<html>
<body>
<div id="model" composite>
...
</div>
</body>
</html>
```
Bei Composites, die mit dem Attribut [condition](markup.md#condition) deklariert
werden, hängt der Aufruf der Methoden vom Ergebnis der Bedingung ab.
[Mehr erfahren](mvc.md#dock)
#### Synchronization
Das View-Model-Binding umfasst neben der statischen Verknüpfung und
Zuordnung von HTML-Elementen (View) zum JavaScript-Objekt (Model) auch die
Synchronisation von Werten zwischen den HTML-Elementen und den Feldern im
JavaScript-Objekt. Die Synchronisation hängt von Ereignissen ab, die
für das HTML-Element mit dem Attribut [events](markup.md#events) deklariert
sind und wird nur ausgeführt, wenn eines der definierten Ereignisse
eintritt.
[Mehr erfahren](mvc.md#synchronization)
#### Validation
Die Synchronisation der Werte zwischen den HTML-Elementen (View) und den Feldern
vom JavaScript-Objekt (Model) kann durch Validierung überwacht und
gesteuert werden. Die Validierung wird in HTML durch die Kombination der
Attribute [validate](markup.md#validate) und [events](markup.md#events)
deklariert und erfordert eine entsprechende Validierungsmethode im
JavaScript-Objekt.
[Mehr erfahren](mvc.md#validation)
#### Events
Ereignisse, genauer gesagt die Interaktion zwischen View und Modell, werden beim
View-Model-Binding ebenfalls berücksichtigt. Die Methoden zur Interaktion
werden nur im Model implementiert. Im Markup selbst ist keine Deklaration
erforderlich. Das View-Model-Binding kennt die verfügbaren Ereignisse im
HTML und so wird beim Binding das Model nach entsprechenden Methoden durchsucht,
die dann als Event-Listener registriert werden.
```javascript
const contact = {
mail: {
onClick(event) {
const mail = "mailto:mail@local?subject=Test&body=Greetings";
document.location.href = mail;
return false;
}
}
};
```
```html
<html>
<body>
<div id="contact" composite>
<p>
Example for use of events.
</p>
<button id="mail">
Click Me!
</button>
</div>
</body>
</html>
```
[Mehr erfahren](mvc.md#events)
## SiteMap
Die Darstellung der Page lässt sich in Seanox aspect-js mit der SiteMap in
Faces sowie Facets organisieren und über virtuelle Pfade ansprechen. Zu
diesen Zweck stellt SiteMap eine hierarchische Verzeichnisstruktur bereit, die
auf den virtuellen Pfaden aller Faces und Facets basiert. Die SiteMap steuert
den Zugriff und die Visualisierung (Ein- und Ausblenden) der Element -- der
sogenannte Face-Flow. Face-Flow und Visualisierung funktionieren resolut und
verwenden aktiv das DOM zum Einfügen und Entfernen der Faces und Facets.
```
+-----------------------------------------------+
| Page |
| +-----------------------------------------+ |
| | Face A / Partial Face A | |
| | +-------------+ +-------------+ | |
| | | Facet A1 | ... | Facet An | | |
| | +-------------+ +-------------+ | |
| | | |
| | +-----------------------------------+ | |
| | | Face AA | | |
| | | +-----------+ +-----------+ | | |
| | | | Facet AA1 | ... | Facet AAn | | | |
| | | +-----------+ +-----------+ | | |
| | +-----------------------------------+ | |
| | ... | |
| +-----------------------------------------+ |
| ... |
| +-----------------------------------------+ |
| | Face n | |
| | ... | |
| +-----------------------------------------+ |
+-----------------------------------------------+
```
### Page
In einer Single-Page-Application bildet die Page den elementaren Rahmen und die
Laufzeitumgebung der gesamten Anwendung.
### Face
Ein Face ist die primäre Projektion von Modellen/Komponenten/Inhalten.
Diese Projektion kann zusätzliche Unterstrukturen in Form von Facets und
Sub-Faces enthalten. So werden übergeordnete Faces zu partiellen Faces,
wenn sich der Pfad auf ein Sub-Face bezieht. In dem Fall werden alle
übergeordneten Faces teilweise/partiell angezeigt, also ohne deren evtl.
enthaltenen Facets.
### Facet
Facets sind Teile eines Faces (Projektion) und meist keine eigenständige
Darstellung. So können z.B. bei einem Such-Formular die Eingabemaske und
die Ergebnistabelle separate Facets eines Faces sein. Sowohl Faces als auch
Facets sind über virtuelle Pfade erreichbar. Der Pfad zu Facets bewirkt,
dass das umschliessende Face mit all seinen übergeordneten Faces angezeigt
wird.
### Face-Flow
Face-Flow beschreibt die Zugriffssteuerung und die Abfolge von Faces und Facets.
Die SiteMap stellt dazu Schnittstellen, Berechtigungskonzepte und Akzeptoren
bereit, mit denen der Face-Flow kontrolliert und beeinflusst werden kann.
[Mehr erfahren](sitemap.md#sitemap)
## Navigation
Die Navigation kann durch Änderung des URL-Hash im Browser (direkte
Eingabe), durch Verwendung von Hash-Links und in JavaScript mit
`window.location.hash`, `window.location.href`, `SiteMap.navigate(path)` und
`SiteMap.forward(path)` erfolgen.
```html
<a href="#a#b#c">Goto root + a + b + c</a>
<a href="##">Back to the parent</a>
<a href="##x">Back to the parent + x</a>
```
```javascript
SiteMap.navigate("#a#b#c");
SiteMap.navigate("##");
SiteMap.navigate("##x");
```
```javascript
SiteMap.forward("#a#b#c");
SiteMap.forward("##");
SiteMap.forward("##x");
```
Im Unterschied zur navigate-Methode wird die Weiterleitung direkt
ausgeführt, anstatt eine asynchrone Weiterleitung durch Änderung des
Location-Hashes auszulösen.
Relative Pfade ohne Hash am Anfang sind möglich, funktionieren aber nur mit
`SiteMap.navigate(path)`.
[Mehr erfahren](sitemap.md#navigation)
## Berechtigungskonzept
Das Berechtigungskonzept basiert auf permit-Methoden, die als Callback-Methoden
mit der Konfiguration des Face-Flows definiert werden. Es können mehrere
permit-Methoden definiert werden, die mit jedem angeforderten Pfad
überprüft werden. Nur wenn alle permit-Methoden den angeforderten Pfad
mit `true` bestätigen, wird er verwendet und der Renderer macht die
abhängigen Faces und Facets sichtbar.
[Mehr erfahren](sitemap.md#berechtigungskonzept)
### Virtual Paths
Virtuelle Pfade werden für die Navigation und Kontrolle vom Face-Flow
verwendet. Das Ziel kann ein Face, ein Facet oder eine Funktion sein. Bei SPAs
(Single-Page-Applikationen) wird der Ankerteil der URL für die Pfade
verwendet.
```
https://example.local/example/#path
```
In Anlehnung an das Dateisystem werden auch hier absolute, relative und
zusätzlich funktionale Pfade unterstützt. Pfade bestehen
ausschliesslich aus Wortzeichen, Unterstrichen und optional dem Minus-Zeichen
(basierend auf zusammengesetzten IDs). Als Separator und Root wird das
Hash-Zeichen verwendet. Leerzeichen werden nicht unterstützt.
[Mehr erfahren](sitemap.md#virtual-paths)
## Komponenten
Seanox aspect-js ist auf eine modulare und komponentenbasierte Architektur
ausgerichtet. Das Framework unterstützt dazu eine deklarative Kennzeichnung
von Komponenten im Markup sowie automatische Mechanismen für das
View-Model-Binding und das Laden von ausgelagerten Ressourcen zur Laufzeit.
[Mehr erfahren](composite.md)
## Scripting
Seanox aspect-js nutzt Composite-JavaScript. Ein Dialekt, basierend auf dem
JavaScript des Browsers, das um Makros -- einer einfachen Meta-Syntax,
angereichert wurde.
Composite-JavaScript, was auch die Module einschliesst, wird nicht als Element
eingefügt, sondern direkt mit der eval-Methode ausgeführt. Da hierzu
ein isolierter und nicht der globale Gültigkeitsbereich (Scope) verwendet
wird, sind Variablen, Konstanten und Methoden nicht direkt global oder
übergreifend nutzbar, weshalb u.a. das Composite-JavaScript mit [Makros](
#makros) angereichert wurde, welche u.a. solche Aufgaben übernehmen.
[Mehr erfahren](scripting.md)
## Reaktives Rendering
Beim reaktiven Ansatz lösen Änderungen an den Datenobjekten (Models)
ein partielles Auffrischen der Konsumenten in der View aus. Konsumenten sind
alle Ausdrücke, die lesend auf den geänderten Wert eines Datenobjekts
zugreifen. In der View lassen sich Ausdrücke in den HTML-Elementen und im
Freitext verwenden. Die Datenobjekte müssen dann für das reaktive
Rendering `Reactive(object)` oder `Object.prototype.reactive()` nutzen.
```javascript
const Model = {
value: ...
}.reactive();
```
In diesem Beispiel aktualisiert der Renderer automatisch alle HTML-Elemente im
DOM, was Freitexte einschliesst, welche die Eigenschaft `value` vom `Model`
direkt oder indirekt in einem Ausdruck verwenden, wenn sich der Wert der
Eigenschaft `value` ändert oder genauer, wenn ein geänderter Wert im
Datenobjekt final gesetzt wurde, was bei der Verwendung von Getter und Setter
relevant sein kann.
Reactive wirkt permanent rekursiv auf allen Objektebenen und auch auf die
Objekte welche später als Wert hinzugefügt werden. Auch wenn diese
Objekte nicht explizit Reactive nutzen, werden für die referenzierten
Objekte neue Instanzen gebildet. Initiierende Objekte und reaktive Models sind
logisch entkoppelt und werden bidirektional synchronisiert. Im Unterschied dazu
nutzen Views und reaktive Models, wie auch die Models intern, Proxies, die sich
wie die initiierenden Originale verhalten und verwenden lassen. Jedoch sind
Proxies eigenst&aumkl;ndige Instanzen, die kompatibel aber nicht identisch zum
initiierenden Objekt sind. Notwendig ist diese logische Trennung, damit der
Renderer bei Datenänderungen entsprechende Notifications erzeugen und damit
die Konsumenten in der View aktualisieren kann.
[Mehr erfahren](reactive.md)
## API-Erweiterungen
Das JavaScript-API wurde für Seanox aspect-js um einige allgemeine
Funktionen erweitert.
- [Namespace](extension.md#namespace)
- [Element](extension.md#element)
- [Math](extension.md#math)
- [Object](extension.md#object)
- [RegExp](extension.md#regexp)
- [String](extension.md#string)
- [window](extension.md#window)
- [XMLHttpRequest](extension.md#xmlhttprequest)
[Mehr erfahren](extension.md)
## Ereignisse
Seanox aspect-js stellt verschiedene Ereignisse bereit, die u.a. zur
Implementierung von Erweiterungen sowie als Benachrichtigung der Anwendung
über bestimmte Betriebszustände des Frameworks und der
Laufzeitumgebung genutzt werden können.
[Mehr erfahren](events.md)
## Test
Das Test-API unterstützt die Implementierung und Ausführung von
Integrationstests und kann für Suiten (suite), Szenarien (scenario) und
einzelne Testfälle (test case) verwendet werden.
Als modularer Bestandteil von Seanox aspect-js ist das Test-API, mit Ausnahme
der core-Versionen, in allen Releases enthalten. Da das Test-API einige
Besonderheiten in Bezug auf Fehlerbehandlung und Konsolen-Ausgabe bewirkt, muss
das Test-API zur Laufzeit bewusst aktiviert werden.
```javascript
Test.activate();
Test.create({test() {
...
}});
Test.start();
```
[Mehr erfahren](test.md)
### Testfall
Der kleinste Bestandteil in einem Integrationstest, der hier als _Task_
verwendet wird, da _case_ ein Schlüsselwort im JavaScript ist. Tasks
können allein implementiert werden, werden aber meist in einem Szenario
verwendet.
```javascript
Test.activate();
Test.create({test() {
Assert.assertTrue(true);
}});
Test.start();
```
Task ist primär ein Meta-Objekt.
```
{name:..., test:..., timeout:..., expected:..., ignore:...}
```
[Mehr erfahren](test.md#testfall)
### Szenario
Ein Szenario ist eine Abfolge von vielen Testfällen (Tasks).
```javascript
Test.activate();
Test.create({test() {
Assert.assertTrue(true);
}});
Test.create({name:"example", timeout:1000, test() {
Assert.assertTrue(true);
}});
Test.create({error:Error test() {
throw new Error();
}});
Test.create({error:/^My Error/i, test() {
throw new Error("My Error");
}});
Test.create({ignore:true, test() {
Assert.assertTrue(true);
}});
Test.start();
```
[Mehr erfahren](test.md#szenario)
### Suite
Eine Suite ist ein komplexes Paket aus verschiedenen Testfällen, Szenarien
und anderen Suiten. In der Regel besteht eine Suite aus verschiedenen Dateien,
die dann einen komplexen Test darstellen. Ein Beispiel für eine gute Suite
ist eine Kaskade von verschiedenen Dateien und wo der Test in jeder Datei und an
jeder Stelle gestartet werden kann. Dies ermöglicht einen Integrationstest
auf verschiedenen Ebenen und mit unterschiedlicher Komplexität.
[Mehr erfahren](test.md#suite)
### Assert
Die Testfälle werden mit Behauptungen (Assertions) implementiert. Das
Test-API bietet elementare Aussagen, die erweitert werden können. Die
Funktionsweise ist einfach: Wenn eine Behauptung nicht wahr ist, tritt ein
Fehler auf, optional mit einer individuellen Fehlermeldung.
```javascript
Test.activate();
Test.create({test() {
Assert.assertTrue(true);
Assert.assertTrue("message", true);
Assert.assertFalse(false);
Assert.assertFalse("message", false);
Assert.assertEquals(expected, value);
Assert.assertEquals("message", expected, value);
Assert.assertNotEquals(unexpected, value);
Assert.assertNotEquals("message", unexpected, value);
Assert.assertSame(expected, value);
Assert.assertSame("message", expected, value);
Assert.assertNotSame(unexpected, value);
Assert.assertNotSame("message", unexpected, value);
Assert.assertNull(null);
Assert.assertNull("message", null);
Assert.assertNotNull(null);
Assert.assertNotNull("message", null);
Assert.fail();
Assert.fail("message");
}});
Test.start();
```
[Mehr erfahren](test.md#assert)
### Konfiguration
Optional kann das Test-API mit jedem Start konfiguriert werden. Als Parameter
wird ein Meta-Objekt erwartet. Die darin enthaltene Konfiguration wird partiell
übernommen und unbekanntes wird ignoriert.
```javascript
Test.start({auto: boolean, ouput: {...}, monitor: {...}});
```
[Mehr erfahren](test.md#konfiguration)
### Monitoring
Das Monitoring überwacht den Testablauf während der Ausführung
und wird über die verschiedenen Schritte und Status informiert. Der Monitor
ist optional. Ohne diesen werden Informationen zum Testverlauf in der Konsole
ausgegeben.
[Mehr erfahren](test.md#monitoring)
### Control
Der Testverlauf und die Verarbeitung der einzelnen Tests kann per Test-API
gesteuert werden.
```javascript
Test.start();
Test.start({auto: boolean});
```
Der Start kann manuell oder bei Verwendung von `auto = true` durch das Laden der
Seite erfolgen. Wenn die Seite bereits geladen ist, wird der Parameter `auto`
ignoriert und der Start sofort ausgeführt.
Weitere Methoden zur Steuerung und Kontrolle der Testausführung sind
verfügbar.
[Mehr erfahren](test.md#control)
### Events
Ereignisse (Events) bzw. deren Callback-Methoden sind eine weitere Form zur
Überwachung der Testausführung. Die Callback-Methoden werden für
entsprechende Ereignisse beim Test-API registriert und funktionieren dann
ähnlich dem Monitor.
```javascript
Test.listen(Test.EVENT_***, function(event, status) {
...
});
```
[Mehr erfahren](test.md#events)
- - -
[Motivation](motivation.md) | [Inhalt](README.md#einf-hrung) | [Expression Language](expression.md)