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

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>

Related

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

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.

Vuetify v-date-picker formatting is wonky - how can I fix?

v-date-picker formatting is screwy. The popup is chopped off at the bottom and there's a transparent gutter on the right hand side. Is this a known bug? The popup seems to always be the width of the container. I removed all CSS and simplified the app to the test below.
<!-- the layout (admin.vue) -->
<template>
<v-app>
<v-main>
<Nuxt class="ma-2"/>
</v-main>
</v-app>
</template>
<!-- the page -->
<template>
<div>
<v-menu :close-on-content-click="true" :nudge-right="40" transition="scale-transition" offset-y>
<template v-slot:activator="{ on }">
<v-text-field label="Date listed" prepend-icon="mdi-calendar" :value="form.dateListed" v-on="on"/>
</template>
<v-date-picker v-model.lazy="form.dateListed" locale="en-us" header-color="primary" tabindex="0"/>
</v-menu>
</div>
</template>
<script>
export default {
layout: 'admin',
data() {
return {
form: {
},
}
},
}
</script>
enter image description here
You need to add min-width: auto to the v-menu like this:
<v-menu min-width="auto" v-model="menus.date">
...
</v-menu>

Is there a Vue or Vuetify component that does not render ANY output?

I have a custom navMenu component that I show twice on my page - once across the top, and once hidden in a v-navigation-drawer until the screen width gets small enough to show it:
<template>
<nav>
<v-app-bar app hide-on-scroll>
<template #extension v-if="$vuetify.breakpoint.smAndUp">
<v-container>
<v-row>
<v-spacer />
<navMenu :items="menuItems" />
<v-spacer />
</v-row>
</v-container>
</template>
<v-app-bar-nav-icon
#click="toggleDrawer()"
v-if="$vuetify.breakpoint.xs"
/>
<img id="logo"
alt="corporate logo"
src="#/assets/full_logo.svg"
width="200"
height="60"
/>
<v-spacer />
<h3 class="info--text headline">My Fancy Website</h3>
</v-app-bar>
<v-navigation-drawer app
v-model="drawer"
v-if="$vuetify.breakpoint.smAndDown">
<navMenu :items="menuItems" />
</v-navigation-drawer>
</nav>
</template>
NavMenu.vue
<template>
<v-col v-for="(item, index) in items" :key="index">
<div v-if="item.children">
<v-menu transition="slide-y-transition" bottom>
<template #activator="{ on }">
<v-btn text v-on="on">{{ item.label }}</v-btn>
</template>
<v-list>
<v-list-item
v-for="(child, j) in item.children"
:key="j"
router
:exact="child.exact"
:to="{ name: child.routeName }"
>
<v-list-item-title class="text-capitalize">
{{ child.label }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
<div v-else>
<v-btn text router :to="{ name: item.routeName }" :exact="item.exact">
{{ item.label }}
</v-btn>
</div>
</v-col>
</template>
...you may have noticed a fatal flaw in my component: you can't iterate on a root element! Simple enough, wrap it in a <div />, right? Wrong. Wrapping the contents of the template in a div really screws up the layout of the menu items - it renders them stacked vertically instead of horizontally - I think the CSS is looking for a direct child or something.
Is there some alternative element that I can use for the template to satisfy the "one root element" edict that doesn't render any output? Oh, and I tried using the <template /> element already - you can't use it as a root element.
You were so close to finding the answer...
Actually, you should put in a div container, but then just change the display property of the container so the items are still positioned horizontally:
NavMenu.vue
<template>
<div style="display: flex">
<v-col v-for="(item, index) in items" :key="index">
...
I updated your code below. Essentially, all you need to do is to move the entire <v-navigation-drawer> component into your NavMenu.vue file and add the additional drawer prop. Since the <v-navigation-drawer component doesn't actually do much on its own. The #input event listener is only so that you can have the parent component update the drawer value outside of the child component.
<template>
<nav>
<v-app-bar app hide-on-scroll>
<template #extension v-if="$vuetify.breakpoint.smAndUp">
<v-container>
<v-row>
<v-spacer />
<navMenu :items="menuItems" />
<v-spacer />
</v-row>
</v-container>
</template>
<v-app-bar-nav-icon
#click="toggleDrawer()"
v-if="$vuetify.breakpoint.xs"
/>
<img id="logo"
alt="corporate logo"
src="#/assets/full_logo.svg"
width="200"
height="60"
/>
<v-spacer />
<h3 class="info--text headline">My Fancy Website</h3>
</v-app-bar>
<navMenu
v-if="$vuetify.breakpoint.smAndDown"
:drawer="drawer"
:items="menuItems"
#navInput="toggleDrawer()"
/>
</nav>
</template>
NavMenu.vue
<template>
<v-navigation-drawer app :value="drawer" #input="$emit('navInput')>
<v-col v-for="(item, index) in items" :key="index">
<div v-if="item.children">
<v-menu transition="slide-y-transition" bottom>
<template #activator="{ on }">
<v-btn text v-on="on">{{ item.label }}</v-btn>
</template>
<v-list>
<v-list-item
v-for="(child, j) in item.children"
:key="j"
router
:exact="child.exact"
:to="{ name: child.routeName }"
>
<v-list-item-title class="text-capitalize">
{{ child.label }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
<div v-else>
<v-btn text router :to="{ name: item.routeName }" :exact="item.exact">
{{ item.label }}
</v-btn>
</div>
</v-col>
</v-navigation-drawer>
</template>
<script>
export default {
props: {
drawer: Boolean,
items: {
type: Array,
default: () => []
}
}
};
</script>

Vuetify: Adaptive v-date-picker width v-dialog and v-menu

I want to do adaptive v-date-picker, i.e when phone page width then date picker open in v-dialog, and when desktop then data picker open in v-menu.
It's my try:
<template>
<div>
<template v-if="$vuetify.breakpoint.xsOnly">
<v-dialog
ref="dialog"
v-model="modal"
persistent
width="290px"
>
<template v-slot:activator="{ on }">
<slot name="input" ref="input" v-on="on"/>
</template>
<slot name="picker" ref="picker"/>
</v-dialog>
</template>
<template v-else>
<v-menu
ref="menu"
v-model="menu"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="290px"
>
<template v-slot:activator="{ on }">
<slot name="input" ref="input" v-on="on"/>
</template>
<slot name="picker" ref="picker"/>
</v-menu>
</template>
</div>
</template>
<script>
export default {
name: "v-date",
data() {
return {
menu: false,
modal: false,
}
},
methods: {
close() {
this.menu = false;
this.modal = false;
}
}
}
</script>
But v-on doesn't work. I try :listeners="on", it doesn't work too...
For example use component:
<v-date>
<template v-slot:input>
<v-text-field
label="Дедлайн"
v-model="data.deadline"
readonly
/>
</template>
<template v-slot:picker>
<v-date-picker v-model="data.deadline" no-title scrollable>
<v-spacer></v-spacer>
<v-btn text color="primary" #click="$refs.deadline.close()">ОК</v-btn>
</v-date-picker>
</template>
</v-date>
Thanks to KrasnokutskiyEA for the idea.
Work version:
<template>
<div>
<template v-if="$vuetify.breakpoint.xsOnly">
<v-dialog
ref="dialog"
v-model="modal"
persistent
width="290px"
>
<template v-slot:activator="{ on }">
<slot name="input" ref="input" :on="on"/>
</template>
<slot name="picker" ref="picker"/>
</v-dialog>
</template>
<template v-else>
<v-menu
ref="menu"
v-model="menu"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="290px"
>
<template v-slot:activator="{ on }">
<slot name="input" ref="input" :on="on"/>
</template>
<slot name="picker" ref="picker"/>
</v-menu>
</template>
</div>
</template>
<script>
export default {
name: "v-date",
data() {
return {
menu: false,
modal: false,
}
},
methods: {
close() {
this.menu = false;
this.modal = false;
}
}
}
</script>
Use:
<v-date>
<template v-slot:input="{ on }">
<v-text-field
label="Дедлайн"
v-model="data.deadline"
readonly
v-on="on"
/>
</template>
<template v-slot:picker>
<v-date-picker v-model="data.deadline" no-title scrollable>
<v-spacer></v-spacer>
<v-btn text color="primary" #click="$refs.deadline.close()">ОК</v-btn>
</v-date-picker>
</template>
</v-date>

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>