1

I have a 2D array within my Javascript. The array is assigned the variable 'arr' and looks like this:

[
  [
    "0",
    {
      cat_id: "1",
      cat_name: "Tiger",
    },
  ],
  [
    "1",
    {
      cat_id: "2",
      cat_name: "Lion",
    },
  ],
  [
    "2",
    {
      cat_id: "3",
      cat_name: "Cheetah",
    },
  ],
  [
    "3",
    {
      cat_id: "5",
      cat_name: "Leopard",
    },
  ],
]

I want to give the user a neat little HTML table on screen, with the keys being the header and the values being the rows, thus:

cat_id cat_name
1 Tiger
2 Lion
3 Cheetah
4 Leopard

I've been looking at many articles on StackOverflow and the web in general, but I'm not a Javascript expert and, honestly, I can't get anything to work. Any help greatly appreciated!

My HTML contains the empty table code:

const arr = [["0", { cat_id: "1", cat_name: "Tiger", },], ["1", { cat_id: "2", cat_name: "Lion", },], ["2", { cat_id: "3", cat_name: "Cheetah", },], ["3", { cat_id: "5", cat_name: "Leopard", },],]

var tableBody = document.getElementById("wrap");
tableBody.innerHTML = "";
arr.forEach(function (row) {
  var newRow = document.createElement("tr");
  tableBody.appendChild(newRow);
  if (row instanceof Array) {
    row.forEach(function (cell) {
      var newCell = document.createElement("td");
      newCell.textContent = cell;
      newRow.appendChild(newCell);
    });
  } else {
    newCell = document.createElement("td");
    newCell.textContent = row;
    newRow.appendChild(newCell);
  }
});
<table id="wrap"></table>

Because arr is a 2D array, right now my code just produces this as a table:

0   [object Object]
1   [object Object]
2   [object Object]
3   [object Object]

I understand why this is happening, but my JS are simply not good enough to get the result I'm trying to achieve.

Edit

The properties of the nested objects won't be known ahead of time so the solution needs to be dynamic.

eg:

arr = [
  ["0", { dog_id: "8866", dog_colour: "Brown", dog_desc: "Small, bit grumpy" }],
  ["1", { dog_id: "8867", dog_colour: "Black", dog_desc: "Nice dog" }],
  ["2", { dog_id: "8868", dog_colour: "Brown", dog_desc: "Dead" }],
  ["3", { dog_id: "8869", dog_colour: "Blonde", dog_desc: "Barks a lot" }],
];
pilchard
  • 12,414
  • 5
  • 11
  • 23
  • You simply need to access the property you want to show in the table column `cell.cat_name` – pilchard Aug 12 '23 at 09:24
  • Then iterate over the [`Object.entries`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) – pilchard Aug 12 '23 at 09:29
  • Sorry, I should have clarified that I cannot hard-code any declarations to look for specific keys (like 'cat_name') because this code will also receive other arrays that will have different keys and lengths. For, for example, the same code may receive a 2D array whose child array has the keys dog_id, dog_size, dog_colour. In essence, my page needs to be able to drill into that array, into the child-array that actually contains the data I want, then build an HTML table that has the keys as the column headers and the values as the rows. Those keys will change in both content and number. – Tezcatlipoca Aug 12 '23 at 09:31
  • duplicate: [Make HTML Table from Array of Unknown Objects](https://stackoverflow.com/questions/33860918/make-html-table-from-array-of-unknown-objects) – pilchard Aug 12 '23 at 09:37
  • You'll need to show an example of the expanded data; will it be more objects? or more properties in a single object per row – pilchard Aug 12 '23 at 09:38
  • So an example of the expanded one might be: arr = [ [ "0", { "dog_id": "8866", "dog_colour": "Brown", "dog_desc": "Small, bit grumpy" } ], [ "1", { "dog_id": "8867", "dog_colour": "Black", "dog_desc": "Nice dog" } ], [ "2", { "dog_id": "8868", "dog_colour": "Brown", "dog_desc": "Dead" } ], [ "3", { "dog_id": "8869", "dog_colour": "Blonde", "dog_desc": "Barks a lot" } ] ] Exact same array structure, just - in this case - three keys instead of two in the child array – Tezcatlipoca Aug 12 '23 at 09:50
  • In that case the example I've given below will work. You can implement your own sorting if you need the columns in a specific order. – pilchard Aug 12 '23 at 09:51

1 Answers1

1

Here is a slightly more robust solution, which will accommodate objects with different numbers of properties, and avoids the need to sort the keys by using a single array of column header keys to access each successive row's properties.

It first gathers all the Object.keys of all the nested objects and uses these to build the header row. It then iterates over each object in the original array and uses the accumulated column keys to access the properties to build the value rows. If the property is not present in the object it uses nullish coallescing ?? to assign an empty string to the column for that row.

const arr = [
  ["0", { cat_id: "1", cat_name: "Tiger" }],
  ["1", { cat_id: "2", cat_name: "Lion", "cat_desc": "Roars"}],
  ["2", { cat_id: "3", cat_name: "Cheetah" }],
  ["3", { cat_id: "5", cat_name: "Leopard" }],
];

const columns = new Set(arr.flatMap(([, obj]) => Object.keys(obj)));

// Create the header
const header = document.createElement("thead");
const headerRow = document.createElement("tr");
header.appendChild(headerRow);
for (const column of columns) {
  const newCell = document.createElement("th");
  newCell.scope = "col"
  newCell.textContent = column;
  headerRow.appendChild(newCell);
}

// Create the body 
const body = document.createElement("tbody");
for (const [, obj] of arr) {
  const newRow = document.createElement("tr");
  body.appendChild(newRow);
  for (const column of columns) {
    const newCell = document.createElement("td");
    newCell.textContent = obj[column] ?? "";
    newRow.appendChild(newCell);
  }
}

// Query and update the table
const table = document.getElementById("wrap");
table.innerHTML = "";
table.append(header, body);
table {
  min-width: 400px;
  text-align: left;
}
<table id="wrap"></table>

see:

Original answer

Here is an example that iterates the Object.entries of each nested data object to first build a header row from the keys, and then data rows from the values. This assumes that any further columns will be added as properties to the single object at index 1 of the nested arrays.

It first maps the original array to be an 2d array of [key, value] tuples and sorts each array of tuples by key. It then iterates the keys of the first element in the array to create a header. It then proceeds to iterate each element in the array to build each data row.

const arr = [
  ["0", { cat_id: "1", cat_name: "Tiger" }],
  ["1", { cat_id: "2", cat_name: "Lion" }],
  ["2", { cat_id: "3", cat_name: "Cheetah" }],
  ["3", { cat_id: "5", cat_name: "Leopard" }],
];

var tableBody = document.getElementById("wrap");

tableBody.innerHTML = "";

const data = arr.map(a =>
  Object.entries(a[1]).sort(([a], [b]) => a.localeCompare(b))
);

const headerRow = document.createElement("tr");
tableBody.appendChild(headerRow);
for (const [key] of data[0]) {
  const newCell = document.createElement("td");
  newCell.textContent = key;
  headerRow.appendChild(newCell);
}

for (const datum of data) {
  const newRow = document.createElement("tr");
  tableBody.appendChild(newRow);
  for (const [, value] of datum) {
    const newCell = document.createElement("td");
    newCell.textContent = value;
    newRow.appendChild(newCell);
  }
}
<table id="wrap"></table>

Relevant topics:

pilchard
  • 12,414
  • 5
  • 11
  • 23
  • That's it! That is *precisely* what I needed. The code can take any array of the type I'm giving it and turns it into an HTML table with the keys as column headers and the values as rows. Thank you so much for your time on this. I'm not a JS developer, so what is childsplay to you was causing enormous headaches to me. It's now working, and I can go through your code to teach myself the process and learn where I went wrong. – Tezcatlipoca Aug 12 '23 at 09:53