Florian C.: Abfrage dynamisches Formularfeld

Moin liebe Community,

ich versuche derzeit in HTML ein Formular zu erstellen, das dynamisch neue Eingabefelder hinzufügen kann. Derzeit bin ich aber bei dem Problem, wie ich den Namen eindeutig beschreiben kann, damit ich den Input der Eingabefelder auf der nächsten Seite abrufen kann. Ich war am überlegen von der for-Schleife des Java-Skriptes die Variable zu nehmen. Kann mir irgendjemand Lösungsansätze liefern?

index.php:

<script type="text/javascript">
<!--
function clone_this(objButton)
{
if(objButton.parentNode)
    {
    tmpNode=objButton.parentNode.cloneNode(true);
    objButton.form.appendChild(tmpNode);
    for(j=0;j<objButton.form.lastChild.childNodes.length;++j)
        {
        if(objButton.form.lastChild.childNodes[j].type=='text')
            {
            objButton.form.lastChild.childNodes[j].value='';
            break;
            }
        }
    objButton.value="Obere Zutat entfernen";
    objButton.onclick=new Function('f1','this.form.removeChild(this.parentNode)');
    }
}
 
<form action="/inc/rezepte.php" method="POST">  
                        
   <h1>Zutatenliste f&uuml;r <?php echo $Rezeptname; ?></h1>
   <button type="submit" name="zutatenliste">Zutaten festlegen</button>                   
   <button type="button" class="cancelbtn">
   <a href="../">&Uuml;berspringen</button></a>
                            
   <br><label>Zutaten</label>
   <div>
      <input type="text" size="20" name="EINDEUTIGE_ID"><br>
      <input type="button" value="Weitere Zutat hinzuf&uuml;gen" onclick="clone_this(this)">
   </div>
</form>

rezepte.php:

if (isset($_POST['zutatenliste'])) // Wenn Zutaten zum Rezept hinzugefügt werden sollen
{
    print_r($_POST['EINDEUTIGE_ID']);
} 

Derzeit wird halt nur das zuletzt beschriebene Feld ausgegeben, da alle Felder die gleiche ID haben. Wie kann ich nun am besten eine Eindeutige ID für die kopierten Felder festlegen und Abfragen wie viele Felder erstellt und befüllt wurden?

Vielen Dank im Voraus!

  1. Hallo Florian,

    deine Serversprache ist PHP, da kannst Du auch ein Array posten.

       <input type="text" name="zutaten[]">
    

    Wenn Du 12 input-Elemente dieser Art im Form hast, findest Du nach dem Submit in PHP ein Array mit 12 Elementen in $_POST['zutaten'].

    Wie es in deinem Fall mit der Belabelung aussieht, da bin ich mir nicht ganz klar. Es sind N gleichartige input-Elemente. Normalerweise braucht jedes Eingabefeld ein Label - aber um das zuzuordnen, musst Du eindeutige IDs vergeben, was die Sache wieder lästig macht. Ob ein einziges "Zutaten" Label über der Eingabeliste genügt, da bin ich nicht sicher.

    Dein Cancel-Button ist übrigens kaputtes HTML, da überschneiden sich zwei Elemente. Von denen brauchst Du nur eins, denn interaktive Elemente darf man nicht schachteln. Also entweder Link oder Button, aber nicht eins im anderen.

    Und wenn Du deinen PHP Code im UTF-8 Encoding speicherst und das charset des Dokuments passend setzt (entweder per HTTP Header Content-Type oder per <meta charset> Angabe (solange meta-Angaben noch erlaubt sind ohne dass Zuckerbergs Anwälter losbrüllen…), dann kannst Du auch Umlaute in deinen Quellcode schreiben und musst Dich nicht mit HTML Symbolen abm&uuml;hen. Ggf brauchst Du in der php.ini auch noch den Eintrag default_charset = "UTF-8".

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Moin Rolf,

      probiere ich mich nachher mal dran. Vielen dank für deinen Wertvollen Input! Ich versuche mal alles umzusetzen, was du so vorgeschlagen hast.

      LG Florian

    2. Tach!

      Wie es in deinem Fall mit der Belabelung aussieht, da bin ich mir nicht ganz klar. Es sind N gleichartige input-Elemente. Normalerweise braucht jedes Eingabefeld ein Label - aber um das zuzuordnen, musst Du eindeutige IDs vergeben, was die Sache wieder lästig macht.

      Man kann das Eingabegeld als Kind vom Label-Element notieren, dann braucht es keine IDs zur Zuordnung.

      <label>
        Feldname
        <input type="text" name="whatever">
      </label>
      

      dedlfix.

      1. Hallo dedlfix,

        Man kann das Eingabegeld als Kind vom Label-Element notieren,

        Ich weiß, aber es geht die Rede, dass nicht jeder Screenreader damit sauber zurecht kommt.

        Rolf

        --
        sumpsi - posui - obstruxi
      2. @@dedlfix

        Man kann das Eingabegeld als Kind vom Label-Element notieren, dann braucht es keine IDs zur Zuordnung.

        Laut HTML-Spec nicht. Wegen fehlerhafter Software wie Dragon (text-to-speech) aber doch.

        😷 LLAP

        --
        „Dann ist ja auch schrecklich, dass wir in einem Land leben, in dem nicht nur Bildungswillige leben, sondern auch hinreichende Zahlen von Bekloppten. Das darf ich so locker formulieren, ich bin ja jetzt Rentner und muss nicht mehr auf jedes Wort achten.“
        — Joachim Gauck über Impfgegner
      3. Hello,

        Wie es in deinem Fall mit der Belabelung aussieht, da bin ich mir nicht ganz klar. Es sind N gleichartige input-Elemente. Normalerweise braucht jedes Eingabefeld ein Label - aber um das zuzuordnen, musst Du eindeutige IDs vergeben, was die Sache wieder lästig macht.

        Man kann das Eingabegeld als Kind vom Label-Element notieren, dann braucht es keine IDs zur Zuordnung.

        Wenn HTML jetzt Geld kostet, lassen wir es lieber ;-P

        <label>
          Feldname
          <input type="text" name="whatever">
        </label>
        

        dedlfix.

        Glück Auf
        Tom vom Berg

        --
        Es gibt nichts Gutes, außer man tut es!
        Das Leben selbst ist der Sinn.
  2. @@Florian C.

    ich versuche derzeit in HTML ein Formular zu erstellen

    „Derzeit“ heißt seit 10 Jahren? Oder du hattest vor 10 Jahren mal was mit HMTL und JavaScript gemacht und seitdem nicht mehr? Oder – was ich vermute – du verwendest die falschen Quellen im Web.

    <script type="text/javascript">
    

    type="text/javascript" ist default, kann also weggelassen werden: <script>


    <!--
    

    Das Auskommentieren von JavaScript (oder CSS) war vor Urzeiten mal … das ist aber schon länger als 10 Jahre her. Weg damit!

    Übrigens fehlten in deinem Code das schließende --> (was natürlich zusammen mit dem öffnenden wegkommt) und auch das schließende </script> (was da sein muss).


        tmpNode=objButton.parentNode.cloneNode(true);
    

    Globale Variablen sind i.a.R. keine gute Idee. Das sollte ein lokale sein:

        const tmpNode=objButton.parentNode.cloneNode(true);
    

    (const, weil sich der Wert von tmpNode im weiteren Programmablauf nicht mehr ändert.)


        for(j=0;j<objButton.form.lastChild.childNodes.length;++j)
    

    Dito:

        for(let j=0;j<objButton.form.lastChild.childNodes.length;++j)
    

    (let, weil sich der Wert von j mit jedem Schleifendurchlauf ändert.)

    Die Codezeile könnte aber etliche Leerzeichen vertragen:

        for (let j = 0; j < objButton.form.lastChild.childNodes.length; ++j)
    

    Wenn du nur über die Elemente iterierst, bietet sich eine for … of-Schleife an:

        for (let node of objButton.form.lastChild.childNodes)
    

    Innerhalb der Schleife dann node statt objButton.form.lastChild.childNodes[j].

    Wenn du auch den Index brauchst (z.B. um eine eindeutige ID zu generieren), kannst du die forEach-Methode verwenden:

    objButton.form.lastChild.childNodes.forEach(function (node, index) {
        // …
    });
    

    was man üblicherweise mit Pfeilfunktion schreibt:

    objButton.form.lastChild.childNodes.forEach((node, index) => {
        // …
    });
    

    Innerhalb der Schleife dann node statt objButton.form.lastChild.childNodes[j] und index statt j.


       <h1>Zutatenliste f&uuml;r <?php echo $Rezeptname; ?></h1>
    

    Das sieht nach einem schwerwiegendem Fehler aus. Wo kommt $Rezeptname her? Nutzergenerierter Inhalt?

    Du darfst niemals Nutzereingaben, Datenbankinhalte etc. unbearbeitet in HTML ausgeben. ⚠️ Sicherheitslücke! Immer mit htmlspecialchars() absichern:

       <h1>Zutatenliste für <?php echo htmlspecialchars($Rezeptname); ?></h1>
    

       <button type="button" class="cancelbtn">
       <a href="../">&Uuml;berspringen</button></a>
    

    Abgesehen davon, dass man interaktive Elemente nicht verschachteln darf, stimmt die Verschachtelung an sich nicht. Was in der Reihenfolge <🌞><🌜> geöffnet wird, muss in der Reihenfolge </🌜></🌞> geschlossen werden.

    Buttons dienen zur Auslösung von Aktionen auf der Seite; Links (<a href="">) zum Sprung zu einer anderen Stelle. Um letzteres handelt es sich hier, also:

       <a href="../">Überspringen</a>
    

    Oder wenn den die Styles von .cancelbtn wirken sollen:

       <a href="../" class="cancelbtn">Überspringen</a>
    

       <br><label>Zutaten</label>
       <div>
          <input type="text" size="20" name="EINDEUTIGE_ID"><br>
    

    <br> ist hier unangebracht; verwende CSS zum layouten (bspw. display: block).

    Das <label> ist hier nicht besser als ein <span>. Für die explizite Zuordnung zum Eingabefeld müsste es ein for-Attribut haben mit demselben Wert wie das id-Attribut des Eingabefelds (was dann zusätzlich zu name da sein muss).

    Oder halt implizit durch Verschachtelung (dedlfix sprach es an), was aber leider nicht immer wirkt.


          <input type="button" value="Weitere Zutat hinzuf&uuml;gen" onclick="clone_this(this)">
    

    Verwende <button> für Buttons – aus Gründen.

    😷 LLAP

    --
    „Dann ist ja auch schrecklich, dass wir in einem Land leben, in dem nicht nur Bildungswillige leben, sondern auch hinreichende Zahlen von Bekloppten. Das darf ich so locker formulieren, ich bin ja jetzt Rentner und muss nicht mehr auf jedes Wort achten.“
    — Joachim Gauck über Impfgegner
    1. Hallo Gunnar,

      durch die erforderliche ID-Verbindung von Label und Input haben wir aber wieder das Anfangsproblem: Wie vergebe ich automagisch die richtige ID. Aus der Länge der Zutatenliste ableiten kann man sie nicht, weil es ja auch möglich ist, Einträge zu löschen.

      Ich frag mich aber, was im Label drinstehen soll. "Zutat" ? Das ist eigentlich albern, denn dann habe ich nachher ein Dokument

      und das find ich für sehende User richtig doof.

      Ist es machbar, auf die Labels der input Elemente zu verzichten und statt dessen aria-label="Zutat" an's input-Element zu hängen? Wer auf Screenreader angewiesen ist, braucht diese Angabe sicherlich.

      Ob das Design mit dem "Hinzufügen" Button hinter jeder Zutat ideal ist, ist eine andere Frage. Eigentlich sollte man ja nur einen Button haben, denn es wird ja immer nur am Ende angefügt. Und man bräuchte eine Umsortierfunktion (up/down Buttons pro Element oder drag+drop).

      Ohne Sortierfunktion sähe das bei mir so aus:

      <h2>Zutaten</h2>
      <button>Zutaten festlegen</button>
      <ul class="zutaten">
      <li>
        <input value="Hugo" type="text" aria-label="Zutat" name="zutaten[]" width="20">
        <button type="button">Löschen</button>
      </li>
      </ul>
      <button type="button" id="newIngredient">Weitere Zutat</button>
      <script>
      // Vorlage für neue Rows aus dem ersten Listitem erstellen
      const rowTemplate = document.querySelector(".zutaten li").innerHTML;
      // Liste der Zutaten 
      const ingredients = document.querySelector(".zutaten");
      // Click-Handler für neue Zutaten auf den Button legen
      document.getElementById("newIngredient").addEventListener("click", function(event) {
         const newItem = document.createElement("li");
         newItem.innerHTML = rowTemplate;
         const newInput = newItem.querySelector("input");
         newInput.value = "";
         ingredients.appendChild(newItem);
         newItem.querySelector("input").focus();
      });
      // Click-Handler zum Löschen auf die Liste legen, click bubbelt dorthin
      ingredients.addEventListener("click", function(event) {
         // event.target ist das geklickte Element, nur auf Buttons reagieren
         if (event.target.tagName != "BUTTON") return;
         event.target.parentElement.remove();
      });
      </script>
      

      Dazu noch eine Prise CSS für die Liste

      ul {
        list-style: none;
        padding: 0;
      }
      
      li {
        margin-bottom: 0.3em;
      }
      
      

      Rolf

      --
      sumpsi - posui - obstruxi
      1. @@Rolf B

        Ist es machbar, auf die Labels der input Elemente zu verzichten und statt dessen aria-label="Zutat" an's input-Element zu hängen?

        Kann man. Oder besser aria-labelledby:

        <span id="label">Zutat</span>
        <input name="zutaten[]" aria-labelledby="label"/>
        <input name="zutaten[]" aria-labelledby="label"/>
        

        Die Frage ist, ob es sinnvoll ist, allen Eingabefeldern dieselbe Beschriftung zu verpassen. Da müsste man mal Nutzer befragen.

        Ob das Design mit dem "Hinzufügen" Button hinter jeder Zutat ideal ist, ist eine andere Frage. Eigentlich sollte man ja nur einen Button haben, denn es wird ja immer nur am Ende angefügt.

        Ja, den Button natürlich nur einmal.

        Womöglich braucht man auch nur ein Eingabefeld, wenn man die Zutatenliste so implementiert wie die Todo-Liste von Inclusive Components: Codepen + Artikel.

        😷 LLAP

        --
        „Dann ist ja auch schrecklich, dass wir in einem Land leben, in dem nicht nur Bildungswillige leben, sondern auch hinreichende Zahlen von Bekloppten. Das darf ich so locker formulieren, ich bin ja jetzt Rentner und muss nicht mehr auf jedes Wort achten.“
        — Joachim Gauck über Impfgegner
        1. Hallo,

          <span id="label">Zutat</span>
          <input name="zutaten[]" aria-labelledby="label"/>
          <input name="zutaten[]" aria-labelledby="label"/>
          

          Die Frage ist, ob es sinnvoll ist, allen Eingabefeldern dieselbe Beschriftung zu verpassen. Da müsste man mal Nutzer befragen.

          in diesem Fall, wo es ja gleichartige Eingabefelder sind, erscheint es mir durchaus sinnvoll, die gesamte Liste mit Zutaten zu labeln.

          Hmm, hab ich gerade Liste gesagt ...?

          Womöglich braucht man auch nur ein Eingabefeld, wenn man die Zutatenliste so implementiert wie die Todo-Liste von Inclusive Components: Codepen + Artikel.

          Auch ein guter Ansatz. Kenn ich so aus dem Aufgabenplaner von MS Teams, das wir in der Firma intensiv nutzen.

          Keine Angst, ich will doch nur spielen
           Martin

          --
          The taste of love: The more you get, the more you want
          (aus The Lightning Seeds: Sense)
          1. Hallo Der,

            Hmm, hab ich gerade Liste gesagt ...?

            Hast Du. Ich auch - siehe mein Codebeispiel von gestern.

            Rolf

            --
            sumpsi - posui - obstruxi
            1. Hallo,

              Hmm, hab ich gerade Liste gesagt ...?

              Hast Du. Ich auch - siehe mein Codebeispiel von gestern.

              oh, tatsächlich. Ist mir gar nicht aufgefallen.
              Wundert mich ja, dass Gunnar das nicht auch gleich aufgegriffen hat. 🤔

              Keine Angst, ich will doch nur spielen
               Martin

              --
              The taste of love: The more you get, the more you want
              (aus The Lightning Seeds: Sense)
          2. @@Der Martin

            Die Frage ist, ob es sinnvoll ist, allen Eingabefeldern dieselbe Beschriftung zu verpassen. Da müsste man mal Nutzer befragen.

            in diesem Fall, wo es ja gleichartige Eingabefelder sind, erscheint es mir durchaus sinnvoll, die gesamte Liste mit Zutaten zu labeln.

            Evtl. fieldset/legend verwenden.

            Hmm, hab ich gerade Liste gesagt ...?

            Das bringt mich auf die Idee, das Label per CSS counter() um die laufende Nummer zu ergänzen.

            Codepen

            (Die beispielhafte Vorbelegung der values ist beispielhaft.)

            😷 LLAP

            --
            „Dann ist ja auch schrecklich, dass wir in einem Land leben, in dem nicht nur Bildungswillige leben, sondern auch hinreichende Zahlen von Bekloppten. Das darf ich so locker formulieren, ich bin ja jetzt Rentner und muss nicht mehr auf jedes Wort achten.“
            — Joachim Gauck über Impfgegner
            1. @@Gunnar Bittersmann

              Codepen

              (Die beispielhafte Vorbelegung der values ist beispielhaft.)

              „Tagespegel“ 🍻

              😷 LLAP

              --
              „Dann ist ja auch schrecklich, dass wir in einem Land leben, in dem nicht nur Bildungswillige leben, sondern auch hinreichende Zahlen von Bekloppten. Das darf ich so locker formulieren, ich bin ja jetzt Rentner und muss nicht mehr auf jedes Wort achten.“
              — Joachim Gauck über Impfgegner
    2. Wow klasse! Vielen dank für die ganzen Anregungen und Hilfestellungen!

    3. Vielen lieben dank Gunnar!

      Das ist echt viel Input aber viele wichtige Sachen, die ich versuchen werde größtenteils umzusetzen. Ja habe eventuell ältere Quellen gefunden, aber es mittlerweile mithilfe eines Arrays umsetzen können, das es läuft. Dennoch hast du ja noch viele andere wichtige Anmerkungen zu dem Code gehabt. Bin echt dankbar das du dir so viel Zeit genommen hast und freue mich drauf alles umzusetzen!

      LG Florian

  3. Lieber Florian,

    ergänzend zu allen meinen Vorrednern möchte ich Dir vorschlagen, dass Du Deine Feldnamen nummerierst. So könnte ingredient-1 der ID-Wert der ersten Zutat sein. Serverseitig wirst Du eben alle $_POST-Schlüssel mit dem „Vornamen“ ingredient- auswerten und dem Rezept zuordnen müssen. Du kannst die jeweilige Nummer entweder ignorieren, oder die Reihenfolge sogar beherzigen.

    Clientseitig genügt es, wenn Du genau einen Button verwendest, der für das Hinzufügen einer neuen Zutat verwendet wird:

    <ul>
      <li>
        <label for="ingredient-1">
          Zutat
          <input name="ingredient-1" id="ingredient-1" type="text">
        </label>
      </li>
      <li>
        <button id="add-ingredient">Zutat hinzufügen</button>
      </li>
    </ul>
    

    Die Funktion zum Hinzufügen einer Zutat klont das erste <li>, ändert die Attribute for, name und id gemäß einer Zählervariablen in Deinem Script und hängt sie vor dem letzten <li> in der Liste ein. Das geht mit ulNode.insertBefore(newNode, existingNode).

    Für den User bedeutet das, dass er bei Betätigen des Buttons genau vor diesem eine neue Zeile mit einer neuen möglichen Zutat angezeigt bekommt. Dazu braucht er natürlich den Hinweis, dass leere Zutaten ignoriert werden, da Du nur so das Löschen einer Zutat hinbekommst.

    Willst Du für die Zutatenliste eine Reihenfolge steuerbar machen, dann braucht es anstelle von ul natürlich ol, sowie passende Buttons bei jedem <li>, welche das Verschieben nach unten und oben ermöglichen. Hier bietet es sich an, dass man den jeweiligen Button via CSS auf display:none setzt, wenn es sich um die letzte bzw. erste Zutat handelt. Diese Buttons sollten dann lediglich dafür sorgen, dass die Textinhalte der Textinputs ausgetauscht werden, denn sonst müsstest Du tatsächlich die Listenpunkte im DOM umsortieren und dazu passend ihre Attributwerte der Nummerierung gemäß anpassen. Kann man machen, geht auch ganz gut, ist aber deutlich aufwendiger.

    Liebe Grüße

    Felix Riesterer

    1. Vielen dank für die ganzen lieben Kommentare und Hilfestellungen! Ich habe mittlerweile mein Problem Eigenständig lösen können, indem ich die eingaben in einem Array speichere und dann auf der nächsten Seite das Array abrufe. Dadurch hat sich für mich auch das Problem der Eindeutigkeit der Textfelder erledigt. Ich werde dennoch schauen das ich eure Verbesserungsvorschläge weitestgehend umsetze.

      Vielen Dank nochmal an alle die sich die Zeit genommen haben zu helfen!

      LG Florian