How to addclass to parent when click child menu in VUEjs - javascript

When I click sub menu in the following template, how do I add an active class to the <a> of a sibling <router-link> (of menu1 or menu2)?
<ul class="depth1">
<li class="m1">
<router-link to="/introduce/Introduce" #click="selected = 1" :class="{ active: selected == 1 }"><span>menu1</span></router-link>
<ul class="depth2 sm1">
<li><router-link to="" #click="selected = 1">sub menu</router-link></li>
<li><router-link to="" #click="selected = 1">sub menu</router-link></li>
<li><router-link to="" #click="selected = 1">sub menu</router-link></li>
</ul>
</li>
<li class="m2">
<router-link to="/introduce/Introduce" #click="selected = 2" :class="{ active: selected == 2 }"><span>menu2</span></router-link>
<ul class="depth2 sm1">
<li><router-link to="" #click="selected = 2">sub menu</router-link></li>
<li><router-link to="" #click="selected = 2">sub menu</router-link></li>
<li><router-link to="" #click="selected = 2">sub menu</router-link></li>
</ul>
</li>
</ul>
<script>
export default {
data: function () {
return {
selected: false,
};
},
methods: {
},
};
</script>

Your attempt is close to working, but the click handler isn't called because it's not applied correctly.
To add a click handler to the root element of <router-link> (i.e., the <a> tag), use #click.native:
<router-link to="" #click.native="selected = 1">sub menu</router-link>
^^^^^^^

but, why not use vue-router's default behaviors (link automatically get active class when the target route is active) like the below demo shows
const Home = {
template: '<div><h2>Home</h2></div>'
}
const About = {
template: '<div><h2>About</h2></div>'
}
const Users = {
template: `
<div>
<h2>Users</h2>
<router-view></router-view>
</div>
`
}
const User = {
template: '<div>{{ $route.params.username }}</div>'
}
const router = new VueRouter({
mode: 'history',
routes: [{
path: '/',
component: Home
},
{
path: '/about',
component: About
},
{
path: '/users',
component: Users,
children: [{
path: ':username',
name: 'user',
component: User
}]
}
]
})
new Vue({
router,
}).$mount('#app')
a.router-link-active {
color: #f66;
}
li.router-link-active a {
color: #f66;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h1>Active Links</h1>
<ul>
<li>
<router-link to="/">/</router-link>
</li>
<li>
<router-link to="/" exact>/ (exact match)</router-link>
</li>
<li>
<router-link to="/users">/users</router-link>
</li>
<li>
<router-link to="/users" exact>/users (exact match)</router-link>
</li>
<li>
<router-link to="/users/evan">/users/evan</router-link>
</li>
<li>
<router-link to="/users/evan#foo">/users/evan#foo</router-link>
</li>
<li>
<router-link :to="{ path: '/users/evan', query: { foo: 'bar' }}">
/users/evan?foo=bar
</router-link>
</li>
<li>
<!-- #635 -->
<router-link :to="{ name: 'user', params: { username: 'evan' }, query: { foo: 'bar' }}" exact>
/users/evan?foo=bar (named view + exact match)
</router-link>
</li>
<li>
<router-link :to="{ path: '/users/evan', query: { foo: 'bar', baz: 'qux' }}">
/users/evan?foo=bar&baz=qux
</router-link>
</li>
<li>
<router-link to="/about">/about</router-link>
</li>
<router-link tag="li" to="/about">
<a>/about (active class on outer element)</a>
</router-link>
</ul>
<router-view class="view"></router-view>
</div>

Related

How to show button checking a localStorage key with Vue?

When I log out I delete the token. Here is how I check if the token is deleted:
const isLoggedOut = () => {
let token = window.localStorage.getItem('token')
if (token === undefined || token === null || token.length === 0) {
console.log("user is logged out")
return true
}
else {
console.log("user is logged in")
return false
}
}
Inside the navbar I want the Login button to be hidden if the user is logged in.
This is how the navbar looks like:
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<router-link class="navbar-brand" :to="{ name: 'Home' }">Home</router-link>
<ul class="navbar-nav">
<li class="nav-item">
<router-link class="text-white" :to="{ name: 'Login' }" v-if="isLoggedOut">Login</router-link>
</li>
<li class="nav-item">
<router-link class="text-white ml-2" :to="{ name: 'Register' }">Register</router-link>
</li>
<li class="nav-item">
<router-link class="text-white ml-2" :to="{ name: 'Dashboard' }">Dashboard</router-link>
</li>
<li class="nav-item">
<button class="text-white ml-2" #click="logOut">Logout</button>
</li>
</ul>
</nav>
Later edit, this is the App.vue file without the styles. I log in and Login button still appears in the navbar.
<script setup>
import { reactive,ref, computed } from 'vue'
import { useRouter } from "vue-router"
const router = useRouter()
const token = ref(null);
const setToken = (token) => {
token.value = token;
window.localStorage.setItem('token', token)
}
const clearToken = () => {
token.value = null;
window.localStorage.removeItem('token')
}
const isLoggedOut = computed(() => token.value === null)
const logOut = () => {
clearToken()
router.push({name:'Login'})
}
</script>
<template>
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<router-link class="navbar-brand" :to="{ name: 'Home' }">Home</router-link>
<ul class="navbar-nav">
<!-- Show Login button only if user is logged out -->
<li class="nav-item">
<router-link class="text-white" :to="{ name: 'Login' }" v-if="isLoggedOut">Login</router-link>
</li>
<li class="nav-item">
<router-link class="text-white ml-2" :to="{ name: 'Register' }">Register</router-link>
</li>
<li class="nav-item">
<router-link class="text-white ml-2" :to="{ name: 'Dashboard' }">Dashboard</router-link>
</li>
<li class="nav-item">
<button class="text-white ml-2" #click="logOut">Logout</button>
</li>
</ul>
</nav>
<router-view/>
</template>
window.localStorage.getItem('token') is not reactive. If the value changes, you will not be notified.
Instead of getting logged in state from localStorage, you can manage it through internal state which will then manage the local storage.
for example:
const token = ref(null);
const setToken = (value) => {
token.value = value;
window.localStorage.setItem('token', token)
}
const clearToken = () => {
token.value = null;
window.localStorage.removeItem('token')
}
const isLoggedIn = computed(()=>token.value !== null)
then you can use isLoggedIn as a reactive (computed) value.
You can store this in a separate auth.js and export the methods and token.
example
The button is still visible because the component (App.vue in your case) is not reloaded, meaning the check whether you have a token or not is not performed, respectively the computed is not going to be updated because as mentioned above window.localStorage is not reactive.
One way to overcome this is to use a store (pinia/vuex/composable that will hold the value all the time) and router hooks, like beforeRouteEnter or the global one - beforeEach.
You can also try to make localStoragereactive like in this example here
Which might be overkill.
Just make sure you moved token functions and operations in a separated file (or even as a composable)

How to switch icon when containing class 'active' in Vuejs

I have a bottom navbar with icons. How can when the router-link has class 'active' it will turn into an active icon? Default icon 1 is active.
<li>
<router-link v-if="active" :class="{active}" active-class="active">
Icon 1 active
</router-link>
<router-link v-if="!active" :class="{active}" active-class="active">
Icon 1
</router-link>
</li>
<li>
<router-link v-if="active" :class="{active}" active-class="active">
Icon 2 active
</router-link>
<router-link v-if="!active" :class="{active}" active-class="active">
Icon 2
</router-link>
</li>
<li>
<router-link v-if="active" :class="{active}" active-class="active">
Icon 3 active
</router-link>
<router-link v-if="!active" :class="{active}" active-class="active">
Icon 3
</router-link>
</li>
The solution:
Binding the HTML class:
<li>
<router-link #click="isActive1 = true" :class="{ active: isActive1 }">
Icon 1
</router-link>
</li>
<li>
<router-link #click="isActive2 = true" :class="{ active: isActive2 }">
Icon 2
</router-link>
</li>
<li>
<router-link #click="isActive3 = true" :class="{ active: isActive3 }">
Icon 3 Active
</router-link>
</li>
Implement isActive variables:
data: {
isActive1: true, // default icon1
isActive2: false,
isActive3: false,
}
And implement watchers:
watch: {
isActive1(val) {
if(val === true) {
this.isActive2 = false
this.isActive3 = false
}
},
isActive2(val) {
if(val === true) {
this.isActive1 = false
this.isActive3 = false
}
},
isActive3(val) {
if(val === true) {
this.isActive1 = false
this.isActive2 = false
}
}
}
Documentation: https://v2.vuejs.org/v2/guide/class-and-style.html

dynamically changing "to" attribute In router-link vue

I am new to Vue and straggling with generating sidebar list where each list element on click open its components. I am using vue router. I understand the theory how it should work, but obviously I am missing something because I can't solve it. I don't know how to dynamically change the path.
I generated sidebar list using router-link
<template>
<div class="sidebarListItems">
<router-link href="#"
:to="calcRut"
class="list-group-item list-group-item-action bg-light"
v-for="title in mapTitles"
:key="title.map"
:title="title.map"
#click="callMap">
{{title.map}}
</router-link>
</div>
</template>
<script>
import bus from "./js/bus"
import mapNames from "./json/popis_karata.json"
export default {
name: "PageSidebarList",
props: ["title"],
data(){
return {
mapTitles:mapNames,
ruth=""
}
},
methods: {
callMap(event){
bus.$emit("openMap")
},
calcRuth(){
for (var i=0; i<this.routes.length; i++){
var newruth = this.routes[i].path
this.ruth = newruth
}
}
},
computed: {
routes(){
return this.$router.options.routes
}
}
When I put my path directly as a string (:to="/zup" or :to="/reg") it's working, but I would like that those paths are dynamically generated depending on which list element I clicked.
Here's how I do it. Try extracting the v-for on the level above. If you don't want to use an actual element, try <template>
<ul class="flex flex-col w-full space-y-1">
<li v-for="item in items" :key="item.link">
<router-link class="flex items-center h-8 px-4 rounded-lg hover:bg-white" :class="{ 'bg-white': routeMatches(item) }" :to="{ name: item.link }">
<div class="text-sm text-gray-700">{{ item.name }}</div>
</router-link>
</li>
</ul>
Edit, also format your to="" correctly to be :to="{name: 'namehere'}"
how about this solution with a switch and a programmatic router change
<template>
<div id="app">
<ul class="nav">
<li class="nav-item" #click="routeChanger('home')">Home</li>
<li class="nav-item" #click="routeChanger('page1')">Page1</li>
<li class="nav-item" #click="routeChanger('page2')">Page2</li>
<li class="nav-item" #click="routeChanger('page3')">Page3</li>
<li class="nav-item" #click="routeChanger('page4')">Page4</li>
</ul>
</div>
</template>
<script>
export default {
name: "App",
methods: {
routeChanger(routeParam) {
switch (routeParam) {
case "home":
this.$router.push("/home");
break;
case "page1":
this.$router.push("/page1");
break;
//...
default:
this.$router.push("/404");
break;
}
}
}
};
</script>
<style>
</style>
Maybe my answer would be useful to someone so I am posting my solution.
I didn't found solution to loop separately navbar elements from one data file and router-lik :to path attribute from routes file.
It work if I use routes file for everything.
<router-link
v-for="route in $router.options.routes"
:key="route.path"
:to="route.path"
class="list-group-item list-group-item-action bg-light">
{{route.title}}
</router-link>

Angular highlighting based on routerLinkActive

i want to toggle highlighting for the routerLinkActive based on the routes,
i have a dashboard component and both the dashboard menu item and labels menu item refer to the same component, i want to add class to the li items based on the route
for dashboard the route will be
http://localhost:4300/dashboard
and for the labels , the route will be
http://localhost:4300/dashboard/5d1bb53877ed8702d8a01940
Code for the menu item
<ul class="sidebar-nav">
<li [ngClass]="{ active: rlal && rlal.isActive == false }">
<a [routerLink]="['/dashboard']" (click)="loadSnippet(null)">
<mat-icon>dashboard</mat-icon>
<span>Dashboard</span>
</a>
</li>
<h3 *ngIf="labelList && labelList.length!=0">Labels</h3>
<ul class="sidebar-nav">
<li *ngFor="let label of labelList" [ngClass]="rlal && rlal.isActive ? 'active' : ''"
routerLinkActive="rlal.isActive">
<a [routerLink]="['/dashboard', label._id]" routerLinkActive #rlal="routerLinkActive"
(click)="loadSnippet(label)">
<mat-icon>label</mat-icon>
<span>{{ label.label_name }} </span>
</a>
</li>
</ul>
Routes:
{
path: "dashboard",
component: DashboardComponent,
canActivate: [LoggedInGuard]
},
{
path: "dashboard/:labelId",
component: DashboardComponent,
canActivate: [LoggedInGuard]
}
The highlighting works fine for the individual label items, but for the dashboard , the li is not getting highlighted
If you want the dashboard link to be active only if there's no route parameter, you need to add routerLinkActive and [routerLinkActiveOptions]="{exact: true}" to you dashboard li node.
Also, you don't need to set the active class via ngClass, because routerLinkActive will do that for you.
<li routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a routerLink="/dashboard" (click)="loadSnippet(null)">
<mat-icon>dashboard</mat-icon>
<span>Dashboard</span>
</a>
</li>
...
<li routerLinkActive="active" *ngFor="let label of labelList">
<a [routerLink]="['/dashboard', label._id]" (click)="loadSnippet(label)">
<mat-icon>label</mat-icon>
<span>{{ label.label_name }}</span>
</a>
</li>

Target clicked element in Vue

In Vue.js how do you target/detect the clicked element to perform a basic toggle class?
I've written this which toggles successfully but when you click an a the class is applied to to all li's
HTML
<ul id="app" class="projects">
<li v-bind:class="dyClass">Project A</li>
<li v-bind:class="dyClass">Project B</li>
<li v-bind:class="dyClass">Project C</li>
<li v-bind:class="dyClass">Project D</li> </ul>
JS
new Vue({
el: '#app',
data: {
show: false,
},
computed: {
dyClass: function() {
return {
show: this.show
}
}
}
})
I'm new to Vue.
EDIT.
I managed to get it working with a mix of help from below, but I dont seem to be able to get the toggle effect.
<ul id="app" class="projects">
<li :class="{show:selected == 1}">
<a href="" #click.prevent.stop="selected = 1" >Exposure</a>
</li>
<li :class="{show:selected == 2}">
<a href="" #click.prevent.stop="selected = 2" >Exposure</a>
</li>
<li :class="{show:selected == 3}">
<a href="" #click.prevent.stop="selected = 3" >Exposure</a>
</li>
</ul>
and
new Vue({
el: '#app',
data: {
selected: false,
}
});
You can pass element to function ($event):
:click=dyClass($event) Then in computed:
dyClass: function(event) {
event.target.toggleClass('whatever')
}
As i understand you have some li items and you want to add an active class when a specific li gets clicked.
Solution:
the "html" part:
<div id="app">
<ul class="projects">
<li v-for="project in projects"
:key="project.id"
:class="{ active: project.id === activeProjectId }"
#click="activeProjectId = project.id"
>
{{ project.name }}
</li>
</ul>
</div>
The "vue" part
new Vue({
el: "#app",
data: {
projects: [
{ id: 1, name: 'Project A' },
{ id: 2, name: 'Project B' },
{ id: 3, name: 'Project C' },
{ id: 4, name: 'Project D' },
],
activeProjectId: 1
},
})
Then add some css to the 'active' class.
For more, See the fiddle

Categories