1

I've nested data I'd like to display in tabular format. Here is some example data; in the real application there may sometimes be dozens of entries in rows.

<script>
export default {
  name: 'Example',

  data () {
    return {
      results: [
        { a: 'AAA', rows: { BBB: 'CCC' } },
        { a: 'D', rows: { E: 'F', G: 'H' } },
        { a: 'Tom, Dick & Harry', rows: { x: 'y' } },
      ],
    }
  },
}
</script>

Note: the inner value ('CCC', 'F', 'H', 'y') might end up being a further nested array or object.

I tried this grid layout, with v-for loops:

<template>
<div style="width:100%">
  <div v-for="(res,key) in results" :key="key" class="results">
    <div class="A">{{res.a}}</div>
    <div v-for="(c,b) in res.rows" :key="b">
      <div class="B">{{b}}</div>
      <div class="C">{{c}}</div>
    </div>
  </div>
</div>
</template>
<style scoped>
.results {display:grid;grid-template-columns: 1fr 1fr;}
.A {font-weight:bold;grid-column:1/3;}
.B {grid-column:1;padding:0.5rem;}
.C {grid-column:2;padding:0.5rem;}
</style>

(Aside: the more logical grid-column:1/2, to get a span of two columns, seems to do nothing...)

This gives me the wrong layout; I've appended a good old-fashioned table layout to show what I am trying to achieve (HTML for that at end of question).

incorrect grid, followed by desired table layout

Do I have a simple bug? Or, is there a whole better approach to using vue and grid together? (Bearing in mind, as mentioned above, that I may end up with a 3rd v-for loop.)

By the way, last time I tried using grid I gave up, and used <table> tags; I got around the problem of v-for creating html tags by putting it on a <tbody>, i.e. having a table body for each section. But that won't scale to a 3rd v-for loop. And I'd rather use CSS grid, if I can, as it gives more power for responsive layouts.

The table HTML, for reference:


  <table border="1" cellspacing=0 style="width:100%;">
    <tr>
      <td colspan="2"><b>AAA</b></td>
    </tr>
    <tr>
      <td>BBB</td>
      <td>CCC</td>
    </tr>
    <tr>
      <td colspan="2"><b>D</b></td>
    </tr>
    <tr>
      <td>E</td>
      <td>F</td>
    </tr>
    <tr>
      <td>G</td>
      <td>H</td>
    </tr>
    <tr>
      <td colspan="2"><b>Tom, Dick & Harry</b></td>
    </tr>
    <tr>
      <td>x</td>
      <td>y</td>
    </tr>
  </table>

2 Answers 2

3

I just stumbled upon the solution of how to deal with vue v-for loops, when you don't want them to create DOM elements, which was exactly what I needed here.

First to explain the problem, and why I got the wrong layout: CSS grids insist on applying the layout to the grid container's child elements. (As far as I can tell it is completely inflexible on this, e.g. offering no way to tag a div as not being part of the layout.)

Anyway, the solution was to switch my inner <div v-for=""> into a <template v-for="">

<template>
<div style="width:100%">
  <div v-for="(res,key) in results" :key="key" class="results">
    <div class="A">{{res.a}}</div>
    <template v-for="(c,b) in res.rows">
      <div class="B" :key="b">{{b}}</div>
      <div class="C" :key="'_'+b">{{c}}</div>
    </template>
  </div>
</div>
</template>

There is one downside: you cannot put the :key on the <template> tag. Fair enough, given that it does not get rendered. But that means every child element of the <template> needs a :key, and they each have to be different. I have used a hacky kind of workaround above. (See also https://stackoverflow.com/a/67784407/841830 )

In my actual application, I have dropped those two :key definitions, and used <!-- eslint-disable vue/require-v-for-key --> to stop eslint complaining. It is my understanding this won't cause any problems as the whole of res will be replaced if it changes. But if not sure, go with using :key everywhere.

Sign up to request clarification or add additional context in comments.

Comments

1

Try with arrays instead objects:

new Vue({
  el: '#demo',
  data () {
    return {
      results: [
        { a: 'AAA', rows: [{id:'BBB'}, {id:'CCC', more: [{id: '1'}, {id: '2'}]}] },
        { a: 'D', rows: [{id:'E'}, {id:'F', more: [{id: '1'}, {id: '2'}]}, {id:'G'}, {id:'H', more: [{id: '1'}, {id: '2'}]}] },
        { a: 'Tom, Dick & Harry', rows: [{id:'x'}, {id:'y', more: [{id: '1'}, {id: '2'}]}] },
      ],
    }
  },
})


Vue.config.productionTip = false
Vue.config.devtools = false
.results {display:grid;grid-template-columns: 1fr 1fr;}
.A {font-weight:bold;grid-column:1/3;}
.B {grid-column:1;padding:0.5rem;}
.C {grid-column:2;padding:0.5rem;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">

<div style="width:100%">
  <div v-for="(res,key) in results" :key="key" class="results">
    <div class="A">{{res.a}}</div>
    <div v-for="(c,b) in res.rows" :key="b">
      <div class="B">{{c.id}}</div>
      <div v-for="(z,i) in c.more" :key="i">
        <div class="C">{{z.id}}</div>
      </div>
    </div>
  </div>
</div>
      
</div>

3 Comments

Interesting. This doesn't look like it scales to the 3rd nested loop (unless you are suggesting unrolling all nested objects into a single array?). But I'm more interested in why using the array sends the cells horizontally, whereas using the object sends the cells vertically. Can you explain it?
Hey mate, if you want more nested loops just put more v-for loops. In your example you have have 2 divs in nested v-for but in cases b:c and x:y only one object.
I updated answer with more nested objects (if that's what you want to accomplish), take a look pls, cheers :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.