vue.js v-for on two table rows - vue.js

Vue 2, no webpack. I want to render two trs at a time, for main and detail expandable row. This is what I'm trying to achieve:
<table>
<tbody>
<div v-for="item in items">
<tr></tr>
<tr class="detail-row"></tr>
</div>
</tbody>
</table>
The problem is that <div> is an invalid child of tbody. How to render two <tr>s at each for loop iteration?

This is the way you solve it in browsers that support template.
<table>
<tbody>
<template v-for="item in items">
<tr></tr>
<tr class="detail-row"></tr>
</template>
</tbody>
</table>
If you need to support browsers that do not support template, I typically resort to a render function.
Here is a working example of both.
console.clear()
new Vue({
el: "#app",
data: {
items: [{
master: "Master",
detail: "Detail"
},
{
master: "Master",
detail: "Detail"
},
]
}
})
new Vue({
el: "#app2",
data: {
items: [{
master: "Master",
detail: "Detail"
},
{
master: "Master",
detail: "Detail"
},
]
},
render(h){
// build the rows
let rows = []
for (let item of this.items){
rows.push(h("tr", [h("td", [item.master])]))
rows.push(h("tr", {class: "detail-row"}, [h("td", [item.detail])]))
}
// add rows to body
let body = h("tbody", rows)
// return the table
return h("table", [body])
}
})
.detail-row{
background-color: lightgray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<h2>Using template</h2>
<div id="app">
<table>
<tbody>
<template v-for="item in items">
<tr><td>{{item.master}}</td></tr>
<tr class="detail-row"><td>{{item.detail}}</td></tr>
</template>
</tbody>
</table>
</div>
<h2>Using render function</h2>
<div id="app2"></div>

In newer version of VueJS, it wants an index. So the solution would look like
<table>
<tbody>
<template v-for="(item, index) in items">
<tr :key="index">
<td>{{item.master}}</td>
</tr>
<tr :key="index" class="detail-row">
<td>{{item.detail}}</td>
</tr>
</template>
</tbody>
</table>

If you want to use in double tag.
Or want to use a separate component in the template div within the table tr tags (as in a new component) you could use style="display: contents" in the first div to keep the table rows inline with each other.
Vue component
<table>
<template v-for="v-for="(price, index) in prices">
<div :key="price.id" style="display: contents">
<tr><td>{{price.id}}</td><td>{{price.name}}</td></tr>
<tr col-span="2">{{price.desc}}</tr>
</div>
</template>
</table>
Or if you want to use a separate component for the rows
Table.vue
<template>
<div>
<table class="table">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
</tr>
</thead>
<tbody>
<template v-for="item in items">
<my-component :item=“item”/>
</template>
</tbody>
</table>
</div>
</template>
my-component.vue
<template>
<div style="display: contents">
<tr>
<td>{{item.firstname}}</td>
<td>{{item.lastname}}</td>
</tr>
<tr>
<td colspan="2" >
{{item.description}}
</td>
</tr>
</div>
</template>

Instead of using display: content in the div and you want the rows linked together you can use display: table-row-group type
<table>
<div v-for="v-for="(price, index) in prices"
v-bind:key="price.id"
style="display: table-row-group">
<tr>
<td>{{price.id}}</td><td>{{price.name}}</td>
</tr>
<tr col-span="2">
{{price.desc}}
</tr>
</div>
</table>

Related

How do I target a dynamic second table in vue2

I am using v-for in Nuxt vue2 to create a table with a table with in it but cant find any way to target the second table.
This is the HTML I am using and the command lines I am using to target the data.
The first table appears to work but I have tried everything I can think of to target the second table. Hopefully someone can show me how to do it and more importantly explain why there is a problem with the inner table.
<template>
<div>
Working with the DOM Vue2
<table ref="table1">
<tr v-for="index in 5" :key="index">
<th>Company{{ index }}</th>
<th>
<table ref="tableChild">
<tr v-for="index in 2" :key="index">
<th>Inner table Line{{ index }}</th>
</tr>
</table>
</th>
</tr>
</table>
</div>
</template>
<script>
export default {
mounted() {
//-------- targeting the two tables --------------
console.log("table1:", this.$refs.table1);
console.log("table1-all children:", this.$refs["table1"].children);
console.log("table1-all children0:", this.$refs["table1"].children[0]);
console.log("tableChild:", this.$refs.tableChild);
console.log("tableChild-all children:", this.$refs["tableChild"].children);
console.log("tableChild-all children0:", this.$refs["tableChild"].children[0]);
}
}
</script>
<style>
</style>
To access the inner table data by using ref. Here you go :
this.$refs.tableChild[0].children[0]
Live Demo :
new Vue({
el: '#app',
mounted() {
console.log("table1:", this.$refs.tableChild[0].children[0].innerText);
}
})
table, tr, th {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table ref="table1">
<tr v-for="index in 5" :key="index">
<th>Company{{ index }}</th>
<th>
<table ref="tableChild">
<tr v-for="index in 2" :key="index">
<th>Inner table Line{{ index }}</th>
</tr>
</table>
</th>
</tr>
</table>
</div>

VueJs router-link not working, but link copied from it works in another tab

I am building an webapp with VueJs. I have three pages, apps, registries, identities. In the apps page there is a table with apps with a link1 (localhost:8080/apps/{appid}/regs) to view it's registry in each row. Similarly in registry page, there is a link2
(localhost:8080/apps/{appid}/regs/{regid}/ids) to view it's app.
My problem is, when I click on link1 it takes me to the registries page, but when I click on link2 it takes me to localhost:8080 in stead of localhost:8080/apps/{appid}/regs/{regid}/ids. But when I copy the address from link2 and paste in address bar it takes me to the desired page. What am I doing wrong?
Also I am getting following warning in the console -
[Vue warn] missing param for named route "regs": Expected "appid" to
be defined
[Vue warn] missing param for named route "ids": Expected "appid" to be
defined
router.js
{
path: "apps",
name: "apps",
component: () => import("./components/protected/Apps"),
},
{
path: "apps/:appid/regs",
name: "regs",
props: true,
component: () => import("./components/protected/Regs"),
},
{
path: "apps/:appid/regs/:regid/ids",
name: "ids",
props: true,
component: () => import("./components/protected/Ids"),
},
Apps.vue
<template>
<table class="table table-striped mg-b-0">
<thead>
<tr>
<th class="tx-left w-5">#</th>
<th class="tx-left">Application</th>
<th class="tx-left text-center w-5">Action</th>
</tr>
</thead>
<tbody>
<tr v-bind:key="index" v-for="(app, index) in apps">
<td class="masked">{{index+1}}</td>
<td>{{app.name}}</td>
<td class="text-center">
<router-link class="bg-white" :to="{ name: 'regs', params: { appid: app.id}}">
<i class="fa fa-border fa-eye" title="View Registries"/>
</router-link>
</td>
</tr>
</tbody>
</table>
</template>
Regs.vue
<template>
<template v-if="appid">
<table class="table table-striped mg-b-0">
<thead>
<tr>
<th class="tx-left w-5">#</th>
<th class="tx-left">Registry</th>
<th class="tx-left text-center w-5">Action</th>
</tr>
</thead>
<tbody>
<tr v-bind:key="index" v-for="(reg, index) in regs">
<td class="masked">{{index+1}}</td>
<td>{{reg.name}}</td>
<td class="text-center">
<router-link class="bg-white" :to="{ name: 'ids', params: { appid: appid, regid: reg.id}}">
<i class="fa fa-border fa-eye" title="View Identities"/>
</router-link>
</td>
</tr>
</tbody>
</table>
</template>
</template>
<script>
export default {
name: "Regs",
props: ['appid'],
....
}
Ids.vue
<template>
<template v-if="appid && regid">
<table class="table table-striped mg-b-0">
<thead>
<tr>
<th class="tx-left w-5">#</th>
<th class="tx-left">Identity</th>
</tr>
</thead>
<tbody>
<tr v-bind:key="index" v-for="(id, index) in ids">
<td class="masked">{{index+1}}</td>
<td>{{id.name}}</td>
</tr>
</tbody>
</table>
</template>
</template>
<script>
export default {
name: "Ids",
props: ['appid','regid'],
....
}
</script>
It seems that you are already using the props appid' and 'regid' where the props are not yet fully ready in your component itself. Thats why the vue returned a warning.
You could put a condition first before you execute your vfor to ensure that the props has already a value. like this one
Regs.vue
<template v-if="appid">
<tr v-bind:key="index" v-for="(reg, index) in regs">
<td class="masked">{{index+1}}</td>
<td>{{reg.name}}</td>
<td class="text-center">
<router-link class="bg-white" :to="{ name: 'ids', params: { appid: appid,
regid: reg.id}}">
<i class="fa fa-border fa-eye" title="View Identities"/>
</router-link>
</td>
</tr>
</template>
Ids.vue
<template v-if="appid && regid">
<tr v-bind:key="index" v-for="(id, index) in ids">
<td class="masked">{{index+1}}</td>
<td>{{id.name}}</td>
</tr>
</template>

Store loop object in vue.js

I would like to store loop object in id of HTML element so that I can use it when click on this element. My code is like below
<tr v-for="obj in data">
<td v-for="(value, key) in obj" :id="obj"></td>
</tr>
But it is not working. Could anyone help me in this regard ?
You Can Call Method On Click Of td element of table , and pass current obj to that method.
var app = new Vue({
el: '#app',
data: {
arr:[{name:'Demo User',email:'demoUser#gmail.com'}, {name:'James Bond',email:'james#gmail.com'}]
},
methods:{
getData:function(item){
alert(item.name+"--"+item.email);
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<table border="1">
<tbody>
<tr v-for="item in arr">
<td v-for="(value, key) in item" v-on:click="getData(item)">{{value}}</td>
</tr>
</tbody>
</table>
</div>

Vue: List rendering returns error

I am simply trying to render a list in my view and I keep seeing the error:
[Vue warn]: Property or method "client" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
Here's my view:
<div class="col-xs-6 col-xs-offset-3 text-center" id="vueapp">
<div class="table table-awaken" v-if="clients">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr v-for="client in clients">
<td>{{ client }}</td>
</tr>
</tbody>
</div>
</div>
and my vue instance:
var vote = new Vue({
el: "#vueapp",
data: {
clients: [
{ name: 'x' }
]
}
})
It is because you have thead and tbody that are not children of a table element. Browsers do interesting things when you break HTML rules like that.
See these docs which state that tbody and thead must be children of a table.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead
If you wrap those elements in a table element, it begins working. I am not sure if this is because of Vue or the browser.
Try this HTML instead:
<div class="col-xs-6 col-xs-offset-3 text-center" id="vueapp">
<div class="table table-awaken">
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr v-for="client in clients">
<td>{{ client }}</td>
</tr>
</tbody>
</table>
</div>
</div>
And here is a working jsfiddle: https://jsfiddle.net/kg638x4f/

Vue.js table is rendered outside

I am trying to render a table. The data is dynamic and comes from an array. It works fine, except: the table content is rendered outside of the table on top of the page.
I would like it to render inside the table. Would anyone be able to help?
This what the code looks like:
Vue.js:
Vue.component('word', {
props: ['word-list'],
template: '#word-template'
});
new Vue({
el: '#root'
});
HTML:
<div id="root">
<word :word-list="{{json_encode($commonWords) }}"></word>
<template id="word-template">
<table class="table">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr v-for="(value, key) in wordList" :wordList="wordList">
<td> #{{ key }} </td>
<td> #{{ value }} </td>
</tr>
</tbody>
</table>
</template>
</div>
Note: This is used with Laravel, thats why there is an # before double curly braces.
You can't define the template for your component inside the template for your Vue. You need to move it outside.
<div id="root">
<word :word-list="{{json_encode($commonWords) }}"></word>
</div>
<template id="word-template">
<table class="table">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr v-for="(value, key) in wordList" :wordList="wordList">
<td> #{{ key }} </td>
<td> #{{ value }} </td>
</tr>
</tbody>
</table>
</template>
If you leave the template for the component inside the template for the root, the root will compile the template for the component as part of it's own template.