1

Data Object:

{
    "headers": {
        "location": "Location",
        "postcode": "Postcode",
        "contributors": "Contributors",
        "contributions": "Contributions",
        "percentage": "Percentage"
    },
    "rows": [
        {
            "postcode": "3018",
            "contributors": 2,
            "contributions": 2,
            "location": "Seaholme",
            "percentage": 67
        },
        {
            "postcode": "3013",
            "contributors": 1,
            "contributions": 1,
            "location": "Yarraville West",
            "percentage": 33
        }
    ]
}

Template:

<thead>
<tr>
    <th v-for="(v, k) in data.result.headers" :key="k">
    {{ v }}
    </th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in data.result.rows" :key="i">
    <td :key="j" v-for="(col, j) in row">
        {{ col }}
    </td>
</tr>
</tbody>

Output: enter image description here

So the table header and body are two separate objects. While the header seems to follow the order but the row objects don't. How can I make sure they always align correctly?

eozzy
  • 66,048
  • 104
  • 272
  • 428
  • Fix whatever's populating the API to be more consistent in the object property order. Or, even better, don't depend on object property order at all – CertainPerformance Jul 21 '21 at 22:56
  • @CertainPerformance Fixing the API is out of scope. As for not relying on object order, whats the alternative? Using Map? – eozzy Jul 21 '21 at 23:00
  • I don' t know Vue so I can't say what the syntax would be, but the idea would be to, inside the tbody loop, reference the properties in `data.result.headers` instead of doing `(col, j) in row` – CertainPerformance Jul 21 '21 at 23:02
  • 1
    Note the resulting order isn't actually caused by Vue, but rather it's [how object iteration works in JavaScript](https://stackoverflow.com/a/5525820/6277151). – tony19 Jul 21 '21 at 23:31
  • [Vue docs](https://v3.vuejs.org/guide/list.html#v-for-with-an-object) - *When iterating over an object, the order is based on the enumeration order of `Object.keys()`, which isn't guaranteed to be consistent across JavaScript engine implementations.* – Michal Levý Jul 22 '21 at 05:10

1 Answers1

3

You can create a computed property of the rows. This would be the same list but with the keys ordered in the order of the header keys. Here is a possible solution:

new Vue({
  el: "#app",
  data: () => ({
    "headers": { "location": "Location", "postcode": "Postcode", "contributors": "Contributors", "contributions": "Contributions", "percentage": "Percentage" },
    "rows": [
      { "postcode": "3018", "contributors": 2, "contributions": 2, "location": "Seaholme", "percentage": 67 },
      { "postcode": "3013", "contributors": 1, "contributions": 1, "location": "Yarraville West", "percentage": 33 }
    ]
  }),
  computed: {
    orderedRows() {
      const headers = Object.keys(this.headers);
      return this.rows.map(row => 
        headers.reduce((orderedRow, key) => 
          ({ ...orderedRow, [key]: row[key] })
        , {})
      );
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <table>
    <thead>
      <tr>
        <th v-for="(v, k) in headers" :key="k">{{ v }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(row, i) in orderedRows" :key="i">
        <td v-for="(col, j) in row" :key="j">{{ col }}</td>
      </tr>
    </tbody>
  </table>
</div>

Another possible way inspired from @CertainPerformance comment:

new Vue({
  el: "#app",
  data: () => ({
    "headers": { "location": "Location", "postcode": "Postcode", "contributors": "Contributors", "contributions": "Contributions", "percentage": "Percentage" },
    "rows": [
      { "postcode": "3018", "contributors": 2, "contributions": 2, "location": "Seaholme", "percentage": 67 },
      { "postcode": "3013", "contributors": 1, "contributions": 1, "location": "Yarraville West", "percentage": 33 }
    ]
  }),
  computed: {
    headerKeys() {
      return Object.keys(this.headers);
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <table>
    <thead>
      <tr>
        <th v-for="(v, k) in headers" :key="k">{{ v }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(row, i) in rows" :key="i">
        <td v-for="(header, j) in headerKeys" :key="j">{{ row[header] }}</td>
      </tr>
    </tbody>
  </table>
</div>
Majed Badawi
  • 27,616
  • 4
  • 25
  • 48