Displaying data from nested objects

I’m working on a script that would be a marksheet-type thing where teachers would simply click through questions and it would calculate scores for students and generate stats for the whole class/group.

I want to display data from nested objects that are templates for different types of tests, reflecting their structure: the number of parts, the number of questions in each part and the number of points exam takers can get for a correct answer. I need a function that would work with all of these templates.

Here is a shortened but hopefully clear example:

const b1listening = {
  testName: 'PET Listening',
  maxScore: 25,
  totalscore: null,
  testStructure: [
    {
      partNumber: 1,
      taskType: 'Three-option multiple choice',
      partscore: null,
      questions: [
        {
          questionNumber: 1,
          value: 1,
          score: null
        },
        {
          questionNumber: 2,
          value: 1,
          score: null
        },

// ...

        {
          questionNumber: 7,
          value: 1,
          score: null
        }
      ]
    },
    {
      partNumber: 2,
      taskType: 'Three-option multiple choice',
      partscore: null,
      questions: [
        {
          questionNumber: 8,
          value: 1,
          score: null
        },

// ...

        {
          questionNumber: 12,
          value: 1,
          score: null
        },
        {
          questionNumber: 13,
          value: 1,
          score: null
        }
      ]
    },
    {
      partNumber: 3,
      taskType: 'Gap fill',
      partscore: null,
      questions: [
        {
          questionNumber: 14,
          value: 1,
          score: null
        },
        {
          questionNumber: 15,
          value: 1,
          score: null
        },

// ...

        {
          questionNumber: 19,
          value: 1,
          score: null
        }
      ]
    },
    {
      partNumber: 4,
      taskType: 'Three-option multiple choice',
      partscore: null,
      questions: [
        {
          questionNumber: 20,
          value: 1,
          score: null
        },

// ...

        {
          questionNumber: 25,
          value: 1,
          score: null
        }
      ]
    }
  ]
};

See all of the templates here
Wherever there is an array within the main object (see partNumber and questionNumber) that means there might be a variable number of those across different templates. Scoring is variable as well: with some questions it’s either 1 point or 0, with others it’s 2 or 0 and with others it’s 1 or 2 or 0.

Here is the function I managed to put together:

function buildOutput(obj) {
  const marksheet = document.querySelector('.marksheet');

  marksheet.innerHTML =
    `
      <h1>${obj.testName}</h1>
      <h1>${obj.date} – ${obj.class}</h1>
      <h2 class="block">${obj.studentName} – TOTAL: <span class="scores">${obj.totalScore} / ${obj.maxScore}</span></h2>

      ${Object.values(obj.testStructure)
      .map(val =>
        `
            <h3 class="block">PART ${val.partNumber} ${val.taskType} <span class="scores">${val.partScore}</span></h3>

            ${Object.values(val.questions)
          .map(qn =>
            val.partNumber === 4 && val.taskType === 'Key word transformation'
              ? `<p>
                  <span class="qn-number">${qn.questionNumber}</span>
                  <button class="button btn-incorrect">0</button>
                  <button class="button btn-one">1</button>
                  <button class="button btn-two">2</button>
                  <span class="scores">${qn.score === null ? `&#9650` : qn.score}</span>
                    </p>`
              :
              `<p>
                  <span class="qn-number">${qn.questionNumber}</span>
                  <button class="button btn-incorrect">X</button>
                  <button class="button btn-correct">&#10003</button>
                  <span class="scores">${qn.score === null ? `&#9650` : qn.score}</span>
                  </p>`
          ).join("")
        }
          `
      ).join("")
    }
    `
};

It seems to work but to be honest it’s the outcome of trial and error rather than anything else. Any comments appreciated.

Levente

Just a few comments…

  1. Any particular reason you are using Object.values? If obj is just the object you setup in the first code example, you can access the first part of test structure just by using obj.testStructure[0] and to map through them obj.testStructure.map

  2. I would personally put your HTML display code in separate functions and then for each part you can pass the part object to that function where it would fill in the HTML placeholders and return the resulting HTML.

That way you can isolate the HTML (that may change over time) and also keep it out of your map loops (making them easier to read). This is rather than trying to cram them into a ternary operator. I try to keep display code out of the logic as much as possible.

Another benefit is that each function can then represent each template type. One for three-option multiple choice, another for gap fill etc. If you later introduce a new question type, you create the new template function and put it in with minimal wiring.

Try these out and see what you can come up with. If you do it right, I think these tips will shorten up your code quite a bit and improve readability. :slight_smile:

I’m not entirely sold on the idea of map at all, tbh. If you’re building a string, walk (forEach) the array and build the string, rather than compiling an array which you then turn into a string. The only time I could see real need for the map->join combination would be if join were to be inserting some actual ‘glue’ between the pieces of the result.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.