Skip to content
Snippets Groups Projects
Verified Commit fcc71af1 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

chore: commit save point

parent ac97ce5a
No related branches found
No related tags found
No related merge requests found
Forms can be bound to a structure just like other structures via the updater class.
Suppose we have an html form with some controls.
```
<div>
<form id="form1">
<input type="checkbox" name="checkbox">
<input type="text" name="text">
<input type="radio" name="radio">
<select name="select">
<option>value1</option>
<option>value2</option>
</select>
<textarea name="textarea">
</textarea>
<input type="button" name="button">
</form>
</div>
```
A new updater is created in this case via the following call.
```
const updater = new Updater(document.getElementById('form1'));
```
With the method `Updater.getSubject()` you can get the structure of the updater. If you want to use an already defined structure, you can pass it to the updater as a second parameter.
```
let subject = updater.getSubject();
console.log(subject);
```
Now we want a click on the checkbox to be mapped in the data structure.
To do this we need to extend the html with the `data-monster-bind` attribute.
The values or states of controls are mapped to the data structure via this binding.
```
<div>
<form id="form1">
<!-- data-monster-bind added -->
<input type="checkbox" name="checkbox" data-monster-bind="path:state">
</form>
</div>
```
In this case the status of the checkbox is mapped to the key `state`. If the checkbox is selected the field `state` contains the value `on` , otherwise state is undefined.
If you want to use another value instead of `on`, you can set the attribute `value`.
```
<input type="checkbox" name="checkbox" value="checked" data-monster-bind="path:state">
```
To see the magic the handling must still be switched on.
```
updater.enableEventProcessing()
```
Voila!
<p>
On this page you will find a sample implementation for many HTML5 controls. The binding is set up in both
directions, so that a change to the control also causes a change to the values, and changes to the values cause a change to the
control.
</p>
<p>
You can save the updater as a temporary variable in the console. In our case Chrome assigns the variable <code>temp1</code> to
the object.
</p>
<p>
Note that instead of <code>temp1</code> you have to take the variable from your browser.
</p>
<p>
And then access the data array using <code>temp1.getSubject().</code>
</p>
<p>
For example, to enable the checkbox you can set <code>temp1.getSubject().value.checkbox="YES"</code>. To deactivate the checkbox
you have to remove the value <code>temp1.getSubject().value.checkbox=undefined</code>
</p>
<p>
Note that you have to take the variable from your browser instead of temp1.
</p>
<p>
At the bottom you will find the JSON with all values
</p>
<h3>Examples</h3>
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script>-->
<!--<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism-coy.css" rel="stylesheet"/>-->
<template id="example1">
<style>
[readonly] {
background-color: #e9e9e9;
border: 1px solid grey;
}
div {
box-sizing: border-box;
}
.table {
display: grid;
grid-template-columns: 1fr 1fr 4fr;
}
.cell.control {
padding: 5px 20px;
}
.cell.value {
padding: 5px 20px;
}
.json {
margin: 5px 20px;
padding: 20px;
border: 4px solid #424242;
margin-top: 20px
}
</style>
<div id="container">
<div class="table">
<div class="cell control"><select type="input" data-monster-bind="path:value.multiple" multiple>
<option value="a" data-monster-attributes="selected path:value.multiple | call:checkstate">A
</option>
<option value="b" data-monster-attributes="selected path:value.multiple | call:checkstate">B
</option>
<option value="c" data-monster-attributes="selected path:value.multiple | call:checkstate">C
</option>
</select></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.multiple"></div>
<div class="cell code"></div>
<div class="cell control"><select type="input" data-monster-bind="path:value.select"
data-monster-attributes="value path:value.select">
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.select"></div>
<div class="cell code"></div>
<div class="cell control"><input type="checkbox" data-monster-bind="path:value.checkbox" value="YES"
data-monster-attributes="checked path:value.checkbox | call:checkstate">
</div>
<div class="cell value"><input id="my" readonly data-monster-attributes="value path:value.checkbox"></div>
<div class="cell code"></div>
<div class="cell control"><input type="search" name="search" data-monster-bind="path:value.search"
data-monster-attributes="value path:value.search"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.search"></div>
<div class="cell code"></div>
<div class="cell control"><input type="tel" name="tel" data-monster-bind="path:value.tel"
data-monster-attributes="value path:value.tel"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.tel"></div>
<div class="cell code"></div>
<div class="cell control"><input type="time" name="time" data-monster-bind="path:value.time"
data-monster-attributes="value path:value.time"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.time"></div>
<div class="cell code"></div>
<div class="cell control"><input type="url" name="url" data-monster-bind="path:value.url"
data-monster-attributes="value path:value.url"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.url"></div>
<div class="cell code"></div>
<div class="cell control"><input type="week" name="week" data-monster-bind="path:value.week"
data-monster-attributes="value path:value.week"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.week"></div>
<div class="cell code"></div>
<div class="cell control"><input type="color" name="color" data-monster-bind="path:value.color"
data-monster-attributes="value path:value.color"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.color"></div>
<div class="cell code"></div>
<div class="cell control"><input type="radio" name="doit" data-monster-bind="path:value.radio" value="2"
data-monster-attributes="checked path:value.radio | call:checkstate"><br>
<input type="radio" name="doit" data-monster-bind="path:value.radio" value="1"
data-monster-attributes="checked path:value.radio | call:checkstate"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.radio"></div>
<div class="cell code"></div>
<div class="cell control"><input type="range" name="range" data-monster-bind="path:value.range"
data-monster-attributes="value path:value.range"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.range"></div>
<div class="cell code"></div>
<div class="cell control"><input type="password" name="password" data-monster-bind="path:value.password"
data-monster-attributes="value path:value.password"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.password"></div>
<div class="cell code"></div>
<div class="cell control">
<input type="number" name="number" data-monster-bind="path:value.number"
data-monster-attributes="value path:value.number"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.number"></div>
<div class="cell code"></div>
<div class="cell control">
<input type="month" name="month" data-monster-bind="path:value.month"
data-monster-attributes="value path:value.month"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.month"></div>
<div class="cell code"></div>
<div class="cell control"><input type="email" name="email" data-monster-bind="path:value.email"
data-monster-attributes="value path:value.email"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.email"></div>
<div class="cell code"></div>
<div class="cell control"><input type="datetime-local" name="datetime-local"
data-monster-bind="path:value.datetime-local"
data-monster-attributes="value path:value.datetime-local"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.datetime-local"></div>
<div class="cell code"></div>
<div class="cell control">
<input type="date" name="date" data-monster-bind="path:value.date"
data-monster-attributes="value path:value.date"></div>
<div class="cell value"><input readonly data-monster-attributes="value path:value.date"></div>
<div class="cell code"></div>
</div>
<div class="json"
data-monster-replace="path:value | tojson"></div>
</div>
</template>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism-coy.css" rel="stylesheet"/>
<script type="module">
'use strict';
import {Updater} from "https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/updater.js";
document.addEventListener('readystatechange', () => {
if (document.readyState == 'complete') {
let included = false;
let example1 = document.getElementById('example1');
let article = document.querySelector('article');
if (article) {
article.appendChild(example1.content);
included = true;
} else {
// for local debug
document.body.appendChild(example1.content);
}
document.querySelectorAll('div[class*=control]').forEach((element) => {
let col = element.nextElementSibling.nextElementSibling;
let source = (element.innerHTML);
let pre = document.createElement('pre');
pre.setAttribute('class', 'prettyprint source')
let code = document.createElement('code');
col.appendChild(pre);
pre.appendChild(code);
source = Prism.highlight(source, Prism.languages.html, 'html');
code.innerHTML = source
})
const container = document.getElementById('container');
const updater = new Updater(container, {
value: {
input: "Init Value - input",
textarea: "Init Value - textarea",
file: "Init Value - file",
checkbox: "Init Value - checkbox",
radio: "Init Value - radio",
multiple: ['a', 'c']
}
});
updater.run();
updater.enableEventProcessing();
console.log(updater);
}
});
</script>
In diesem Artikel geht es um das Erstellen einer eigenen
[Web Komponente](https://developer.mozilla.org/en-US/docs/Web/Web_Components).
Mit Hilfe von Klassen und Funktionen von [Monster](https://monsterjs.org)
Zuerst erstellen wir eine von `CustomControl` abgeleitete Klasse. Die Elternklasse
`CustomControl` implementiert bereits einige Funktionen, die das Leben im folgenden
leichter machen.
```
class Button extends CustomControl {
}
```
Der nächste Schritt legt fest, welchen Tag das Control im HTML bekommen soll.
```
class Button extends CustomControl {
static getTag() {
return "monster-button";
}
}
```
Damit man später das Control konfigurieren kann, müssen wir die Möglichkeit,
Optionen anlegen zu können, schaffen. Wir können hier auf die Strukturen der Klasse
`CustomControl` aufsetzen und brauchen nur ein eigenes Property `default` anzulegen.
```
class Button extends CustomControl {
// ..... other implementations
get defaults() {
return Object.assign({}, super.defaults, {})
}
}
```
Über diese Struktur werden auch die Templates des Controls definiert. So kann man
dem Control ein Standard mitgeben und dem Anwender des Controls die Möglichkeit geben
die Templates anzupassen.
Jetzt machen wir uns also an das Aussehen des Controls und erstellen dazu ein Template.
Wir wollen das der Button einen einfachen HTML5-Button verwendet.
```
<button>Hello!</button>
```
Das Haupttemplate des Buttons wird über die Option `templates.main` gepflegt.
Wir müssen das HTML also in die oben eingeführte `default` Struktur einfügen. Wir können
das HTML direkt hier angeben.
```
return Object.assign({}, super.defaults, {
templates: {
main: `<button>Hello!</button>`
},
})
```
Um die Übersicht bei größerne Templates zu behalten, kann man alternativ das
Template auch in eine Funktion auslagern und hier nur die Funktion aufrufen.
```
return Object.assign({}, super.defaults, {
templates: {
main: getTemplate()
},
})
```
Wenn wir jetzt den HTML-Tag `<monster-button></monster-button>` in eine HTML-Datei einfügen
erhalten wir soweit so unspektakulär einen Button.
![Screenshot](images/button1.png)
Jetzt kommt die Magie. Im DOM gibt es zwei wichtige Methoden, die beim Einbinden und
beim Entfernen eines Controls aufgerufen werden.
Wird ein Control in das DOM eingehängt, so wird die Methode
[`connectedCallback`](https://developer.mozilla.org/de/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks) aufgerufen.
Beim Entfernen eines Controls aus dem DOM wird dagegen die Methode
[`disconnectedCallback`](https://developer.mozilla.org/de/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks) aufgerufen.
aufgerufen.
Wir implementieren die beiden Methoden in unserer neuen Klasse.
```
class Button extends CustomControl {
// ..... other implementations
connectedCallback() {
super.connectedCallback();
}
disconnectedCallback() {
super.disconnectedCallback();
}
}
```
Innerhalb dieser beiden Methoden können wir nun zum Beispiel Strukturen initailisieren oder
Eventhandler hinzufügen und wieder entfernen.
Das `CustomControl` besitzt zwei weitere wichtige Methoden, die
man überschreiben kann, um das Control zu initialisieren. Diese beiden Methoden
haben keinen direkten Methodennamen, sondern verbergen sich hinter einem
Symbol-Schlüssel.
```
import {
assembleMethodSymbol,
initMethodSymbol,
} from "@schukai/monster/dist/modules/dom/customelement.js";
class Button extends CustomControl {
// ..... other implementations
[initMethodSymbol]() {
super[initMethodSymbol]();
}
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
}
}
```
Die Methode `[initMethodSymbol]()` wird direkt vom Konstruktor aufgerufen und dient zur einmaliegen initialisierung
interner Strukturen.
Die Methode `[assembleMethodSymbol]()` wird beim ersten einbinden des Kontrols in das
DOM aufgerufen. Wird das Control wieder entfernt und erneut eingebunden, so wird
`[assembleMethodSymbol]()` nicht nochmal aufgerufen.
@startuml filename2.png
Alice --> Bob
@enduml
We need the `Formatter` and `Translations` class and the `parseLocale()` function to handle translations.
In the context of the DOM we can also use the `getLocaleOfDocument()` method.
```
import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/i18n/translations.js';
import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/text/formatter.js';
import {parseLocale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/i18n/locale.js';
import {getLocaleOfDocument} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/locale.js';
```
Let's start with the function `parseLocale()`. This function can create a `Locale` object from a string.
So we can create the corresponding `Locale` object from the string `en_GB`.
```
const locale = parseLocale('en_GB')
// ↦ Locale {}
```
If we move in the browser, so we can also use the function `getLocaleOfDocument()`.
This function returns a locale object as a result.
This function looks if the HTML tag `<html lang="en">` has a lang attribute.
If no locale is defined, the default value is assumed to be `en`.
We now need an object with the translations. For this we use the `Translations` class.
We can either define the translations ourselves or load them via an API.
```
// define translations
const translation = new Translations(parseLocale('en-GB'));
translation.assignTranslations({
text1: 'hello world!',
text2: {
'one': 'click once',
'other': 'click ${n | tostring} times' // this is where the pipe comes in
}
});
// fetch from API
const translation = new Fetch('https://example.com/${language}.json').getTranslation('en-GB');
// ↦ https://example.com/en.json
```
If we now have a translation, we can now read the desired strings. For this
we use the method `Translation.getText()` or the method `Translation.getPluralRuleText()`
for texts with more than one number.
```
const message = translation.getText('text1');
// -> hello world
```
To translate texts with number references, enter the desired number.
```
let n=1;
const message1 = translation.getPluralRuleText('text2', n);
// -> click once
n=2
const message2 = translation.getPluralRuleText('text2', n);
// -> click ${n} times
```
To replace the placeholder now, the formatter is used.
```
const text = new Formatter({n}).format(message2);
console.log(text)
// ↦ click 2 times
```
Finally, let's take a look at the formatter class from the i18n module.
```
import {Formatter} from
'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/i18n/formatter.js';
```
A tranlate object can be passed directly to this class.
There is also the possibility to pass values in the format string.
```
// define translation
const translations = new Translations('en')
.assignTranslations({
// text with placeholder
message: "${animal} has eaten the ${food}!"
});
// without marker and inline values
new Formatter({}, translations).format("message::animal=dog::food=cake")
// ↦ dog has eaten the cake!
```
Voila!
documentation/tutorials/images/button1.png

717 B

{
"01-getting-started": {
"title": "Getting Started"
},
"02-what-is-monster": {
"title": "What is Monster?"
},
"03-dom": {
"title": "DOM",
"children": {
"dom-form-handling": {
"title": "Form handling"
},
"form-example": {
"title": "Form examples"
},
"dom-based-templating-implementation": {
"title": "DOM-based templating implementation"
},
"how-to-write-a-customcontrol": {
"title": "How to write a CustomControl"
}
}
},
"04-i18n": {
"title": "Internationalization",
"children": {
"i18n-locale-and-formatter": {
"title": "Locale & Formatter"
}
}
},
"09-browser-compatibility": {
"title": "Browser compatibility"
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment