@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,032 lines (840 loc) • 35.7 kB
Markdown
[Expression Language](expression.md) | [Inhalt](README.md#markup) | [Scripting](scripting.md)
- - -
# Markup
Mit Seanox aspect-js wird der deklarative Ansatz von HTML aufgegriffen und
erweitert. Neben der Expression-Language werden den HTML-Elementen
zusätzliche Attribute für Funktionen und dem View-Model-Binding
bereitgestellt. Der entsprechende Renderer ist in der Composite-Implementierung
enthalten und überwacht das DOM ab dem BODY Element aktiv über den
MutationObserver und funktioniert und reagiert rekursiv auf Veränderungen.
## Inhalt
* [Attribute](#attribute)
* [composite](#composite)
* [condition](#condition)
* [events](#events)
* [id](#id)
* [import](#import)
* [interval](#interval)
* [iterate](#iterate)
* [message](#message)
* [namespace](#namespace)
* [notification](#notification)
* [output](#output)
* [release](#release)
* [strict](#strict)
* [render](#render)
* [validate](#validate)
* [@-Attribute](#-attribute)
* [Expression Language](#expression-language)
* [Scripting](#scripting)
* [Customizing](#customizing)
* [Tag](#tag)
* [Selector](#selector)
* [Acceptor](#acceptor)
* [Härtung](markup.md#härtung)
## 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.
### 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>
```
Als Komponente setzen sich Composites aus verschiedenen [Ressourcen](
composite.md#ressourcen) (Markup, CSS, JS) zusammen, die sich auf Basis der
(Composite-)ID in das Modul-Verzeichnis auslagern lassen und erst bei Bedarf zur
Laufzeit nachgeladen werden.
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.
```html
<article id="example" composite static>
...
</article>
```
Details zur Verwendung von Composites / modularen Komponenten werden in den
Abschnitten [Composites](composite.md) und [Model-View-Controller](mvc.md)
beschrieben.
### 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>
```
Bei der Kombination mit dem Attribut [interval](#interval) ist zu beachten, dass
mit dem Entfernen des Elements aus dem DOM auch der zugehörige Timer
beendet wird. Wird das Element mit einer späteren Auffrischung wieder in
das DOM aufgenommen, startet ein neuer Timer, wird also nicht fortgesetzt.
```html
<article interval="{{1000}}" condition="{{Model.visible}}">
...
</article>
```
Die Verwendung vom condition-Attribut in Verbindung mit eingebettetem JavaScript
ist als SCRIPT element mit dem Typ `composite/javascript` als
Composite-JavaScript möglich, da hier der Renderer die Kontrolle über
die Skript-Ausführung hat und nicht der Browser.
```html
<script type="composite/javascript" condition="{{Model.visible}}">
...
</script>
```
Details zur Verwendung von eingebettetem JavaScript werden im Abschnitt
[Scripting](#scripting) beschrieben.
### 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)).
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.
```html
<span id="output1">{{#text1.value}}</span>
<input id="text1" type="text"
events="input change" render="#output1"/>
```
Beispiel zur synchronen Auffrischung vom HTML-Element _output1_ mit den
Ereignissen _Input_ oder _Change_ beim HTML-Element _text1_. In dem Beispiel
wird der Eingabewert von _text1_ synchron mit _output1_ ausgegeben.
```javascript
const Model = {
validate(element, value) {
return true;
},
text1: ""
};
```
```html
<form id="Model" composite>
<input id="text1" type="text"
validate events="input change"/>
<input type="submit" value="submit"
validate events="click"/>
</form>
```
Beispiel zur Implementierung und kombinierten Verwendung der Attribute `events`
und `validate`. In dem Beispiel wird der Eingabewert vom Composite-Feld _text1_
nur dann in das gleichnamige Feld im JavaScript-Objekt übernommen und die
Validierung durchgeführt, wenn mindestens eines der Ereignisse: _Input_
oder _Change_ eintritt.
### id
Die ID (Bezeichner) hat in Seanox aspect-js eine elementare Bedeutung. Sie
bildet die Grundlage für das [View-Model-Binding](
mvc.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.
### 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.
```javascript
const Model = {
publishForm() {
const form = document.createElement("form");
const label = document.createElement("label");
label.textContent = "Input";
form.appendChild(label);
const input = document.createElement("input");
input.value = "123";
input.type = "text";
form.appendChild(input);
const submit = document.createElement("input");
submit.type = "submit";
form.appendChild(submit);
return form;
},
publishImg() {
const img = document.createElement("img");
img.src = "https://raw.githubusercontent.com/seanox/aspect-js/master/test/resources/smile.png";
return img;
}
};
```
```html
<article import="{{Model.publishImg()}}">
loading image...
</article>
<article import="{{Model.publishForm()}}">
loading form...
</article>
```
Beispiel für den Import einer entfernten Ressource per HTTP-Methode GET.
```html
<article import="{{'https://raw.githubusercontent.com/seanox/aspect-js/master/test/resources/import_c.htmlx'}}">
loading resource...
</article>
<article import="https://raw.githubusercontent.com/seanox/aspect-js/master/test/resources/import_c.htmlx">
loading resource...
</article>
```
Beispiel für den Import per DataSource-URL. Wird nur eine URL angegeben,
wird die URL für Daten und Transformation daraus abgeleitet.
```html
<article import="{{'xml:/example/content'}}">
loading resource...
</article>
<article import="xml:/example/content">
loading resource...
</article>
```
Beispiel für den Import per DataSource-URL mit spezifischer Daten- und
Transformations-URL. Als Wert sind die Daten-URL (locator der XML-Datei) und
nachfolgend getrennt durch ein Leerzeichen die Transformations-URL (locator vom
XSLT-Template) angegeben.
```html
<article import="{{'xml:/example/data xslt:/example/style'}}">
loading resource...
</article>
<article import="xml:/example/data xslt:/example/style">
loading resource...
</article>
```
Beim Einfügen von Inhalten aus der DataSource, wird der Typ von
JavaScript-Blöcken automatisch in `composite/javascript` geändert und
erst durch den Renderer ausgeführt. So wird gewährleistet, dass das
JavaScript ggf. abhängig vom umschliessenden condition-Attribut
ausgeführt wird.
### 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
<span interval="1000">
...
</span>
<span interval="{{1000 +500}}">
...
</span>
```
Das interval-Attribut kann für einfache HTML-Elemente und auch komplexe
HTML-Konstrukte verwendet werden. So wird das SPAN-Element alle 1000ms
aktualisiert. Ein aktives Intervall reagiert dabei dynamisch auf
Veränderungen im DOM und beginnt automatisch wenn das HTML-Element im DOM
hinzugefügt wird und endet wenn es aus dem DOM entfernt wird. Womit sich
das interval-Attribut gut in Kombination mit dem condition-Attribut verwenden
und steuern lässt.
```html
<span interval="1000" condition="{{IntervalModel.isVisible()}}">
...
</span>
```
Mit der Kombination von Intervall und Variablen-Expression ist z.B. die
Umsetzung eines permanenten Zählers einfach.
```html
{{counter:0}}
<p interval="1000">
{{counter:parseInt(counter) +1}}
{{counter}}
</p>
```
Auch die Verwendung vom interval-Attribut in Verbindung mit eingebettetem
JavaScript ist als Composite-JavaScript möglich.
```html
<script type="composite/javascript" interval="1000">
...
</script>
```
### 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>
```
### 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="email" type="text" placeholder="email 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="email address"
pattern="^\w+([\w\.\-]*\w)*@\w+([\w\.\-]*\w{2,})$"
validate message="{{Messages['Model.email.validation.message']}}"
events="input change" render="#Model"/>
<input type="submit" value="submit" validate events="click"/>
</form>
```
### namespace
Vergleichbar mit Packages in anderen Programmiersprachen, lassen sich Namespaces
(Namensräume) zur hierarchischen Strukturierung von Komponenten, Ressourcen
und Geschäftslogik nutzen.
Auch wenn Packages kein Merkmal von JavaScript sind, lassen sich diese auf
Objektebene durch das Verketten von Objekten zu einem Objektbaum abbilden. Dabei
bildet jede Ebene des Objektbaums einen Namespace, was auch als Domain
betrachtet werden kann.
Im Markup bilden sich Namespaces aus den IDs verschachtelter Composites, wenn
diese das Attribut `namespace` verwenden.
```html
<div id="masterdata" composite namespace>
<div id="regions" composite namespace>
Namespace: masterdata.regions
</div>
</div>
```
Befinden sich weitere Elemente mit einer ID zwischen den Composites, haben diese
keine Auswirkungen.
```html
<div id="masterdata" composite namespace>
<section id="section">
<form id="form">
<div id="regions" composite namespace>
Namespace: masterdata.regions
Elements section and form are ignored
</div>
</form>
</section>
</div>
```
Noch spezieller sind in einer Namespace-Kette Composites ohne das Attribut
`namespace`. Diese Composites wirken entkoppelnd und haben selbst keinen
Namespace. Innerhalb dieser Composites können dann neue Namespaces begonnen
werden, unabhängig von übergeordneten Namespaces.
```html
<div id="Imprint" composite namespace>
Namespace: Imprint
<div id="Contact" composite>
Namespace: Contact
<div id="Support" composite namespace>
Namespace: Support
<div id="Mail" composite namespace>
Namespace: Support.Mail
</div>
<div id="Channel" composite namespace>
Namespace: Support.Channel
</div>
...
</div>
<div id="Community" composite namespace>
Namespace: Community
<div id="Channel" composite namespace>
Namespace: Community.Channel
</div>
...
</div>
</div>
</div>
```
Eingeführt wurde dieses Verhalten mit dem Gedanken an Micro-Frontends,
welche eigene Domains nutzen und an verschiedenen Stellen wiederverwendet werden
sollen. So lassen sich in der statischen Welt von Seanox aspect-js
Domain-bezogene Komponenten umsetzen.
Namespaces haben zudem Auswirkungen auf Ressourcen und Module. So haben
Namespaces im Markup erstmal nur textuellen Charakter und können auch ohne
ein korrespondierendes JavaScript-Objekt (Model) existieren und verwendet
werden. Im Markup wird lediglich die Syntax der Namespaces geprüft. Ist
diese gültig, werden die Namespaces direkt auf den Pfad von Modulen und
deren Ressourcen angewendet und erweitern den Pfad ab dem Modul-Verzeichnis.
```
+ modules
- common.css
- common.js
+ community
- channel.css
- channel.html
- channel.js
- ...
- imprint.css
- imprint.html
- imprint.js
+ support
- mail.css
- mail.html
- mail.js
- ...
- ...
- index.html
```
Details zu Namespaces werden auch in den Abschnitten [Erweiterung - Namespace](
extension.md#namespace) und [Composites - Namespace](composite.md#namespace)
beschrieben.
### 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>
```
### 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.
```javascript
const Model = {
publishForm() {
const form = document.createElement("form");
const label = document.createElement("label");
label.textContent = "Input";
form.appendChild(label);
const input = document.createElement("input");
input.value = "123";
input.type = "text";
form.appendChild(input);
const submit = document.createElement("input");
submit.type = "submit";
form.appendChild(submit);
return form;
},
publishImg() {
const img = document.createElement("img");
img.src = "https://raw.githubusercontent.com/seanox/aspect-js/master/test/resources/smile.png";
return img;
}
};
```
```html
<article output="{{Model.publishImg()}}">
loading image...
</article>
<article output="{{Model.publishForm()}}">
loading form...
</article>
```
Beispiel für den Output einer entfernten Ressource per HTTP-Methode GET.
```html
<article output="{{'https://raw.githubusercontent.com/seanox/aspect-js/master/test/resources/import_c.htmlx'}}">
loading resource...
</article>
<article output="https://raw.githubusercontent.com/seanox/aspect-js/master/test/resources/import_c.htmlx">
loading resource...
</article>
```
Beispiel für den Output per DataSource-URL. Wird nur eine URL angegeben,
wird die URL für Daten und Transformation daraus abgeleitet.
```html
<article output="{{'xml:/example/content'}}">
loading resource...
</article>
<article output="xml:/example/content">
loading resource...
</article>
```
Beispiel für den Output per DataSource-URL mit spezifischer Daten- und
Transformations-URL. Als Wert sind die Daten-URL (locator der XML-Datei) und
nachfolgend getrennt durch ein Leerzeichen die Transformations-URL (locator vom
XSLT-Template) angegeben.
```html
<article output="{{'xml:/example/data xslt:/example/style'}}">
loading resource...
</article>
<article output="xml:/example/data xslt:/example/style">
loading resource...
</article>
```
Beim Einfügen von Inhalten aus der DataSource, wird der Typ von
JavaScript-Blöcken automatisch in `composite/javascript` geändert und
erst durch den Renderer ausgeführt. So wird gewährleistet, dass das
JavaScript ggf. abhängig vom umschliessenden condition-Attribut
ausgeführt wird.
### 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>
```
### 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.
```javascript
const Model = {
_status1: 0,
getStatus1() {
return ++Model._status1;
},
_status2: 0,
getStatus2() {
return ++Model._status2;
},
_status3: 0,
getStatus3() {
return ++Model._status3;
}
};
```
```html
Target #1:
<span id="outputText1">{{Model.status1}}</span>
Events: Wheel
<input id="text1" type="text"
events="wheel"
render="#outputText1, #outputText2, #outputText3"/>
Target #2:
<span id="outputText2">{{Model.status2}}</span>
Events: MouseDown KeyDown
<input id="text1" type="text"
events="mousedown keydown"
render="#outputText2, #outputText3"/>
Target #3:
<span id="outputText3">{{Model.status3}}</span>
Events: MouseUp KeyUp
<input id="text1" type="text"
events="mouseup keyup"
render="#outputText3"/>
```
Das Beispiel enthält 3 Eingabefelder mit unterschiedlichen Ereignissen
(`events`) und Zielen (`render`), die jeweils hochzählende Textausgaben
darstellen und auf entsprechende Ereignisse reagieren.
__Alternativ kann auch das [reaktive Rendering](reactive.md) verwendet werden,
wo Änderungen in den Datenobjekten eine partielle Aktualisierung der View
auslösen.__
### 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.
Beispiel zum Standardverhalten:
```html
<div id="SmallExample" composite>
```
```
+ modules
- smallExample.css
- smallExample.js
- smallExample.html
- index.html
```
Beispiel mit dem Attribut [strict](#strict):
```html
<div id="SmallExample" composite strict>
```
```
+ modules
- SmallExample.css
- SmallExample.js
- SmallExample.html
- index.html
```
Die Kombination mit dem Attribut [validate](#validate) beeinflusst das
Synchronisieren der Daten so, dass die Synchronisation nur ausgeführt wird,
wenn die Validierung explizit true zurückgibt. In allen anderen Fällen
wird der Wert nicht synchronisiert. Womit im zu synchronisierenden Ziel nach
einer Benutzereingabe nur zulässige Werte vorhanden sind, was z.B. bei
einem als erforderlich (required) deklarierten Eingabefeld beachtet werden muss,
wenn der Benutzer die Eingabe zeichenweise löscht. So bleibt in diesem
konstruierten Beispiel das letzte Zeichen im zu synchronisierenden Ziel
erhalten.
```javascript
const Model = {
validate(element, value) {
return !!value.trim();
},
text1: ""
};
```
```html
<form id="Model" composite>
<input id="text1" type="text"
validate strict events="input change"/>
<input type="submit" value="submit"
validate events="click"/>
</form>
```
### 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.
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`.
#### true
Die Validierung war erfolgreich. Es wird kein Fehler angezeigt und das
Standard-Verhalten (action) vom Browser wird verwendet. Wenn möglich wird
der Wert mit dem Modell synchronisiert.
#### not true and not undefined/void
Die Validierung ist fehlgeschlagen und es wird ein Fehler angezeigt. Der
Rückgabewert gibt an, dass das Standard-Verhalten (action) vom Browser
nicht ausgeführt werden soll und somit blockiert wird. Bei Kombination mit
dem Attribut [strict](#strict), wird ein möglicher Wert nicht mit dem
Modell synchronisiert.
#### text
Die Validierung ist mit einer Fehlermeldung fehlgeschlagen. Sollte die
Fehlermeldung leer sein, wird alternativ die Meldung vom message-Attribut
verwendet. Bei Kombination mit dem Attribut [strict](#strict), wird ein
möglicher Wert nicht mit dem Modell synchronisiert.
#### undefined/void
Die Validierung ist fehlgeschlagen und es wird ein Fehler angezeigt. Ohne einen
Rückgabewert wird das Standard-Verhalten (action) vom Browser
ausgeführt. Dieses Verhalten ist z.B. für die Validierung von
Eingabefeldern wichtig, damit die Eingabe zur Benutzeroberfläche gelangt.
Bei Kombination mit dem Attribut [strict](#strict), wird ein möglicher Wert
nicht mit dem Modell synchronisiert.
Eine allgemeine Strategie oder Standard-Implementierung zur Fehlerausgabe wird
bewusst nicht bereitgestellt, da diese in den meisten Fällen zu starr ist
und mit geringem Aufwand als zentrale Lösung individuell implementiert
werden kann.
```css
input[type='text']:not([title]) {
background:#EEEEFF;
border-color:#7777AA;
}
input[type='text'][title=''] {
background:#EEFFEE;
border-color:#77AA77;
}
input[type='text'][title]:not([title='']) {
background:#FFEEEE;
border-color:#AA7777;
}
```
```javascript
const Model = {
validate(element, value) {
const PATTER_EMAIL_SIMPLE = /^\w+([\w\.\-]*\w)*@\w+([\w\.\-]*\w{2,})$/;
const test = PATTER_EMAIL_SIMPLE.test(value);
return test || ("Invalid " + element.getAttribute("placeholder"));
},
text1: ""
};
```
```html
<form id="Model" composite>
<input id="text1" type="text" placeholder="e-mail address"
validate events="input change" render="#Model"/>
Model.text1: {{Model.text1}}
<input type="submit" value="submit" validate events="click"/>
</form>
```
In dem Beispiel erwartet das Eingabefeld eine E-Mail-Adresse. Der Wert wird
fortlaufend bei der Eingabe überprüft und bei einem ungültigen
Wert wird eine Fehlermeldung in das Attribut `title` geschrieben, bzw. bei einem
gültigen Wert wird der Inhalt vom Attribut `title` gelöscht. Unterhalb
vom Eingabefeld ist die Kontrollausgabe vom korrespondierenden Feld im
JavaScript-Objekt (Model). Ohne das Attribut [strict](#strict) wird das Feld
fortlaufend mit der Eingabe synchronisiert.
## @-Attribute
Expressions werden erst nach dem Laden der Seite durch den Renderer
aufgelöst. Bei einigen HTML-Elementen kann das störend sein, wenn die
Attribute bereits durch den Browser interpretiert werden. So z.B. das
src-Attribute bei Ressourcen wie dem img-Tag. Für diese Fälle lassen
sich @-Attribute nutzen. Diese funktionieren wie Templates für Attribute.
Der Renderer wird deren Wert auflösen und dann die gleichnamigen Attribute
zum Element hinzufügen. Danach verhalten sich diese wie alle anderen
Attribute, inkl. dem Aktualisieren durch den Renderer, wenn die Attribute
Expressions beinhalten.
```html
<img @src="{{...}}"/>
```
## Expression Language
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
<article title="{{Model.title}}">
{{'Hello World!'}}
...
</article>
```
Details zu Syntax und Verwendung werden im Abschnitt
[Expression Language](expression.md) beschrieben.
## Scripting
Eingebettetes Scripting bringt einige Besonderheiten mit sich. Das
Standard-Scripting wird vom Browser automatisch und unabhängig vom
Rendering ausgeführt. Daher wurde das Markup für das Rendering um den
zusätzlichen Skript-Typ `composite/javascript` erweitert, der das normale
JavaScript verwendet, im Vergleich zum Typ `text/javascript` vom Browser aber
nicht erkannt und somit nicht direkt ausgeführt wird. Der Renderer hingegen
erkennt den JavaScript-Code und führt diesen mit jedem relevanten
Renderzyklus aus. Auf diese Weise kann die Ausführung vom SCRIPT-Element
auch mit dem Attribut `condition` kombiniert werden.
```html
<script type="composite/javascript">
...
</script>
```
Details zur Verwendung vom Composite-JavaScript inkl. Modulen wird im Abschnitt
[Scripting](scripting.md) beschrieben.
## Customizing
### Tag
Benutzerdefinierte HTML-Elemente (Tags) übernehmen das komplette Rendering
in Eigenverantwortung. Der Rückgabewert bestimmt, ob die Standardfunktionen
des Renderers verwendet werden oder nicht. Nur der Rückgabewert `false`
(nicht void, nicht leer) beendet das Rendering für ein benutzerdefiniertes
HTML-Element, ohne die Standardfunktionen des Renderers zu verwenden.
```javascript
Composite.customize("foo", function(element) {
...
});
```
```html
<article>
<foo/>
</article>
```
### Selector
Selektoren funktionieren ähnlich wie benutzerdefinierte Tags. Im Vergleich
zu diesen, verwenden Selektoren einen CSS-Selektor um Elemente zu erkennen.
Dieser Selektor muss das Element aus Sicht des übergeordneten
Eltern-Elements ansprechen. Selektoren sind flexibler und multifunktionaler. Auf
diese Art können verschiedene Selektoren mit unterschiedlichen Funktionen
für ein Element zutreffen.
Selectoren werden nach der Reihenfolge ihrer Registrierung iterativ durchlaufen
und deren Callback-Methoden ausgeführt. Der Rückgabewert der
Callback-Methode bestimmt dabei, ob die Iteration abgebrochen wird oder nicht.
Nur der Rückgabewert `false` (nicht void, nicht leer) beendet die Iteration
über andere Selektoren und das Rendering für den Selektor wird ohne
Verwendung der Standardfunktionen beendet.
```javascript
Composite.customize("a:not([href])", function(element) {
...
});
Composite.customize("a.foo", function(element) {
...
});
```
```html
<article>
<a class="foo"></a>
</article>
```
### Acceptor
Acceptors sind eine besondere Art der Anpassung vom Rendering. Im Vergleich zu
den anderen Möglichkeiten, geht es hier um die Manipulation von Elementen
vor dem Rendering. Dies ermöglicht individuelle Änderungen an
Attributen und/oder dem Markup, bevor der Renderer sie verarbeitet. Damit hat
ein Acceptor keinen Einfluss auf die Implementierung vom Rendering.
```javascript
Composite.customize(function(element) {
...
});
```
## Härtung
In Seanox aspect-js ist eine Härtung vom Markup vorgesehen, was die
Manipulation vom Markup zur Laufzeit erschwert. Zum einen wird mit einer
Condition ausgeblendetes Markup physisch aus dem DOM entfernt und zum anderen
überwacht der Renderer Manipulationen an Attributen zur Laufzeit. Diese
Überwachung basiert auf einem Filter mit statischen Attributen. Statische
Attribute werden mit dem Anlegen eines Elements im DOM gelesen und bei
Manipulation (Löschen / Ändern) wieder hergestellt.
Zur Konfiguration statischer Attribute wird die Methode
`Composite.customize(...)` mit dem Parameter `@ATTRIBUTES-STATICS` verwendet.
Die Konfiguration kann mehrfach erfolgen. Die einzelnen statischen Attribute
werden dann zusammengefasst. Dabei sind alle @-Parameter unabhängig von der
Gross- und Kleinschreibung.
```javascript
Composite.customize("@ATTRIBUTES-STATICS", "action name src type");
Composite.customize("@Attributes-Statics", "required");
Composite.customize("@attributes-statics", "method action");
...
```
```html
<form method="POST" action="/service">
<input type="user" name="user"
<input type="password" name="password"/>
<input type="submit"/>
</form>
```
- - -
[Expression Language](expression.md) | [Inhalt](README.md#markup) | [Scripting](scripting.md)