Linuchs: Javascript: 2-dimensionales Array erstellen

problematische Seite

Moin,

ich hänge am Problem fest, ein zweidimensionales Array zu erstellen.

Im Prinzip funktioniert das, die erwarteten Worte werden angezeigt:

var arr2  = ["Blumen","Obst"];
var arr3  = ["Apfel","Birne"];
arr2[1]   = arr3;
console.log( arr2[1][1] ); // Birne
console.log( arr2[0]    ); // Blumen

Nun HTML-Objekte:

var arr_playlists = document.getElementsByClassName("playlist");

for ( let i=0; i<arr_playlists.length; i++ ) {
  let arr_li  = arr_playlists[i].getElementsByTagName("li");
  // 2. Ebene
  console.log( arr_li[0].innerHTML ); // Sloop, La Bamba, D-Hymne - OK
  arr_playlists[i] = arr_li;
  console.log( arr_playlists[i][0].innerHTML ); // Sloop, La Bamba, D-Hymne - Fehler
}

Fehlermeldung: Uncaught TypeError: arr_playlists[i][0] is undefined

Habe ich einen Denkfehler oder geht das mit Objekten nicht?

  1. problematische Seite

    Hallo Linuchs,

    arr_playlists ist zunächst mal ein eindimensionales Array von HTML Elementen.

    Wenn Du ein zweidimensionales Array willst, musst Du auch eins bauen. Entweder ein neues Array, oder du baust arr_playlists um.

    Umbau, zum Beispiel so:

    arr_playlists[i] = [ arr_playlists[i], arr_li ];
    

    Das HTML Element in arr_playlists[i] gelangt so nach arr_playlists[i][0] und arr_li nach arr_playlists[i][1].

    Man könnte das als schmutzig bezeichnen, weil Du in arr_playlists die Typen änderst. Ich finde das nicht - solange dieser Schmutz auf die Initialisierung beschränkt bleibt.

    Schicker ist es, die map-Methode von Arrays aus dem Array-Prototypen herauszuholen und auf die Nodelist anwenden, die getElementsByClassName liefert:

    let arr_playlists = Array.prototype.map.call(
       document.getElementsByClassName("playlist"),
       (pl, i) => [ pl, pl.getElementsByTagName("li") ]
    );
    

    JavaScript ist so geil - das heutige JS hat mit dem, was es 2000 gab, kaum noch was zu tun 😉

    Statt getElementsByClassName oder getElementsByTagName würde ich übrigens konsequent auf querySelectorAll setzen. Browser können CSS Selektoren sehr gut suchen, dafür sind sie optimiert.

    let arr_playlists = Array.prototype.map.call(
       document.querySelectorAll(".playlist"),
       (pl, i) => [ pl, pl.querySelectorAll("li") ]
    );
    

    Statt [ pl, pl.querySelectorAll("li") ] könnte man noch
    [ pl, [... pl.querySelectorAll("li") ] ] schreiben. Das ... ist der Spread-Operator, damit verteilst Du die Elemente eines auflistbaren Objekts in ein Array. Von querySelectorAll bekommst Du eine (statische) NodeList, und Nodelisten sind funktionsärmer als Arrays.

    Seit dem Tod des IE kann man damit bedenkenlos arbeiten.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. problematische Seite

      Hallo Rolf,

      danke für deine Antwort. Das habe ich nicht verstanden, wo kommt pl und i her?

      let arr_playlists = Array.prototype.map.call(
         document.querySelectorAll(".playlist"),
         (pl, i) => [ pl, pl.querySelectorAll("li") ]
      );
      

      Jedenfalls liefert mir

      console.log( "Titel 1,1=[" +arr_playlists[1][1].innerHTML +"]" ); // Rivers of Babylon
      

      Titel 1,1=[undefined]

      1. problematische Seite

        Hallo Linuchs,

        bitte entschuldige, ich habe nicht gut nachgedacht. Ich hatte Dich so verstanden, als wolltest Du ein Array haben, das pro Eintrag ein Pärchen aus Playlist-Element und LI-Liste enthält.

        Deine weiteren Beiträge zeigen, dass Du ein 2D Array willst, das nur die li Einträge pro Playlist enthält, und das sollte dein ursprünglicher Code auch tun. Ich werde an dieser Stelle nicht mehr drauf eingehen, die Diskussion ist ja schon weiter.

        Mein Code sollte sowas produzieren:

        +------------+------------------+
        |            | +----+----+----+ |
        |  playlist  | | li | li | li | |
        |            | +----+----+----+ |
        +------------+------------------+
        |            | +----+----+----+ |
        |  playlist  | | li | li | li | |
        |            | +----+----+----+ |
        +------------+------------------+
        |            | +----+----+----+ |
        |  playlist  | | li | li | li | |
        |            | +----+----+----+ |
        +------------+------------------+
        

        Aber das wäre dann ja schon dreidimensional gewesen.

        Ich bin aber noch eine Antwort auf deine Frage nach dem pl und i schuldig. Ich wende die map-Methode von Arrays auf die Einträge einer Nodelist an. WENN eine Nodelist ein Array wäre, könnte man das so schreiben:

        nodelist.map(callback);
        

        Aber das ist sie nicht, deshalb der Umweg über .call:

        Array.prototype.map.call(nodelist, callback);
        

        Damit kann man map auf jedes Ding anwenden, das einen Indexzugriff und eine length-Eigenschaft hat.

        map übergibt dem Callback 3 Werte - siehe Wiki. Die beiden ersten nehme ich mit dieser Pfeilfunktion entgegen:

        (pl, i) => [ pl, pl.querySelectorAll("li")
        

        Mit einer herkömmlichen anonymen Funktion würde man statt dessen schreiben:

        function(pl, i) { return [ pl, pl.querySelectorAll("li") ]; }
        

        Das ist (fast) das Gleiche. Die Unterschiede werden im verlinkten Wiki-Artikel zu Pfeilfunktionen erklärt.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          Hallo Rolf,

          Array.prototype.map.call(nodelist, callback);
          

          würde das hier

          [... nodelist].map(callback);
          

          auch funktionieren?

          Gruß
          Jürgen

          1. problematische Seite

            Hallo Jürgen,

            ja, sicher. Es wäre aber drölf Nanosekunden langsamer, weil die Nodelist dann zuerst in ein Array umkopiert wird.

            Rolf

            --
            sumpsi - posui - obstruxi
    2. problematische Seite

      Dieses hier

      var arr_playlists  = document.querySelectorAll(".playlist");
      for ( let i=0; i < arr_playlists.length; i++ ) {
        let arr_li        = arr_playlists[i].querySelectorAll("li");
        console.log( "Titel " +i +",1=[" +arr_li[1].innerHTML +"]" );
        arr_playlists[i]  = [ arr_playlists[i], arr_li ];
      }
      console.log( "Titel 1,1=[" +arr_playlists[1][1].innerHTML +"]" ); // Rivers of Babylon
      

      ergibt

      Titel 0,1=[Hamburger Fährjung, SeemannsChor Bremerhaven]
      Titel 1,1=[Rivers of Babylon, Mecklenburger Drehorgelorchester]
      Titel 2,1=[Europa Hymne]
      Uncaught TypeError: arr_playlists[1][1] is undefined
      

      Also stimmt was nicht mit dem Aufbau des Arrays? arr_li ist doch ein Array und müsste mit

      arr_playlists[i]  = arr_li;
      

      die zweite Ebene aufmachen, der Wert arr_playlists[1][1].innerHTML ist undefined

  2. problematische Seite

    Und hier der, stark nach Vanille riechende „Fußweg“:

    <ol class="playlist">
    	<li>Sloop</li>
    	<li>La Bamba</li>
    	<li>D-Hymne</li>
    </ol>
    
    <ol class="playlist">
    	<li>Yesterday</li>
    	<li>Knockin on Havens Door</li>
    	<li>Lady in Red</li>
    </ol>
    
    <pre id="Ausgabe">Ausgaben:</pre>
    
    
    <script>
    var arr_playlists = document.getElementsByClassName("playlist");
    ausgabe( arr_playlists.length + " Listen ");
    for ( let i=0; i<arr_playlists.length; i++ ) {
    	let arr_li  = arr_playlists[i].getElementsByTagName("li");
    	ausgabe( 
    		"\t" 
    		+ 'Liste '
    		+ (i+1) 
    		+ ': '
    		+ arr_li.length 
    		+ ' Titel gefunden:'
    	);
    	// 2. Ebene
    	for ( let k=0; k< arr_li.length; k++ ) {
    		arr_playlists[i][k] = arr_li[k];
    		ausgabe( 
    			"\t\t Liste " 
    			+ ( i + 1 )
    			+ ',  Titel ' 
    			+ ( k + 1 ) 
    			+ ': „' 
    			+ arr_playlists[i][k].innerHTML
    			+ '“' 
    		);
    	}
    }
    
    function ausgabe(s) {
    		document.getElementById('Ausgabe').innerHTML += s + "\n";
    }
    
    </script>
    

    Das zu Grunde liegende HTML habe ich „reingeneert“.

    Ausgaben:

    Ausgaben:2 Listen 
    	Liste 1: 3 Titel gefunden:
    		 Liste 1,  Titel 1: „Sloop“
    		 Liste 1,  Titel 2: „La Bamba“
    		 Liste 1,  Titel 3: „D-Hymne“
    	Liste 2: 3 Titel gefunden:
    		 Liste 2,  Titel 1: „Yesterday“
    		 Liste 2,  Titel 2: „Knockin on Havens Door“
    		 Liste 2,  Titel 3: „Lady in Red“
    
    1. problematische Seite

      Hallo RW,

      danke, so funktioniert es:

      var arr_playlists  = document.querySelectorAll(".playlist");
      for ( let i=0; i < arr_playlists.length; i++ ) {
      
        let arr_li  = arr_playlists[i].querySelectorAll("li");
        for ( let j=0; j < arr_li.length; j++ ) {
          arr_playlists[i][j]  = arr_li[j];
        }
      }
      console.log( "Titel 1,1=[" +arr_playlists[1][1].innerHTML +"]" );
      
      1. Alles gut. Ich habe aber im vorigen Jahrtausend mal gelernt, „j“ nicht als Iterator bzw. Variable zu verwenden, denn das ist (in etlichen Schriftarten) dem „i“ optisch zu ähnlich…

        Freilich hat man damals auch viel Mist gebaut (z.B. die oft krude Syntax der Shell, von BASIC, Perl, C, … „verbrochen“) aber das mit dem Weglassen des „j“ würde ich bis heute bejahen.)

        1. Hallo,

          Ich habe aber im vorigen Jahrtausend mal gelernt, „j“ nicht als Iterator bzw. Variable zu verwenden, denn das ist (in etlichen Schriftarten) dem „i“ optisch zu ähnlich…

          das stammt möglicherweise noch aus der Zeit, als Schreibmaschinen keine Typen für die Ziffern 0 und 1 hatten, weil man die ja mit dem Großbuchstaben O und dem kleinen l nachbilden konnte. Schön oder gar sinnvoll ist das aber nicht.

          Aus ähnlichen Gründen wurden übrigens bei deutschen Autokennzeichen bis etwa 1995/96 die Buchstaben I und Q für die zusätzliche Buchstabengruppe nicht verwendet, weil sie mit der Ziffer 1 oder dem Buchstaben O bzw. der Ziffer 0 verwechselt werden konnten. Seit Einführung der FE-Schrift für Autokennzeichen ist das hinfällig.

          Worauf ich hinaus will: Wer für seine Entwicklungsumgebung Schriften verwendet, bei denen diese Verwechslungsgefahr besteht, sollte bei der Ursache ansetzen, nicht bei möglichen Auswirkungen.

          Freilich hat man damals auch viel Mist gebaut (z.B. die oft krude Syntax der Shell, von BASIC, Perl, C, … „verbrochen“) aber das mit dem Weglassen des „j“ würde ich bis heute bejahen.)

          Das sei dir unbenommen; ich selbst halte das aber für Unfug.

          Möge die Übung gelingen
           Martin

          --
          Darmstadt? Ist das nicht da, wo immer der Urologen-Kongress tagt?
      2. problematische Seite

        Hallo Linuchs,

        danke, so funktioniert es:

        Nein. Frag mal nach arr_playlists[1].length

        Jetzt weiß ich auch, was schief geht. arr_playlists ist eine Nodelist und kein Array. Deswegen geht das Zuweisen einer Nodelist von li Elementen an arr_playlists[i] nicht - eine Nodelist ist readonly und Zuweisungen werden ignoriert.

        arr_playlists[0] ist das HTMLElement Objekt, das zum ersten div mit playlist Klasse gehört. Wenn Du an arr_playlists[i][0] etwas zuweist, bekommt dieses HTMLElement Objekt ein Property namens "0" verpasst.

        Mach also ein Array draus:

        var arr_playlists  = Array.from(document.querySelectorAll(".playlist"));
        

        Äh - var ist was für Opa - was uns beide zwar nicht stört, weil wir beide alt genug sind, um einer sein zu können, aber trotzdem...

        const arr_playlists  = Array.from(document.querySelectorAll(".playlist"));
        

        Äh - Array.from rafft kein Internet Explorer, und dann kann man auch den Spread-Operator nehmen:

        const arr_playlists  = [... document.querySelectorAll(".playlist") ];
        

        Äh - ich war ja schon bei Array.prototype.map, und das ist immer noch sinnvoll:

        const arr_playlists = Array.prototype.map.call(
           document.querySelectorAll(".playlist"),
           playlist => [... playlist.querySelectorAll("li") ]
        );
        

        Damit solltest Du ein 2D Array aller li Elemente erhalten. Ich bin allerdings nicht sicher, ob das im Ergebnis zu einer ordentlichen Softwarearchitektur für die ganze Seite führt, dazu habe ich mir das noch nicht genau genug angeschaut. Ich würde aber annehmen, dass ein Array, dass die Einträge mehrerer Playlists sammelt, auf ein COBOL-Programmdesign schließen lässt.

        Meine erste Chefin sagte immer: Ein guter COBOL-Programmierer schreibt in jeder Programmiersprache COBOL-Programme…

        Was sich umdrehen lässt. Wenn einem JavaScript-Programmierer COBOL zustößt, versucht er auch dort, JavaScript-Idiome zu verwenden. Wogegen sich COBOL aber tretend und kreischend zur Wehr setzt 😂 und protestiert, dass Oma Grace dabei in 3 Achsen im Grab rotiere!!!

        Rolf

        --
        sumpsi - posui - obstruxi