Call a function with customized datatable in Vuejs - javascript

I just started with Vuejs (Composition API) and trying to create my own kind of "datatable".
Managed to create the columns and the rows and all is great (event the call via ajax).
But now I have some kind of problem which I cant figure out how to fix.
I'm calling the Datatable component like this:
<DataTable :config="{
data: {
type: 'remote',
url: '/api/categories/get'
},
columns: [
{
field: 'id',
title: '#'
},
{
field: 'name',
title: 'Name'
},
{
field: 'order',
title: 'Placement'
},
{
field: 'actions',
title: 'Actions',
template: 'SOME HTML & CALL FUNCTION'
}
]
}" />
as you can see in the last column there is a new key called 'template'.
the point is to create an "html" value, like buttons and stuff like that.
On the Datatable component I'm checking to see if there is "template" key, and if it's exists I want to display it
I did something like that:
<template v-if="column.template">{{ HTML }}</template>
I managed to show the buttons/everything else. the problem that I cant make the button to call any function.
Let's say I want to add delete button, something like that:
field: "actions",
title: "Actions",
template: () => "<button #click="delete(id)" >delete</button>"
How can I make it work?
if I'm not using it's correctly, would love to hear & learn how to do it right.
Thanks in advance

You can use scoped slots to render some templates that also have access to the state of the child component.
So you would define a slot in your table that have the name of the column <slot v-bind="item" :name="column.name" /> and the v-bind="item" would allow you to access the row data in the slot definition in the parent.
<template>
<table>
<thead>
<tr>
<th v-for="column in columns">{{column.title}}</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items">
<td v-for="column in columns">
<template v-if="column.name">{{item[column.name]}}</template>
<slot v-bind="item" :name="column.name"></slot>
</td>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts">
defineProps<{
items: any[],
columns : {title? : string, name? : string}[]
}>()
</script>
<style>
th {
font-weight: bold;
}
</style>
And then use it like this
<script setup lang="ts">
import { ref } from 'vue';
import MyTable from './components/MyTable.vue';
const items = ref([{msg : 'Hello' }, { msg: 'World' }]);
</script>
<template>
<main>
<MyTable :items="items" :columns="[{ title : 'Message', name: 'msg' }, { name: 'delete'}]">
<template #delete="item">
<button #click="items.splice(items.findIndex(x => x.msg === item.msg), 1)">Delete</button>
</template>
</MyTable>
</main>
</template>

Related

How can I access nested data via an array with parsed json

I am creating a vuetify simple table that is going to display various data elements. The problem is, some of those elements are based on relationships and nested. Getting the top level data is fine, and if I pull the nested data as a standalone, it works fine as well.
However, what I want to do is utilize an array to avoid repetitive html code for the table. Is this possible at all?
Below is the code as constructed for the table itself.
HTML:
<v-simple-table fixed-header height="300px">
<template v-slot:default>
<thead>
<tr>
<th class="text-left">
Attribute
</th>
<th class="text-left">
Value
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(serviceProperty, idx) in serviceProperties"
:key="idx">
<th>{{ serviceProperty.label }}</th>
<td>{{ service[serviceProperty.value] }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
JS:
export default {
name: "Details",
data() {
return {
loading: true,
service: {},
serviceProperties: [
{
label: 'Description',
value: 'description'
},
{
label: 'Location',
value: 'organization.locations[1].streetAddress'
},
{
label: 'EIN',
value: 'organization.EIN'
}
]
};
},
props: ["serviceId"],
async created() {
this.service = await Vue.$serviceService.findOne(this.serviceId);
this.loading = false;
},
};
This seems unnecessarily complicated.
Consider using computed, like this
...
computed: {
mappedData() {
return this.service.map(item => {
Description: item.Description,
Location: item.organization.locations[1].streetAddress,
EIN: item.organization.EIN
})
}
}
...
You can then access the data in the template with:
...
<element v-for="item in mappedData">
{{item.Description}}
{{item.Location}}
{{item.EIN}}
</element>
...

How can I avoid mutation prop and modify the same in other component?

I'm a new Dev VUE.JS and reading a lot too about "share/pass" data to anothers components, but I'm not getting perceive this examples useful in my component.
Table.vue - Fetch API and populate a table.
File.vue - Instance Table.vue and here I'm trying to override the component variable.
Table:
<template>
<v-simple-table class="mt-5" id="tableOs" dense >
<thead>
<tr>
<th v-for="h in headers" v-bind:key="h.value">{{h.text}}</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" v-bind:key="item.id">
<td>{{item.id}}</td>
<td>{{item.customer}}</td>
<td>{{item.neighbor}}</td>
</tr>
</tbody>
</v-simple-table>
</template>
<script>
export default {
data (){
return{
headers: [
{ text: 'Id', value: 'id' },
{ text: 'Name', value: 'customer' },
{ text: 'Neighbor', value: 'neighbor' }
],
items: this.pitems,
}
},
props:{
'pitems': []
},
async created(){
const res = await fetch(
"http://localhost:5000/buscarx/3",
{
method:"GET"
}
);
const data = await res.json()
this.items = data
}
}
</script>
and my Files.vue
<template>
<v-container
id="dashboard"
fluid
tag="section"
>
<v-btn #click="modify()"> </btn>
<TableOS />
</v-container>
</template>
<script>
export default {
components:{
Table: () => import('./components/Table')
},
methods:{
modify(){
console.log(this.item)
}
}
}
</script>
Anyone can me help, suggest the best way to use this fetch and modify the data in the table to use a Filter for example in the future?
You can use props to pass variables from one component to another.
For example, you need to fetch your items data in the created hook inside File.vue. Save the fetched items in items array.
Then, you can pass this items array via prop to the Table.vue component.
You can modify, filter, map and perform other tasks to the items array in the File.vue. The changes will be reactive and automatically reflect in the Table.vue file.
You can also share variables between components using vuex. But, in case of variable sharing between child and parent, like in your case, you don't need this. But if the relation between components is not so simple or they are not related you have to use vuex.

Vue.js b-table slots not displaying correctly

My Vue-bootstrap table is not rendering slots correctly.
If I copy the examples of the Bootstrap-Vue website to my Vue application, the slots are not rendered. The application compiles without errors.
Some slots (e.g. table-busy) works correctly, but others don't
<template>
<b-table
:fields="tableHeaders"
:items="tableRows"
:busy="!tableLoaded"
>
<template v-slot:cell()="data">
<i>{{ data.value }}</i>
</template>
</b-table>
</template>
<script>
export default {
data() {
tableHeaders: [
{
key: 'Employee',
stickyColumn: true,
isRowHeader: true,
variant: 'primary',
},
'Status'
],
tableRows: [
Employee: 'Emp 1', status: 'Active',
Employee: 'Emp 2', status: 'Active',
]
}
</script>
Data values are not shown in italic font style. The compiler doesn't show any errors. Does anyone know how this can be solved?

Filtering through a table as user types to input bar

I have a table that is populated based on an array of country objects and I also have a search bar which will interact with the table by live filtering through the countries array and only showing the countries that partially or fully matches what the user inputs in the search bar.
The problem is I am new to vue and I am having trouble figuring out how to get this to work. If someone can look at my code and point me towards the right directly or what I am doing wrong that would be great!
So right now my logic is that I have a v-model on the text field which will bind whatever the user types to a data value called "filterBy".
My understanding is probably incorrect, but what I am thinking now is that by creating a filteredCountries function inside computed, and since computed will run whenever a variable inside the function changes, it will automatically get called whenever something is typed inside the searchbar, thus filtering the countries array and the table will get rerendered.
<template>
<div class="countries-table">
<div class="countries-search-bar">
<v-flex xs12 sm6 md3>
<v-text-field
v-model="filterBy"
placeholder="Search by country name or alpha2"
/>
</v-flex>
</div>
<v-data-table
:headers="headerValues"
:items="items"
:pagination.sync="pagination"
item-key="id"
class="elevation-1"
:rows-per-page-items="[300]"
>
<template v-slot:headers="props">
<tr>
<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>
<th>
Edit
</th>
</tr>
</template>
<template v-slot:items="props">
<tr :active="props.selected" #click="props.selected = !props.selected">
<td>{{ props.item.country_alpha2 }}</td>
<td class="text-xs-right">{{ props.item.country_name }}</td>
<boolean-cell
custom-class="text-xs-right"
:input="props.item.is_active"
:output="{ true: 'Yes', false: 'No' }"
></boolean-cell>
<date-cell
custom-class="text-xs-right"
:input="props.item.updated_at"
></date-cell>
<td class="text-xs-right" #click="triggerEdit(props.item)">
<v-icon class="edit-icon">edit</v-icon>
</td>
</tr>
</template>
</v-data-table>
</div>
</template>
<script>
import BooleanCell from '~/components/global-components/Table/BooleanCell'
import DateCell from '~/components/global-components/Table/DateCell'
export default {
components: {
BooleanCell,
DateCell
},
props: {
headerValues: {
type: Array,
required: true
},
items: {
type: Array,
required: true
}
},
computed: {
filteredCountries() {
return this.items.filter(country => {
return country.country_name.includes(this.filterBy)
})
}
},
data() {
return {
pagination: {
sortBy: 'country_alpha2'
},
filterBy: ''
}
},
methods: {
changeSort(headerValue) {
if (this.pagination.sortBy === headerValue) {
this.pagination.descending = !this.pagination.descending
} else {
this.pagination.sortBy = headerValue
this.pagination.descending = false
}
}
}
}
</script>
The table stays the same with the current code I have despite me typing things inside the search bar.
Can someone show me the what I am doing wrong?
For the v-data-table items you are using items which is coming as a prop. You should use filteredCountries computed property.

Rendering items via props after handling some processing using Vue.js and Vue CLI 3

I have a main component called App.vue and a child one MyTable.vue which wraps a table of data and showing only the 10 first rows, i'm working with vue cli 3 and when i ran the npm run serve command and go to the given address, it renders only the head of my table, but when i add some code in the mounted() function inside MyTable.vue like console.log() it renders also the body of my table, the problem comes back when i refresh my page, how can i deal with that ?
these is my components
App.vue
<template>
<div class="main-page">
<my-table title="todos" :cols="todo_attr" :rows_data="todo_data"></my-table>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
import todos from './assets/todos.json'
export default {
name: 'app',
data(){
return{
todo_attr:[
"todoId","id","title","completed"
],
todo_data:[]
}
},
components: {
MyTable
},
mounted(){this.todo_data=todos;}
}
</script>
MyTable.vue
<template>
<div class="vet-container">
<table>
<thead>
<th class="tab-head-cell" v-for="col in cols" :key="col">{{col}}</th>
</thead>
<tbody>
<tr class="tab-rows_data-row" v-for="row in currentPageData" :key="row.id">
<td class="tab-rows_data-cell" v-for="(cell,key,index) in row" :key="key+index" > {{cell}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'my-table',
props: {
title: String,
cols: {},
rows_data: {}
},
data() {
return {
currentPageData: {}
};
},
methods:{
createFirstPage(){
this.currentPageData = this.rows_data.slice(0, 10);
}
}
,
mounted() {
this.createFirstPage();
}
}
</script>
First, you declared cols and rows_data as objects in MyTable.vue but you declared them as arrays in App.vue. You also declared currentPageData as an object instead of an array. It may cause some errors.
Second, you should prefer do this:
<template>
<div class="vet-container">
<table>
<thead>
<th class="tab-head-cell" v-for="col in cols" :key="col">{{col}}</th>
</thead>
<tbody>
<tr class="tab-rows_data-row" v-for="row in currentPageData" :key="row.id">
<td
class="tab-rows_data-cell"
v-for="(cell,key,index) in row"
:key="key+index" >{{cell}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'my-table',
props: {
title: String,
cols: Array,
rows_data: Array,
},
data() {
return {
index: 0,
size: 10,
};
},
computed: {
currentPageData() {
const start = this.index * this.size;
const end = start + this.size;
return this.rows_data.slice(start, end);
},
},
};
</script>
You could then pass index in props and change it on parent on click on buttons.
Little explanation of the computed property: this property act like calculated data. You can use it just like any other data or props and you can calculate its content based on other stuff, like here, with the current index and the size of page.

Categories

Resources