2

I've got a JSON feed with some tabular data and I'm using Vue.js to show a HTML table:

<tr v-for="row, index in content">
    <td class="index" v-text="index + 1"></td>
    <td v-for="column in row" v-text="column"></td>
</tr>

This feed returns an array for each row, but some rows have named keys. With this simple foreach-loop it ignores the keys and this will generate a table that doesn't show the data in a correct way.

How can I use the keys to generate the table? I've looked into getting the highest value from the keys and use Array.fill(), but I think there must be a better way.

This is my data:

{
  "content": [
    [
      "",
      "In scope:",
      "Not in scope:"
    ],
    [
      "Cars",
      "",
      "X"
    ],
    [
      "Bikes",
      "X",
      ""
    ],
    {
      "0": "Houses",
      "2": "X"
    }
  ]
}

Please note that this is fictional data, the actual data can vary so I can't have a solution with a fixed number of columns.

Actual output:

|        | In scope: | Not in scope: |
|--------|-----------|---------------|
| Cars   |           | X             |
| Bikes  | X         |               |
| Houses | X         |

Expected output:

|        | In scope: | Not in scope: |
|--------|-----------|---------------|
| Cars   |           | X             |
| Bikes  | X         |               |
| Houses |           | X             |
Stephan Vierkant
  • 9,674
  • 8
  • 61
  • 97
  • Are you able to preprocess the data (before handing it to the view)? In that case you might want to transform it into a known structure, such as: https://jsfiddle.net/exh824v1/1/ - Does that meet your requirements? – Caramiriel Jun 24 '19 at 19:23
  • 1
    I'd hope the first element contains all the columns header definitions, but that's just an assumption. – Caramiriel Jun 24 '19 at 19:25
  • From the top of my head I can only think of the `Array.fill()` way you are describing. Would be interesting if there's a native Vue.js solution. – dweipert Jun 24 '19 at 19:29

5 Answers5

3

It looks like your first row is a header row. If you can accept that as the source of truth for the length of the following items, you can make a simple function that maps over the array and enforces that length for all the items. This will normalize the items, making them all arrays, fill in holes, remove items that overflow the length, and ignore non-numeric keys on objects:

let o = {
    "content": [
      [
        "",
        "In scope:",
        "Not in scope:"
      ],
      [
        "Cars",
        "",
        "X"
      ],
      [
        "Bikes",
        "X",
        "",
        "extra data!"          // < will be removed 
      ],
      {
        "non number": "huh?",  // < will be removef
        "0": "Houses",         // will fill in index 1 with undefined
        "2": "X",
        
      }
    ]
  }
const enforceLength = (l, arr) => arr.map(arr => Array.from({length: l, ...arr}))
let l = o.content[0].length

o.content = enforceLength(l, o.content)

console.log(o.content)

If you put this in a method on your component you could simple call it in the template with something like:

<tr v-for="row, index in normalize(content)">
Mark
  • 90,562
  • 7
  • 108
  • 148
0

Your idea to use Array#fill seems fine to me: you need to define the values for the gaps (empty string in your case).

But since the indexes of your non-Array object seem to always be numeric, finding the maximum key is a bit easier. Although ES6 specs do not define the order for what Object.keys returns, all current JS engines produce the keys in numerical order (when the keys represent positive integers within the 64-bit range), so you can just pop the last value from it:

column in Object.assign(Array(+Object.keys(row).pop()+1).fill(""), row)
trincot
  • 317,000
  • 35
  • 244
  • 286
-1

The data isn't ideal, because object properties don't assume numbered indexes (the object obviously won't "fill in gaps" if other numbers are declared as keys). You could map to a new array, filling in any undefineds on elem[1] within a computed property in your Vue component:

computed: {
  contentAsArrays() {
    return this.content.map(e => {
      if (e[1] === undefined) e[1] = "";
      return e;
    });
  },
},

And reference this computed property in your template:

<tr v-for="row, index in contentAsArrays">
rwest88
  • 9
  • 5
  • 1
    I didn't downvote this, but I suspect it's not a great solution because it's not general enough. The OP says: `the actual data can vary so I can't have a solution with a fixed number of columns.` So `e[1]` might be not always be where the undefined data is. That last object could as easily be `{ "0": "Houses", "1": "X" }` – Mark Jun 24 '19 at 19:57
-2

With this simple foreach-loop it ignores the keys

Assuming you're referring to Array.prototype.forEach, the 2nd param to the handler func is the iteration index:

let arr = ['zero', 'one'];
arr[10] = 'ten';

arr.forEach((value, index) => console.log(value, index));
junvar
  • 11,151
  • 2
  • 30
  • 46
-2

To me i think the issue is that the loop is moving with an index and suddenly you don't have that index.

So what i believe happens is:

It's looping index=0 gives "Bikes"

Than looping index=1 gives "X",

Than looping index=2 gives "" (third member in the object)

And than you're giving it a two member object and it goes 0 to 1

So try to save it as:

     - "0":"houses"
     - "1":""
     - "2":"X"
Eliran Efron
  • 621
  • 5
  • 16