How to sync checkboxes between Vue 3 parent and child components - javascript

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>

Related

Managing state for multiple instances in Vue 3 with Pinia and Element Plus

I am encountering a problem with multiple default objects sharing the same state, and I am unable to separate the binding. I need to manage the state for each instance independently.
The parent and child components pass data to each other, and both communicate directly with the store.
The issue is that when clicking on the 'Add' button in the parent component creates a new row in the child component and adds a default objects (el-input and el-select) to an array, then all the elements are changed to the same state and are not independent.
I am using Vue 3, Pinia for store management, and Element Plus for UI components.
Parent -
<template>
<div>
<!-- first table-->
<el-row>
<div class="first-control-buttons">
<div>
<label class="first-label">First</label>
</div>
<div class="actions-control">
<div class="left-actions-control">
<el-radio-group v-model="store.firstRadio" class="ml-4">
<el-radio label="or" size="small">OR</el-radio>
<el-radio label="and" size="small">AND</el-radio>
</el-radio-group>
</div>
<div class="vertical-divider"></div>
<div class="right-actions-control">
<el-button :icon="Delete" circle #click="removeAllClick('firstCards')"/>
<el-button :icon="Plus" circle #click="store.addCard('firstCards')" />
</div>
</div>
</div>
</el-row>
<childTable :cards="store.firstCards" typeOfCard="firstCards"
#removeCard="store.removeCard"></childTable>
<!-- second table-->
<el-row>
<div class="first-control-buttons">
<div>
<label class="include-exclude-label">Second</label>
</div>
<div class="actions-control">
<div class="left-actions-control">
<el-radio-group v-model="store.secondRadio" class="ml-4">
<el-radio label="or" size="small">OR</el-radio>
<el-radio label="and" size="small">AND</el-radio>
</el-radio-group>
</div>
<div class="vertical-divider"></div>
<div class="right-actions-control">
<el-button :icon="Delete" circle #click="removeAllClick('secondCards')"/>
<el-button :icon="Plus" circle #click="store.addCard('secondCards')"/>
</div>
</div>
</div>
</el-row>
<childTable :cards="store.secondCards"
typeOfCard="secondCards" #removeCard="store.removeCard">
</childTable>
</div>
</template>
<script lang="ts" setup>
import {ref, computed, watch, onMounted} from 'vue'
import { Plus, Delete } from '#element-plus/icons-vue'
import childTable from './childTable.ce.vue';
import {useStore} from "../../../stores/useStore";
const store = useStore();
function removeAllClick(typeOfCards: string) {
if (store.firstCards && typeOfCards === "firstCards") {
ElMessageBox.confirm(`Are you sure you wish to empty?`,'Confirmation', {
}).then(() => {
store.firstCards = []
}).catch(() => {
console.log("cancel button was pressed")
})
} else if (store.secondCards && typeOfCards === "secondCards") {
ElMessageBox.confirm(`Are you sure you wish to empty`,'Confirmation', {
}).then(() => {
store.secondCards = []
}).catch(() => {
console.log("cancel button was pressed")
})
}
}
</script>
Child -
<template>
<el-row>
<div class="internal-form-details">
<div class="top-inputs-internal">
<table style="width: 100%">
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th>Action</th>
<th>Rule</th>
</tr>
</thead>
<tbody>
<tr v-for="(card, index) in cards" :key="index">
<td>
<el-select
v-model="store.typeOptionValue"
class="m-2 select-box">
<el-option
v-for="item in typeOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
</td>
<td>
<SharedSelect
placeholder="Select"
:selectAllOption="true"
:typeOption="store.typeOptionValue">
</SharedSelect>
</td>
<td>
<el-select
v-model="store.freqAndVeloValue"
class="m-2 select-box velocity">
<el-option
v-for="item in freqAndVeloOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
</td>
<td>
<div class="internal-rule-column">
<!-- if Freq -->
<div
v-if="store.freqAndVeloValue === 'freq'"
class="internal-frequency-inputs">
<el-input
v-model="store.freqValue"
#input="validateNumberInput"
type="number"
min="0"
class="frequency-value"/>
<span>or more times</span>
<el-select
v-model="store.freqTimeValue"
class="m-2 time-select">
<el-option
v-for="item in freqTimeOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<el-select
v-if="store.freqTimeValue === 'inTheLast'"
v-model="store.freqTimePeriodValue"
class="m-2 time-period-select">
<el-option
v-for="item in freqTimePeriodOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
</div>
<!-- if Vel -->
<div v-else class="internal-velocity-inputs">
<span>spanning</span>
<el-input
v-model="store.veloValue"
#input="validateNumberInput"
type="number"
min="0"
class="velocity-value"
/>
<span>days</span>
<el-select
v-model="store.veloIncreasedAndDecreasedValue"
class="m-2 select-box increased"
:popper-append-to-body="false"
>
<el-option
v-for="item in veloTimeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<span>by</span>
<el-input
v-model="store.veloNumberValue"
#input="validateNumberInput"
type="number"
min="0"
class="velocity-number-value"
/>
<span>% or more</span>
</div>
<div class="remove-button-row">
<el-button :icon="Delete" circle #click="removeCards(index, typeOfCard)"/>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</el-row>
</template>
<script lang="ts" setup>
import {onMounted, ref, onUnmounted, watch, computed} from "vue";
import {useStore} from "#/stores/useStore";
import SharedSelect from "../../shared/SharedSelect.ce.vue";
const store = useStore();
const defaults = defineProps({
typeOfCard: {
type: String,
default: "",
},
cards: {
type: Array,
default: [],
},
});
onMounted(() => {
const foundObject = typeOptions.find((option) => option.value === store.typeOptionValue).url;
store.fetchTypeOptionValues(foundObject);
});
watch(() => store.typeOptionValue, () => {
const foundObject = typeOptions.find((option) => option.value === store.typeOptionValue).url;
store.fetchTypeOptionValues(foundObject);
})
const emit = defineEmits(["removeCard"]);
const removeCards = (index, typeOfCard) => {
emit("removeCard", {index, typeOfCard});
};
</script>
Store -
import { defineStore } from "pinia";
export const useStore = defineStore({
id: "Yehu",
state: () => ({
firstRadio: "or",
secondRadio: "or",
input: "",
cards: [],
firstCards: [],
secondCards: [],
drawer: false,
dataTable: [],
freqValue: 1,
veloValue: 7,
typeOptionValue: "becImpre",
firstSecondValue: "first",
freqAndVeloValue: "freq",
veloNumberValue: 1,
freqTimeValue: "atAnyTime",
freqTimePeriodValue: "minutes",
veloIncreasedAndDecreasedValue: "increased",
}),
getters: {},
actions: {
addCard(typeOfCard?: string) {
const cardDefaultValues = {
firstSecondValue: "include",
typeOptionValue: "becImpre",
freqAndVeloValue: "freq",
freqValue: 1,
freqTimeValue: "atAnyTime",
veloValue: 7,
veloIncreasedAndDecreasedValue: "increased",
veloNumberValue: 1,
};
if (typeOfCard === "firstCards") {
this.firstCards.push({
...cardDefaultValues,
key: this.firstCards.length + 1,
});
} else if (typeOfCard === "secondCards") {
this.secondCards.push({
...cardDefaultValues,
key: this.secondCards.length + 1,
});
} else {
this.cards.push({
...cardDefaultValues,
key: this.cards.length + 1,
});
}
},
removeCard(props: object) {
if (this.firstCards && props.typeOfCard === "firstCards") {
this.firstCards.splice(props.index, 1);
} else if (this.secondCards && props.typeOfCard === "secondCards") {
this.secondCards.splice(props.index, 1);
} else {
this.cards.splice(props.index, 1);
}
},
},
});
After verifying the things, I found that I needed to use 'card' inside my component because I am iterating through an array that I am passing from the parent component.

Vue 3 custom checkbox components bound to array of selected values

I've been trying to create a simple component with a styled checkbox and a corresponding label. The values (strings) of all selected checkboxes should be stored in an array. This works well with plain html checkboxes:
<template>
<div>
<div class="mt-6">
<div>
<input type="checkbox" value="EVO" v-model="status" /> <label for="EVO">EVO</label>
</div>
<div>
<input type="checkbox" value="Solist" v-model="status" /> <label for="Solist">Solist</label>
</div>
<div>
<input type="checkbox" value="SPL" v-model="status" /> <label for="SPL">SPL</label>
</div>
</div>
<div class="mt-3">{{status}}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
let status = ref([]);
</script>
It results in the following, desired situation:
Now if I replace those checkboxes with my custom checkbox component, I can't get it to work. If I check a box, it's emitted value seems to replace the status array instead of it being added to or removed from it, resulting in the following:
So all checkboxes are checked by default for some reason, when I click on one of them they all get unchecked and the status value goes to false and clicking any of the checkboxes again will check them all and make status true.
Now I get that returning whether the box is checked or not in the emit returns a true or false value, but I don't get how Vue does this with native checkboxes and how to implement this behaviour with my component.
Here's the code of my checkbox component:
<template>
<div class="mt-1 relative">
<input
type="checkbox"
:id="id ?? null"
:name="name"
:value="value"
:checked="modelValue ?? false"
class="bg-gray-200 text-gold-500 rounded border-0 w-5 h-5 mr-2 focus:ring-2 focus:ring-gold-500"
#input="updateValue"
/>
{{ label }}
</div>
</template>
<script setup>
const props = defineProps({
id: String,
label: String,
name: String,
value: String,
errors: Object,
modelValue: Boolean,
})
const emit = defineEmits(['update:modelValue'])
const updateValue = function(event) {
emit('update:modelValue', event.target.checked)
}
</script>
And the parent component only uses a different template:
<template>
<div>
<div class="mt-6">
<Checkbox v-model="status" value="EVO" label="EVO" name="status" />
<Checkbox v-model="status" value="Solist" label="Solist" name="status" />
<Checkbox v-model="status" value="SPL" label="SPL" name="status" />
</div>
<div class="mt-3">{{status}}</div>
</div>
</template>
I've tried to look at this answer from StevenSiebert, but it uses an object and I want to replicate the original Vue behaviour with native checkboxes.
I've also referred the official Vue docs on v-model, but can't see why this would work different with native checkboxes than with components.
Your v-model is the same for every checkbox, maybe like following snippet:
const { ref } = Vue
const app = Vue.createApp({
setup() {
const status = ref([{label: 'EVO', status: false}, {label: 'Solist', status: false}, {label: 'SPL', status: false}])
return {
status
}
},
})
app.component('Checkbox', {
template: `
<div class="mt-1 relative">
<input
type="checkbox"
:id="id ?? null"
:name="name"
:value="value"
:checked="modelValue ?? false"
class="bg-gray-200 text-gold-500 rounded border-0 w-5 h-5 mr-2 focus:ring-2 focus:ring-gold-500"
#input="updateValue"
/>
{{ label }}
</div>
`,
props:{
id: String,
label: String,
name: String,
value: String,
errors: Object,
modelValue: Boolean,
},
setup(props, {emit}) {
const updateValue = function(event) {
emit('update:modelValue', event.target.checked)
}
return {
updateValue
}
}
})
app.mount('#demo')
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.18/tailwind.min.css" integrity="sha512-JfKMGsgDXi8aKUrNctVLIZO1k1iMC80jsnMBLHIJk8104g/8WTaoYFNXWxFGV859NY6CMshjktRFklrcWJmt3g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div>
<div class="mt-6" v-for="box in status">
<Checkbox v-model="box.status" :value="box.label" :label="box.label" name="status"></Checkbox>
</div>
<div class="mt-3">{{status}}</div>
</div>
</div>
I can pass an array as the v-model to the Checkbox component and mark is as checked if the value is within that array. When the checkbox gets toggles, I add or remove the value to/from the array depending if it's already in there.
Parent component:
<template>
<div>
<div class="mt-6">
<Checkbox v-for="box in ['EVO', 'Solist', 'SPL']" v-model="status" :value="box" :label="box" name="status" />
</div>
<div class="mt-3">{{status}}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
let status = ref([]);
</script>
Checkbox component:
<template>
<div class="mt-1 relative">
<input
type="checkbox"
:id="id ?? null"
:name="name"
:value="value"
:checked="modelValue.includes(value)"
class="bg-gray-200 text-gold-500 rounded border-0 w-5 h-5 mr-2 focus:ring-2 focus:ring-gold-500"
#input="updateValue"
/>
{{ label }}
</div>
</template>
<script setup>
const props = defineProps({
id: String,
label: String,
name: String,
value: String,
errors: Object,
modelValue: Boolean,
})
const emit = defineEmits(['update:modelValue'])
const updateValue = function(event) {
const arr = props.modelValue;
if(arr.includes(props.value)) { // Remove if present
arr.splice(arr.indexOf(props.value), 1)
}
else { // Add if not present
arr.push(props.value)
}
emit('update:modelValue', arr)
}
</script>
Or to accomodate for booleans as well (like native checkboxes):
<template>
<!-- ... -->
<input
type="checkbox"
:value="value"
:checked="isChecked"
#input="updateValue"
<!-- ... -->
/>
<!-- ... -->
</template>
<script setup>
// ...
const isChecked = computed(() => {
if(props.modelValue instanceof Array) {
return props.modelValue.includes(props.value)
}
return props.modelValue;
})
const updateValue = function(event) {
let model = props.modelValue;
if(model instanceof Array) {
if(isChecked()) {
model.splice(model.indexOf(props.value), 1)
}
else {
model.push(props.value)
}
}
else {
model = !model
}
emit('update:modelValue', model)
}

Biding Vue-router params in a component

I have the following View :
<template>
<div class="home">
<Quiz :quizID="this.$route.params.id"/>
<p>{{this.$route.params.id}}</p>
</div>
</template>
The value of this.$route.params.id is correctly displayed between the <p> tags. But the component above doesn't get this value as a parameter. If I force it by writing <Quiz :quizID="3"/>, it works and the component Quizwith the corresponding ID is displayed.
But as soon I put a variable, nothing works anymore.
Edit : As suggested, here is the Quiz component
<div>
<div class="container flex justify-center mx-auto">
<div class="flex flex-col">
<div class="w-full">
<div class="border-b border-gray-200 shadow">
<table>
<tbody class="bg-white">
<tr class="whitespace-nowrap" v-for="player in players" :key="player.quizID">
<td class="px-6 py-4 text-sm text-gray-500" v-if="player.quizID === quizID">
{{player.quizID}}
</td>
<td class="px-6 py-4" v-if="player.quizID === quizID">
{{player.name}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Quiz',
data(){
return {
quizzes: [],
players: []
}
},
props: [
'quizID'
],
methods: {
async fetchQuizzes() {
const res = await fetch(`http://127.0.0.1:8000/api/quizs`)
const data = await res.json()
return data.data
},
async fetchPlayers() {
const res = await fetch(`http://127.0.0.1:8000/api/players`)
const data = await res.json()
return data.data
}
},
async created(){
this.quizzes = await this.fetchQuizzes();
this.players = await this.fetchPlayers();
},
}
</script>
What's wrong?

Infinite scroll render in ReactJS with React-List using table takes too long

I want to render large lists of data inside a table. I am using the React-List library found online, but when the user scrolls about 200 items, the rest of them take too much time to load.
I achieved something like this:
After about 200 items, I get these warnings in the console, and the list starts to render slower and slower like in the image below:
I use React-List for rendering this table and a get request to get all the data once, code will be shown below.
import React from 'react';
import axios from 'axios';
import { toastr } from '../../../components/toastr/toastr.component';
import { Table } from 'react-bootstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faTrashAlt } from '#fortawesome/free-solid-svg-icons';
import DaysJS from 'react-dayjs';
import Loading from '../../../components/loading/loading.component';
import Tooltip from '../../../components/tooltip/tooltip.component';
import ReactList from 'react-list';
import './error.styles.scss';
class ErrorPage extends React.Component {
constructor() {
super();
this.state = {
pending: true,
errors: []
}
};
componentDidMount() {
axios.get('/api/logError').then(resp => {
this.setState({ pending: false, errors: resp.data });
}).catch(() => toastr('error', 'Eroare la preluarea datelor!'));
};
renderItem = (index, key) => {
return (
<tr key={key}>
<td>
{this.state.errors[index].id}
</td>
</tr>
)
};
renderTable (items, ref) {
return (
<div style={{maxHeight: '400px', overflowY: 'scroll'}}>
<Table bordered striped hover size="sm">
<tbody ref={ref}>
{items}
</tbody>
</Table>
</div>
);
}
renderRow = (index, key) => {
const entry = this.state.errors[index]
return (
<tr key={key}>
<td width='40px' className='text-center'>{index}</td>
<td width='40px' className='text-center'>{entry.id_user}</td>
<td width='200px'>{entry.email}</td>
<td width='200px'>{entry.name}</td>
<td width='200px'>{entry.action}</td>
<td>{entry.error}</td>
<td width='120px' className='text-center'><DaysJS format='DD.MM.YYYY - HH:MM'>{entry.createdAt}</DaysJS></td>
<td width='30px' className='cursor-pointer text-center' data-tip data-for='tooltip'>
<Tooltip id='tooltip' message='Șterge eroare'/>
<FontAwesomeIcon className='text-danger' icon={faTrashAlt} />
</td>
</tr>
);
}
render() {
const { pending, errors } = this.state;
return (
<div className='error-page mt-3 row'>
<Loading pending={pending} />
<div className='col-sm-4 fw-bold'>Total erori: {errors.length}</div>
<div className='col-sm-4'>
<h4 className='text-center'>Erori</h4>
</div>
<div className='col-sm-4 text-end'>
<button className='btn btn-danger btn-sm'>
<FontAwesomeIcon icon={faTrashAlt} className='me-1' />
Șterge toate
</button>
</div>
<div className='col-sm-12'>
<Table bordered size="sm">
<thead>
<tr>
<th width='40px'>Id user</th>
<th width='200px'>Email</th>
<th width='200px'>Unitate</th>
<th width='200px'>Acțiune</th>
<th>Eroare</th>
<th width='120px'>Data</th>
<th width='38px'></th>
</tr>
</thead>
</Table>
<ReactList
itemsRenderer={(items, ref) => this.renderTable(items, ref)}
itemRenderer={this.renderRow}
length={errors.length}
/>
</div>
</div>
);
};
};
export default ErrorPage;
I used in AngularJS a library called ng-infinite-scroll that rendered the items with no problem with infinite scroll and I tried to find something similar for ReactJS.

Create dynamic table component Vue JS

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>

Categories

Resources