Search code examples
javascriptvue.jsvuejs3v-for

v-for seems to change the array order


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?


Solution

  • 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>