Vue components: Using slots within slots - vue.js

I have v-data-table as its own component to make it more generic for my components. I would like to have the option to provide that component how each item in the table is formatted. That would be done with a slot. But the problem is, that the provided slot has a slot for v-data-table. See the example, this is difficult to explain.
In my DataTable component I have:
<v-data-table
:headers="tableHeaders"
:items="items"
:item-key="itemKey"
:loading="isLoading"
v-model="selectedRows"
show-select
>
<!-- What do i put here? -->
</v-data-table>
And I use that data table from my other component like this:
<data-table
:table-headers="tableHeaders"
:items="messages"
:is-loading="isLoading"
:selected-rows="selectedRows"
:item-key="'MessageID'"
>
<template v-slot:item.actions="{ item }">
<v-btn :to="`messages/${item.MessageID}`" icon>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</template>
<template v-slot:item.CreatedDateTime="{ item }">
<span>{{ format(new Date(item.CreatedDateTime)) }}</span>
</template>
</data-table>
I need the result to be rendered as:
<v-data-table
:headers="tableHeaders"
:items="items"
:item-key="itemKey"
:loading="isLoading"
v-model="selectedRows"
show-select
>
<template v-slot:item.actions="{ item }">
<v-btn :to="`messages/${item.MessageID}`" icon>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</template>
<template v-slot:item.CreatedDateTime="{ item }">
<span>{{ format(new Date(item.CreatedDateTime)) }}</span>
</template>
</v-data-table>
So the problem is, how do I provide the templates within data-table tag to be rendered within v-data-table tag?

I had the exact same issue. I found a solution utilizing the $scopedSlotes object, like this:
<v-data-table
:headers="tableHeaders"
:items="items"
:item-key="itemKey"
:loading="isLoading"
v-model="selectedRows"
show-select
>
<template
v-for="slot in Object.keys($scopedSlots)"
:slot="slot"
slot-scope="scope"
>
<slot
:name="slot"
v-bind="scope"
></slot>
</template>
</v-data-table>

You will have to re-expose the slots in your "wrapper" component:
<v-data-table
:headers="tableHeaders"
:items="items"
:item-key="itemKey"
:loading="isLoading"
v-model="selectedRows"
show-select
>
<template v-slot:body="bodyParams">
<slot name="body" v-bind="bodyParams" />
</template>
<template v-slot:expanded-item="expandedParams">
<slot name="expanded-item" v-bind="expandedParams" />
</template>
<template v-slot:header="headerParams">
<slot name="header" v-bind="headerParams" />
</template>
<template v-slot:footer="footerParams">
<slot name="footer" v-bind="footerParams" />
</template>
<template v-slot:item="itemParams">
<slot name="item" v-bind="itemParams" />
</template>
<template v-slot:progress="progressParams">
<slot name="progress" v-bind="progressParams" />
</template>
<!-- put here the other slots you are interested in -->
</v-data-table>

Related

How can I properly enable slots and scoped slots usage when wrapping a Vuetify component?

I currently do this to enable slots and scoped slots usage.
<template>
<v-text-field
v-bind="computedAttrs"
v-on="$listeners"
>
<!-- Cycling through slots -->
<template v-for="(_, name) in $slots">
<template :slot="name">
<slot :name="name"></slot>
</template>
</template>
<!-- Cycling through scoped slots -->
<template
v-for="(_, name) in $scopedSlots"
#[name]="data"
>
<slot
:name="name"
v-bind="data"
></slot>
</template>
</v-text-field>
</template>
I don't understand why I cannot do this to enable slots usage.
<template>
<v-text-field
v-bind="computedAttrs"
v-on="$listeners"
>
<!-- Cycling through slots -->
<template
v-for="(_, name) in $slots"
#[name]
>
<slot :name="name"></slot>
</template>
<!-- Cycling through scoped slots -->
<template
v-for="(_, name) in $scopedSlots"
#[name]="data"
>
<slot
:name="name"
v-bind="data"
></slot>
</template>
</v-text-field>
</template>
I saw people doing this, but I don't understand why it doesn't work for me. What am I doing wrong?
Note: I'm using Vue2.

how can i use a slot inside a v-for to customize each column of a data-table?

in this example of vuetify documentation for data-tables as described
here, to customize the calories column we’re using the item.calories slot:
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
class="elevation-1"
>
<template v-slot:item.calories="{ item }">
<v-chip
:color="getColor(item.calories)"
dark
>
{{ item.calories }}
</v-chip>
</template>
</v-data-table>
</v-app>
</div>
but i need v-for to customize all the columns, so that it can be something like:
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
class="elevation-1"
>
<v-for :list="headers">
<template v-slot:item.header="{ item }">
<v-chip
:color="getColor(item.header)"
dark
>
{{ item.calories }}
</v-chip>
</template>
</v-for>
</v-data-table>
</v-app>
</div>
Unfortunately, this is not working.
Does anybody know how can I deal with this issue?
Thanks in advance!
You can acheive that by using the 'slot' prop instead of the v-slot directive as the following example:
<v-chip
v-for="header in headers"
:key="header.value"
:slot="`item.${header.value}`"
slot-scope="{item}"
>
{{ item[header.value] }}
</v-chip>
note that you should replace teh value key that I'm using with the identifier key of each column object in your code.

Vuetify access v-slot:item in datatable custom component

I've made a custom datatable component, where my Table.vue file is shown below:
<template>
<div>
<v-data-table
:headers="headers"
:items="items"
:search="search"
:loading="loading"
loading-text="Loading... Please wait"
dense
>
<template v-slot:top>
<v-toolbar dark flat dense>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
></v-text-field>
<v-spacer></v-spacer>
</v-toolbar>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
name: "Table",
props: [
"headers",
"items",
"title",
"itemsPerPage",
"loading",
],
data: function () {
return {
search: '',
}
},
methods: {
},
};
</script>
And I'm using it like that:
<Table
:headers="headers"
:items="groups"
:loading="loading"
title="Baseny"
>
</Table>
Everything is fine, but I want to add custom columns with actions for every input (every input has different ID)
Normally (without a custom component) I'd use the following code:
<v-data-table
:headers="headers"
:items="times"
:items-per-page="5"
:search="search"
:loading="loading"
loading-text="Ładowanie... Proszę czekać"
>
<template v-slot:top>
<v-toolbar dark flat dense>
<v-toolbar-title>Lista zajęć</v-toolbar-title>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Szukaj"
single-line
hide-details
></v-text-field>
<v-spacer></v-spacer>
<v-btn
color="primary"
:to="{ name: 'admin.times.create' }"
>
Dodaj zajęcie
</v-btn>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-icon
small
class="mr-2"
#click="show(item)"
>
mdi-pool
</v-icon>
<v-icon
small
class="mr-2"
#click="edit(item)"
>
mdi-pencil
</v-icon>
</template>
</v-data-table>
I'm using this v-slot:
<template v-slot:item.actions="{ item }">
<v-icon
small
class="mr-2"
#click="show(item)"
>
mdi-pool
</v-icon>
<v-icon
small
class="mr-2"
#click="edit(item)"
>
mdi-pencil
</v-icon>
</template>
However, when I wrote the custom reusable table components it didn't work when I put these lines into tag.
How can I use my custom components properly in this scenario?
What you want to achieve is I believe a wrapper component. You want to wrap a component on top of another one to let him have some custom properties that you want to reuse in your application.
What you need is a small snippet that will allow your slots to be used:
<!-- pass through scoped slots -->
<template v-for="(_, scopedSlotName) in $scopedSlots" v-slot:[scopedSlotName]="slotData">
<slot :name="scopedSlotName" v-bind="slotData" />
</template>
<!-- pass through normal slots -->
<template v-for="(_, slotName) in $slots" v-slot:[slotName]>
<slot :name="slotName" />
</template>
You can find the source of this here
Basically, here how you can rewrite your CustomTable.vue:
<template>
<div>
<v-data-table
v-bind="$attrs"
v-on="$listeners"
:search="search"
loading-text="Loading... Please wait"
dense
>
<!-- pass through scoped slots -->
<template
v-for="(_, scopedSlotName) in $scopedSlots"
v-slot:[scopedSlotName]="slotData"
>
<slot :name="scopedSlotName" v-bind="slotData" />
</template>
<!-- pass through normal slots -->
<template v-for="(_, slotName) in $slots" v-slot:[slotName]>
<slot :name="slotName" />
</template>
<template v-slot:top>
<v-toolbar dark flat dense>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
></v-text-field>
<v-spacer></v-spacer>
</v-toolbar>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
name: "CustomTable",
props: ["title"],
data: function () {
return {
search: "",
};
},
methods: {},
};
</script>
I made a codesandbox to show you how it works:
https://codesandbox.io/s/vuetify-2-forked-3lp9y?file=/src/components/CustomTable.vue
I also added automatic attribute and listeners bindings on your table, to allow you to use all the features that Vuetify provides.

How to specify when a Vue Component has extra functionality?

I have a table component, and this table component has the ability to select attributes, however I only want this ability to be available when I need it to be, aka not every time it's rendered. How do I do this?
Functionality snippet from Component.vue to only be active when in a certain file ie Component2.vue
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<b-table-column field="columnValue" v-slot="props2" class="attr-column">
<b-table :bordered="false" class="attr-table" :striped="true" :data="props2.row.columnValues">
<b-table-column field="columnName" v-slot="itemProps">
<SelectableAttribute :attr-name="props2.row.fieldClass" :attr-id="itemProps.row.id" :model-id="itemProps.row.id" model-name="NewParticipant">
{{ itemProps.row.value }}
</SelectableAttribute>
</b-table-column>
</b-table>
</b-table-column>
</b-table>
Component.vue
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<b-table :striped="striped" :bordered="false" :data="participants" detailed class="participant-table" :row-class="() => 'participant-row'">
<b-table-column field="primaryAlias" :label="t('participant.table.primary_alias')" v-slot="props">
<template v-if="props.row.primaryAlias">
<SelectableAttribute attr-name="Alias" :attr-id="props.row.primaryAlias.id" :model-id="props.row.id" model-name="NewParticipant">
{{ props.row.primaryAlias.value }}
</SelectableAttribute>
</template>
<template v-else>-</template>
</b-table-column>
<b-table-column field="primaryEmail" :label="t('participant.table.primary_email')" v-slot="props">
<template v-if="props.row.primaryEmail">
<SelectableAttribute attr-name="Email" :attr-id="props.row.primaryEmail.id" :model-id="props.row.id" model-name="NewParticipant">
{{ props.row.primaryEmail.value }}
</SelectableAttribute>
</template>
<template v-else>-</template>
</b-table-column>
<b-table-column field="primaryAddress" :label="t('participant.table.primary_address')" v-slot="props">
<template v-if="props.row.primaryAddress">
<SelectableAttribute attr-name="Address" :attr-id="props.row.primaryAddress.id" :model-id="props.row.id" model-name="NewParticipant">
{{ props.row.primaryAddress.value }}
</SelectableAttribute>
</template>
<template v-else>-</template>
</b-table-column>
*<b-table-column field="primaryPhone" :label="t('participant.table.primary_phone')" v-slot="props">
<template v-if="props.row.primaryPhone">
<SelectableAttribute attr-name="Phone" :attr-id="props.row.primaryPhone.id" :model-id="props.row.id" model-name="NewParticipant">
{{ props.row.primaryPhone.value }}
</SelectableAttribute>
</template>
<template v-else>-</template>
</b-table-column>*
<b-table-column v-slot="props" cell-class="cell-action">
<slot v-bind="props.row">
</slot>
</b-table-column>
<template slot="detail" slot-scope="props">
<b-table class="attrs-detail-container" :data="tableDataToDataValueCells(props.row)" cell-class="with-bottom-border">
<b-table-column field="columnName" v-slot="props">
<b>{{ props.row.columnName }}</b>
</b-table-column>
<b-table-column field="columnValue" v-slot="props2" class="attr-column">
<b-table :bordered="false" class="attr-table" :striped="true" :data="props2.row.columnValues">
<b-table-column field="columnName" v-slot="itemProps">
<SelectableAttribute
:attr-name="props2.row.fieldClass"
:attr-id="itemProps.row.id"
:model-id="itemProps.row.id"
model-name="NewParticipant"
>
{{ itemProps.row.value }}
</SelectableAttribute>
</b-table-column>
</b-table>
</b-table-column>
</b-table>
</template>
</b-table>
</template>
You can achieve this by using props that you pass while calling that component. That prop could be anything you need. Here is a small example with simple true/false prop:
// Set the prop in your called component, in this case a boolean
props: {
myBoolean: Boolean
}
// Passing the prop from your parent to the component, where it has to be a property, in this case called myBooleanFromParent
<my-component :myBoolean="myBooleanFromParent"></my-component>
// In your component your template changes according to the passed prop from your parent
<template>
<div>
<div v-if="myBoolean">
If myBoolean is true
</div>
<div v-else>
Else, so if myBoolean is false
</div>
</div>
</template>
This is, as stated, a small and simple example. You can pass any kind of data with props. It could also be a object full of data to handle multiple conditions.

Switching child components inside v-data-table using buttons(filters)

I have a parent component with 2 buttons that I want to use as filters. the child component is called inside a v-data-table.
ScanGrid(parent):
<template>
<v-card class="ma-5" v-else>
<v-flex row xs12 class="pa-3 ml-3">
<div class="mr-3 mt-2">
<h3>Regrouper par :</h3>
</div>
<div>
<v-btn-toggle color="success" v-model="groupBy">
<v-btn
text
value="barCode"
class="lowerCase"
v-ripple="{ class: 'success--text' }"
>
Code barre
</v-btn>
<v-btn
text
value="productDef"
class="lowerCase"
v-ripple="{ class: 'success--text' }"
>
Définition de produit
</v-btn>
</v-btn-toggle>
</div>
</v-flex>
<v-card-text>
<v-layout align-center>
<v-data-table
:headers="headers"
:items="items"
item-key="StorageName"
show-expand
single-expand
:expanded="expanded"
hide-default-footer
#click:row="clickedRow"
>
<template v-slot:expanded-item="{ item }">
<td :colspan="12">
<ScanGridCode :item="item"></ScanGridCode>
</td>
</template>
<template v-slot:expanded-item="{ item }">
<td :colspan="12">
<ScanGridDef :item="item"></ScanGridDef>
</td>
</template>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</template>
The children components I want to switch using the buttons are called ScanGridCode and ScanGridDef inside templates in v-data-table. I tried to find examples of filtering buttons online but couldn't find anything like what I want to do.
I'm using Vue 2.6.10 with Vuetify 2.1.7
You're almost done already, since you already have the v-btn-toggle set up with v-model="groupBy". All you still need is to add v-if to each template, like:
<template v-if="groupBy=='barCode'" v-slot:expanded-item="{ item }">