Vue transitions and instant fade out of the component - vue.js

Currently when using transition in Vue I'm facing the problem that some of the components on the page disappear instantly whereas the rest fade out normally with the whole page.
This is my transition set up as minimal reproducible example, is working here Codepen
You can see when switching from Home route to any other route the button disappears instantly while the rest of the view disappears according to the fade properties set using css (and vue transitions).
// https://github.com/groloop/vuejs-2-series
Vue.use(Vuetify);
Vue.use(VueRouter);
var Home = {
template: '<div> <h2>Home</h2> <v-tooltip left> <template v-slot:activator="{ on }"> <v-btn color="primary" dark v-on="on">Left</v-btn> </template> <span>Left tooltip</span> </v-tooltip> </div>'
}
var AboutUs = {
template: '<h2>About Us</h2>'
}
var Contact = {
template: '<h2>Contact</h2>'
}
var NotFound = {
template: '<h2>Not Found</h2>'
}
var router = new VueRouter({
history: false,
routes: [
{ path: '/', name: 'home', component: Home },
{ path: '/about-us', name: 'about-us', component: AboutUs },
{ path: '/contact', name: 'contact', component: Contact },
{ path: '*', name: 'not-found', component: NotFound }
],
});
new Vue({
el: '#app',
router: router
});
.fade-enter-active,
.fade-leave-active {
transition-duration: 0.5s;
transition-property: opacity;
transition-timing-function: ease-in;
}
.fade-enter-active {
transition-delay: .5s;
}
.fade-enter,
.fade-leave-active {
opacity: 0
}
<link href="https://unpkg.com/vuetify#1.5.16/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vuetify#1.5.16/dist/vuetify.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.10/vue.js"></script>
<div id="app">
<v-toolbar>
<v-toolbar-title>TEST</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items>
<v-btn flat href="#/">Home</v-btn>
<v-btn flat href='#/about-us'>About us</v-btn>
<v-btn flat href='#/contact'>Contact</v-btn>
<v-btn flat href='#/test'>Test</v-btn>
</v-toolbar-items>
</v-toolbar>
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</div>

That's probably due to how v-tooltip works internally. You can see that if you take the button component outside of the template slot in the v-tooltip component, transition works properly.
The v-tooltip component might have a destroy lifecycle hook that simply destroys the rendered DOM node, which can cause issues. A workaround will be to keep the <router-view> alive:
<transition name="fade" mode="out-in">
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
However, this might not be desirable, especially if you have very heavy components used by the router, or if you have a lot of views to be kept alive:
Solution 1: Cap the max number of components to keep alive
Since you are only concerned with the fading out of the last destroyed component, you can use the max attribute on the <keep-alive> wrapper so that you only keep the last destroyed element. Something like this will work:
<transition name="fade" mode="out-in">
<keep-alive v-bind:max="2">
<router-view></router-view>
</keep-alive>
</transition>
Solution 2: Explicitly include components that need to be kept alive
Alternatively, you can selectively keep the components with <v-tooltip> alive. In your example, only the Home component is having a problem, so you can give it a name:
var Home = {
template: '<div> <h2>Home</h2> <v-tooltip> <template v-slot:activator="{ on }"> <v-btn color="primary" dark v-on="on">Left</v-btn> </template> <span>Left tooltip</span> </v-tooltip> </div>',
name: 'Home'
}
And then dynamically bind an array to the include attribute on the <keep-alive> component:
<transition name="fade" mode="out-in">
<keep-alive :include="componentsToKeepAlive">
<router-view></router-view>
</keep-alive>
</transition>
In your JS:
new Vue({
el: '#app',
router: router,
data: {
componentsToKeepAlive: ['Home']
}
});

Related

Vue keeping specific component alive under router-view

In my home page I have two components. I want only one to be kept alive.
HomeView.vue
<template>
<div>
<keep-alive>
<AliveComponent />
</keep-alive>
<DeadComponent />
</div>
</template>
In my App.vue I have kept the <router-view /> alive as well
<template>
<Header />
<main>
<keep-alive include="HomeView">
<router-view />
</keep-alive>
</main>
<Footer />
</template>
The problem is the <keep-alive include="HomeView"> is keeping both components alive. I tried to replace it with include="AliveComponent" but turns out nothing is alive.
Is there any way to do it?
Make sure your components have a valid name that you are planning to use in keep-alive. This could be an issue if you did not provide a name to your "AliveComponent" because the include property matches the component name.
I produced a demo here in which I create two routes. Each route has a component and each component has its state. When changing any component's state and switching to another route, the component which is included in the keep-alive will have preserved state while others will not.
// 1. Define route components.
// These can be imported from other files
const AliveC = {
name: "AliveComponent",
template: `<div>
<button #click="count = count + 1">Increase Alive Count</button>
<h3>Alive Component Count - {{ count }}</h3>
</div>`,
data() {
return {
count: 0,
};
},
}
const DeadC = {
name: "DeadComponent",
template: `<div>
<button #click="count = count + 1">Increase Dead Count</button>
<h3>Dead Component Count - {{ count }}</h3>
</div>`,
data() {
return {
count: 0,
};
},
}
// 2. Define some routes
// Each route should map to a component.
// We'll talk about nested routes later.
const routes = [
{ path: '/', component: AliveC },
{ path: '/dead', component: DeadC },
]
// 3. Create the router instance and pass the `routes` option
// You can pass in additional options here, but let's
// keep it simple for now.
const router = VueRouter.createRouter({
// 4. Provide the history implementation to use. We are using the hash history for simplicity here.
history: VueRouter.createWebHashHistory(),
routes, // short for `routes: routes`
})
// 5. Create and mount the root instance.
const app = Vue.createApp({})
// Make sure to _use_ the router instance to make the
// whole app router-aware.
app.use(router)
app.mount('#app')
// Now the app has started!
<html>
<script src="https://unpkg.com/vue#3"></script>
<script src="https://unpkg.com/vue-router#4"></script>
<div id="app">
<p>
<router-link to="/">Switch To Alive Component Route</router-link>  
<router-link to="/dead">Switch To Dead component Route</router-link>
</p>
<!-- route outlet -->
<!-- component matched by the route will render here -->
<router-view v-slot="{ Component }">
<keep-alive include="AliveComponent">
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</html>

How to extend the onClick handler behaviour for a `router-link` in vue3 router

I have a <router-link /> that I want to use to navigate to another page in a Vue 3 application, but I also want to run another function when this link is clicked.
Right now I have to use an extra <span /> element to wrap the <router-link /> and add the #click attribute there to rely on event bubbling. Adding the #click handler on <router-link /> causes the router link not to work and the browser thinks it is just a normal anchor href.
<span #click="handleClose(imageId)">
<router-link
:to="{name: 'image', params: {'imageId': imageId}}"
class="permalink">
Permalink
</router-link>
</span>
Is there a better way?
You must use the .native modifier on the #click event of router-link. The reason is quite simple - router-link is a Vue component but it does not emit a click event. Therefore you have to listen for the native click event coming from the browser.
https://github.com/vuejs/vue-router/issues/800
var router = new VueRouter({
routes:
[
{path: '/', component: {
template: '#first',
methods:
{
showAlert(text)
{
window.alert(text);
}
}
}},
{path: '/second', component: {
template: '#second',
methods:
{
showAlert(text)
{
window.alert(text);
}
}
}},
]
});
new Vue(
{
template: '#main',
router: router,
}).$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">test</div>
<template id="main">
<div>
<router-view></router-view>
</div>
</template>
<template id="first">
<router-link to="/second" #click.native="showAlert('second')">GOTO second</router-link>
</template>
<template id="second">
<router-link to="/" #click.native="showAlert('first')">GOTO first</router-link>
</template>
To avoid potential weird concurrency bugs (the view change could happen before your method being called, method which would then be attached to an unmounted component), I would do a programmatic navigation in your method:
<template>
<button type="button" #click="handleClose(imageId)">
Permalink
</button>
</template>
<script>
import router from '#/wherever/your/router/is/initialized';
export default {
methods: {
handleClose(imageId) {
// Do your stuff
router.push({ name: 'image', params: {'imageId': imageId}});
}
}
}
</script>

quill error with Invalid Quill Container on Vuejs when used with vuetify tab

Error on plugin quill occurred when i placed the editor in a tab container of Vuetify. It is created under the mounted hook.
The error in Console is
quill Invalid Quill container undefined
[Vue warn]: Error in mounted hook: "TypeError: Cannot read property
'on' of undefined"
Below is the vue file.
<template>
<v-app class="panel" ref="panel">
<v-tabs fixed-tabs v-model="tab">
<v-tabs-slider></v-tabs-slider>
<v-tab key="1" href="#tab1">
Tab 1
</v-tab>
<v-tab key="2" href="#tab2">
Tab 2
</v-tab>
<v-tabs-items v-model="tab">
<v-tab-item key="1" value="tab1">
<div class="formPanel" ref="formPanel">
<div class="title-text" ref="title">Edit text in tab 1</div>
<div ref="editor" v-html="value"></div>
</div>
</v-tab-item>
<v-tab-item key="2" value="tab2">
<v-card-text>This is tab 2</v-card-text>
</v-tab-item>
</v-tabs-items>
</v-tabs>
</v-app>
</template>
<script>
import Quill from 'quill';
export default {
data: function () {
return {
tab: 'editor'
};
},
mounted() {
var toolbarOptions = [
['bold', 'italic', 'underline', 'strike'],
[{ 'size': ['small', false, 'large', 'huge'] }],
];
this.editor = new Quill(this.$refs.editor, {
modules: { toolbar: toolbarOptions },
placeholder: 'Edit text',
theme: 'snow'
});
},
};
</script>
<style scoped>
</style>
this is probably due to the fact that
<div ref="editor" v-html="value"></div> is inside a child component's slot v-tab-item which is conditionally rendered.
that means that the v-tab-item is mounted AFTER the parent's mounted() executes, so the content (including your refs) are not available.
If you can defer the initialization until the child has mounted then you can access the ref, but getting that to work is a complex endeavor.
Instead, I would opt to define a component that handles quill initialization and that can be nested in the tab.
ie:
<v-tab-item key="1" value="tab1">
<MyQuillComponent v-model="value"/>
</v-tab-item>

Vuetify: Navigation drawer: communicate v-model changes to parent co

I have created a Sidebar component using Vuetify's navigation drawer. The code looks something like this:
<template>
<v-navigation-drawer persistent clipped v-model="isVisible" fixed app>
<!-- content of the sidebar goes here -->
</v-navigation-drawer>
</template>
<script>
export default {
name: 'Sidebar',
props: {
visible: Boolean,
},
data() {
return {
isVisible: this.visible,
};
},
}
</script>
Please note that I am duplicating the visible prop with the isVisible data. I tried using the prop directly in the v-model but every time the sidebar closed, I would get a warning in the console about changing props directly, as they would be overwritten when the parent re-renders.
In the parent view, I have a button on the toolbar that is supposed to change icon depending on the visibility of the toolbar.
<template>
<v-container fluid>
<sidebar :visible="sidebarVisible"/>
<v-toolbar app :clipped-left="true">
<v-btn icon #click.stop="sidebarVisible = !sidebarVisible">
<v-icon v-html="sidebarVisible ? 'chevron_right' : 'chevron_left'"/>
</v-btn>
</v-toolbar>
<v-content>
<router-view/>
</v-content>
<v-footer :fixed="fixed" app>
<span>© 2017</span>
</v-footer>
</v-container>
</template>
<script>
import Sidebar from '#/components/Sidebar.vue';
export default {
name: 'MainView',
data() {
return {
sidebarVisible: false,
fixed: false,
title: 'Title',
};
},
components: {
Sidebar,
},
};
</script>
The problem I have is that if I close the sidebar by clicking outside of it, the icon of the button on the toolbar does not change to chevron-left. Moreover, in order to bring the sidebar back, I need to click on the button twice.
Clearly this is because the sidebarVisible data in the main view is not updated when the sidebar closes. How do I make sure that sidebarVisible is updated when the sidebar closes?
I am use next construction...
in my component
<template>
<v-navigation-drawer v-model="localDrawer"></v-navigation-drawer>
</template>
...
<script>
export default {
props: { value: { type: Boolean } },
data: () => ({
localDrawer: this.value
}),
watch: {
value: function() {
this.localDrawer = this.value
},
localDrawer: function() {
this.$emit('input', this.localDrawer)
}
}
}
</script>
in parent layer
<app-drawer v-model="drawer"></app-drawer>
it's work for me
Use v-bind:value or :value to bind the drawer value from props.
Child component:
<template>
<v-navigation-drawer v-bind:value="drawer"></v-navigation-drawer>
</template>
<script>
export default {
props : ['drawer']
}
</script>
Parent component:
<template>
<app-side-bar :drawer="drawer"/>
<v-app-bar app clipped-left>
<v-app-bar-nav-icon #click.stop="drawer = !drawer"></v-app-bar-nav-icon>
</v-app-bar>
</template>
Vuetify navigation drawer issue fix:
Reset your browser window to default 100%
Here is the code,
Template:
<nav>
<v-toolbar flat app>
<v-toolbar-side-icon class="grey--text" #click="toggle"></v-toolbar-side-icon>
<v-toolbar-title class="text-uppercase grey--text">
<span class="font-weight-light">Repo</span>
<span>hub</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn flat color="grey">
<span>Sign Out</span>
<v-icon right>exit_to_app</v-icon>
</v-btn>
</v-toolbar>
<v-navigation-drawer app v-model="drawer" class="indigo">
<p>test</p>
</v-navigation-drawer>
</nav>
Script:
export default {
data() {
return {
drawer: false
};
},
methods:{
toggle(){
this.drawer = !this.drawer;
}
}
};

Vuetify text displaying on the right side of the v-navigation-drawer

I am a new programmer and trying to make a non-toggling side bar, like admin page.
The router-view doesnt display on the right side of the page.
I know that I don't have a href link for each of the navigation list, where and how can I do that?
also, is the Apps causing conflict with the sellingSummary page?
and am I inserting the router-view in the wrong place?
I need help, thank you !!!!
image
<template>
<div>
<v-app id="inspire">
<v-navigation-drawer
stateless
value="true"
>
<v-list>
<v-list-group
no-action
prepend-icon="account_circle"
value="true"
>
<v-list-tile slot="activator">
<v-list-tile-title>admins</v-list-tile-title>
</v-list-tile>
<v-list-tile
v-for="(admin, i) in admins"
:key="i"
#click=""
>
<v-list-tile-title v-text="admin[0]"></v-list-tile-title>
<v-list-tile-action>
<v-icon v-text="admin[1]"></v-icon>
</v-list-tile-action>
</v-list-tile>
</v-list-group>
</v-list>
</v-navigation-drawer>
<div>
<router-view></router-view>
</div>
</v-app>
</div>
</template>
<script>
export default {
data() {
return {
admins: [
['Management', 'people_outline'],
['Settings', 'settings']
]
}
}
}
</script>
./router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import AppS from '#/components/SellingPage/AppS'
import sellingSummary from '#/components/SellingPage/subPage/sellingSummary'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/SellingPage',
name: 'AppS',
component: AppS
},
{
path: '/SellingPage',
name: 'sellingSummary',
component: sellingSummary
}
]
})
./components/SellingPage/subPage/sellingSummary
<template>
<div>
<p>Hi, first</p>
</div>
</template>
./components/SellingPage/AppS
<template>
<div>
<app-header></app-header>
<v-card>
<app-selling>
</app-selling>
</v-card>
</div>
</template>
<script>
import Header from '../Header.vue';
import Selling from './Selling.vue';
export default {
components: {
'app-header': Header,
'app-selling': Selling
}
}
</script>
<v-navigation-drawer
disable-route-watcher
enable-resize-watcher
:clipped="clipped"
v-model="sideNav"
dark
app
>
try this
For starters, you can't have two routes in your router configured for different components. The Router will only use the first entry.
In your component Vue file add the following property in the data property
drawer: true,
Example:
export default {
data: () => ({
drawer: true
})
}
The Navigation drawer has an item data property which can be used to loop through existing menu items.
In some of the properties, you can add a path attribute directing to an existing path on the router when that menu item is pressed.
Here is an example:
https://codepen.io/wernerm/pen/rzqqbR