MSC
MSC

Reputation: 3386

Rendering TRs in Vue using a custom component

While I can render an HTML table fine using v-for and inline mustache syntax, I cannot achieve the same result using a component.

Vue / the browser removes the wrapping TABLE tag and inserts TRs outside a TABLE context, so they do not render properly:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>

<div id="app">

  <h2>Vue table rows inline (works)</h2>
  <table border="1">
    <tr>
      <th>Name</th>
      <th>Value</th>
    </tr>
    <tr v-for="(row, index) in mydata">
      <td>{{row.name}}</td>
      <td>{{row.value}}</td>
    </tr>
  </table>

  <h2>Vue table rows using component (broken)</h2>
  <table border="1">
    <tr>
      <th>Name</th>
      <th>Value</th>
    </tr>
    <my-row v-for="(row, index) in mydata" :key="row.name" :name="row.name" :val="row.value"></my-row>
  </table>

</div>

<script>
  Vue.component('my-row', {
    props: ['name', 'val'],
    template: '<tr><td>{{name}}</td><td>{{val}}</td></tr>'
  })

  new Vue({
    el: "#app",
    data: {
      mydata: [{
          name: "A",
          value: 1
        },
        {
          name: "B",
          value: 2
        },
        {
          name: "C",
          value: 3
        }
      ]
    }
  })
</script>

You can see this also at https://jsfiddle.net/MCAU/eywraw8t/128217/

What do I need to do to get the component version to work? (Adding a TBODY doesn't make any difference.)

Upvotes: 2

Views: 1970

Answers (2)

MSC
MSC

Reputation: 3386

Oh I have now found https://v2.vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats which explains that TRs are a special case and require the following syntax instead:

<tr is="my-row" v-for="(row, index) in mydata" :key="row.name" :name="row.name" :val="row.value"></tr>

Upvotes: 3

blobmaster
blobmaster

Reputation: 852

using functionnal component as suggested in : Vue js error: Component template should contain exactly one root element may do the trick.

copy/paste here :

if, for any reasons, you don't want to add a wrapper (in my first case it was for <tr/> components), you can use a functionnal component.

Instead of having a single components/MyCompo.vue you will have few files in a components/MyCompo folder :

  • components/MyCompo/index.js
  • components/MyCompo/File.vue
  • components/MyCompo/Avatar.vue

With this structure, the way you call your component won't change.

components/MyCompo/index.js file content :

import File from './File';
import Avatar from './Avatar';   

const commonSort=(a,b)=>b-a;

export default {
  functional: true,
  name: 'MyCompo',
  props: [ 'someProp', 'plopProp' ],
  render(createElement, context) {
    return [
        createElement( File, { props: Object.assign({light: true, sort: commonSort},context.props) } ),
        createElement( Avatar, { props: Object.assign({light: false, sort: commonSort},context.props) } )
    ]; 
  }
};

And if you have some function or data used in both templates, passed them as properties and that's it !

I let you imagine building list of components and so much features with this pattern.

Upvotes: 0

Related Questions