Beispiel:Custom-element-range-number.html

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche
<!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>