Store loop object in vue.js - vuejs2

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>

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>

Passing b-icon to <td> element in VueJS

I want to pass a piece of HTML to a table-data-element using VueJS. The following demonstrates my scenario:
<template>
<div>
<div v-if="someObject.properties" style="margin-top: 20px;" class="table-responsive-md">
<table class="table table-striped">
<thead>
<tr>
<th style="text-align: left" scope="col">Some icons</th>
</tr>
</thead>
<tbody v-for="(property, index) in someObject.properties" :key="index">
<tr>
<td style="text-align: center" v-html="getIconWhenSomeRequirementIsMet(property)"/>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script lang="ts">
...
getIconWhenSomeRequirementIsMet (property: any): string {
if (property.type === 'animal') return '<b-icon-check class="h3 mb-0" style="color:green;"/>'
if (property.type === 'human') return '<b-icon-check class="h3 mb-0" style="color:yellow;"/>'
return '<b-icon-x class="h3 mb-0" style="color:red;"/>'
}
</script>
The code above is a minimal example of my Vue single file component. However, this way, I get empty fields in my table instead of the actual icons. Isn't there a simple and clean approach to achieve this?
The reason it doesn't work is because you can't use v-html to render custom components.
Instead, here's two different ways you can do this.
The first is to pre-define your b-icon-* and use v-if, v-else-if and v-else to match which icon to show.
The second is to dynamically bind properties using v-bind, this way you can use a method to do it, like you are now, but instead return the properties based on the type.
new Vue({
el: "#app",
data() {
return {
items: [
{ type: "animal" },
{ type: "human" },
{ type: "alien" },
],
fields: ['Type', 'Icon 1', 'Icon 2']
}
},
methods: {
getIconWhenSomeRequirementIsMet (type) {
/* Default properties */
const properties = {
icon: 'x',
style: 'color: red',
class: 'h3 mb-0'
};
if (type === 'animal') {
properties.icon = 'check';
properties.style = 'color: green;';
}
else if (type === 'human') {
properties.icon = 'check';
properties.style = 'color: yellow;';
}
return properties;
}
}
})
<link href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="//unpkg.com/bootstrap-vue#2.7.0/dist/bootstrap-vue.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app">
<div class="table-responsive-md">
<table class="table table-striped">
<thead>
<tr>
<th v-for="field in fields" >{{ field }}</th>
</tr>
</thead>
<tbody>
<tr v-for="{ type } in items">
<td>
{{ type }}
</td>
<td>
<b-icon-check v-if="type === 'animal'" variant="success" class="h3 mb-0">
</b-icon-check>
<b-icon-check v-else-if="type === 'human'" variant="warning" class="h3 mb-0">
</b-icon-check>
<b-icon-x v-else variant="danger" class="h3 mb-0">
</b-icon-x>
</td>
<td>
<b-icon v-bind="getIconWhenSomeRequirementIsMet(type)"></b-icon>
</td>
</tr>
</tbody>
</table>
</div>
</div>

vue.js v-for on two table rows

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>

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.