How to make available all slots and events with a different name - vuejs2

I created an ExpansionPanel component based on Vuetify <v-expansion-panel>. The idea is to have a component completely identical to the Vuetify one, plus a couple of useful features (loading, items counter, etc).
ExpansionPanel
<template>
<v-expansion-panel
v-bind="$props"
v-on="$listeners"
>
<template v-for="(_, name) in $slots">
<template :slot="name">
<slot :name="name"></slot>
</template>
</template>
<template
v-for="(_, name) in $scopedSlots"
#[name]="data"
>
<slot
:name="name"
v-bind="data"
></slot>
</template>
<!-- TODO: $listeners -->
<v-expansion-panel-header v-bind="headerProps">
<!-- TODO: slots -->
<v-row
align="center"
no-gutters
>
<span>{{ heading }}</span>
<v-spacer></v-spacer>
<v-chip
v-if="items !== undefined && !loading"
class="mr-4"
color="primary"
label
small
>
{{ $tc('component.expansionPanel.item', items) }}
</v-chip>
</v-row>
</v-expansion-panel-header>
<v-progress-linear
v-if="loading"
indeterminate
></v-progress-linear>
<!-- TODO: $listeners -->
<v-expansion-panel-content v-bind="contentProps">
<!-- TODO: slots -->
<v-row
v-if="message"
justify="center"
>
<v-col cols="auto">
{{ message }}
</v-col>
</v-row>
<slot
v-else
name="content"
></slot>
</v-expansion-panel-content>
</v-expansion-panel>
</template>
<script>
import { VExpansionPanel } from 'vuetify/lib'
export default {
extends: VExpansionPanel,
props: {
/* Custom props */
heading: String,
items: Number,
loading: Boolean,
headerProps: Object,
contentProps: Object
},
computed: {
message () {
return this.loading
? 'Loading items...'
: this.items !== undefined && !this.items
? 'No data available'
: null
}
}
}
</script>
As you can see I'm only able to pass props to <v-expansion-panel-header> and <v-expansion-panel-content> sub components. I need a way to also use their slots and listeners without conflicting with the <v-expansion-panel> ones, maybe a way to rename them before making them available.

Related

Which is the best way to extend a Vuetify component?

I searched a lot and this seems to be the best way.
CustomVTextField.vue
<template>
<v-text-field
v-bind="$props"
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>
<script>
import { VTextField } from 'vuetify/lib'
export default {
extends: VTextField,
props: {
/* Setting defaults */
outlined: {
default: true
},
dense: {
default: true
}
}
}
</script>
Let's use it now.
<template>
<v-container fluid>
<CustomVTextField
v-model="firstName"
placeholder="Insert first name"
hint="I'm an hint"
#click="handleClick"
>
<template #label>
<b>First Name</b>
</template>
</CustomVTextField>
</v-container>
</template>
<script>
import CustomVTextField from '../components/CustomVTextField.vue'
export default {
components: {
CustomVTextField
},
data () {
return {
firstName: null
}
},
watch: {
firstName () {
console.log('first name:', this.firstName)
}
},
methods: {
handleClick () {
console.log('click handled')
}
}
}
</script>
This is the output.
As you can see v-model, props, slots and listeners are usable. Let's do the same thing with another component.
CustomVChipGroup.vue
<template>
<v-chip-group
v-bind="$props"
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-chip-group>
</template>
<script>
import { VChipGroup } from 'vuetify/lib'
export default {
extends: VChipGroup,
props: {
/* Setting defaults */
activeClass: {
default: 'primary--text'
}
}
}
</script>
Let's use it now.
<template>
<v-container fluid>
<CustomVChipGroup
v-model="selectedAnimal"
mandatory
#change="handleChange"
>
<v-chip value="DOG">
Dog
</v-chip>
<v-chip value="CAT">
Cat
</v-chip>
</CustomVChipGroup>
</v-container>
</template>
<script>
import CustomVChipGroup from '../components/CustomVChipGroup.vue'
export default {
components: {
CustomVChipGroup
},
data () {
return {
selectedAnimal: null
}
},
watch: {
selectedAnimal () {
console.log('selected animal:', this.selectedAnimal)
}
},
methods: {
handleChange () {
console.log('change handled')
}
}
}
</script>
This is the output.
The question should be clear now: which is the best way to extend a Vuetify component without occurring in these sort of errors?
Edit #1
#KaelWatts-Deuchar pointed out the same thing here (comments section), so I followed his solution.
CustomVTextField.vue
<template>
<v-text-field
v-bind="$attrs"
v-on="$listeners"
outlined
dense
>
<!-- 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>
Everything works the same, except...
<template>
<v-container fluid>
<CustomVTextField
v-model="firstName"
placeholder="Insert first name"
hint="I'm an hint"
:outlined="false"
:dense="false"
#click="handleClick"
>
<template #label>
<b>First Name</b>
</template>
</CustomVTextField>
</v-container>
</template>
As you can see I'm not able to change the component defaults.
CustomVChipGroup.vue
<template>
<v-chip-group
v-bind="$attrs"
v-on="$listeners"
active-class="primary--text"
>
<!-- 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-chip-group>
</template>
Console errors are solved, but watcher on selection is no more triggered.
<template>
<v-container fluid>
<CustomVChipGroup
v-model="selectedAnimal"
mandatory
#change="handleChange"
>
<v-chip value="DOG">
Dog
</v-chip>
<v-chip value="CAT">
Cat
</v-chip>
</CustomVChipGroup>
</v-container>
</template>
<script>
import CustomVChipGroup from '../components/CustomVChipGroup.vue'
export default {
components: {
CustomVChipGroup
},
data () {
return {
selectedAnimal: null
}
},
watch: {
/* BROKEN */
selectedAnimal () {
console.log('selected animal:', this.selectedAnimal)
}
},
methods: {
handleChange () {
console.log('change handled')
}
}
}
</script>
Edit #2
To solve both issues I came up with this solution.
CustomVChipGroup.vue
<template>
<v-chip-group
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-chip-group>
</template>
<script>
export default {
model: {
event: 'change'
},
computed: {
computedAttrs () {
return {
'active-class': 'primary--text',
...this.$attrs
}
}
}
}
</script>
Unfortunately changing defaults is only possible if used attr and computedAttrs are both kebab-case or camelCase. I don't know if this is the best solution, but for now goes well.

Passing props from parent component to child component on dialog box vue

So I want to bind batch_code data from dashboard.vue parent to review.vue child component
so, the dashboard contains details like the batch_code, then I have trouble passing the data to the review component, of which it will get the batch_code upon clicking the "Rate and Review" button
when I did try, I am just getting null values from returning said data. any suggestions?
dashboard.vue
<template>
<div>
<v-col cols="10" class="mx-auto">
<v-card class="pa-4" outlined>
<v-card-title class="pb-0 pt-2">Dashbard</v-card-title>
<div v-if="checkifEmpty()">
<v-row>
<v-col
v-for="item in myBatch.all_batch"
:key="item.batch_code"
cols="6"
>
<v-card class="ma-2" outlined>
<div class="d-flex">
<v-avatar class="ma-3" size="150" tile>
<v-img :src="item.image"></v-img>
</v-avatar>
<div>
<v-card-title class="pb-0 pt-2"
>{{ item.offer }} ({{ item.level }})</v-card-title
>
<v-card-text>
<div class="mt-0">{{ item.techer_name }}</div>
<div class="mt-0">{{ item.batch_name }}</div>
<div class="Heading 6 pb-0">
{{ item.start_date }} -
{{ item.end_date }}
</div>
<div class="subtitle-1 pb-0">{{ item.type }}</div>
</v-card-text>
</div>
<v-btn elevation="3" v-on:click="openReviewDialog"
>Rate and Review!</v-btn
>
</div>
</v-card>
</v-col>
</v-row>
</div>
<div v-else>
<v-card-text class="pb-0 pt-2"
>You have no enrolled offers</v-card-text
>
</div>
</v-card>
</v-col>
<review />
</div>
</template>
<script>
import store from "../../store/index";
import review from "./review"
export default {
name: "Dashboard",
components:{
review,
},
computed: {
myBatch() {
return store.getters.getMyOffers;
},
},
methods: {
checkifEmpty() {
let batch = this.myBatch;
if (batch == null || batch.all_batch.length == 0) {
return false;
} else {
return true;
}
},
openReviewDialog() {
this.$store.dispatch("setreviewDialog");
this.sidebarFront = false;
}
},
};
</script>
<style>
</style>
‍‍‍review.vue
<template>
<v-row justify="center">
<v-dialog v-model="reviewDialog" persistent max-width="900px">
<v-card>
<v-card-title class="justify-center">
<span class="headline font-weight-bold"
>Rate and Review this Course!</span
>
</v-card-title>
<v-card-text>
<v-container fluid>
<v-row>
<v-col cols="12" sm="12" md="12">
<v-form
ref="userReview"
v-model="userReviewForm"
lazy-validation
>
<v-text-field
rounded
outlined
v-model="subject"
label="Subject"
required
></v-text-field>
<v-text-field
rounded
outlined
v-model="batch_code"
label="batch_code"
readonly
></v-text-field>
<v-textarea
rounded
outlined
v-model="review"
counter="250"
label="Review"
required
></v-textarea>
<v-rating v-model="rating">
<template v-slot:item="props">
<v-icon
:color="props.isFilled ? 'orange lighten-1' : 'grey lighten-1'"
size = "30"
#click="handleRatingChange(props)">mdi-star</v-icon>
</template>
</v-rating>
<div>
<v-btn
:loading="loginLoader"
large
block
rounded
elevation="0"
color="primary"
#click="submit"
>
Submit
</v-btn>
</div>
</v-form>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<div class="close"> <v-btn color="error" text #click="closeReviewDialog()"> Close </v-btn></div>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</template>
<script>
import store from "../../store/index";
export default {
props: {
item:{
batch_code: null;
}
},
name: "review",
data() {
return {
getters: store.getters,
rating: null
};
},
computed: {
reviewDialog: function () {
return this.getters.getreviewDialog;
},
},
methods: {
closeReviewDialog: function () {
//this.show = false;
//this.$refs.card.hide();
//store.dispatch("removeLoginError");
store.dispatch("setreviewDialog");
},
handleRatingChange(props){
console.log(props.index + 1)
this.rating = props.index +1
}
},
};
</script>
'''
p.s: i don't know if it's different when calling props for a component than to a dialog box.
just update your code like below tips,
openReviewDialog() {
this.$store.dispatch("setreviewDialog", **your_rating_data**);
this.sidebarFront = false;
}
so update your dispatch/action accordingly in store.
and when loading your form just pull data from the store using getter and show on dialog.

How to make v-skeleton-loader inside v-for in vuetify?

I am trying to show a v-skeleton-loader in Vuetify. I have used v-if and v-else. If the image is not loaded, then it should show the skeleton loader. Otherwise, it should should show the image. This is my code:
<template>
<v-col v-for="option in options" :key="option.id" cols="6">
<v-lazy :options="{ threshold: 0.5 }" min-height="130">
<v-hover v-slot="{ hover }">
<v-card id="options_card" link width="160">
<v-sheet v-if="!images" class="px-3 pt-3 pb-3">
<v-skeleton-loader max-width="300" type="image"></v-skeleton-loader>
</v-sheet>
<v-img
v-else
id="thumbnail"
width="100%"
height="130"
:src="option.thumbnail"
></v-img>
</v-card>
</v-hover>
</v-lazy>
</v-col>
</template>
<script>
export default {
data() {
return {
images: false,
}
},
mounted() {
this.images = true
},
}
</script>
But the v-skeleton-loader is not seen on the screen.
VImage has a placeholder slot that would be used for customizing the loader component to be shown while the image is loading:
<v-img>
<template v-slot:placeholder>
<v-sheet>
<v-skeleton-loader />
</v-sheet>
</template>
</v-img>
demo
<v-img>
<template v-slot:placeholder>
<v-sheet>
<v-skeleton-loader />
</v-sheet>
</template>
</v-img>

Conditional vue component show

I'm building a generic table component on VUE, and pass by props the reference source.
Example:
<drk-table table="users"></drk-table>
After this, I use the Axios to load the data from my server in the mounted() event.
This works fine when I have only one component in my template.
But if I try to reuse the component with different props in a conditional v-if, only the first table its load:
<div v-if="currentPage == 0">
<drk-table table="users"></drk-table>
</div>
<div v-else-if="currentPage == 1">
<drk-table table="admins"></drk-table>
</div>
*(currentPage is a data field)*
However if I do the same with v-show it's works:
<div v-show="currentPage == 0">
<drk-table table="users"></drk-table>
</div>
<div v-show="currentPage == 1">
<drk-table table="admins"></drk-table>
</div>
<template>
<div>
<v-app-bar app clipped-left color="amber">
</v-app-bar>
<v-navigation-drawer v-model="drawer" app clipped color="grey lighten-4">
</v-navigation-drawer>
<v-content>
<v-container fluid fill-height>
<v-layout align-center justify-center>
<v-flex text-xs-center>
<div v-if="currentPage == 0">
<drk-table table="users"></drk-table>
</div>
<div v-else-if="currentPage == 1">
<drk-table table="admins"></drk-table>
</div>
</v-flex>
</v-layout>
</v-container>
</v-content>
<v-footer color="indigo" app>
</v-footer>
</div>
</template>
<script>
export default {
data: () => ({
drawer: false,
itens: [
{ icon: 'contacts', text: 'item0' },
{ icon: 'settings', text: 'item1' },
],
currentPage : 0,
}),
methods:{
goPage(item){
console.log("goint to "+item);
this.currentPage = item;
}
}
};
</script>
The official documentation from VUE says that the v-if will recreate the item every time who the condition turns TRUE.
My doubt about this is why the v-if doesn't work properly when I use the same component with different props?
In this case, I need to change my mounted() event?

How can i pass background color as Prop in for loop?

its me again!
so i have a own component:
<template>
<div class='mynewcomponent'>
<v-layout>
<v-flex xs12 sm6 offset-sm3>
<v-card v-bind:style="{ backgroundColor: this.myColor}">
<!-- Picture
<v-img
src="https://cdn.vuetifyjs.com/images/cards/sunshine.jpg"
height="200px"
>
</v-img>
-->
<v-card-title primary-title>
<div>
<slot name="header">Top western road trips</slot>
<br>
<slot name="TestDesciption">1,000 miles of wonder</slot>
</div>
</v-card-title>
<v-card-actions>
<v-btn flat>Share</v-btn>
<v-btn flat color="purple">Explore</v-btn>
<v-spacer></v-spacer>
<v-btn icon #click="show = !show">
<v-icon>{{ show ? 'keyboard_arrow_down' : 'keyboard_arrow_up' }}</v-icon>
</v-btn>
</v-card-actions>
<v-slide-y-transition>
<v-card-text v-show="show">
I'm a thing. But, like most politicians, he promised more than he could deliver. You won't have time for sleeping, soldier, not with all the bed making you'll be doing. Then we'll go with that data file! Hey, you add a one and two zeros to that or we walk! You're going to do his laundry? I've got to find a way to escape.
</v-card-text>
</v-slide-y-transition>
</v-card>
</v-flex>
</v-layout>
</div>
</template>
<script>
export default {
data: () => ({
show: false,
myColor:'#ffffff'
})
}
</script>
and in my about.vue i load it in a for loop:
<template>
<div class='about'>
<mynewcomponent v-for="(item,index) in 100"/>>
<template v-slot:header>
<h3 style="text-align: left;"><span style="color: #3366ff">ID: 1234</span></h3>
</template>
<template v-slot:TestDesciption>
<h3 style="text-align: left">example shit</h3>
</template>
</mynewcomponent>
</div>
</template>
<script>
import myNewComponent from '#/components/myNewComponent.vue'; // # is an alias to /src
export default {
name: 'about',
components: {
'mynewcomponent': myNewComponent
}
}
</script>
now i want the even and odd Cards in other background color.
i tried everything what google says but whitout success.
i will pass the color if index % 2 == 0 (even or odd)
how can i pass the color in the for loop ?
or can someone tell me a better way to do this?
Thank you
You can create a method to bind the class attribute and pass the index as a parameter. For each line, you can evaluate and return a different class to this element.
You can check am example here
methods:{
spanClass: function(index) {
return {
in: index % 2 === 0,
out: index % 2 !== 0
}
}
li.in {
background-color:red;
}
li.out {
background-color:black;
}
<template>
<div class='about'>
<mynewcomponent v-for="(item,index) in 100"/>>
<template v-slot:header>
<h3 style="text-align: left;"><span :class="spanClass(item)">ID: 1234</span></h3>
</template>
<template v-slot:TestDesciption>
<h3 style="text-align: left">example shit</h3>
</template>
</mynewcomponent>
</div>
</template>