Vue Router only applies active class once and not again - javascript

WORKAROUND FOUND, SEE EDIT4
my router is acting a bit funny.
It works perfectly for the first link you click, however It doesn't apply the active class after the first time, if I refresh the page it will apply. you can see in the gif here:
Here is my navigation bar code:
<template>
<div class="nav">
<div class="nav__logo-box">
<img src="/logo-no-text-morning.png" :class="$mq" alt />
</div>
<div class="nav__nav-list">
<div class="nav__link">
<router-link to="/" exact-active-class="active-page">
<i class="fas fa-horse-head fa-3x"></i>
<p>Home</p>
</router-link>
</div>
<div class="nav__link">
<router-link to="/about" exact-active-class="active-page">
<i class="fas fa-info-circle fa-3x"></i>
<p>About</p>
</router-link>
</div>
<div class="nav__link">
<router-link to="/gallery" exact-active-class="active-page">
<i class="fas fa-images fa-3x"></i>
<p>Gallery</p>
</router-link>
</div>
<div class="nav__link">
<router-link to="/contact" exact-active-class="active-page">
<i class="fas fa-envelope-open-text fa-3x"></i>
<p>Contact</p>
</router-link>
</div>
</div>
</div>
</template>
Here is my router index.ts
import Vue from "vue"; import VueRouter, { RouteConfig } from "vue-router"; import Home from "../views/Home.vue"; Vue.use(VueRouter); const routes: Array<RouteConfig> = [ {
path: "/",
name: "Home",
component: Home }, {
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("#/views/About.vue") }, {
path: "/contact",
name: "Contact",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("#/views/Contact.vue") }, {
path: "/gallery",
name: "Gallery",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("#/views/Gallery.vue") } ]; const router = new VueRouter({ routes }); export default router;
EDIT:
here's the a look at the web browser debugger:
It for some reason is adding an router-link-active to the home page (probably because its path is just "/").
And then it will add the exact-active just once, but then clicking on another page it will not change. it will stay on the page you was on before, only way to make it change is to refresh the page.
EDIT2:
So now I have decided to store the current page using the following code:
methods: {
getCurrentPage: function() {
this.currentPage = this.$router.currentRoute.name;
}
},
However you can see the behaivour here, it again doesn't update properly and lags behind, it's maybe one or two pages behind where I actaully am.
EDIT3:
I have tried to use a computed method to watch instead but it does not update, it just gets the first page its on and doesnt update again.
data: function() {
return {
currentPage: ""
};
},
computed: {
getCurrentPage: function() {
return this.$router.currentRoute.name;
}
},
mounted() {
console.log(this.$router.currentRoute.name);
this.currentPage === this.getCurrentPage;
}
EDIT4: (WORKAROUND FOUND)
So with the help of the comments, I've got a workaround that will work for now. it's this:
computed: {
getCurrentPage: function() {
return this.$route.name;
}
}
On the links I then bind the active page class:
<div class="nav__nav-list">
<div class="nav__link">
<router-link to="/" :class="{ currentpage: getCurrentPage === 'Home' }">
<i class="fas fa-horse-head fa-3x"></i>
<p ref="homeLink">Home</p>
</router-link>
</div>
<div class="nav__link">
<router-link to="/about" :class="{ currentpage: getCurrentPage === 'About' }">
<i class="fas fa-info-circle fa-3x"></i>
<p ref="aboutLink">About</p>
</router-link>
</div>
<div class="nav__link">
<router-link to="/gallery" :class="{ currentpage: getCurrentPage === 'Gallery' }">
<i class="fas fa-images fa-3x"></i>
<p ref="galleryLink">Gallery</p>
</router-link>
</div>
<div class="nav__link">
<router-link to="/contact" :class="{ currentpage: getCurrentPage === 'Contact' }">
<i class="fas fa-envelope-open-text fa-3x"></i>
<p ref="contactLink">Contact</p>
</router-link>
</div>
</div>

Related

Vue JS 3: How to pass data from one component to another other?

I'm trying to share data stored in a variable favorite_count within the Favorites component in Favorites.vue file. I want to share that data with the App Component in the App.vue file but I'm not able to. I would want that if I change the value of favorite_count in the Favorites component, it changes in the App Component. Done quite some research on the web but no success yet. Any ideas on what I could be doing wrong?
Favorites.vue file
<template>
<div class="row m-5">
<h3 class="col-8">Your Favorites</h3>
<div class="col-4">
<p class="pull-right m-1">Picks
<span >{{ favorite_count }}</span> / 5</p>
</div>
<hr>
</div>
</template>
<script>
export default {
name: 'favorites',
data() {
return {
favorite_count: 5,
}
},
methods: {
changeFavoriteCount() {
this.favorite_count = this.favorite_count + 2;
},
emitToParent (event) {
this.$emit('childToParent', this.favorite_count)
}
}
}
</script>
App.vue file
<template>
<div class="navbar navbar-expand-md navbar-dark bg-primary">
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav">
<li class="nav-item">
<router-link to="/favorites" class="btn btn-info">
Favorites ( {{ favorite_count }} )
</router-link>
</li>
</ul>
</div>
</div>
</template>
<script>
import Favorites from './components/Favorites.vue'
export default {
name: 'App',
components: {
Favorites
},
data () {
return {
favorite_count: 0,
}
}
}
</script>
If you are going to use <router-view> later in your application, I would suggest this solution
If you were going to include Favorites inside <template> in App.vue, you can use props:
1. Declare your 'shared' variable in the parent component (App.vue)
data () {
return {
favorite_count: 0,
}
},
2. Define props in your child component (Favorites.vue)
export default {
props: { favorite_count: Number },
...
}
3. Pass favorite_count as prop to Favorites
<template>
...
<Favorites :favorite_count="favorite_count" ... />
</template>
If you will need to update favorite_count - emit an event to parent component. More about it in Vue docs
Edit: Just to clarify: If you are going to update favorite_count from Favorites.vue, you need to emit an event to App.vue to avoid mutating props.
That also means you need to move your changeFavoriteCount() function to App.vue and apply a listener to your child component which will call this function:
// App.vue
<template>
...
<Favorites
#your-update-event="changeFavoriteCount"
:favorite_count="favorite_count" ...
/>
</template>
...
changeFavoriteCount(newVal) {
this.favorite_count = newVal;
},
change your Favourite.vue file like this
<template>
<div class="row m-5">
<h3 class="col-8">Your Favorites</h3>
<div class="col-4">
<p class="pull-right m-1">
Picks <span>{{ favorite_count }}</span> / 5
<button #click="changeFavoriteCount">Click me to change favorite_count</button>
</p>
</div>
<hr />
</div>
</template>
<script>
export default {
name: "favorites",
data() {
return {
favorite_count: 5,
};
},
methods: {
changeFavoriteCount() {
this.favorite_count = this.favorite_count + 2;
this.emitToParent();
},
emitToParent() {
this.$emit("childToParent", this.favorite_count);
},
},
};
</script>
and the App.vue file like this
<template>
<div class="navbar navbar-expand-md navbar-dark bg-primary">
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav">
<li class="nav-item">
<router-link to="/favorites" class="btn btn-info">
<Favorites #childToParent="updateFavorite" />
Favorites ( {{ favorite_count }} )
</router-link>
</li>
</ul>
</div>
</div>
</template>
<script>
import Favorites from './components/Favorites.vue'
export default {
name: 'App',
components: {
Favorites
},
data () {
return {
favorite_count: 0,
}
},
methods: {
updateFavorite(data) {
this.favorite_count = data;
},
},
}
</script>

Prevent click event in Vue conditonally

I am disabling document body scroll when my mobile nav is open which is working as expected. However it was not removing the overflow hidden when user would click a link to another route/page. I created a simple method to remove the overflow hidden when a link is clicked in the nav when the menu is open, which does work with one small caveat. When the user is on say page "home" and the mobile nav is OPEN, when they click the link "home" inside of the mobile nav it is closing the menu, and I understand I have done that with the method I created. Is there a way to prevent that event from firing when clicking the link of the page you are on?
<header :class="{ 'header-active': activeHamburger }">
<nav class="nav-row nav-row--primary" aria-label="Main Navigation">
<ul class="row--flex justify--space-between">
<li>
<router-link to="/" #click="removeOverflowHidden();
">
home
</router-link>
</li>
<li>
<router-link to="About" #click="removeOverflowHidden();
">
about
</router-link>
</li>
<li>
<router-link to="Work" #click="removeOverflowHidden();
">
work
</router-link>
</li>
<li>
<router-link to="Contact" #click="removeOverflowHidden();
">
contact
</router-link>
</li>
</ul>
</nav>
</header>
data() {
return {
activeHamburger: false
};
},
watch: {
activeHamburger: function() {
if (this.activeHamburger) {
document.documentElement.style.overflow = "hidden";
return;
}
document.documentElement.style.overflow = "auto";
}
},
methods:{
removeOverflowHidden() {
this.activeHamburger = false;
}
}
You can pass the route value to the method and check that it's not same as the current route before executing.
<template>
<header :class="{ 'header-active': activeHamburger }">
<nav class="nav-row nav-row--primary" aria-label="Main Navigation">
<ul class="row--flex justify--space-between">
<li>
<router-link to="/" #click="removeOverflowHidden('home');
">
home
</router-link>
</li>
<li>
<router-link to="About" #click="removeOverflowHidden('about');
">
about
</router-link>
</li>
<li>
<router-link to="Work" #click="removeOverflowHidden('work');
">
work
</router-link>
</li>
<li>
<router-link to="Contact" #click="removeOverflowHidden('contact');
">
contact
</router-link>
</li>
</ul>
</nav>
</header>
</template>
<script>
export default {
data() {
return {
activeHamburger: false
};
},
watch: {
activeHamburger: function() {
if (this.activeHamburger) {
document.documentElement.style.overflow = "hidden";
return;
}
document.documentElement.style.overflow = "auto";
}
},
methods:{
removeOverflowHidden(value) {
if (this.$route.path !== value) {
this.activeHamburger = false;
}
}
}
}
</script>
I haven't seen your routes but you can also use this.$route.name if you prefer and adjust accordingly the values you pass to the method.

Passing json data from one vue page to another vue page and render the chart

I am traversing a json data inside a table in vue and passing some of it to a different page using router link.
Projects.vue
<tbody class>
<tr v-for="project in projects" aria-rowindex="1" class>
<td aria-colindex="1" class>{{project.name}}</td>
<td aria-colindex="2" class>{{project.date}}</td>
<td aria-colindex="3" style="width: 100px">
<router-link to={name: 'Performance', params: {{project.Line_base}} }>
<i class="fa fa-eye"></i>
</router-link>
<i class="fa fa-edit"></i>
<i class="fa fa-remove icons" style="color: red"></i>
<i class="fa fa-share-alt"></i>
</td>
<!-- <td aria-colindex="4" class>{{project.short_description}}</td> -->
</tr>
import axios from 'axios';
import Performance from "./Performance";
export default {
components: {
Performance
},
name: "builder-basic",
data() {
return {
projects: []
};
},
mounted () {
axios
.get('http://some.api.net/v1/projects/')
.then(response => (this. projects = response.data.entities))
},
};
</script>
This project.Line_base is an array of integers, that I want to pass to
Performance.vue
<template>
<div class="animated fadeIn">
<b-card header-tag="header" footer-tag="footer">
<div slot="header">Performance - Models vs Baseline</div>
<div class="card-body" >
<div class="chart-wrapper" >
<b-card header="Lift Chart" >
<div class="chart-wrapper">
<line-example chartId="chart-line-01" :styles="chartStyle" />
</div>
</b-card>
</div>
</div>
<div class="card-body" >
<div class="chart-wrapper">
<b-card header="Accuracy & Sensitivity" >
<bar-example chartId="chart-bar-01" :styles="chartStyle" />
</b-card>
</div>
</div>
</b-card>
</div>
</template>
<script>
import axios from "axios";
import LineExample from "./charts/LineExample";
import BarExample from "./charts/BarExample";
export default {
name: 'Performance',
components: {
LineExample,
BarExample
},
computed: {
chartStyle () {
return {
height: '500px',
position: 'relative'
}
}
}
}
};
</script>
I have the index.js where I have mentioned the routers, which are working well if I am not passing any data to performance, from where I will generate a graph using charts.
index.js
let router = new Router({
mode: 'hash', // https://router.vuejs.org/api/#mode
linkActiveClass: 'open active',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: '/',
redirect: '/dashboard',
name: 'Home',
component: DefaultContainer,
children: [
{
path: 'Performance',
name: 'Performance',
component: Performance
}])
The data is not being passed, I followed some of the articles already discussed on stack overflow
Send the params like this;
<router-link :to="{name: 'Performance', params: { Line_base: project.Line_base }}">
<i class="fa fa-eye"></i>
</router-link>
In your Performance.vue Component you need to get de params from the route like this.
computed: {
lineBase () {
return this.$route.params.Line_base
}
}
It seems like you are making a syntaxerror. This the example from the vue router docs:
<!-- named route -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
Further on in the docs it specifies router-link properties, stating:
route.params
type: Object
An object that contains key/value pairs of dynamic segments and star
segments. If there are no params the value will be an empty object.
Compare that to your code:
<router-link to={name: 'Performance', params: {{project.Line_base}} }>
It seems you are forgetting the semicolon before to (which is shorthand for v-bind:to. Without that whatever comes after will just be passed as a string rather than a javascript expression. This should help.
You could try rewriting to:
<router-link :to="{name: 'Performance', params: {Line_base: project.Line_base} }">
Hope that helps!
EDIT: added clarification

Get path/route/namespace for current/parent component/view in vue.js

I've been working on a sub menu system for vue.js that gets populated by the current route's children. I recently posted a question about it and it was answered here.
Now I'm trying to improve upon that but I'm having trouble finding out how to get a component's path or namespace (not sure what word to use). Currently I see what I want in the Vue Dev tools I just don't now how to get those properties.
I've tried {{$route.path}} but that only gives me the full path.
Another thing I've tried which kind of helps is storing the current path the first time I load the menu. Which preserves the path I want to be appending to. The only issue is when i navigate directly to the page it loads the menu with the pages url which then breaks the functionality.
Here is the code:
<template>
<nav id="sidebar">
<div class="sidebar-header">
<h3>Bootstrap Sidebar</h3>
</div>
<h2>Route: {{ }}</h2>
<ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==path)">
<li v-for="child in route.children">
<a class="nav-item" :key="index">
<router-link :to="{path: path+'/'+child.path}" exact-active-class="active">
<icon :icon="route.icon" class="mr-2" /><span>{{ child.path }}</span>
</router-link>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
data() {
return {
path: this.$route.path
}
},
methods: {
},
}
</script>
I really want something closer to this though, where instead of using $route.path to return the full path like /traveler/Create I want something to just return /traveler or whatever the path for it's router-view is:
<template>
<nav id="sidebar">
<div class="sidebar-header">
<h3>Bootstrap Sidebar</h3>
</div>
<ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.path)">
<li v-for="child in route.children">
<a class="nav-item" :key="index">
<router-link :to="{path: $route.path+'/'+child.path, params: { idk: 1 }}" exact-active-class="active">
<icon :icon="route.icon" class="mr-2" /><span>{{ child.path }}</span>
</router-link>
</a>
</li>
</ul>
</nav>
</template>
<script>
import { travelerroutes } from '../../router/travelerroutes'
export default {
data() {
console.log(travelerroutes);
return {
travelerroutes,
collapsed: true
}
},
methods: {
toggleCollapsed: function (event) {
this.collapsed = !this.collapsed
}
}
}
</script>
To get the path of the current component I had to just use the $Route.matched property. In my case because I didn't want to include the childrens' paths I used the first match like this $Route.matched[0].path
you can learn more about it here
I also used it to update my other question/answer
Essentially you can use it in a template like this:
<template>
<nav id="sidebar">
<div class="sidebar-header">
<h3>Bootstrap Sidebar</h3>
</div>
<ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.matched[0].path)">
<li v-for="child in route.children">
<a class="nav-item" :key="index">
<router-link :to="route.path+'/'+child.path" exact-active-class="active">
<icon :icon="route.icon" class="mr-2" /><span>{{ child.name }}</span>
</router-link>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
data() {
return {
}
}
}
</script>

Vue-Router only works on some routes

My vue-router routes the URL on all menu buttons correctly, but does not show each Vue component properly. A demonstration can be found here.
Inside of my HTML (I am using Vuefy)
<div class="navbar-start">
<a class="navbar-item">
<router-link to="/" class="router-link"> // <-- THIS WORKS
Home
</router-link>
</a>
<a class="navbar-item">
<router-link to="/items" class="router-link"> // <-- THIS WORKS
My Products
</router-link>
</a>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
<router-link to="/information" class="router-link"> // <-- DOES NOT WORK
Info
</router-link>
</a>
<div class="navbar-dropdown is-boxed">
<a class="navbar-item">
<router-link to="/about" class="router-link"> // <-- THIS WORKS
About
</router-link>
</a>
<a class="navbar-item">
<router-link to="/terms" class="router-link"> // <-- DOES NOT WORK
Terms
</router-link>
</a>
</div>
</div>
</div>
My router.js file is set up the following:
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/about',
name: 'about',
component: () => import('./views/About.vue')
},
{
path: '/new',
name: 'create-item',
component: () => import('./views/CreateItem.vue')
},
{
path: '/',
name: 'home',
component: Home
},
{
path: '/items',
name: 'my-items',
component: () => import('./views/MyItems.vue')
},
{
path: '/signin',
name: 'sign-in',
components: () => import('./views/SignIn.vue')
},
{
path: '/terms',
name: 'terms',
components: () => import('./views/Terms.vue')
},
{
path: '/information',
name: 'info',
components: () => import('./views/Info.vue')
}
]
})
Additionally, my App.vue file shows the router-view properly, along with the menu.
<template>
<div id="app">
<div id="nav">
<Menu/>
</div>
<router-view/>
</div>
</template>
<script type="text/javascript">
import Menu from '#/components/Menu.vue'
export default {
components: {
Menu
}
}
</script>
The following is a photograph of my navigation. To repeat, clicking on 'info' and 'terms' (submenu of info) do not load their respective Vue components, but does change the URL.
I triple-checked my syntax and checked the documentation, but could not seem to find my error. The platform at which my code is hosted at can be found here. Any help would be appreciated. Thanks, Edwin.
I found the error. I spelled 'component' instead of 'components' several times in my routes.js file.

Categories

Resources