Vue modify v-data-table column item templates in the parent component - vue.js

I'm using Vuetify and made a component including a v-data-table. How could I change the table column templates in the parent component?
Child component example:
<template>
<v-card>
<v-data-table :items="items" :headers="headers">
<!-- ???????? -->
</v-data-table>
</v-card>
</template>
<script>
export default {
name: "ChildComponent",
props: {
items: Array,
headers: Array,
},
};
</script>
Parent component:
The item.id is just an example, I need a common solution for any kind of object field.
<template>
<ChildComponent :items="items" :headers="headers">
<template v-slot:item.id="{ item }">
<v-chip> {{ item.id }} </v-chip>
</template>
</ChildComponent>
</template>
<script>
import ChildComponent from "./ChildComponent";
export default {
components: {
ChildComponent,
},
data: () => ({
items: [/* ... */],
headers: [/* ... */],
}),
};
</script>
I guess I need dynamic slots but not really know how to use them in this case.

Need to modify the ChildComponent on the following way:
<template>
<v-card>
<v-data-table :items="items" :headers="headers">
<template
v-for="header in headers"
v-slot:[`item.${header.value}`]="{ item }"
>
<slot :name="[`item.${header.value}`]" :item="item">
{{ getVal(item, header.value) }}
</slot>
</template>
</v-data-table>
</v-card>
</template>
<script>
export default {
name: "childComponent",
props: {
items: Array,
headers: Array,
},
methods: {
/*
https://stackoverflow.com/a/40270942/6936938
*/
getVal(item, path) {
return path.split(".").reduce((res, prop) => res[prop], item);
},
},
};
</script>
The getVal method is for the nested item fields. Without it you can't use paths like item.author.name.

Try to create a scoped slot in the child component with item.id as name and pass item as scoped value :
<v-data-table :items="items" :headers="headers">
<template v-slot:item.id="{ item }">
<slot :name="item.id" :item="item"></slot>
</template>
</v-data-table>

Related

this.$refs.key is undefined inside method

I have the following component based on Vuetify card component:
CardTemplate
<template>
<HeightCalculator>
<template #default="{ height }">
<v-card>
<v-card-title ref="title">
{{ title }}
</v-card-title>
<v-card-text
class="overflow-y-auto"
:style="{ height: calcHeight(height) }"
>
<slot></slot>
</v-card-text>
</v-card>
</template>
</HeightCalculator>
</template>
<script>
import HeightCalculator from '../HeightCalculator.vue'
export default {
props: {
title: {
type: String,
required: true
}
},
components: {
HeightCalculator
},
methods: {
calcHeight (height) {
return `calc(${height} - ${this.$refs.title.clientHeight})`
}
}
}
</script>
NOTE: HeightCalculator is just a renderless component which returns a calculated height (in this case is going to be something like 50vh).
As you can see this.$refs.title is undefined therefore I cannot access to its .clientHeight. What am I doing wrong?

How to render a custom component in <b-table> Bootstrap Vue? (Vue.js 2)

How can I show a custom component- BaseButton.vue in the rows of the table, if table component and button are common components?
I made the component BaseTable.vue. I use this component for all tables.
<template>
<b-table :fields="fields" :items="items"></b-table>
</template>
<script>
export default {
props: {
fields:{
type: Array,
require: true
},
items:{
type: Array,
require: true
},
}
fields - object in a parent component.
items - object from API.
Parent component (base button doesn't appear in table on page):
<template>
<base-table :fields="fields" :items="items">
<template #cell(actions)>
<base-button></base-button>
</template>
<base-table>
</template>
<script>
import BaseButton from '...';
export default {
data() {
return {
fields: [
{ key: 'Caption', label: 'Name' },
{ key: 'PreviousMonthCounter', label: 'Prev. month' },
{ key: 'CurrentMonthCounter', label: 'Curr. month' },
{ key: 'actions', label: 'Action' }
],
items: []
}
},
components: {
BaseButton
}
}
And now I need to render BaseButton.vue in the last column of the table and I can't to make it - table has only text fields from items.
Need to create a child component (BaseTable):
<template>
<b-table :items="items" :fields="fields">
<slot v-for="slot in Object.keys($slots)" :name="slot" :slot="slot" />
<template v-for="slot in Object.keys($scopedSlots)" :slot="slot" slot-scope="scope">
<slot :name="slot" v-bind="scope"></slot>
</template>
</b-table>
</template>
And use it in the parent component like , but must be replaced by your component name.
For example:
<base-table :fields="fields" :items="items">
<template #cell(nameOfTheField)="{item}">
{{ item.key }}
</template>
<template #cell(nameOfTheField)>
<base-button></base-button>
</template>

Can Vue.Draggable be used with Vuetify v-data-table and allow utilisation of table v-slot:item.<name>?

Vuetify v-data-table supports several types of slots: v-slot:body, v-slot:item and v-slot:item.<name>.
We have been using v-slot:item.<name> extensively, as these provides a flexible way to style and process content in individual columns AND allow the table headers to be programmatically changed.
I'd like to add draggability to my v-data-table rows and have got this working using Vue.Draggable.
However the draggable component requires use of the v-data-table v-slot:body i.e. taking control of the full body of the table and thereby losing the flexibility of v-slot:item.<name>.
Is there a way these two components can be used together and provide v-slot:item.<name> support?
I have created a DataTableRowHandler component which allows v-slot:item.<name> support.
This is placed inside the draggable component, inserts the table <tr> element and feeds off the same "headers" array to insert <td> elements and v-slot:item.<name> entries. If no v-slot:item.<name> is defined then the cell value is output, in the same way that v-data-table works.
Here is the example component usage:
<v-data-table
ref="myTable"
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
class="elevation-1"
>
<template v-slot:body="props">
<draggable
:list="props.items"
tag="tbody"
:disabled="!allowDrag"
:move="onMoveCallback"
:clone="onCloneCallback"
#end="onDropCallback"
>
<data-table-row-handler
v-for="(item, index) in props.items"
:key="index"
:item="item"
:headers="headers"
:item-class="getClass(item)"
>
<template v-slot:item.lock="{ item }">
<v-icon #click="item.locked = item.locked ? false : true">{{
item.locked ? "mdi-pin-outline" : "mdi-pin-off-outline"
}}</v-icon>
</template>
<template v-slot:item.carbs="{ item }">
{{ item.carbs }}
<v-icon>{{
item.carbs > 80
? "mdi-speedometer"
: item.carbs > 45
? "mdi-speedometer-medium"
: "mdi-speedometer-slow"
}}</v-icon>
</template>
</data-table-row-handler>
</draggable>
</template>
</v-data-table>
Here is the DataTableRowHandler component code
<template>
<tr :class="getClass">
<td v-for="(header, index) in headers" :key="index">
<slot :item="item" :name="columnName(header)">
<div :style="getAlignment(header)">
{{ getNonSlotValue(item, header) }}
</div>
</slot>
</td>
</tr>
</template>
<script>
export default {
name: "DataTableRowHandler",
components: {},
props: {
itemClass: {
type: String,
default: "",
},
item: {
type: Object,
default: () => {
return {};
},
},
headers: {
type: Array,
default: () => {
return [];
},
},
},
data() {
return {};
},
computed: {
getClass() {
return this.itemClass;
}
},
methods: {
columnName(header) {
return `item.${header.value}`;
},
getAlignment(header) {
const align = header.align ? header.align : "right";
return `text-align: ${align}`;
},
getNonSlotValue(item, header) {
const val = item[header.value];
if (val) {
return val;
}
return "";
},
},
};
</script>
An example of it's use is in this codesandbox link
I checked the source code of VDataTable and found that the content in tbody is generated by genItems().
The following example is implemented with functional components and is fully compatible with v-slot:item*:
<v-data-table ref="table" ...>
<template #body="props">
<draggable
v-if="$refs.table"
tag="tbody"
:list="props.items"
>
<v-nodes :vnodes="$refs.table.genItems(props.items, props)" />
</draggable>
</template>
<template #item.name="{ item }">
...
</template>
</v-data-table>
Here is the definition of the VNodes component:
components: {
VNodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes,
}
}

Vue2 How to use nested <template> tags?

I'm using Vue2's latest version.
I have 2 components; 1st component:
<stack>
<template #first>
<template #cell(details)="row">
{{ row.details ? "Hide" : "Show" }}
</template>
</template>
</stack>
2nd component:
<template>
<div>
<slot name="first"></slot>
</div>
</template>
<script>
export default {
data() {
return {
row: {
name: 'Test',
details: true
}
}
},
}
</script>
I want to simply insert the contents of the <template #first> from the 1st component into the slot in the 2nd component while maintaining everything inside the <template #first> tag.
So the desired outcome would look like this; the slot would be replaced with the contents of the <template #first> tag:
<template>
<div>
<template #cell(details)="row">
{{ row.details ? "Hide" : "Show" }}
</template>
</div>
</template>
<script>
export default {
data() {
return {
row: {
name: 'Test',
details: true
}
}
},
}
</script>
However, the 1st component does not work like this. It says that I cannot have a template inside a template. Is this actually possible what I'm trying to achieve here? If yes, how?

Vue. Change the contents of one slot from another

I have a component with certain slots.
<v-split-container ref="splitContainer" class="panel-panel">
<template slot="left">
Test
</template>
<template slot="right">
<router-view></router-view> //Show Compontent A
</template>
<template slot="bottom"> //Slot B
Hello
</template>
</v-split-container>
Can I, from component A, change the contents of slot B by calling a function inside the component?
Hi you can do it with Scoped Slot. I have created an example for you how to do it. Please not that I am using v-slots(Some context here) only usable from vue v2.6.
Please take a look at the code example: https://codesandbox.io/s/2030jo20j0?fontsize=14
Child Component
<template>
<div>
<div>
<slot name="msgSlot">{{msg}}</slot>
</div>
<br>
<slot name="bottom" :updateMsg="updateMsg"></slot>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data: () => ({
msg: "Default Message"
}),
methods: {
updateMsg(text) {
this.msg = text;
}
}
};
</script>
Parent Component
<template>
<div id="app">
<HelloWorld>
<template v-slot:msgSlot></template>
<template v-slot:bottom="{updateMsg}">
<input type="text" v-model="msg">
<button #click="updateMsg(msg)">Change Msg</button>
</template>
</HelloWorld>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
data: () => ({
msg: ""
}),
components: {
HelloWorld
}
};
</script>