Vue transition breaking layout while transitioning - vue.js

I am facing a problem where, when i close the search text field (nav-search component), the layout breaks for a very short amount of time (less than a second). I think it is caused by the fact that the searchClosed property is updated to false before the animation ends, so the UI content that should be hidden appears before the search text field has fully disapeared.
How could i fix this behavior? Am i doing something wrong? I have no clue how to make :class="{'d-flex justify-space-around' : searchClosed, 'hidden': !searchClosed} sync with the transition effect.
Here is my block of code:
<template>
<v-app dark>
<v-app-bar fixed app class="app-bar">
<v-row>
<v-col cols="2">
<v-app-bar-nav-icon
aria-label="show-or-hide-navigation-menu"
#click.stop="drawer = !drawer"
/>
</v-col>
<v-col :class="{'d-flex justify-space-around' : searchClosed, 'hidden': !searchClosed}">
<nuxt-link aria-label="home-page" to="/" class="d-flex">
<v-img
:src="require('~/assets/images/example_Logo.svg')"
max-height="55px"
max-width="110px"
class="mb-1"
contain
></v-img>
</nuxt-link>
</v-col>
<v-col cols="2" :class="{'d-flex justify-end' : searchClosed, 'hidden' : !searchClosed}">
<v-btn aria-label="show-user-menu" icon>
<v-icon>mdi-account-circle</v-icon>
</v-btn>
<v-btn
aria-label="show-or-hide-search-input"
icon
#click="searchClosed = false"
>
<v-icon>mdi-magnify</v-icon>
</v-btn>
</v-col>
<transition name="slide-fade">
<nav-search
v-show="!searchClosed"
#search-opened="searchClosed = false"
#search-closed="searchClosed = true"
></nav-search>
</transition>
</v-row>
</v-app-bar>
<v-navigation-drawer v-model="drawer" temporary fixed app>
<v-btn #click="handleDarkMode">Toggle Dark</v-btn>
<template v-if="loggedIn === undefined">
<nuxt-link to="/register">
<v-btn>Register</v-btn>
</nuxt-link>
<nuxt-link to="/login">
<v-btn>login</v-btn>
</nuxt-link>
</template>
<template v-else>
<v-btn #click="logout">Logout</v-btn>
</template>
</v-navigation-drawer>
<v-main>
<nav-search-results-list v-if="displaySearchResults" />
<Nuxt #click.native=";(searchClosed = true), (searchResults = false)" />
</v-main>
<v-footer :absolute="!fixed" app>
<span>© {{ new Date().getFullYear() }}</span>
</v-footer>
</v-app>
</template>
<script>
import NavSearch from '~/components/NavSearch.vue'
export default {
name: 'MainLayoutComponent',
components: { NavSearch },
data() {
return {
searchClosed: true,
drawer: false,
fixed: false,
title: 'exampleApp',
}
},
computed: {
displaySearchResults() {
return this.$store.state.search.displaySearchResults
},
loggedIn() {
return this.$auth.$storage.getState('user')
},
},
methods: {
handleDarkMode() {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
},
async logout() {
await this.$auth.logout('local')
this.$auth.$storage.removeUniversal('user')
},
},
}
</script>
<style scoped>
.slide-fade-enter-active {
transition: all 0.9s ease;
}
.slide-fade-enter,
.slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
.app-bar >>> .v-toolbar__content {
padding: 2px !important;
}
.hidden{
display: none;
}
</style>

Related

V-select issue in Vuetify 3

I'm using Vuetify 3.0.0-beta.0 ~ for my project (because it is the only version that supports vue3), and having a bit weird issue
I want to implement the same thing as described there https://codepen.io/reijnemans/pen/vYNadMo?editors=1010 with v-select involved, so I was needed to use Vuetify
copied snippet
<v-select
:items="items"
label="Standard"
>
<template v-slot:selection="{ item, index }">
<img :src="item.image">{{ item.name }}</template>
</template>
<template v-slot:item="{ item }">
<img :src="item.image">{{ item.name }}</template>
</v-select>
My Component:
<template>
<div class="resourceSelectors">
<v-col cols="10" lg="4" class="mx-auto">
<div class="text-center">
<h2 class="indigo--text" style="margin-bottom: 30px">Some Test H2</h2>
</div>
<v-col class="d-flex" cols="12" sm="6">
<v-select
:items="items"
label="Standard">
<template v-slot:selection="{ item }">
<img :src="item.image">{{ item.name }}
</template>
<template v-slot:item="{ item }">
<img :src="item.image">{{ item.name }}
</template>
</v-select>
</v-col>
</v-col>
</div>
</template>
<script>
import { mapState } from "vuex";
/* eslint-disable */
export default {
name: "testComponent",
data() {
return {
// hardware Configuration Validation Rules
items: [
{ name: 'Foo', image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'},
{ name: 'Bar', image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'},
{ name: 'Hoo', image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'},
{ name: 'Coo', image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'}],
}
}}
When I'm trying to run the above component I always get this weird error Failed setting prop "type" on <select>: value text is invalid. TypeError: Cannot set property type of #<HTMLSelectElement> which has only a getter,
Did anyone faced similar issue before?
In Vuetify 3, you need some workarounds to style the items in v-select, because the item slot resets the entire styling.
You should use the menu-props, with it you can pass props through to the v-menu component. It accepts an object with anything from /api/v-menu. This allows you to close the field on click.
In the item slot, you should use a v-list-item with an #click property to set the model.
I made an example here with a selection of symbols:
<script setup>
const symbols = [
'ab-testing',
'abacus',
'account',
'account-alert',
]
const form = { symbol: '', }
</script>
<template>
<v-select
v-model="form.symbol"
:items="symbols"
label="Symbol"
:prepend-inner-icon="'mdi-'+form.symbol"
:menu-props="{
closeOnClick: true,
closeOnContentClick: true,
}"
>
<template v-slot:selection="{ item, index }">
{{ item.value }}
</template>
<template v-slot:item="{ item, index }">
<v-list-item
:title="item.title"
:prepend-icon="'mdi-'+item.title"
#click="form.symbol = item.title"
>
</v-list-item>
</template>
</v-select>
</template>
I hope it helps you.
I couldn't find correct solution but I just wanted to share what I did about scoped slot. I think we should use item.raw to access name and image. And the next problem is how to make it clickable to trigger select event that I didn't know yet :(
const { createApp } = Vue
const { createVuetify } = Vuetify
const vuetify = createVuetify()
const app = createApp({
data() {
return {
value: null,
items: [
{
name: 'Foo',
image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'
},
{
name: 'Bar',
image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'
},
{
name: 'Hoo',
image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'
},
{
name: 'Coo',
image: 'https://www.gravatar.com/avatar/b17065ea1655f1e3283aac8d8fc16019?s=48&d=identicon&r=PG'
}
]
}
}
});
app.use(vuetify).mount('#app');
<link href="https://cdn.jsdelivr.net/npm/vuetify#3.0.0-beta.9/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#3.0.0-beta.9/dist/vuetify.min.js"></script>
<div id="app">
<div class="resourceSelectors">
<v-col cols="10" lg="4" class="mx-auto">
<div class="text-center">
<h2 class="indigo--text" style="margin-bottom: 30px">Some Test H2</h2>
</div>
<v-col class="d-flex" cols="12" sm="6">
<v-select
v-model="value"
:items="items"
item-title="name"
item-value="name"
label="Standard">
<template v-slot:item="{item}">
<v-list-item
:prepend-avatar="item.raw.image"
:title="item.raw.name"
/>
</template>
</v-select>
</v-col>
</v-col>
</div>
</div>

How do I lazy load item lists on Vuejs and Vuetify's lazyload?

Im trying to make an infinite scroll list but it's really not lazy loading, been stuck with this for hours, the whole list will come in.
.....
<v-col
v-for="(post, i) in posts"
:key="i"
cols="12"
>
<v-lazy
v-model="isActive"
:options="{
threshold: .5
}"
transition="fade-transition"
>
{{Content here}}
</....
API used for test : https://jsonplaceholder.typicode.com/posts
There is a new virtual-scroller component, but it doesn't work with responsive content like grid rows/cols. Instead use v-lazy...
I discovered that the columns need to have defined min-height (approx. to the expected height of the cards) in order for the v-lazy intersection observer to work. Use something like a v-sheet or v-responsive to set the min-height and contain the cards.
Also bind the v-model of the v-lazy to each post (ie: post.isActive), instead of a global isActive var...
<v-col lg="3" md="4" sm="6" cols="12" v-for="(post, index) in posts">
<v-sheet min-height="250" class="fill-height" color="transparent">
<v-lazy
v-model="post.isActive" :options="{
threshold: .5
}"
class="fill-height">
<v-card class="fill-height" hover>
<v-card-text>
<v-row :key="index" #click="">
<v-col sm="10" cols="12" class="text-sm-left text-center">
#{{ (index+1) }}
<h2 v-html="post.title"></h2>
<div v-html="post.body"></div>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-lazy>
</v-sheet>
</v-col>
Demo: https://codeply.com/p/eOZKk873AJ
I can suggest another solution with v-intersect, which works perfect for me.
Sorry, the snippet may be not working as composed of my code, but the idea should be pretty clear
<template>
<v-list class="overflow-y-auto" max-height="500">
<v-list-item v-for="item in items">
{{ item.name }}
</v-list-item>
<v-skeleton-loader v-if="moreDataToAvailable" v-intersect="loadNextPage" type="list-item#5" />
</v-list>
</template>
<script lang="ts">
import Vue from 'vue'
const pageSize = 10
export default Vue.extend({
data(): any {
return {
pageLoaded: 0,
totalCount: 100,//fetch from API
items: []
}
},
computed: {
moreDataToAvailable (): boolean {
return Math.ceil(this.totalCount / pageSize) - 1 > this.pageLoaded
}
},
methods {
async loadNextPage (entries: IntersectionObserverEntry[]) {
if (entries[0].isIntersecting && this.moreDataToAvailable) {
const nextPage = this.pageLoaded + 1
const loaded = await this.loadPage(nextPage) //API call
loaded.data.forEach((item: any) => this.items.push(item))
this.totalCount = loaded.totalCount
this.pageLoaded = nextPage
}
},
}
})
</script>

How to unit test a variable in vue and vuetify with jest?

This might be pretty simple.
I have a component that renders a receive a message via props and renders it in a v-card-text. It doesn't do that in an optimal way, the whole v-if is messy, but it works.
<template>
<!-- Needed to be rendered big -->
<div v-if="start == true" class="ma-0">
<v-card class="text-xs-right" dark color="rgb(0,140,69)" height="50" width="1060" >
<v-btn dark text icon #click= "isOpen = !isOpen;" color="rgb(0,140,69)" >
<v-icon dark>fullscreen</v-icon>
</v-btn>
<v-btn class="mx-2" icon dark small color="rgb(0,140,69)">
<v-icon dark>thumb_up</v-icon>
</v-btn>
</v-card>
<v-card v-model='text' color="" height="200" width="1060">
<v-card-text ref="tcardbig" color="rgb(0,140,69)" style="color:rgb(0,140,69);display:flex;height:100%;justify-content:center;" class="display-2 font-weight-black align-center"> {{text}}</v-card-text>
</v-card>
</div>
<!-- Needed to be rendered small -->
<div v-else class="ma-2">
<v-card class="text-xs-right" dark color="rgb(0,140,69)" height="50" width="500" >
<v-btn dark text icon #click= "isOpen = !isOpen;" color="rgb(0,140,69)" >
<v-icon dark>fullscreen</v-icon>
</v-btn>
<v-btn class="mx-2" icon dark small color="rgb(0,140,69)">
<v-icon dark>thumb_up</v-icon>
</v-btn>
</v-card>
<v-card v-model='text' color="" height="350" width="500">
<v-card-text ref="tcardsmall" color="rgb(0,140,69)" style="color:rgb(0,140,69);display:flex;height:100%;justify-content:center;" class="display-2 font-weight-black align-center"> {{text}}</v-card-text>
</v-card>
</div>
</template>
<script>
export default {
data(){
return {
text: 'lorem ipsum',
start: true
}
},
props: ['textProps', 'startProps'],
mounted(){
this.text = this.textProps
this.start = this.startProps
}
}
</script>
I'm trying to unit test it with jest. How do I check if the text is properly rendered?
In other how I check the {{text}} in the OR how I check if the text variable of the component has the value I sent via prop. What am I doing wrong?
import {createLocalVue, mount} from '#vue/test-utils'
import textCard from './textCard.vue'
import Vue from 'vue'
import Vuetify from 'vuetify'
Vue.use(Vuetify)
const localVue = createLocalVue()
describe('textCard.vue', () => {
const wrapper = mount(textCard,
{
localVue,
vuetify,
propsData: {
text: 'Random Text',
start: false
},
}
)
it('renders a vue instance', () => {
expect(mount(textCard).isVueInstance()).toBe(true);
});
const content = wrapper.find({ ref: 'tcardsmall' })
it('Checks the if the variable text is correct', () => {
expect(content.selector).toMatch('Random Text')
})
}
)

reset a vuetify stepper

I'm looking for a function who can resetting my stepper made with vuetify.
the e1 is set as 0 but if I make a function who reset this value to 0, it doesn't work and the stepper set as the same screen.
It is possible to reset a stepper to default state
Find the working codepen here: https://codepen.io/chansv/pen/wvvzddP?editors=1010
<div id="app">
<v-app id="inspire">
<v-stepper v-model="step" vertical>
<v-stepper-header>
<v-stepper-step step="1" :complete="step > 1">Your Information</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="2" :complete="step > 2">Your Address</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="3">Misc Info</v-stepper-step>
</v-stepper-header>
<v-stepper-items>
<v-stepper-content step="1">
<v-text-field label="Name" v-model="registration.name" required></v-text-field>
<v-text-field label="Email" v-model="registration.email" required></v-text-field>
<v-btn color="primary" #click.native="step = 2">Continue</v-btn>
</v-stepper-content>
<v-stepper-content step="2">
<v-text-field label="Street" v-model="registration.street" required></v-text-field>
<v-text-field label="City" v-model="registration.city" required></v-text-field>
<v-text-field label="State" v-model="registration.state" required></v-text-field>
<v-btn flat #click.native="step = 1">Previous</v-btn>
<v-btn color="primary" #click.native="step = 3">Continue</v-btn>
</v-stepper-content>
<v-stepper-content step="3">
<v-text-field label="Number of Tickets" type="number"
v-model="registration.numtickets" required></v-text-field>
<v-select label="Shirt Size" v-model="registration.shirtsize"
:items="sizes" required></v-select>
<v-btn flat #click.native="step = 2">Previous</v-btn>
<v-btn color="primary" #click.prevent="submit">Save</v-btn>
</v-stepper-content>
</v-stepper-items>
</v-stepper>
</v-app>
</div>
const defaultReg = Object.freeze({
name:null,
email:null,
street:null,
city:null,
state:null,
numtickets:0,
shirtsize:'XL'
});
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
step:1,
registration: Object.assign({}, defaultReg),
sizes:['S','M','L','XL']
}
},
methods:{
submit() {
this.registration = Object.assign({}, defaultReg);
this.step = 1;
}
}
})
A simpler approach at resetting your stepper is by using the key prop assigning to it a value and then in the function increasing this value. Something like this:
<template>
<v-stepper
:key="stepperKey"
v-model="e1"
>
...
</v-stepper>
</template>
<script>
export default {
data () {
return {
e1: 1,
stepperKey: 0
}
},
methods: {
increaseKey () { this.stepperKey++ }
}
}
</script>
The key prop or attribute is a build in Vue.js feature. Even if you don't see it it's been used on the back. Changing the key will trigger a re render.
If you have doubt about the key attribute/prop here is a nice article about it

Extracting the information in a prop in a Vue child component

I'm passing a object as a prop to a child component but I can't reference one of its elements (user_id) to use in a method in that child component.
My code (slightly abbreviated) is:
<template>
<div class="back">
<v-app id="inspire">
<v-content>
<v-container fluid>
<v-card flat>
<v-card-title>
<div>
<div class="headline">
{{data.title}}
</div>
<span class="grey--text">{{data.user}} said {{data.created_at}}</span>
</div>
<v-spacer></v-spacer>
<v-badge color="deep-orange accent-3" left overlap>
<span slot="badge">7</span>
<v-icon color="grey lighten-1" large>
insert_comment
</v-icon>
</v-badge>
<!--<v-btn color="deep-orange accent-3">5 replies</v-btn>-->
</v-card-title>
<v-card-text v-html="data.body"></v-card-text>
<v-card-actions v-if="own">
<v-btn icon small>
<v-icon color="deep-orange accent-3">edit</v-icon>
</v-btn>
<v-btn icon small>
<v-icon color="red">delete</v-icon>
</v-btn>
</v-card-actions>
</v-card>
<return-button></return-button>
</v-container>
</v-content>
</v-app>
</div>
</template>
<script>
export default {
name: "ShowQuestion",
props: ['data'],
data() {
return {
own: this.Own(),
}
},
methods: {
Own: function () {
return this.UserID() == this.user_id <---HERE BE DRAGONS! (reported as 'undefined')
},
UserID: function () {
... returns the 'sub' from the JWT token
return sub;
}
},
}
</script>
While the correct information is being displayed in the template, I also need to be able to compare the user's ID from the token with that contained in the prop (i.e. data.user_id). My reading suggests the solution will involve converting the object to an array, but that's beyond my current skill level too. I'd appreciate some pointers to a solution.
Thanks,
Tom
If you can render data.user_id in your template you can use it anywhere, but I'd probably do something like this to solve your problem:
props: ['data']
data() {
return {
}
},
computed: {
UserId() {
return //however you get your id
}
},
Then your v-if could just be this:
<v-card-actions v-if="UserId === data.user_id">