HTML-Benutzereingaben

Benutzereingaben im Web funktionieren über bekannte Bedienelemente
Vornehmlich Textfelder, allerdings mit zunehmender Spezialisierung

Das <form>-Element

Gruppiert Eingabeelemente die logisch zusammengehören
Alle aktiven Kinder-Eingabeelemente eines Formulars werden übertragen
Gibt Übertragungsmethode vor: method-Attribut
HTTP-Verben GET und POST mit unterschiedlicher Semantik
Gibt Übertragungsziel vor: action-Attribut
Angabe einer URL

Eingabeelemente

Typischstes Eingabelemenet ist <input type="text"> für Tastatureingaben
Kann anhand des type-Attributes für bestimmte Eingaben spezialisiert werden
Benötigtes Attribut: name
  • Unter diesem Namen wird der vom Benutzer eingetragene Wert an den Server verschickt
  • Wenn dieses Attribut fehlt, wird der Wert nicht übermittelt
Hilfreiches Attribut: value
  • Ändert sich durch Benutzereingaben
  • Kann zur Vergabe von Standardwerten genutzt werden

Texteingaben mit <input type="text">

Einzeiliges Eingabeelement
Mögliche Zeilenumbrüche werden vor dem Abschicken entfernt

Abschicken von Formularen mit <input type="submit">

Knopf zum Abschicken des Formulars
Dargestellter Text kann mit dem value-Attribut beeinflusst werden
Nur der Wert des gedrückten Knopfes wird übertragen
Auch die anderen Knöpfe über einen Namen verfügen sollten, gilt nur einer dieser Knöpfe als aktiv

Formular mit method="GET"

Formular überträgt die Eingabeelemente innerhalb des Formulars als Teil der URL
Ziel wird durch action definiert und soll in diesem Fall die Werte einfach nur ausgeben
Problem: Das Eingabefeld für Kreditkartennummern hat keinen Namen
Wird daher nicht übertragen, wird aber dennoch angezeigt
Ziel /form: Nimmt beliebige Parameter entgegen und zeigt Sie in JSON-Darstellung an
  • Werte im query-Objekt stehen für GET-Parameter
  • Werte im body-Objekt stehen für POST-Parameter
  • framework_projects/vorlesung/pages/form-greet.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <p>
        Ihr Name:
        <input type="text" name="username">
      </p>
      <p>
        Ihre Kreditkartennummer:
        <input type="text">
      </p>
      <input type="submit" name="accept" value="Name setzen!">
      <input type="submit" name="decline" value="Bestimmt nicht!!">
    </form>
    

HTML-Eingabeelemente

Wesentliche verfügbare Eingabeelemente
Im Rahmen dieser Vorlesung kein Anspruch auf Vollständigkeit, relevante Elemente sind aber:
Einige “typische” Bedienelemente fehlen allerdings und müssen mit HTML, SVG und JavaScript nachgebaut werden
  • Baumartige Strukturen können nicht mit einem TreeControl (oder ähnlich) angezeigt werden
  • Es gibt keine Visualierungen für Diagramme
  • DropDown-Menüs können nur sehr eingeschränkt optisch angepasst werden
Große Auswahl an Bibliotheken mit Bedienelementen
Sowohl quelloffen als auch kommerziell

Vergabe von Beschriftungen mit dem <label>-Element

Zusammenhang zwischen einem beschreibenden Text und dem beschrieben Bedienelement
Semantische Beziehung herstellen
  1. Möglichkeit: Herstellung des Zusammenhangs über Eltern-Kind Beziehung
    Ein <label>-Element fungiert als Beschriftung für ein Kind-Eingabeelement
  2. Möglichkeit: Herstellung des Zusammenhangs über das id-Attribut
    Das referenzierte Element muss über eine id verfügen, auf die das <label>-Element mit dem Attribut for verweisen kann.
Auszeichnung mit <label>-Elementen erleichert Bedienung
  • Notwendige Angabe für Screenreader
  • Klick auf das <label> hebt Eingabeelement hervor
  • framework_projects/vorlesung/pages/form-label.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <label>
        Benutzername:
        <input type="text" name="username">
      </label>
      <label for="the-password">
        <h1>Passwort:</h1>
      </label>
      <input type="password"
             name="password"
             id="the-password">
      <input type="submit">
    </form>
    

Vergabe von Platzhaltertexten

Zweck: Beispielhafte Eingabeformate verdeutlichen
Ersetzen keinesfalls ein beschreibendes <label>
Der Text des placeholder-Attributs wird bei leerem value-Attribut angezeigt
Platzhalter wird keinesfalls übertragen
  • framework_projects/vorlesung/pages/form-input-placeholder.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <label>
        Bitte CD-Key eingeben:
        <input type="text"
               name="serialno"
               placeholder="XXX-YYY-ZZZ">
      </label>
      <input type="submit">
    </form>
    

Frühzeitige Validierung von Formularen mit passenden Eingabeelementen

Motivation: Benutzer so früh wie möglich auf Fehler hinweisen
Dank HTML5 auch ohne JavaScript möglich
Allerdings: Trotzdem serverseitige Validierung nötig
Validierung im Browser ist ein Komfortgewinn, keine Sicherheitsmaßnahme

Eingaben als verplfichtend markieren mit required

Attribut required funktioniert auf allen Eingabeelementen
Verpflichtet den Benutzer zur Eingabe, Formular lässt sich teilweise ausgefüllt nicht abschicken
Standardmäßige Hervorhebung von nicht (oder falsch) ausgefüllten Feldern
In vielen Browsern: Kurzer Texthinweis zur Art des Fehlers
  • framework_projects/vorlesung/pages/form-required.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <p>
        Ihr Name:
        <input type="text" name="username" required>
      </p>
      <p>
        Ihre Kreditkartennummer:
        <input type="text" name="cc" required>
      </p>
      <input type="submit" name="accept" value="Name setzen!">
      <input type="submit" name="decline" value="Bestimmt nicht!!">
    </form>
    

Vorgaben für <input>-Elemente

Begrenzung der Eingabelänge mit dem Attribut maxlength
Angabe als Ganzzahl, blockiert weitere Eingaben wenn diese Länge erreicht wurde
Eingabe auf bestimmte Muster beschränken mit dem pattern-Attribut
Angabe eines regulären Ausdrucks
Vorsicht: required häufig immer noch nötig um leere Eingaben abzufangen
pattern-Attribut wird z.B. für leere Eingaben ignoriert
  • framework_projects/vorlesung/pages/form-input-validate.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <label>
        Twitter Benutzername:
        <input type="text"
               name="username"
               pattern="^@[A-Za-z0-9_]{1,15}$">
      </label>
      <label>
        Passwort:
        <input type="password"
               name="password"
               required>
      </label>
      <input type="submit">
    </form>
    

Numerische Eingaben

Eingabe über ein <input>-Element vom Typ number
  • Wird in einigen Desktop-Browsern ähnlich wie ein Spinner-Element dargestellt
  • Wird bei einigen mobilen Browsern mittels spezieller Eingebamethoden umgesetzt (Zifferntastatur, Ziffernräder, …)
Angabe von Grenzen mit den Attributen min und max
Beide Grenzen sind inklusiv
Angabe von vorgeschriebenen Schrittweiten mit step
Beschränkt die Eingabe auf Werte, die sich ausgehend vom Minimum mit der Schrittweite erreichen lassen
  • framework_projects/vorlesung/pages/form-input-number.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <label>
        Anzahl Arbeitsstunden:
        <input type="number"
               name="workhours"
               min="20"
               max="38"
               step="2">
      </label>
      <input type="submit">
    </form>
    

Datums- und Zeiteingaben

Datumsangaben (ohne Zeit) mit type="date"
Alternativ existiert noch datetime-local für Angaben ohne Zeitzone
Zeitangaben (ohne Datum) mit type="time"
Stets ohne Angabe einer Zeitzone
Keinerlei festgelegte Darstellung
Lückenhafte Unterstützung auf dem Desktop, gute Unterstützung bei mobilen Geräten
  • framework_projects/vorlesung/pages/form-input-date-time.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <label>
        Datum:
        <input type="date"
               name="start-date">
      </label>
      <label>
        Zeit:
        <input type="time"
               name="start-time">
      </label>
      <input type="submit">
    </form>
    

Emails

Angabe von Emails mit type="email"
  • Validiert grundsätzliche Struktur der Adresse, nicht aber tatsächliche Existenz des Postfachs
  • Erzwingt keine top level domain, a@b ist technisch gesehen eine valide Addresse
  • Auf Mobilgeräten: Anpassungen an der virtuellen Tastatur
  • framework_projects/vorlesung/pages/form-input-email.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <label>
        Email:
        <input type="email"
               name="email">
      </label>
      <input type="submit">
    </form>
    

Farben

Angabe von Farbwerten mit type="color"
Bei unterstützten Browsern: Nutzung eines nicht näher spezifizierten Dialogs
  • framework_projects/vorlesung/pages/form-input-color.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <label>
        Lieblingsfarbe:
        <input type="color"
               name="fav">
      </label>
      <input type="submit">
    </form>
    

Mehrzeilige Eingaben mit <textarea>

Anders als <input>: Start- und End-Tag notwendig
Kein value-Attribut, enthaltener Text ist der angezeigte und der übermittelte Text
Vorsicht: Text wird exakt so angezeigt wie er im Quelltext notiert ist
Inklusive aller einrückungs-bedingten Leerzeichen
Breite und Höhe bestimmen mit den Attributen cols und rows
  • Müssen ganzzahlige Werte > 0 sein
  • Alternativ normal über die CSS-Eigenschaften width und height
  • framework_projects/vorlesung/pages/form-textarea.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <textarea name="longtext" cols="80" rows="5">
        1
    2
        3
      </textarea>
      <input type="submit">
    </form>
    

Checkboxen mit dem <input type="checkbox">-Element

Übertragung des value-Attributes erfolgt nur, wenn der Benutzer die Checkbox angewählt hat
Vorauswahl mit dem leeren checked-Attribut möglich
Das input-Element steht nur für das Checkbox-Bedienelement, nicht für Text
Verwendung eines Labels sinnvoll
  • framework_projects/vorlesung/pages/form-checkbox.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <label>
        <input type="checkbox"
               name="subscribe"
               value="soul"
               checked>
        Abschluss einer Fegefeuer-Versicherung
      </label>
      <input type="submit">
    </form>
    

1-aus-N-Auswahl mit dem select-Element

<select>-Element mit <option>-Elementen als Kinder
  • Text im <option>-Element wird angezeigt, keine untergeordneten HTML-Elemente zulässig
  • Attribut value des <option>-Elements legt den tatsächlich übertragenen Wert fest
  • framework_projects/vorlesung/pages/form-select.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <p>
        Lieblingsband:
        <select name="band">
          <option value="0">ASP</option>
          <option value="1">Coppelius</option>
          <option value="2">Mantus</option>
          <option value="3">Lacrimosa</option>
          <option value="4">Samsas Traum</option>
        </select>
      </p>
      <input type="submit">
    </form>
    

1-aus-N-Auswahl mit <input type="radio">-Elementen

Identische name-Attribute bestimmen aus welchen Gruppen nur ein Element ausgewählt werden darf
Unterscheidung des gewählten Elements über das value-Attribut
  • framework_projects/vorlesung/pages/form-radio.hbs
    { "template": "html" }
    ---
    <form method="GET" action="/form">
      <label>
        <input type="radio"
               name="band"
               value="0">
        ASP
      </label>
      <label>
        <input type="radio"
               name="band"
               value="1">
        Coppelius
      </label>
      <input type="submit">
    </form>
    

Vorschläge für Texteingaben mit <datalist>

Erweitert 1-aus-N-Auswahl um beliebige Eingaben
Funktioniert ohne JavaScript
<datalist> mit <option>-Kindern wie bei <select>
Allerdings nur Wertangaben möglich, keine Texte für Benutzer
  • framework_projects/vorlesung/pages/form-datalist.hbs
    { "template": "html" }
    ---
    <label>Lieblingsband:
      <input list="bands" name="band" />
    </label>
    <datalist id="bands">
      <option value="ASP">
      <option value="Coppelius">
      <option value="Eden weint im Grab">
      <option value="Mono Inc.">
      <option value="Lacrimosa">
      <option value="Samsas Traum">
    </datalist>
    

Übergabe von großen Datenmengen mit POST-Anfragen

Problem: Upload von Dateien über die URL ist nicht wirklich praktikabel
De facto limitiert auf 2000 Zeichen
Daher: Übergabe von Daten im Rumpf einer POST Anfrage
Größenlimitierung erfolgt dann durch den Webserver
Darüber hinaus: POST-Anfragen stehen für Veränderungen
Im Rahmen des CRUD-Zyklus für CREATE und häufig auch für UPDATE

Format der übertragenen Daten

Erneutes Problem: In welchem Format sollen die Name-Wert-Paare des Formulars kodiert werden?
Und wie soll dabei mit binären Daten wie Dateien umgegangen werden?
Angabe eines Encoding mit dem enctype-Attribut des Formulars
  • Bestimmt auf welche Art und Weise der Browser die Daten überträgt
  • “Wie genau sieht der String aus der verschickt wird?”
Die Standardlösung: application/x-www-form-urlencoded
Encoding wie in URLs, nur möglicherweise länger
Für Binärdaten: multipart/form-data
  • Encoding in einem textbasierten Format, dass sich dennoch für Binärdaten eignet
  • Praktisch Pflicht für Upload von Dateien

Formular mit method="POST"

Zwei Veränderung im HTML-<form>-Element
  1. method-Attribut des form-Elements ist nun POST
  2. Zu Demonstrationszwecken: action-Attribut um URL-Parameter erweitert
Alle <input>-Elemente können unverändert bleiben
Passende Serialisierung übernimmt der Browser automatisch
Für weitere GET-Parameter: Manuelle Kodierung im action-Attribut
Gemischte Übertragung (z.B. individuell je nach <input>-Element) nicht möglich
  • framework_projects/vorlesung/pages/form-post-intro.hbs
    { "template": "html" }
    ---
    <form method="POST" action="/form">
      <p>
        Ihr Name:
        <input type="text" name="username">
      </p>
      <p>
        Ihre Kreditkartennummer:
        <input type="text">
      </p>
      <input type="submit" name="accept" value="Name setzen!">
      <input type="submit" name="decline" value="Bestimmt nicht!!">
    </form>
    

Anlegen von neuen Datensätzen (I)

Beispiel: Beim Absenden des Formulars neuen Datensatz anlegen und alle Datensätze anzeigen
Exakt eine Seite und exakt eine Route, reagieren auf GET- und auf POST-Anfragen
Problem: Neu-Laden der Seite erzeugt einen neuen Datensatz
  • Grundsätzlich beim tatsächlichen “Neu Laden” (F5)
  • Möglicherweise beim Navigieren mit den Vor- und Zurück-Funktionen
  • framework_projects/vorlesung/controller/post-create-v1.js
    const fhwWeb = require('fhw-web');
    
    module.exports = {
      "index": function(data) {   
        const ratings = fhwWeb.loadJson("ratings");
    
        const rating = data.request.post.rating;
        const name = data.request.post.name;
    
        if (name && name.length > 2 && !isNaN(rating)) {
          ratings.push({
            "name": name,
            "rating": +rating
          });
    
          fhwWeb.saveJson("ratings", ratings);
        }
        
        return ({
          "page": "form-post-create-problem",
          "data": {
            "ratings": ratings
          }
        });
      }
    }
  • framework_projects/vorlesung/pages/form-post-create-problem.hbs
    { "template": "html" }
    ---
    <h1>Bisherige Bewertungen</h1>
    <ul>
      {{#each page.ratings}}
        <li>{{ name }} gibt {{ rating }} Sterne</li>
      {{/each}}
    </ul>
    <form method="POST" action="/api/handlebars/post-create/v1">
      <p>
        Ihr Name:
        <input type="text" name="name" minlength="3">
      </p>
      <p>
        Ihre Bewertung:
        <input type="number" name="rating" min="1" max="5">
      </p>
      <input type="submit" value="Bewerten">
    </form>
    

Anlegen von neuen Datensätzen (II)

Verbesserung: Nutzung einer Umleitung
  • Eine GET-Route mit dem Formular und der Übersicht aller Bewertungen
  • Eine POST-Route zum Anlegen des Datensatzes, danach Umleitung auf die Ursprungsseite
  • framework_projects/vorlesung/controller/post-create-v2.js
    const fhwWeb = require('fhw-web');
    
    module.exports = {
      "index": function(data) {   
        const ratings = fhwWeb.loadJson("ratings");    
        return ({
          "page": "form-post-create-redirect",
          "data": {
            "ratings": ratings
          }
        });
      },
      "create": function(data) {   
        const ratings = fhwWeb.loadJson("ratings");
    
        const rating = data.request.post.rating;
        const name = data.request.post.name;
    
        if (name && name.length > 2 && !isNaN(rating)) {
          ratings.push({
            "name": name,
            "rating": +rating
          });
    
          fhwWeb.saveJson("ratings", ratings);
        }
        
        return ({
          "redirect": "/api/handlebars/post-create/v2"
        });
      }
    }
  • framework_projects/vorlesung/pages/form-post-create-redirect.hbs
    { "template": "html" }
    ---
    <h1>Bisherige Bewertungen</h1>
    <ul>
      {{#each page.ratings}}
        <li>{{ name }} gibt {{ rating }} Sterne</li>
      {{/each}}
    </ul>
    <form method="POST" action="/api/handlebars/post-create/v2">
      <p>
        Ihr Name:
        <input type="text" name="name" minlength="3">
      </p>
      <p>
        Ihre Bewertung:
        <input type="number" name="rating" min="1" max="5">
      </p>
      <input type="submit" value="Bewerten">
    </form>
    

Upload von Dateien mit <input type="file">

Erfordert spezielles Encoding für Binärdaten: multipart/form-data
  • Breite Unterstützung von allen Browsern 🙂
  • Serverseitige Unterstützung kompliziert 🙄
Wesentliche Frage für alle Webserver: Wohin mit den Dateien vor der eigentlichen Verarbeitung?
  • Ablage im Hauptspeicher bei großen Uploads nicht praktikabel
  • Ablage im Dateisystem braucht einen dezidierten, sicheren Ort
Framework unterstützt keine Datei-Uploads!

Exkurs: CSS-Selektoren und Eingabeelemente

Spezielle Selektoren für einige Eingabeelemente
  • Pseudo-Klasse :invalid für invalide Eingaben
  • Pseudo-Klasse :checked für selektierte Checkboxen

Spoiler (oder ähnliches) mit der :checked-Pseudoklasse

  • forms/form-css-spoiler.html | Validieren
    <div class="spoiler">
      <input type="checkbox" id="spoiler-01">
      <label for="spoiler-01">
        Spoiler Anzeigen
      </label>
    
      <div class="text">
        Dark Helmet is Lone Star's father's brother's
        nephew's cousin's former roommate! 😲
      </div>
    </div>
    
  • forms/form-css-spoiler.css
    /* Wird nach der Vorlesung veröffentlicht */