Beispiel:Custom-element-range-number.html
Aus SELFHTML-Wiki
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom-Element mit synchronisierten Range- und Number-Inputs</title>
<script>
"use strict"
// Custom-Element self-slider anlegen
customElements.define('self-slider', class extends HTMLElement {
// selfSlider soll Formularelement sein
static formAssociated = true;
// Festlegen, welche Attribute überwacht werden sollen
static get observedAttributes() {
return ['legend', 'label', 'min', 'max', 'step', 'value'];
}
constructor() {
// Element wird anlgelegt
// super muss als erstes im constructor aufgerufen werden, super ruft den construcor der Elternklasse auf
super();
// Zugriff auf die ElementInternals, wird benötigt, um auf die Werte zuzugreifen, die mit dem Formular versendet werden.
this.internals = this.attachInternals();
// Schatten-DOM anlegen
// mode: 'open' : Vom Dokument aus ist der Zugriff auf das Schatten-Dom möglich.
// mode: 'closed' : Der Zugriff auf das Schatten-Dom ist nicht möglich.
const shadow = this.attachShadow({mode: 'closed'});
// Element für Inhalt, hier ein fieldset, anlegen und ins Schatten-DOM einhängen
this.slider = document.createElement('fieldset');
this.slider.setAttribute('part', 'fieldset');
shadow.appendChild(this.slider);
// CSS anlegen und ins Schatten-Dom einhängen
// :host selektiert das Custom Element
const style = document.createElement('style');
style.textContent = `
:host fieldset { max-inline-size: fit-content; }
:host fieldset * { display: inline-block; vertical-align: top; margin: 0; padding: .1em}
:host input { font-size: .9em; }
`;
shadow.appendChild(style);
// Defaultwerte der Attribute setzen
this.legendAttribute = '';
this.labelAttribute = '';
this.minAttribute = 0;
this.maxAttribute = 100;
this.stepAttribute = 1;
this.valueAttribute = 0;
}
connectedCallback() {
// Element wurde ins DOM eingehängt
// Schatten-DOM aufbauen
// Zufällige ID für das Number-Input, wird fürs Label benötigt
const numberID = Math.random().toString().replace('0.','');
// Legende
this.legend = document.createElement('legend');
this.legend.setAttribute('part', 'legend');
if(this.legendAttribute.length) {
this.legend.innerHTML = this.legendAttribute;
}
else {
this.legend.setAttribute('hidden','hidden');
}
this.slider.appendChild(this.legend);
// Label
this.label = document.createElement('label');
this.label.setAttribute('for', numberID);
this.label.setAttribute('part', 'label');
if(this.labelAttribute.length) {
this.label.innerHTML = this.labelAttribute;
}
else {
this.label.setAttribute('hidden','hidden');
}
this.slider.appendChild(this.label);
// Range-Input
this.range = document.createElement('input');
this.range.setAttribute('type', 'range');
this.range.setAttribute('min',this.minAttribute);
this.range.setAttribute('max',this.maxAttribute);
this.range.setAttribute('step',this.stepAttribute);
this.range.setAttribute('value',this.valueAttribute);
this.range.setAttribute('part', 'range');
this.slider.appendChild(this.range);
// Number-Input
this.number = document.createElement('input');
this.number.setAttribute('id', numberID);
this.number.setAttribute('type', 'number');
this.number.setAttribute('min', this.minAttribute);
this.number.setAttribute('max', this.maxAttribute);
this.number.setAttribute('step', this.stepAttribute);
this.number.setAttribute('value',this.valueAttribute);
this.number.setAttribute('part', 'number');
this.slider.appendChild(this.number);
// Start-Value speichern und ans Formular übergeben
this.currentValue = this.valueAttribute;
this.internals.setFormValue(this.currentValue);
// Eventhandler, um die beiden Inputs zu synchronisieren und den Wert zu speichern und ans Formular zu übergeben
this.slider.addEventListener('input', event => {
if(event.target.type == 'range') {
this.number.value = event.target.value;
}
else if(event.target.type == 'number') {
this.range.value = event.target.value;
}
this.currentValue= event.target.value;
this.internals.setFormValue(this.currentValue);
});
}
attributeChangedCallback(name, oldValue, newValue) { console.log(name, oldValue, newValue);
// Elementparameter wurden geändert
// Achtung attributeChangedCallback wird vor connectedCallback aufgerufen, daher prüfen, ob Elemente schon vorhanden sind
switch(name) {
case 'legend':
this.legendAttribute = newValue;
if(this.legend) {
if(this.legendAttribute && this.legendAttribute.length) {
this.legend.removeAttribute('hidden');
this.legend.innerHTML = this.legendAttribute;
}
else {
this.legend.setAttribute('hidden','hidden');
}
}
break;
case 'label':
this.labelAttribute = newValue;
if(this.label) {
if(this.labelAttribute && this.labelAttribute.length) {
this.label.removeAttribute('hidden');
this.label.innerHTML = this.labelAttribute;
}
else {
this.label.setAttribute('hidden','hidden');
}
}
break;
case 'min':
this.minAttribute = newValue;
if(this.number) this.number.setAttribute('min', this.minAttribute);
if(this.range) this.range.setAttribute('min', this.minAttribute);
break;
case 'max':
this.maxAttribute = newValue;
if(this.number) this.number.setAttribute('max', this.maxAttribute);
if(this.range) this.range.setAttribute('max', this.maxAttribute);
break;
case 'step':
this.stepAttribute = newValue;
if(this.number) this.number.setAttribute('step', this.stepAttribute);
if(this.range) this.range.setAttribute('step', this.stepAttribute);
break;
case 'value':
this.currentValue = newValue;
this.valueAttribute = newValue;
if(this.number) this.number.setAttribute('value',this.valueAttribute);
if(this.range) this.range.setAttribute('value',this.valueAttribute);
this.internals.setFormValue(this.currentValue);
break;
}
}
// Setter und Getter für value.
set value(value) {
this.currentValue = value;
this.number.value = value;
this.range.value = value;
this.internals.setFormValue(value);
}
get value() {
return this.currentValue;
}
} );
// Test des Elements
// Test des Elements
document.addEventListener('DOMContentLoaded', () => {
const sliders = document.querySelectorAll('self-slider');
const out = document.querySelector('output');
// Startwerte abfragen
sliders.forEach((v,i,a) => {
console.log(i,v.value);
if(i == 0) out.innerText = 'Startwerte:';
out.innerText += ` Element ${i}: ${v.value};`;
});
// Werte bei input-Event abfragen
sliders.forEach((v,i,a) => v.addEventListener('input', event => {
console.log(i,event.target.value);
out.innerText = ` Element ${i}: ${event.target.value};`;
}));
// Wert vom 4. Slider ändern
setInterval(() => {
let value = sliders[3].value; //.getAttribute('value');
value++;
if(value > 100) value = 0;
sliders[3].value = value; //setAttribute('value', value);
}, 1000);
// Submit abfangen und Formularwerte anzeigen
const form = document.querySelector('form');
form.addEventListener('submit', (event) => {
event.preventDefault();
const data = new FormData(event.target);
console.log([...data.entries()]);
out.innerText = [...data.entries()];
});
});
</script>
<style>
output { display: block; }
button { font-size: 1em; }
/* CSS für den self-slider */
[name=S1]::part(fieldset) { background-color: yellow; border: none }
self-slider::part(legend) { color: blue; }
self-slider::part(label) { color: red; }
self-slider::part(number) { color: green; }
[name=S2]::part(range) { width: 25em; }
</style>
</head>
<body>
<h1>Custom-Element mit synchronisierten Range- und Number-Inputs</h1>
<form method="get" action="test.html">
<self-slider name="S1" label="Schieber 1" legend="Eingabe 1" min="-10" max="123" value="42" step="0.5"></self-slider>
<self-slider name="S2" legend="Eingabe 2" min="0" max="1000" value="500" step="100"></self-slider>
<self-slider name="S3" label="Schieber 3" min="0" max="1" value="0.5" step="0.01"></self-slider>
<self-slider name="S4" legend="Eingabe 4 (automatisch)" label="Schieber 4"></self-slider>
<button>Submit</button>
<output></output>
</form>
</body>
</html>