effel: createDocumentFragment Beispiel

Hallo !

Kann es sein, dass im Beispiel fuer createDocumentFragment:

    let element  = document.getElementById ('addPerson');
    element.addEventListener ('click', addPersonenRow);
  }
   " )" fehlt ?

Effel
  1. Moin effel,

    Kann es sein, dass im Beispiel fuer createDocumentFragment:

    document.addEventListener('DOMContentLoaded', function () {
        let element  = document.getElementById ('addPerson');
        element.addEventListener ('click', addPersonenRow);
    }
    

    " )" fehlt ?

    Effel

    https://wiki.selfhtml.org/wiki/JavaScript/DOM/Document/createDocumentFragment#Anwendungsbeispiel ist korrigiert. Danke!

    Gruß,

    --
    a.k.a. André
    1. Hallo Ryuno-Ki,

      Danke schön!

      Rolf

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

        ein paar Gedanken zu dem Beispiel:

        <main id="main">
          <table id="personen">
            <thead>
               <tr>
                 <th>Name</th><th>Vorname</th><th>Geburtsdatum</th>
               </tr>
            </thead>
            <tbody/>
          </table>
          <button id="addPerson">Neue Person</button>
        </main>
        
        document.addEventListener('DOMContentLoaded', function () {
          let element  = document.getElementById ('addPerson');
          element.addEventListener ('click', addPersonenRow);
        });
        
        function addPersonenRow() {
          let frag = document.createDocumentFragment();
          let row = frag.appendChild(document.createElement('tr'));
          let col1 = row.appendChild(document.createElement('td'));
          let col2 = row.appendChild(document.createElement('td'));
          let col3 = row.appendChild(document.createElement('td'));
          col1.innerHTML = "<input type='text' name='nachname'>";
          col2.innerHTML = "<input type='text' name='vorname'>";
          col3.innerHTML = "<input type='text' name='gebDat'>";
        
          document.querySelector('#personen').appendChild(frag);
          // frag.childElementCount ist jetzt 0.
        }
        

        Statt mit <table id="personen"> den Namen der Tabelle in einem id Attribut zu verstecken, könnte man auch <table aria-label="Personen"> schreiben. Dann ist auch ohne Überschrift ein zugänglicher Name vorhanden. Die Referenz in JavaScript holen wir dann entsprechend über den Attributselektor:

        document.querySelector('[aria-label=Personen]')
        

        Davon abgesehen ist die Frage, wie die <input> Elemente an ihre Beschriftung kommen.

        Das name Attribut wird gemäß Accessible Name Calculation nicht berücksichtigt. Ich sehe auch nicht, warum das Attribut in diesem Beispiel angegeben werden müsste. Genauso überflüssig ist die Angabe von type="text". Das ist ja der Standardwert.

        Die Beschriftung der Eingabefelder sollte sich aus dem textuellen Inhalt des jeweiligen Spaltenkopfs ergeben. Aber sind Browser und Screenreader schlau genug diesen Zusammenhang auch ohne weitere Angaben zuverlässig herzustellen?

        Eventuell wäre es sinnvoll, die Verbindung über die scope und/oder headers Attribute auf den <th> bzw. <td> Elementen explizit zu machen:

        <tr>
          <th scope="col" id="lastname">Name</th>
          <th scope="col" id="firstname">Vorname</th>
          <th scope="col" id="birthdate">Geburtsdatum</th>
        </tr>
        
        col1.setAttribute('headers', 'lastname')
        col2.setAttribute('headers', 'firstname')
        col3.setAttribute('headers', 'birthdate')
        

        Möglicherweise reicht das schon und eine weitere Angabe direkt auf den <input> Elementen wäre sogar schlecht, da das Label dann doppelt angesagt würde, einmal für die Tabellenzelle und einmal für das Inputelement. Ob das so ist, weiß ich nicht.

        Falls eine direkte Angabe sinnvoll ist, dann mittels aria-labelledby, damit die Labels konsistent sind:

        col1.innerHTML = '<input aria-labelledby="lastname">'
        col2.innerHTML = '<input aria-labelledby="firstname">'
        col3.innerHTML = '<input aria-labelledby="birthdate">'
        

        Vielleicht kann jemand der mehr Ahnung davon hat als ich etwas dazu sagen, was hier die beste Vorgehensweise ist?


        Was den JavaScript-Code angeht sollten die let durch const ersetzt werden. Es wird ja in keinem Fall ein neuer Wert zugewiesen.

        Den Code zum Erstellen der <td> Elemente könnte man auch kürzer schreiben. Die Schnittstelle HTMLTableRowElement stellt die Methode insertCell bereit. Wird kein Index übergeben, fügt sie die Zelle automatisch am Ende der Zeile an. Rückgabewert ist auch hier die Referenz auf das erzeugte <td> Element. Man könnte also schreiben:

        const col1 = row.insertCell()
        const col2 = row.insertCell()
        const col3 = row.insertCell()
        

        Wobei das auch in einer Zuweisung an innerHTML zusammengefasst werden könnte:

        // addPersonenRow ?
        function addPersonRow() {
            const fragment = new DocumentFragment
            const row = fragment.appendChild(document.createElement('tr'))
        
            row.innerHTML = `
                <td headers="lastname">
                    <input aria-labelledby="lastname">
                </td>
                <td headers="firstname">
                    <input aria-labelledby="firstname">
                </td>
                <td headers="birthdate">
                    <input aria-labelledby="birthdate">
                </td>
            `
        
            const table = document.querySelector('[aria-label=Personen]')
            table.appendChild(fragment)
        }
        

        Möglicherweise könnte man auch die entsprechenden Werte aus der zugeordneten Tabelle auslesen und dann mittels Templatesyntax in das Markup einfügen, um sich keine unnötige Abhängigkeit zu schaffen. Vielleicht soll ja mal eine Spalte "Lieblingsfarbe" hinzugefügt werden. Aber das wäre für das worum's hier geht wahrscheinlich unnötige Komplexität.

        Wollte an dem Beispiel im Wiki jetzt nicht rumpfuschen, da ich wie gesagt selbst nicht sicher bin, wie hier das beste Vorgehen wäre, aber vielleicht kann man ja den ein oder anderen Vorschlag einbauen um das Beispiel zu verbessern.

        Edit: Wobei das Beispiel halt insgesamt nicht so richtig sinnvoll ist, da wir ohnehin einen gemeinsamen Elternknoten für den hinzuzufügenden Abschnitt haben. Da lohnt sich der Umweg über DocumentFragment also nicht wirklich.

        Vielleicht eher sowas wie:

        const list = document.getElementById('mylist')
        const items = [
            'Item 1',
            'Item 2',
            'Item 3'
        ]
        
        const fragment = new DocumentFragment
        
        items.forEach(function(item) {
            const li = document.createElement('li')
            li.textContent = item
            fragment.appendChild(li)
        })
        
        list.appendChild(fragment)
        

        Viele Grüße,

        Matthias

        1. Hallo Matthias,

          ich weiß nicht, ob das Beispiel überhaupt was taugt. Ein DocumentFragment von Hand zu schnitzen käme mir vermutlich nicht in den Sinn, ich würde, ähnlich wie Du, ein Template-Literal verwenden, aber eins, das das <tr>-Element schon enthält. Und diesen Wert mit insertAdjacentHTML("beforeend", html) an den Table-Body anhängen.

          DocumentFragment-Objekte finde ich eher im Zusammenhang mit HTML <template> sinnvoll, da erstellt man sie nicht selbst, sondern bekommt sie vom Browser.

          Ob eine Tabelle aus Eingabefeldern bedienbares und zugängliches HTML ist, steht dann wieder auf einem ganz anderen Blatt.

          Rolf

          --
          sumpsi - posui - obstruxi
        2. Hi,

          <main id="main">
            <table id="personen">
              <thead>
                 <tr>
                   <th>Name</th><th>Vorname</th><th>Geburtsdatum</th>
                 </tr>
              </thead>
              <tbody/>
            </table>
            <button id="addPerson">Neue Person</button>
          </main>
          
          // addPersonenRow ?
          function addPersonRow() {
              const fragment = new DocumentFragment
              const row = fragment.appendChild(document.createElement('tr'))
          
              row.innerHTML = `
                  <td headers="lastname">
                      <input aria-labelledby="lastname">
                  </td>
                  <td headers="firstname">
                      <input aria-labelledby="firstname">
                  </td>
                  <td headers="birthdate">
                      <input aria-labelledby="birthdate">
                  </td>
              `
          
              const table = document.querySelector('[aria-label=Personen]')
              table.appendChild(fragment)
          }
          

          egal, wie das Fragment (ne Tabellenzeile) zusammengeschraubt wird, die gehört nicht als Kind an die table, sondern als Enkel - mit dem tbody als Elternteil.

          cu,
          Andreas a/k/a MudGuard