claus ginsel: Anzahl von Kind-Elementen zählen

Moin

ist jetzt zwar nicht besonders wichtig, aber es wurmt mich trotzdem

die Seite lädt eine Liste von Dateinamen in eine Tabelle, im Bsp 3 Dateien:

<table id="tab">
    <tr id="z_tg">
        <td>tg</td>
        <td><a href="https://" ><b>&#11123</b></a></td>
        <td><a><b id="tg">&#128465</b></a></td>
    </tr>
    <tr id="z_dg">
        <td>dg</td>
        <td><a href="https://" ><b>&#11123</b></a></td>
        <td><a><b id="dg">&#128465</b></a></td>
    </tr>
    <tr id="z_wd">
        <td>wd</td>
        <td><a href="https://" ><b>&#11123</b></a></td>
        <td><a><b id="wd">&#128465</b></a></td>
    </tr>
</table>

Die Seite sieht dann so aus (hab die vielen "d" der Übersicht halber rausgenommen aus dem Quelltext)

zeigt die Datei-Liste, im Quelltext sind nur die ersten 2 Buchstaben

Der Mülleimer dient dazu, die Datei auf dem Server löschen zu lassen per Ajax:

    let table = document.getElementById('tab');
    table.onclick = function() {    
        let ID = window.event.target.id;
        if (ID) {
            if (confirm("Die Nachricht "+ID+".pdf wird gelöscht ...") == false) {return;}

            let xhttp = new XMLHttpRequest();
            xhttp.onreadystatechange = function() {
              if (this.readyState == 4 && this.status == 200) {
                alert(xhttp.responseText);
                //Zeile im Browser löschen
                document.getElementById('z_'+ID).remove();
                alert(table.children.length); // ist immer gleich 1!
                if (table.children.length === 0) {
                    table.innerHTML='<tr><td>Keine Nachrichten</td></tr>';
                }
              }
            };
            xhttp.open("GET", "DEL.php?id=" + ID, true);
            xhttp.send();
        }
    };

Die Dateien werden gelöscht, es wird auch die betreffende Zeile im Browser gelöscht. alert gibt mir immer aus als Anzahl 1, egal wieviel Zeilen sind

Nur irgendwann ist die letzte Datei dran: nachdem ich diese gelöscht habe, wird die Zeile nicht eingefügt, alert gibt mir immer auch hier Anzahl = 1 aus.

Auf W3C gibt es ein Bsp mit children.length, da werden die p in einem div gezählt. Aber warum will JS bei mir keine tr in table zählen?

Gruß Claus

akzeptierte Antworten

  1. Hi,

                    alert(table.children.length); // ist immer gleich 1!
    
    
    alert gibt mir  immer aus als Anzahl 1, egal wieviel Zeilen sind
    

    das liegt daran, daß die table genau 1 tbody-Element als Kind hat.
    (auch dann, wenn die Tags dazu fehlen).

    cu,
    Andreas a/k/a MudGuard

    1. Aha, besten Dank

      Gruß Claus

  2. Ich muss doch nochmal nachfragen

    Ist diese Verfahrensweise empfehlenswert:

    if (table.children[0].children.length === 0) {
    					table.innerHTML='<tr><td>&nbsp;&nbsp;&nbsp;Keine Nachrichten</td></tr>';
    				}
    

    Ich frage deshalb, weil ich zuvor auch Tests gemacht habe, bei denen ich den Quellcode lokal gespeichert und geöffnet und dann die Anzahl der Children bestimmt habe, dabei zählte JS richtig, also war tbody dort nicht berücksichtigt.

    Ist meine Variante uneingeschränkt geeignet?

    Gruß Claus

    1. Eine Tabelle kann neben tr-Elementen auch (teilweise mehrere) thead-, tbody-, tfoot-, colgroup-, script-, template- und caption-Elemente enthalten. (Und zusätzlich alles, was ich vergessen habe.) Und ein tbody darf auch nicht nur tr enthalten. Deshalb halte ich deine Vorgehensweise nicht für uneingeschränkt geeignet.

      Was spricht dagegen, einfach mit getElementsByTagName('tr').length die tr zu zählen?

      1. Hallo Friedel

        klingt gut.

        mach ich.

        Gruß Claus

  3. @@claus ginsel

            <td><a href="https://" ><b>&#11123</b></a></td>
            <td><a><b id="tg">&#128465</b></a></td>
    

    Auch wenn HTML-Parser unter gewissen Umständen auch ohne klarkommen: Zeichenreferenzen werden in HTML (XML, …) mit ; abgeschlossen.

    Ich würde überigens keine dezimalen numerischen Referenzen verwenden, Unicode-Copoints werden i.A. hexadezimal notiert: &#x1F5D1;

    Aber warum verwendest du nicht 🗑 im Quelltext?

        let table = document.getElementById('tab');
    

    const wäre angebracht.

        table.onclick = function() {};
    

    Nein, nicht machen. Eventlistener in JavaScript so registrieren:

    table.addEventListener('click', function () {});
    

    Oder so geschrieben:

    table.addEventListener('click', () => {});
    

    Das Problem bei dir: Die Mülleimer kann man nicht anclicken. Einige können es (Mausschubser), viele andere können es nicht (Tastaturbediener bspw.).

    Den click-Eventhandler beim table-Element registrieren und event delegation zu nutzen, ist in Ordnung. Aber die Bedienelemente zum Auslösen von Aktionen müssen <button>s sein.[1] <a>-Elemente ohne href-Attribut sind keine interaktiven Elemente.

    Und auch die <a href="https://"> sehen mir danach aus, dass das auch <button>s sein sollten.

    🖖 Живіть довго і процвітайте

    --
    „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
    — @Grantscheam auf Twitter

    1. Man könnte auch andere Elemente mit viel Aufwand bedienbar machen, aber das wäre irrsinnig. ↩︎

    1. Hallo Gunnar

      was meinst du mit

      Aber warum verwendest du nicht 🗑 im Quelltext?

      Den click-Eventhandler beim table-Element registrieren und event delegation zu nutzen, ist in Ordnung.

      Geht das denn anders, wenn man dynamisch die Liste füllt?

      Geht das hier um Barrierefreiheit?

      Das Problem bei dir: Die Mülleimer kann man nicht anclicken. Einige können es (Mausschubser), viele andere können es nicht (Tastaturbediener bspw.).

      Und auch die <a href="https://"> sehen mir danach aus, dass das auch <button>s sein sollten.

      dahinter steckt tatsächlich ein Link. Und um gleiches Layout zu erhalten, habe ich <a> etwas zweckentfremdet.

      Gruß Claus

      1. @@claus ginsel

        was meinst du mit

        Aber warum verwendest du nicht 🗑 im Quelltext?

        <button>🗑</button>

        Da fehlt natürlich noch eine zugängliche Beschriftung, dazu gleich mehr.

        Den click-Eventhandler beim table-Element registrieren und event delegation zu nutzen, ist in Ordnung.

        Geht das denn anders, wenn man dynamisch die Liste füllt?

        Geht sicher, aber event delegation ist besser.

        Und auch die <a href="https://"> sehen mir danach aus, dass das auch <button>s sein sollten.

        dahinter steckt tatsächlich ein Link.

        Ah, da kommt hinter https:// noch was? Dann ist <a> hierfür in Ordnung.

        Und um gleiches Layout zu erhalten, habe ich <a> etwas zweckentfremdet.

        Das ist nie eine gute Idee, HTML-Elemente nach deren Default-Aussehen auszuwählen.

        Wenn du die Default-Hintergrundfarbe eines Buttons weghaben willst, setze sie mit CSS auf transparent.

        Wenn du die Default-Rahmenfarbe eines Buttons weghaben willst, setze sie mit CSS auf transparent. Aber nicht den Rahmen entfernen!

        button {
        	background: transparent;
        	border-color: transparent;
        }
        

        Wie in diesem Beispiel.

        Das Markup:

        				<button aria-describedby="marmor">
        					<span aria-hidden="true">🗑</span>
        					<span class="visually-hidden">löschen</span>
        				</button>
        

        Das 🗑-Icon wird mit aria-hidden="true" vor Screenreadern versteckt. (Was sollten die da auch sinnvolles vorlesen?)

        Screenreader-Nutzer brauchen eine textuelle Beschreibung der Funktion des Buttons: „löschen“. Diese kann visuell versteckt werden.

        Damit Screenreader nicht „löschen, löschen, löschen, …“ vorlesen ohne bekanntzugeben, was da jeweils gelöscht wird, wird durch aria-describedby die Verbindung zum Bezeichner des jeweiligen Items über dessen ID hergestellt. Die Buttons werden dann als „löschen Marmor, löschen Stein, löschen Eisen“ o.ä. angesagt.

        🖖 Живіть довго і процвітайте

        --
        „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
        — @Grantscheam auf Twitter
        1. Ich hab bisher weder einen Screenreader gesehen noch weiß ich was der macht und wie der arbeitet ;)

          Meine Seiten sind für ein sehr begrenztes Klientel bestimmt, und bisher gabs da auch keine Erfordernisse, sich damit zu befassen. Aber danke für deine Hinweise.

          Eines hab ich immer noch nicht verstanden: wie kriegst du den Mülleimer in den Code, meine Tastatur hat den nicht?

          Gruß Claus

          1. Hallo

            Eines hab ich immer noch nicht verstanden: wie kriegst du den Mülleimer in den Code, meine Tastatur hat den nicht?

            rock'n'roll … ähhh … copy'n'paste?

            Tschö, Auge

            --
            200 ist das neue 35.
          2. Hallo,

            Eines hab ich immer noch nicht verstanden: wie kriegst du den Mülleimer in den Code, meine Tastatur hat den nicht?

            die einfachste Lösung, die mir einfällt:
            Aus der Zeichentabelle (Windows: charmap.exe) kopieren und einfügen.

            Einen schönen Tag noch
             Martin

            --
            Wer kennt ein schönes Autofahrer-Märchen? - Radkäppchen und der böse Golf
            1. Ach so 🤥

              Schönen Feierabend

              Gruß Claus

          3. @@claus ginsel

            Ich hab bisher weder einen Screenreader gesehen

            Bei vielen Geräten ist ein Screenreader bereits im OS intergriert: bei macOS und iOS: VoiceOver, bei Android: TalkBack, bei Windows: Narrator.

            noch weiß ich was der macht und wie der arbeitet ;)

            Léonie Watson demonstriert das anschaulich (anhörbar?) in ihrem Vortrag Bag of Spanners. Mal ab 04:50 reinschauen, dann den ganzen Vortrag.

            Meine Seiten sind für ein sehr begrenztes Klientel bestimmt, und bisher gabs da auch keine Erfordernisse, sich damit zu befassen.

            Manchmal werden Seiten auch nur von einem begrenztes Klientel besucht, weil sie nur für dieses begrenzte Klientel zugänglich sind.

            Übrigens kann auch jemand aus deinem begrenzten Klientel durch Unfall oder Krankheit auf assistive Technologien angewiesen sein.

            Und die Grundlagen für barriefreie Webseiten sind ja keine Raketenwissenschaft:

            • HTML-Elemente gemäß ihrer Bestimmung verwenden, nicht nach ihrem Aussehen
            • click-Events nur für interaktive Elemente (Buttons, Links, evtl. Eingabefelder, …)
            • zugängliche Beschriftungen für interaktive Elemente (Textalternativen für Icons, Label für Eingabefelder) und für Bilder

            🖖 Живіть довго і процвітайте

            --
            „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
            — @Grantscheam auf Twitter
          4. @@claus ginsel

            Eines hab ich immer noch nicht verstanden: wie kriegst du den Mülleimer in den Code, meine Tastatur hat den nicht?

            Dein Mülleimer befindet sich vermutlich nicht auf deinem Schreibtisch, sondern darunter.

            Falls da keiner ist, nimm den großen! 🤪

            🖖 Живіть довго і процвітайте

            --
            „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
            — @Grantscheam auf Twitter
        2. @@Gunnar Bittersmann

          Ich hab das Beispiel noch um einen Punkt erweitert. 😊

          🖖 Живіть довго і процвітайте

          --
          „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
          — @Grantscheam auf Twitter
          1. Guten Morgen Gunnar

            versteh ich dein JS richtig, dass der Eventhandler erst im Ereignisfall auf das auslösende Element gesetzt und zugleich ausgeführt wird?

            Dann benötigt man also event delegation gar nicht

            <button aria-describedby="eisen">
            					<span aria-hidden="true">&#x1F5D1;</span>
            					<span class="visually-hidden">löschen</span>
            				</button>
            

            Und mit diesen aria-Attributen hat man die Barrierefreiheit umgesetzt? Da hast du recht, der Aufwand ist überschaubar.

            Gruß Claus

            1. @@claus ginsel

              versteh ich dein JS richtig, dass der Eventhandler erst im Ereignisfall auf das auslösende Element gesetzt und zugleich ausgeführt wird?

              Ich glaube nicht.

              const itemlist = document.querySelector('table');
              itemlist.addEventListener('click', (event) => {
                // …
              });
              

              ist ja im Wesentlichen das, was du auch hattest. Mit ein paar Unterschieden:

              • Ich nenne das Ding nicht table, sondern itemlist. Ist ja keine richtige Datentabelle. Ich war auch schon geneigt, <table role="presentation"> zu setzen, wodurch Screenreader das nicht als Tabelle ansagen.

              • Ich hab der Tabelle keine ID gegeben; ist ja in meinem Beispiel nicht nötig, weil die einzige. Bei dir ist das völlig richtig, das Ding über dessen ID anzusprechen (wobei "tab" kein sinnvoller Bezeichner ist). Ich würde das allerdings nicht mit getElementById() tun und auch nicht das von @Friedel ins Spiel gebrachte getElementsByTagName() verwenden, sondern querySelector() bzw. querySelectorAll() – ein einheitliches API für alle Fälle.

              • Nicht onclick, sondern addEventListener().

              • Die Eventhandlerfunktion bekommt event als Parameter. Wir werden gleich sehen, warum.

              Es werden nicht Eventhandler für jedes einzelne interaktive Element registriert, sondern ein Eventhandler für deren gemeinsames Vorfahrenelement, wo alle click-Events durch Bubbling eintreffen. Also event delegation.

              Ich habe in meinem Beispiel (Link öffnet auch die Konsole) noch hinzugefügt:

              	console.log(event.target.nodeName);
              

              event.target liefert das Element, das der Nutzer geclickt hat. Beim Click auf den Button steht bei Tastaturbedienung "BUTTON" in der Konsole; bei Mausbedienung hingegen "SPAN", weil ja auf das span-Element mit dem 🗑-Icon geclickt wird. Beim Mausclick außerhalb des Buttons wird "TD" ausgegeben.

              (Die Codezeile ist nur zur Veranschauung und hat im Produktiv-Code nichts zu suchen.)

              Als nächstes wird abgefragt, ob der bei itemlist ankommende click-Event von innerhalb eines Buttons kommt (d.h. vom button selbst (Tastaturbedienung) oder einem darin befindlichen Nachfahrenelement (Mausbedienung)):

              	if (event.target.closest('button')) {
              

              Die Aktion Löschen soll nur ausgeführt werden, wenn ein Löschen-Button betätigt wurde. Wenn man woanders hinclickt, soll nichts passieren.

              (Das hattest du anders? Halte ich für einen Designfehler. Insbesondere, weil bei dir noch der ⤓ hinzukommt; und verschachtelte Clickflächen sind nicht die beste Idee.)

              Wenn ein Löschen-Button betätigt wurde, wird dessen übergeordnetes tr-Element ermittelt (es soll ja genau dieses gelöscht werden, nicht irgendein anderes):

              		const item = event.target.closest('tr');
              

              Bei dir passiert da natürlich noch was anderes. – Und bei jedem Item dasselbe, d.h. die letzte Abfrage in meinem Beispiel

              		if (!item.querySelector('#unsere-liebe')) {
              

              wirst du nicht haben. Die ist nur deshalb, weil unsere Liebe nicht bricht.

              🖖 Живіть довго і процвітайте

              --
              „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
              — @Grantscheam auf Twitter
              1. Cool 👍

                Danke für die Erläuterungen

                Gruß Claus

  4. Moin Claus,

    xhttp.open("GET", "DEL.php?id=" + ID, true);
    xhttp.send();
    

    was passiert denn eigentlich, wenn diese URL irgendwie „automatisch“ aufgerufen wird? Könnte ich per Crawler automatisch ohne weitere Abfrage Inhalte bei dir löschen lassen? Sollte das vielleicht lieber ein POST sein?

    Viele Grüße
    Robert

    1. Hallo Robert,

      es sollte nicht nur, es muss auch. Denn ein GET Request gilt laut RFC 7231 als "safe method" und man erwartet von ihm, dass er am Server gespeicherte Daten nicht verändert.

      Mit einem Link kann man keine POST-Requests auslösen. Insofern hat Gunnar mit seinen Buttons recht; es sollten welche sein. Und zwar Submit-Buttons eines Forms, das die Liste umschließt. Ohne JavaScript führt der Server den DELETE durch und gibt die Liste neu aus. Als Progressive Enhancement kann man mit JavaScript den Submit dieses Forms abfangen und mit Ajax im Hintergrund ausführen.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Guten Morgen Rolf

        Denn ein GET Request gilt laut RFC 7231 als "safe method" und man erwartet von ihm, dass er am Server gespeicherte Daten nicht verändert.

        hab ich mir nun ein Sicherheitsproblem eingefangen?

        Gruß Claus

    2. Hallo Robert,

      xhttp.open("GET", "DEL.php?id=" + ID, true);
      xhttp.send();
      

      was passiert denn eigentlich, wenn diese URL irgendwie „automatisch“ aufgerufen wird?

      die Frage ging mir heute nachmittag auch schon durch denk Kopf.

      Sollte das vielleicht lieber ein POST sein?

      Ja, von der HTTP-Semantik her auf jeden Fall. Nur sicherer wird es dadurch nicht. Auch ein Bot oder ein Script, das Curl oder wget benutzt, kann einen POST-Request absetzen.
      Mit der Umstellung von GET auf POST verhindert man nur "versehentliche" Pannen, etwa dadurch, dass ein Suchmaschinen-Bot irgendeinem Link folgt.

      Einen schönen Tag noch
       Martin

      --
      Wer kennt ein schönes Autofahrer-Märchen? - Radkäppchen und der böse Golf
    3. Guten Morgen Robert

      Ein Crawler wird die Seite nicht finden, ist nicht öffentlich verlinkt, also deep web und auch nur aus dem deep web aufzurufen. Falls doch jemand zufällig die Seite findet, muss er die ID haben, die selbst einen Zufallswert darstellt.

      Wenn beides gegeben, dann ja, dann ist die Datei weg.

      Ich muss noch ergänzen, das Script läuft nur innerhalb einer Session, die über eine Anmeldung gestartet wird.

      Gruß Claus

      1. Moin Claus,

        Ein Crawler wird die Seite nicht finden, ist nicht öffentlich verlinkt, also deep web und auch nur aus dem deep web aufzurufen.

        Es könnte auch dein Crawler sein, den du aus welchem Grund auch immer irgendwann einmal selbst entwickelst.

        Falls doch jemand zufällig die Seite findet, muss er die ID haben, die selbst einen Zufallswert darstellt.

        Ich vermute, dass es mehrere IDs gleichzeitig gibt, d.h., dass man auch einfach IDs durchprobieren kann.

        Wenn beides gegeben, dann ja, dann ist die Datei weg.

        Ich muss noch ergänzen, das Script läuft nur innerhalb einer Session, die über eine Anmeldung gestartet wird.

        Unabhängig davon spricht nach dem Kommentar von @Rolf B einiges dafür HTTP-POST zu verwenden.

        Viele Grüße
        Robert

        1. Hallo Robert

          Ich vermute, dass es mehrere IDs gleichzeitig gibt, d.h., dass man auch einfach IDs durchprobieren kann.

          ich bin bei der Validierung schon sehr restriktiv und es gibt einen brute force Schutz.

          Unabhängig davon spricht nach dem Kommentar von @Rolf B einiges dafür HTTP-POST zu verwenden.

          Ok, ich stell das um.

          Gruß Claus

      2. Hi,

        Ein Crawler wird die Seite nicht finden, ist nicht öffentlich verlinkt, also deep web und auch nur aus dem deep web aufzurufen.

        Ein großer Teil der Angriffe auf IT-Systeme von Firmen erfolgt von innen, unzufriedene/gekündigte Mitarbeiter …

        cu,
        Andreas a/k/a MudGuard

        1. Hi Andreas

          wenn ich jemandem Anmeldedaten gegeben hab, dann hat der nun mal Zugriff.

          Wie willst du denn da Missbrauch verhindern?

          Gruß Claus

          1. Hi,

            Wie willst du denn da Missbrauch verhindern?

            Man kann's zumindest erschweren.

            Mit dem Löschen per get mit id als Parameter braucht's nur ein kleines Scriptchen, das einfach der Reihe nach von 1 bis intMax durchläuft und mit jeder Nummer die Lösch-URL aufruft.

            cu,
            Andreas a/k/a MudGuard

            1. Nun, das geht auch einfacher: da ruft der Mitarbeiter beim Provider an und lässt sich die Zugangsdaten für FTP geben ...

              😉

              1. Hallo claus,

                da ruft der Mitarbeiter beim Provider an und lässt sich die Zugangsdaten für FTP geben ...

                ...was keinesfalls so einfach möglich sein sollte und auf eine Schwachstelle im Sicherheitskonzept des Gesamtsystems hindeutet.

                Rolf

                --
                sumpsi - posui - obstruxi
                1. Hallo Rolf

                  eine Schwachstelle im Sicherheitskonzept

                  Sicherheitskonzept, was ist das?

                  Mein erster Kontakt zu meinem Provider: Ich schickte ihm eine Mail und bat um die Zugangsdaten. Der Provider sah an der Mailadresse, dass die Anfrage zu einer der gehosteten Domains gehörte und schon war ich vertrauenswürdig 😏

                  Aber mal im Ernst: Ich hab von GET auf POST umgestellt Die Nutzung des Service erfordert eine Anmeldung, Passörter vergeb ich. brute force auch von innen heraus endet bei 100 Fehlversuchen mehr kann und muss ich nicht machen, denke ich.

                  Gruß Claus

                  1. Hallo

                    brute force auch von innen heraus endet bei 100 Fehlversuchen

                    Du sperrst einen Angreifer erst nach 100 Zugriffsversuchen aus? Das ist recht großzügig von dir. Diese Grenze sollte sehr viel restriktiver gesetzt werden.

                    Tschö, Auge

                    --
                    200 ist das neue 35.