skip to Main Content

I am trying to get a forEach loop to write a table row that consists of a variable from both a shallow nested object and a deep nested object.

I have this JSON object:

var chardata = [{
    "moveList": [{
        "name"    : "Directional Tests",
        "synergy" : [{ "partner" : "Character A", "moveEqual" : "Direct Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Direct Synergy for B" }]
    },{
        "name"    : "Motion Tests",
        "synergy" : [{ "partner" : "Character A", "moveEqual" : "Motion Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Motion Synergy for B" }]
    },{
        "name"    : "Button Tests",
        "synergy" : [{ "partner" : "Character A", "moveEqual" : "Button Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Button Synergy for B" }]
    }]
}];

And this forEach function:

function buildSynergyTest(data) {
    var table = document.getElementById('CharacterSynergy');
    var html = 'n';

    for (character of data) {
        character.moveList.forEach(function(move, i) {
            html += ` <TR>n`;
            html += `  <TD>${move.name}</TD>n`;
            html += `  <TD>${move.synergy[i].partner}</TD>n`;
            html += `  <TD>${move.synergy[i].moveEqual}</TD></TR>n`;
        });
    }
    
    table.innerHTML += html;
}

buildSynergyTest(chardata);

When I run the forEach loop, the first column translates perfectly. But the second and third columns do not. What’s supposed to happen is this:

<TABLE border=1>
 <TR>
  <TD>Directional Test</TD>
  <TD>Character A</TD>
  <TD>Direct Synergy for A</TD></TR>
 <TR>
  <TD>Motion Test</TD>
  <TD>Character A</TD>
  <TD>Motion Synergy for A</TD></TR>
 <TR>
  <TD>Button Test</TD>
  <TD>Character A</TD>
  <TD>Button Synergy for A</TD></TR>
 <TR>
  <TD>Directional Test</TD>
  <TD>Character B</TD>
  <TD>Direct Synergy for B</TD></TR>
 <TR>
  <TD>Motion Test</TD>
  <TD>Character B</TD>
  <TD>Motion Synergy for B</TD></TR>
 <TR>
  <TD>Button Test</TD>
  <TD>Character B</TD>
  <TD>Button Synergy for B</TD></TR></TABLE>

Instead, I am getting one of two results:

  1. The second and third columns of data (move.synergy.partner and move.synergy.moveEqual) are listed as undefined. But they are defined, all I have to do is a console.log of chardata[0].moveList[0].synergy[0].partner/moveEqual, and they would show up just fine.

  2. If I were to put the [i] symbol in front of synergy, JS breaks with an undefined error. I know that [i] works in for loops, so I have to assume it should also work in this forEach, but for some reason it doesn’t.

The only way I can bring up the desired values is hard coding, by deliberating putting a [0] or [1], but that defeats the purpose of the forEach loop.

How do I rewrite the forEach loop so that it can iterate all columns to be like the expected example?

3

Answers


  1. You need one more loop

    var chardata = [{
        "moveList": [{
            "name"    : "Directional Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Direct Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Direct Synergy for B" }]
        },{
            "name"    : "Motion Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Motion Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Motion Synergy for B" }]
        },{
            "name"    : "Button Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Button Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Button Synergy for B" }]
        }]
    }];
    
    function buildSynergyTest(data) {
        var table = document.getElementById('CharacterSynergy');
        var html = 'n';
    
        for (character of data) {
            character.moveList.forEach(function(move, i) {
                html += ` <TR>n`;
                html += `  <TD>${move.name}</TD>n`;
                move.synergy.forEach(synergy => {
                  html += `  <TD>${synergy.partner}</TD>n`;
                  html += `  <TD>${synergy.moveEqual}</TD></TR>n`;
                })
            });
        }
        
        table.innerHTML += html;
    }
    
    buildSynergyTest(chardata);
    <table id="CharacterSynergy"></table>

    Also, may be easier using map

    const chardata = [{
        "moveList": [{
            "name"    : "Directional Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Direct Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Direct Synergy for B" }]
        },{
            "name"    : "Motion Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Motion Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Motion Synergy for B" }]
        },{
            "name"    : "Button Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Button Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Button Synergy for B" }]
        }]
    }];
    
    function buildSynergyTest(data) {
        const table = document.getElementById('CharacterSynergy');
        
        const html = data.map(character =>
          character.moveList.map(move =>
              `<tr>
                <td>${move.name}</td>
                ${move.synergy.map(synergy =>
                  `
                  <td>${synergy.partner}</td>
                  <td>${synergy.moveEqual}</td>
                  `
                ).join('')}
              </tr>`
          ).join('')
        ).join('')
        
        table.innerHTML += html;
    }
    
    buildSynergyTest(chardata);
    <table id="CharacterSynergy"></table>
    Login or Signup to reply.
  2. You need a second for the synergy entries.

    Important notice: never use innerHTML+= since it re-writes DOM entirely leading to slow performance plus broken event handlers, etc.

    'use strict';
    
    function buildSynergyTest(data) {
        var table = document.getElementById('CharacterSynergy');
        var html = 'n';
    
        for (const character of chardata) {
            character.moveList.forEach(function(move) {
                html += ` <TR>n`;
                html += `  <TD>${move.name}</TD>n`;
                for(const {partner, moveEqual} of move.synergy){
                  html += `  <TD>${partner}</TD>n`;
                  html += `  <TD>${moveEqual}</TD>n`;
                }
                html += `</TR>n`;
            });
        }
        
        table.insertAdjacentHTML('beforeend', html);
    }
    
    buildSynergyTest(chardata);
    <script>
    var chardata = [{
        "moveList": [{
            "name"    : "Directional Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Direct Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Direct Synergy for B" }]
        },{
            "name"    : "Motion Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Motion Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Motion Synergy for B" }]
        },{
            "name"    : "Button Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Button Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Button Synergy for B" }]
        }]
    }];
    </script>
    <table id="CharacterSynergy"></table>
    'use strict';
    
    function buildSynergyTest(data) {
        var table = document.getElementById('CharacterSynergy');
        const html = chardata.map(({moveList}) => moveList.map(({name, synergy}) => 
          `<tr><td>${name}</td><td>${ synergy.flatMap(s => [s.partner, s.moveEqual]).join('</td><td>') }</td></tr>`).join('')).join('');
    
        table.insertAdjacentHTML('beforeend', html);
    }
    
    buildSynergyTest(chardata);
    <script>
    var chardata = [{
        "moveList": [{
            "name"    : "Directional Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Direct Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Direct Synergy for B" }]
        },{
            "name"    : "Motion Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Motion Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Motion Synergy for B" }]
        },{
            "name"    : "Button Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Button Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Button Synergy for B" }]
        }]
    }];
    </script>
    <table id="CharacterSynergy"></table>
    Login or Signup to reply.
  3. Here is another way of doing it:

    var chardata = [{
        "moveList": [{
            "name"    : "Directional Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Direct Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Direct Synergy for B" }]
        },{
            "name"    : "Motion Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Motion Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Motion Synergy for B" }]
        },{
            "name"    : "Button Tests",
            "synergy" : [{ "partner" : "Character A", "moveEqual" : "Button Synergy for A" }, { "partner" : "Character B", "moveEqual" : "Button Synergy for B" }]
        }]
    }];
    
    document.querySelector("table").innerHTML = chardata[0].moveList
     .flatMap(ml=>ml.synergy.map(s=>[ml.name,s.partner,s.moveEqual]))
     .sort(([_,a],[__,b])=>a.localeCompare(b))
     .map(r=>"<tr><td>"+r.join("</td><td>")+"</td></tr>")
     .join("n");
    table {border-collapse:collapse}
    td {border:1px solid grey;padding:4px}
    tr:nth-child(even) {background-color:#eee}
    <table></table>

    In the first .flatMap() loop I collect all the nested subarrays into a flat array and re-arrange them slightly: the property name is inserted as a first element of the returned array, followed by the partner and moveEqual properties of the array in the synergy property.

    The .sort() then sorts the array according to the second column ("Character A/B"). As the JavaScript sort function is now stable the original order of Directional, Motion and Button test is maintained.

    The .map() following the sort then creates some HTML that is evetually .join()ed with a newline character and assigned to the .innerHTML of the first table in the document (feel free to insert an #id selector here instead).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search