Add active and exact-active classes to links #75
Replies: 25 comments 2 replies
-
How would the component know if it's active? Seems like we would only be able to know if it's an exact URL match, which is often not helpful. |
Beta Was this translation helpful? Give feedback.
-
I think what Vue does is consider a link Some examples: On page <a href="/" class="active">Home</a>
<a href="/posts" class="active exact-active">Posts</a>
<a href="/posts/1">Post</a> On page <a href="/" class="active">Home</a>
<a href="/posts" class="active">Posts</a>
<a href="/posts/1" class="active exact-active">Post</a> |
Beta Was this translation helpful? Give feedback.
-
In your first example, wouldn't this be active too? <a href="/posts/1">Post</a> Since it contains the current url? |
Beta Was this translation helpful? Give feedback.
-
No, it's the other way around. I haven't navigated that deep yet, so |
Beta Was this translation helpful? Give feedback.
-
Ahh yes, I dig that. 👌 |
Beta Was this translation helpful? Give feedback.
-
@sebastiandedeyne @reinink is this something that is being worked on? or something that is still wanted. I'd be willing to give a go at a PR for this as it's something I'm interested in using for a current project |
Beta Was this translation helpful? Give feedback.
-
I want to have active states on menu items. Therefore I wrote a wrapper component for the InertiaLink component that should add an active class to the currently active menu item. I started with this: <template>
<InertiaLink :href="href" :class="{active: this.active}" />
</template>
<script>
export default {
props: ['href']
computed: {
active() {
return window.location.href.startsWith(this.href)
}
}
}
</script> This approach does not work because the Another approach might be polling every few miliseconds for the current URL. <script>
export default {
props: ['href']
data() {
return: {
location: window.location.href,
polling: null
}
}
mounted () {
this.polling = setInterval(() => {
this.location = window.location.href
}, 100)
},
beforeDestroy () {
clearInterval(this.polling)
},
computed: {
active() {
return this.location.startsWith(this.href)
}
}
}
</script> This works so far. But I am not happy with this. Basically every link on the site polls every 100ms the current url. But most of the time the location has not changed. I am not sure about performance issues with this, but it atleast keeps the Browser unnecessarily busy. So I thought about using event listeners. Inertia uses <script>
export default {
props: ['href']
data() {
return: {
location: window.location.href,
polling: null
}
}
mounted () {
window.addEventListener('pushstate', this.onLocationChange)
},
beforeDestroy () {
window.removeEventListener('pushstate', this.onLocationChange)
},
methods: {
onLocationChange () {
this.location = window.location.href
}
}
}
</script> Sadly there is no I conclude that currently it is not possible to detect a Inertia page change without polling and therefore an active class can not be added that easily/elegantly. This problem would be easily solvable when Inertia would dispatch an event on changing the history state. So basically this issue depends on #66. |
Beta Was this translation helpful? Give feedback.
-
Any news on this feature? I just ran into the issue how to determine which link is active. |
Beta Was this translation helpful? Give feedback.
-
@bambamboole Ignore my previous post. I found a working solution based on Ping CRM. In Vue it is like this:
Assuming Maybe this helps. |
Beta Was this translation helpful? Give feedback.
-
@marvinrabe I'm trying your solution, but it looks like binding doesn't work. It's rendered once and doesn't change when I click on an InertiaLink =/ |
Beta Was this translation helpful? Give feedback.
-
@anthony-lopez-dev Are you using persistent layouts? The problem is |
Beta Was this translation helpful? Give feedback.
-
It's ok. I've found the issue ^^ Thanks |
Beta Was this translation helpful? Give feedback.
-
@anthony-lopez-dev That's great! Can you share with us what the issue was for people with the same issue in the future? |
Beta Was this translation helpful? Give feedback.
-
Will be nice if Inertia's its own implementation for this, I'm playing a little bit with Svelte and is a little bit mess to approach this active state |
Beta Was this translation helpful? Give feedback.
-
Well for now 🤷♂️, I've currently settled on : export default {
name: 'my-wonderfully-great-navbar',
data: () => ({ activeLink: null }),
mounted() {
this.setActiveLink(this.$inertia.page.url);
axios.interceptors.response.use(response => {
this.setActiveLink(response?.config?.url);
return response;
});
window.addEventListener('popstate', this.onPopState);
},
beforeDestroy () {
window.removeEventListener('popstate', this.onPopState)
},
methods: {
setActiveLink(url) {
this.activeLink = (url || '').replace('/', '').split('?')[0];
},
onPopState(event) {
this.setActiveLink(event?.state?.url);
},
}
}; Then in the template : <inertia-link href="/yolo" :class="['navbar-link', {'is-active': activeLink === 'yolo'}]">YOLO</inertia-link> Hope that helps. ✌ |
Beta Was this translation helpful? Give feedback.
-
@marvinrabe
<template>
<inertia-link v-for="(item, index) in menuItems" :class="{'is-active': isRoute(item.link)}">
{{ item.label }}
</inertia-link>
</template>
<script>
export default {
methods: {
isRoute(name) {
if (name === '/') {
return 'home' === this.$page.route.name;
}
return name === this.$page.route.name;
},
},
data() {
return {
menuItems: [
{
'label': 'Home',
'link': '/',
},
{
'label': 'Order',
'link': 'order',
},
{
'label': 'Setting',
'link': 'setting',
},
],
};
},
};
</script> Hope it'll be useful =) Thanks ! |
Beta Was this translation helpful? Give feedback.
-
Any updates? I think this is a highly requested feature 👀 |
Beta Was this translation helpful? Give feedback.
-
@Larsklopstra This active link feature is present in the demo application Intertia provides, take a look at https://github.com/inertiajs/pingcrm/blob/master/resources/js/Shared/MainMenu.vue If done right, this works like a charm. I just implemented this into my own project :) Code from my Layout.vue
Bind the url() method to the component, in my case: Menu
Important note: Your menu should be in a different component otherwise the code will return an error. Code from my Menu.vue
After this, you can bind a class passing the isUrl() method in your Menu.vue component:
I hope this helped anyone visiting this issue :) |
Beta Was this translation helpful? Give feedback.
-
@NickMeijer00 this is not a feature, this is more maintenance code. This should be provided BY Inertia, not being added by us |
Beta Was this translation helpful? Give feedback.
-
@Larsklopstra I agree, but a lot of people seem to be having trouble adding this 'maintenance code', so till then this is a working solution. |
Beta Was this translation helpful? Give feedback.
-
If you're on a Laravel app using Ziggy, it's pretty straightforward: <inertia-link
v-for="(item, index) in menuItems"
:key="item.route"
:href="item.route"
class="link-class"
:class="{'active-class': route().current() === item.route}"
>
{{ item.label }}
</inertia-link> |
Beta Was this translation helpful? Give feedback.
-
I am using the same named route 3 places in my main nav, but with a different parameter each time, so testing with ziggy's I went with the below, adding a click event to the window to make it "reactive" - based on James Wee's @pxwee5 work in this post: https://blog.usejournal.com/reactive-window-parameters-in-vuejs-fc5de75d7ab5 (Thanks, James!) This is just in my left-nav.vue component. There's probably a better place to put it and other improvements possible. routes/web.php Route::get('{section}', [SectionController::class, 'index'])->name('section.main') resources/js/layouts/left-nav.vue <template>
<div>
<ul>
<li><inertia-link :href="route('section.main', 'books')" :class="{ 'active': urlMatches('books') }">Books</inertia-link></li>
<li><inertia-link :href="route('section.main', 'authors')" :class="{ 'active': urlMatches('authors') }">Authors</inertia-link></li>
<li><inertia-link :href="route('section.main', 'dealers')" :class="{ 'active': urlMatches('dealers') }">Dealers</inertia-link></li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
pathname: '',
}
},
created() {
window.addEventListener('click', (e) => {
let a = e.target;
if ('A' !== a.tagName) {
a = a.closest('A');
}
if (null === a || null === a.href) {
return;
}
const url = new URL(a.href);
this.pathname = url.pathname.substr(1);
});
window.addEventListener('load', () => {
this.pathname = window.location.pathname.substr(1)
});
},
methods: {
urlMatches(str) {
return '' !== this.pathname && this.pathname.includes(str);
}
}
}
</script> The a.closest() stuff is because sometimes the user is clicking an image in an anchor tag, and we need to find the anchor tag, but the click event currentTarget will unfortunately be Edit: and the null checks are for when the user clicks on something other than a link. The 'load' listener sets the active nav for the initial page load, in the case of deep links. I'm using It's a little clunky, and the timing of the active highlighting is a little off, but this works for now. |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
this is my solution
|
Beta Was this translation helpful? Give feedback.
-
So, I actually started working on adding a feature to Inertia this week for active states. However, in the end I decided not to add anything, since Inertia already provides everything you need to do this (as noted in this tweet). Using <!-- URL exact match -->
<Link href="/users" :class="{ 'active': $page.url === '/users' }">Users</Link>
<!-- Component exact match -->
<Link href="/users" :class="{ 'active': $page.component === 'Users/Index' }">Users</Link>
<!-- URL starts with (/users, /users/create, /users/1, etc.) -->
<Link href="/users" :class="{ 'active': $page.url.startsWith('/users') }">Users</Link>
<!-- Component starts with (Users/Index, Users/Create, Users/Show, etc.) -->
<Link href="/users" :class="{ 'active': $page.component.startsWith('Users') }">Users</Link> And, of course, these comparisons can be more complex if you want, using regular expressions. And while this approach is more verbose than My TODO: Get this documented on https://inertiajs.com/links. |
Beta Was this translation helpful? Give feedback.
-
I really like this feature in vue-router, it'd be one less thing for Inertia users to worry about.
Creating this issue here instead of in the client side adapter repositories because it applies to all.
Beta Was this translation helpful? Give feedback.
All reactions