3

I would like to use Vue to render a table with rows but have the cells as a component.

I have made a working example of what I would like to see as the end result and have some code relating to how I think that I could go about this:

HTML

<div id="app">
  <table>
    <thead>
      <tr>
        <th>Row</th>
        <th>ID</th>
        <th>Name</th>
        <th>Age</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(row, rindex) in people">
        <td>{{ rindex }}</td>
        <cell v-for="(value, vindex, rindex) in row" :value="value" :vindex="vindex" :rindex="rindex"></cell>
      </tr>
    </tbody>
  </table>
  <template id="template-cell">
    <td>{{ value }}</td>
  </template>
</div>

JS

// Register component
Vue.component('cell', {
  template: '#template-cell',
  name: 'row-value',
  props: ['value', 'vindex', 'rindex']
  // In here I would like to access value, vindex and rindex
});
// Start application
new Vue({
  el: '#app',
  data: {
    people: [
      {id: 3, name: 'Bob', age: 27},
      {id: 4, name: 'Frank', age: 32},
      {id: 5, name: 'Joe', age: 38}
    ]
  }
});

This is because, in the Vue documentation on v-for they show the example:

And another for the index:

<div v-for="(value, key, index) in object">
  {{ index }}. {{ key }} : {{ value }}
</div>

So logically thinking, I should be able to get the rows index in the cell component without too much issue but this is not so as it comes up with a bunch of errors indicating that the values are undefined.

If anyone could point me in the right direction here then I would be greatly appreciate it as I'm out of ideas with this problem but would really like to understand how to implement this correctly.

(Note: the reason for me wanting to render the cells in the component is to register the change of values, see the answer to another question that I asked here if you're interested)

tony19
  • 125,647
  • 18
  • 229
  • 307
Craig van Tonder
  • 7,497
  • 18
  • 64
  • 109

2 Answers2

9

You just need to enclose your cell component inside an template element, this is because table in HTML expects only td inside tr, when it sees cell, it will not render it(see similar problem with Angular here). However template can be used here as a content fragment and after vue compiles, it will add tr in place of cell in the HTML.

  <tr v-for="(row, rindex) in people">
    <td>{{ rindex }}</td>
    <template>
       <cell  v-for="(value, vindex) in row" :value="value" :vindex="vindex" :rindex="rindex" ></cell>
    </template>
  </tr>

see working pen here.

Community
  • 1
  • 1
Saurabh
  • 71,488
  • 40
  • 181
  • 244
  • Thanks for the codepen example. I was not sure if custom labels like `rindex` can be used as an iterator in `v-for`, and ended up writing a completely different answer. – Mani Dec 15 '16 at 02:06
  • When I was trying to do this I found something similar but it used v-for in the template tag. I figured this to be a bit of a hack to excluded it from my question but when you say what you have and thinking about it now, it's a logical way to approach this and somewhat of an odd case when dealing with tr and td tags as you can't wrap them in any other tags or the table will not render correctly. – Craig van Tonder Dec 15 '16 at 08:14
2

I am not sure if you can replace index with rindex in the v-for constructor. If the implementation of v-for expects the string labels for key and index, then your rindex and vindex will not work.

I see another problem, as quoted from the reference page in question: List Rendering in Vue

When iterating over an object, the order is based on the key enumeration order of Object.keys(), which is not guaranteed to be consistent across JavaScript engine implementations.

Therefore, your first cell may have (id, key, name) in that order, while your second cell may have (name, id, key) - different order. Therefore, the best way is to avoid object iterator for cell and explicitly define the order of appearance as follows:

<tr v-for="(person, index) in people">
    <td>{{ index }}</td>
    <cell :value="index" valueType="index" :rowIndex="index"></cell>
    <cell :value="person.id" valueType="id" :rowIndex="index"></cell>
    <cell :value="person.name" valueType="name" :rowIndex="index"></cell>
</tr>

Now your cell component definition needs to be modified as:

Vue.component("cell", {
    template: "#template-cell",
    name: "row-value",
    props: ["value", "valueType", "rowIndex"],
    // your component code here...
})

Now you can use valueType and rowIndex inside your cell component.

tony19
  • 125,647
  • 18
  • 229
  • 307
Mani
  • 23,635
  • 6
  • 67
  • 54
  • I did notice the note when reading the docs and experienced this to my bewilderment in the past so what you have said does make sense to me, thank you. Two questions, one is that should there not be a prop `"value"` and also, this is a noob question but I mean logically thinking, if I create a listener on each cell it's going to take more resources than creating it per row or does it make no difference? – Craig van Tonder Dec 15 '16 at 09:33
  • My apologies, there should be a `value` field also in `props`. I forgot about it. I will update the answer to include value in props. And for second question regarding performance / resource consumption, yes - you will consume a bit more memory on the browser. I think it does not make much difference as Vue handles resources very well, thus allowing us to create an easy-to-maintain app. Check this out: http://www.stefankrause.net/wp/?p=316 – Mani Dec 15 '16 at 09:45