I'm learning Vue JS. I want to create a component to show data with table that I can reuse in other components. I have 2 props items(data) and columns(column name)
items: [
{
'id':'1',
'title': 'hello',
'description': 'ok ok',
'created_date': '2018-09-09'
},
{
'id':'2',
'title': 'hello 2',
'description': 'ok ok 2',
'created_date': '2018-10-09'
}
],
columns: [ 'id', 'title', 'description', 'created_date']
I want to create a table as bellow
<table class="table">
<thead>
<tr>
<th v-for="(column, index) in columns" :key="index"> {{column}}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in items" :key="index">
<td v-for="(column, indexColumn) in columns" :key="indexColumn">{{item.$column}}</td>
</tr>
</tbody>
I don't know how to get value from item by column name. Is it possible to create a table like this?
Everything is fine just change the value in the template item.$column to item[column]
<table class="table">
<thead>
<tr>
<th v-for="(column, index) in columns" :key="index"> {{column}}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in items" :key="index">
<td v-for="(column, indexColumn) in columns" :key="indexColumn">{{item[column]}}</td>
</tr>
</tbody>
</table>
TABLE COMPONENT WITH DYNAMIC SLOTS
I've created a a simple component for dynamic table with slot option.
<template>
<div id="appTable">
<table>
<thead>
<tr>
<th v-for="(header, i) in headers" :key="i">
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, i) of data" :key="i">
<td v-for="(td, j) of slotsProceced" :key="i + '-' + j">
<slot v-if="td.hasSlot" :name="td.slotName" :item="item" />
<span v-else>{{ item[td.key] }}</span>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AppTable',
props: {
headers: {
type: Object,
required: true
},
data: {
type: Array,
required: true
}
},
setup(props, { slots }) {
const slotsProceced = [];
for (const key in props.headers) {
if (Object.prototype.hasOwnProperty.call(props.headers, key)) {
const slotName = 'item-' + key;
slotsProceced.push({
key: key,
hasSlot: !!slots[slotName],
slotName: slotName
});
}
}
return { slotsProceced };
}
});
</script>
The props to call:
const headTable = {
date: "Date",
price: "Price",
status: "Status",
action: "Action",
};
const dataTable = [
{
date: "2021-02-03 22:04:16",
price: "$32.000",
status: 1,
},
];
To call it:
<AppTable :headers="headTable" :data="dataTable">
<template v-slot:item-action>
<span>Some Action</span>
</template>
<template v-slot:item-status="{ item }">
<span>{{ item.status === 1 ? 'Active' : 'Inactive' }}</span>
</template>
</AppTable>
Related
I have a Vue 3 InertiaJS app that contains a parent/child set of components where the parent component contains a form, along with a child component that searches for users and displays the results in a table where each row has a checkbox, so I can select users and then submit them as part of the form. What I need to do is sync the parent component with the checkbox values from the child component. Here is what I have.
Create.vue
<script setup>
import {Inertia} from '#inertiajs/inertia';
import {ref} from 'vue';
import {Head, Link, useForm} from '#inertiajs/inertia-vue3'
const form = useForm({
name: '',
checkedNames: []
});
const checkedNames = ref([])
const props = defineProps({
users: Object
})
</script>
<template>
<area-user-select
:users="props.users"
#searchUsers="searchUsers"
v-model:checkedNames="checkedNames"
></area-user-select>
</template>
AreaUserSelect.vue (child component)
<script setup>
import { Head } from '#inertiajs/inertia-vue3';
import Icon from '#/Components/Icon.vue';
import {ref} from 'vue';
const props = defineProps({
users: Object,
filters: Object,
checkedNames: Array
})
</script>
<template>
<div>
...
<div v-if="users.data.length > 0">
<table class="table-auto">
<thead>
<tr class="text-left font-bold">
<th class="pb-4 pt-6 px-6 text-sm">Select</th>
<th class="pb-4 pt-6 px-6 text-sm">Name</th>
<th class="pb-4 pt-6 px-6 text-sm">Last Name</th>
<th class="pb-4 pt-6 px-6 text-sm">Member Number</th>
<th class="pb-4 pt-6 px-6 text-sm">Status</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users.data" :key="user.id" class="hover:bg-gray-100 focus-within:bg-gray-100">
<td class="border-t">
<input
type="checkbox"
name="selected"
:value="user.id"
#input="$emit('update:checkedNames', $event.target.value)"
/>
</td>
<td class="border-t">
{{ user.first_name }}
</td>
<td class="border-t">
{{ user.last_name }}
</td>
<td class="border-t">
{{ user.member_number }}
</td>
<td class="border-t">
{{ user.status }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
This works, sort of. In my parent component, if I display the value of checkedName it only contains the value of the last item checked, not an array of all selected values.
What do I need to change so that all selected items are synced back to the parent (Create.vue) component?
You can do it this way:
Bind your parent to your component with 'v-model':
<my-component ... v-model="checkedNames"/>
Docs: Component v-model
Bind your checkboxes to a internal component property:
<input type="checkbox" ... v-model="checked" />
Docs: Form Input Bindings - Checkbox
End send updates with watch to the parent:
watch(checked, (newVal) => { emit('update:modelValue', newVal) });
Docs: Watchers
Here is the playground:
const { createApp, ref, watch } = Vue;
const users = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" }
];
const myComponent = {
props: ['items'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const checked = ref([]);
watch(checked, (newVal) => { emit('update:modelValue', newVal) });
return {emit, checked}
},
template: '#my-checkbox'
}
const App = {
components: {
myComponent
},
setup() {
const checkedNames = ref([]);
const setCheckedNames = (newVal) => checkedNames.value = newVal;
return { users, checkedNames, setCheckedNames }
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
Checked: {{checkedNames}}<br /><br />
<my-component :items="users" v-model="checkedNames"/>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="my-checkbox">
<div v-for="user in items">
<input type="checkbox" name="selected" :value="user.id" v-model="checked" />
<label>{{user.name}}</label>
</div>
</script>
Hey I'm fairly new to Vue and JavaScript and I wanna create a Html table. For my software I wanne have a Table.vue component that can display me different tables like the following .
{
"id": 1,
"text": "Hello"
}
{
"id": 1,
"status": "damaged",
"info": "test",
"text": "content"
}
How do I get the columns of these different tables dynamically and how can I display them?
<template>
<table class="table">
<thead>
<tr>
<th v-for="(column, index) in columns" :key="index"> {{column}}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in items" :key="index">
<td v-for="(column, indexColumn) in columns" :key="indexColumn">{{item[column]}}</td>
</tr>
</tbody>
</table>
</template>
data() {
return {
items: [],
columns: []
}
}
table example
You need to specify both the columns that should be rendered, as well as the rows with all data.
The Table.vue could look like this:
<template>
<table>
<thead>
<tr>
<th v-for="(column, index) in columns" :key="index">
{{ column }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in rows" :key="index">
<td v-for="(column, index) in columns" :key="index">
{{ row[column] }}
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "Table",
props: {
columns: Array,
rows: Array,
},
};
</script>
…and the parent component would then pass the data into Table.vue like this:
<template>
<Table :columns="columns" :rows="rows" />
</template>
<script>
import Table from "./components/Table";
export default {
name: "App",
components: {
Table,
},
data() {
return {
columns: ["id", "title", "description"],
rows: [
{
id: 1,
title: "what is this",
description: "i like to describe",
},
{
id: 2,
title: "wait wat?",
description: "i like to describe, too",
},
],
};
},
};
</script>
See the working codesandbox.
Note that most actual Vue dataTable components use render functions for performance reasons.
Been reading the docs in Vutify for their v-simple-table and v-data-table and I am trying to figure out if there is a way to add a row highlight like you can for a v-data-table.
Such as this?
Vuetify - How to highlight row on click in v-data-table
My table body looks like this:
<v-simple-table dense fixed-header height="90vh" >
<template v-slot:default>
<thead >
<tr >
<th style="font-size: 16px;height: 40px;" width='4%' class="black--text">
Location
</th>
<template v-if="Object.keys(arrAvailableDates).length">
<th style="font-size: 17px; " class="text-center black--text" v-for="(strDate, intIndex) in arrAvailableDates" :key="intIndex">
<span> {{ $moment(strDate).format('D MMM (ddd)') }}</span>
<v-badge tile v-if="total[strDate] > 0" inline color="green" v-bind:content="total[strDate]" > </v-badge>
</th>
</template>
<template v-else>
<th class="text-center" v-for="intHeaderCounter in 6" :key="intHeaderCounter">
<v-skeleton-loader
:key="intHeaderCounter"
type='text'
></v-skeleton-loader>
</th>
</template>
</tr>
</thead>
<tbody v-if="!blnLoading">
<template v-if="Object.keys(arrFiltered).length" >
<tr class="teal lighten-5"
v-for="(arrData, strLocationName) in arrFiltered"
:key="'f-' + strLocationName"
>
<th class="black--text text-darken--3">
{{ strLocationName }} <br/><span class="caption">{{arrData['zip']}}</span>
</th>
<template v-for="(arrAppointmentData, strDate) in arrData['appointment']">
<td :key="strDate" class="text-center ma-0 pa-0" >
<template v-if="typeof(arrAppointmentData) == 'object' ">
<span class="time-slot-x-small" v-for='(intCount, intIndex) in arrAppointmentData' :key="intIndex">
{{ $moment(intIndex, ["HH:mm:ss"]).format('hh:mma')}}
<span class="dot">{{intCount}}</span>
</span>
</template>
<template v-else>
-
</template>
</td>
</template>
</tr>
</template>
I can hover a row and it will show that the row is been hover but I am trying to create a way where if I click on the row, it will change the background color when selected.
An example I am trying to mirror is like this but with a simple table:
https://www.codeply.com/p/hi40H9aug9
Here is a more Vue centric approach...
template:
<tbody>
<tr
v-for="(item,idx) in items"
:key="item.name"
#click="handleClick(idx)"
:class="{ selected: selected.includes(idx) }"
>
<td>{{ item.name }}</td>
<td>{{ item.calories }}</td>
</tr>
</tbody>
script:
data () {
return {
items: [
{
id: 1,
name: "Frozen Yogurt",
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: "1%",
},
],
selected: [],
}
},
methods: {
handleClick(i) {
let pos = this.selected.indexOf(i)
if (pos > -1) {
this.selected.splice(pos, 1);
}
else {
this.selected.push(i)
}
},
}
https://codeply.com/p/PaV3dwWk1j
I am using a Vuetify data table with header and item slots. Now i have a button which by default should be hidden but i only want to show it when a row is selected or when all of them are selected. I assumed i should be able to use the selected data property but that seems to stay empty if i select one row. So not sure how i can hide/show the button.
This is a working pen.
This is the code:-
new Vue({
el: "#app",
data: () => ({
pagination: {
sortBy: "name"
},
selected: [],
headers: [{
text: "Dessert (100g serving)",
align: "left",
value: "name"
},
{
text: "Calories",
value: "calories"
},
{
text: "Fat (g)",
value: "fat"
}
],
desserts: [{
name: "Frozen Yogurt",
calories: 159,
fat: 6.0
},
{
name: "Ice cream sandwich",
calories: 237,
fat: 9.0
}
]
}),
methods: {
toggleAll() {
if (this.selected.length) this.selected = [];
else this.selected = this.desserts.slice();
},
changeSort(column) {
if (this.pagination.sortBy === column) {
this.pagination.descending = !this.pagination.descending;
} else {
this.pagination.sortBy = column;
this.pagination.descending = false;
}
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet" />
<div id="app">
<v-app id="inspire">
<v-data-table v-model="selected" :headers="headers" :items="desserts" :pagination.sync="pagination" select-all item-key="name" class="elevation-1">
<template v-slot:headers="props">
<tr>
<th>
<v-checkbox :input-value="props.all" :indeterminate="props.indeterminate" primary hide-details #click.stop="toggleAll"></v-checkbox>
</th>
<th v-for="header in props.headers" :key="header.text" #click="changeSort(header.value)">
<v-icon small>arrow_upward</v-icon>
{{ header.text }}
</th>
</tr>
</template>
<template v-slot:items="props">
<tr :active="props.selected" #click="props.selected = !props.selected">
<td>
<v-checkbox :input-value="props.selected" primary hide-details></v-checkbox>
</td>
<td class="text-xs-center">{{ props.item.name }}</td>
<td class="text-xs-center">{{ props.item.calories }}</td>
<td class="text-xs-center">{{ props.item.fat }}</td>
</tr>
</template>
</v-data-table>
<v-container>
<v-layout>
<v-flex xs6 class="mt-5">
<v-btn>Hide by default but show on selected</v-btn>
</v-flex>
</v-layout>
</v-container>
</v-app>
</div>
Any help will be appreciated. Thank you.
you can use selected.length.
here is a computed you can add
computed: {
showBtn() {
return this.selected.length > 0
}
},
then use showBtn in your template
<v-btn v-if="showBtn">Hide by default but show on selected</v-btn>
You can also just use it inline, but I prefer using computed, because they cache the value, and make the template more readable
new Vue({
el: "#app",
data: () => ({
pagination: {
sortBy: "name"
},
selected: [],
headers: [
{
text: "Dessert (100g serving)",
align: "left",
value: "name"
},
{ text: "Calories", value: "calories" },
{ text: "Fat (g)", value: "fat" }
],
desserts: [
{
name: "Frozen Yogurt",
calories: 159,
fat: 6.0
},
{
name: "Ice cream sandwich",
calories: 237,
fat: 9.0
}
]
}),
computed: {
showBtn() {
return this.selected.length > 0
}
},
methods: {
toggleAll() {
if (this.selected.length) this.selected = [];
else this.selected = this.desserts.slice();
},
changeSort(column) {
if (this.pagination.sortBy === column) {
this.pagination.descending = !this.pagination.descending;
} else {
this.pagination.sortBy = column;
this.pagination.descending = false;
}
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-data-table v-model="selected" :headers="headers" :items="desserts" :pagination.sync="pagination" select-all item-key="name" class="elevation-1">
<template v-slot:headers="props">
<tr>
<th>
<v-checkbox :input-value="props.all" :indeterminate="props.indeterminate" primary hide-details #click.stop="toggleAll"></v-checkbox>
</th>
<th v-for="header in props.headers" :key="header.text" :class="['column sortable', pagination.descending ? 'desc' : 'asc', header.value === pagination.sortBy ? 'active' : '']" #click="changeSort(header.value)">
<v-icon small>arrow_upward</v-icon>
{{ header.text }}
</th>
</tr>
</template>
<template v-slot:items="props">
<tr :active="props.selected" #click="props.selected = !props.selected">
<td>
<v-checkbox :input-value="props.selected" primary hide-details></v-checkbox>
</td>
<td class="text-xs-center">{{ props.item.name }}</td>
<td class="text-xs-center">{{ props.item.calories }}</td>
<td class="text-xs-center">{{ props.item.fat }}</td>
</tr>
</template>
</v-data-table>
<v-container>
<v-container>
<v-layout>
<v-flex xs6 class="mt-5">
<v-btn v-if="showBtn">Hide by default but show on selected</v-btn>
</v-flex>
</v-layout>
</v-container>
</v-app>
</div>
I have an array - 'items' of objects and each of them contains an array of more objects.
I would like to access the second array 'productPrices' to use v-for. But items.productPrices doesn't work for me. Should I create double loop somehow?
HTML:
<table>
<tbody>
<tr v-for="(element, index) in items.productPrices">
<td>{{ element.name }}</td>
<td>
<span>{{ element.amount }}</span>
</td>
<td>
{{ element.price }}
</td>
</tr>
</tbody>
</table>
JS:
new Vue({
el: "#app",
data: {
items: [
{
name: 'menu',
path: 'menu',
productPrices: [
{ amount: 100, price: 80.61, name: 'first' },
{ amount: 250, price: 72.10 },
{ amount: 500, price: 79.62 },
{ amount: 1000, price: 100.20 },
{ amount: 2500, price: 147.60 },
{ amount: 5000, price: 232.56 }
],
quantity: 0
},
{
name: 'Etui',
path: 'etui',
productPrices: [
{ amount: 100, price: 80.61, name: 'first' },
{ amount: 250, price: 72.10 },
{ amount: 500, price: 79.62 },
{ amount: 1000, price: 100.20 },
{ amount: 2500, price: 147.60 },
{ amount: 5000, price: 232.56 }
],
quantity: 0
},
]
}
})
Here is a fiddle.
While you can do this with <template> ... </template>
as the other two people have answered, if you wish to not nest another loop you can flatten all of the data into one array to be used, it won't be as pretty unless you move it to a function or the like.
Here's how:
<div id="app">
<table>
<tbody>
<!-- <tr v-for="(element, index) in items.map(item => item.productPrices).reduce((joinedArrays, currentArray) => joinedArrays.concat(currentArray), [])"> -->
<tr v-for='(element, index) in currentItem().productPrices'>
<td>{{ element.name }}</td>
<td>
<span>{{ element.amount }}</span>
</td>
<td>
{{ element.price }}
</td>
</tr>
</tbody>
</table>
just wrap it inside a <template v-for="item in items">
<div id="app">
<table>
<tbody>
<template v-for="item in items">
<tr v-for="(element, index) in item.productPrices">
<td>{{ element.name }}</td>
<td>
<span>{{ element.amount }}</span>
</td>
<td>
{{ element.price }}
</td>
</tr>
</template>
</tbody>
</table>
</div>
update on your jsfiddle http://jsfiddle.net/khz30amb/7/
You can do this by following way:-
<template>
<v-container fluid px-4 py-0>
<h4>Test</h4>
<table>
<tbody>
<template v-for="(element, index) in items">
<tr v-for="newElement in element.productPrices" :key="index">
<td>{{ newElement.name }}</td>
<td>
<span>{{ newElement.amount }}</span>
</td>
<td>
{{ newElement.price }}
</td>
</tr>
</template>
</tbody>
</table>
</v-container>
</template>
since in javascript you cant directly access the properties of array of objects for example if you have a case as above :-
where you have a array of objects that is list and you need to access the property product prices you can't just access it like list.productPrices you will first need to iterate over the higher order objects and then only you will have access to the property..
so what you will need to do is first use v-for over the list and then you can access the property productPrices and then you can iterate over it using v-for..
Hope this answers your question and doubts.