Disable nuxt link based on boolean - vuejs2

I can't seem to find a good way to disable a nuxt-link based on a data variable. Could anyone suggest something? I've tried looking at doucmentation but I can't come up with anything.
Say I have a boolean called disable I want to do something like this
<nuxt-link :disabled="disable"></nuxt-link>
I basically just don't want the link to be clickable if the boolean is set to false

<nuxt-link> is essentially <router-link> of Vue Router.
You can disable it using the event prop.
Assuming your one of your data or computed property is disabled boolean:
<nuxt-link :event="disabled ? '' : 'click'"></nuxt-link>
Working example:
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const Baz = { template: '<div>baz</div>' }
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar },
{ path: '/baz', component: Baz }
]
const router = new VueRouter({
routes
})
const app = new Vue({
router,
data(){
return {
disabled: true
}
}
}).$mount('#app')
<script src="https://unpkg.com/vue#2.7.13/dist/vue.min.js"></script>
<script src="https://unpkg.com/vue-router#3.6.5/dist/vue-router.min.js"></script>
<div id="app">
<h1>Hello App!</h1>
<div>
<!-- Assume they are <nuxt-link> because they are the same -->
<router-link
:event="disabled ? '' : 'click'"
to="/foo"
>
Go to Foo ({{ disabled ? 'Disabled' : 'Enabled' }})
</router-link>
<router-link to="/bar">Go to Bar</router-link>
<router-link to="/baz">Go to Baz</router-link>
</div><br />
<button #click="disabled = !disabled">Toggle Foo availability</button><br /><br />
<router-view></router-view>
</div>

I found that the most simple way was to just create a class for the disabled links. I'm still not sure if this is the best way to do it but it works for me and does exactly what I want.
<nuxt-link to="/search" :class="{ disabled: disabled }"> Search </nuxt-link>
my css
.disabled {
color: lightgrey
pointer-events: none
}

Another way to do this, which I'd like the most. Is to change the tag to the button and use the native :disabled state.
<nuxt-link tag="button" :disabled="disabled" to="/some/location">Some location</nuxt-link>
Then just turn the button into the link with the help of styles.

In Nuxt3 this works for me: :to="canNavigate ? link : null"

In Vue Router 4, router-link do not support tag and event props anymore. Here you can have more information.
I'll copy the example from the link above here:
replace
<router-link to="/about" tag="span" event="dblclick">About Us</router-link>
with
<router-link to="/about" custom v-slot="{ navigate }">
<span #click="navigate" #keypress.enter="navigate" role="link">About Us</span>
</router-link>
And I'll give a real example of usage. This piece of code I'm using in a real project, using nuxt and tailwind.
<nuxt-link
v-slot="{ navigate }"
class="flex-grow font-ember"
:to="`lesson/${l.lesson_id}`"
append
custom
>
<button
class="flex items-center"
:class="{
'text-gray-400 cursor-not-allowed': !l.released,
'text-gray-900': l.released,
}"
:disabled="!l.released"
#click="navigate"
>
...
</button>
</nuxt-link>

Just add the event prop if you simply want to disable a nuxt-link
<nuxt-link event="">Go to Foo</nuxt-link>

For this case anyway you can click on it. It displaying disabled but still clickable.
You need to use attribute tag in you nuxt-link
Example here <nuxt-link tag="button" :disabled="disable"></nuxt-link>

None of these seemed to work for me, so I set the :to on the nuxt-link to a computed method. The to attribute on nuxt link will redirect nowhere when empty. For example:
<script>
export default{
computed: {
redirectRequest(){
return this.isMobile ? "" : "/Worklog";
}
}
}
</script>
<template>
<NuxtLink :to="redirectRequest"></NuxtLink>
</template>

Related

Nuxt : #click does not work with Nuxt-link

I am expecting to include on my web application an effect that underlines the section where we are in the list of sections.
I am working with Nuxt.
I don't know why the following code does not change the value of the boolean isActive.
<nuxt-link
:to="`${path}/${filterItem.filter}`"
:style='{"text-decoration": (isActive ? "underline" : "none")}'
#click="selectSeason(filterItem.filter) toggleUnderline()" >
methods: {
selectSeason(filter) {
this.$router.push(`${this.path}/${filter}`)
},
toggleUnderline() {
this.isActive = !this.isActive
}
},
You could probably achieve this with something like this
<template>
<nuxt-link to="/about" :custom="true">
<a #click="test">go to about page</a>
</nuxt-link>
</template>
<script>
export default {
methods: {
test() {
console.log('called test method')
},
},
}
</script>
As shown here: https://github.com/vuejs/router/issues/846#issuecomment-808150258
Even thought, this is probably not the best approach overall and seems quite too hacky for a simple use case.
Use a button tag if you want an action, otherwise put conditional logic on your route but don't mix a client side navigation and some kind of click events.
In Nuxt3 <NuxtLink> will get a class="router-link-active router-link-exact-active" if it's active, so you can use those to underline active link.
Same as router-link, you need to use v-on:click.native
<nuxt-link
:to="`${path}/${filterItem.filter}`"
:style='{"text-decoration": (isActive ? "underline" : "none")}'
#click.native="selectSeason(filterItem.filter) toggleUnderline()"
>
</nuxt-link>
How do we v-on:click nuxt-link?
Nuxt3:
<script lang="ts" setup>
const anyFunction = () => {
console.log('easy')
}
</script>
<template>
<NuxtLink
:to="'/'"
#click.prevent="anyFunction()"
>easy</NuxtLink>
</template>

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>

Is there a way, to "emit" a built in event, by hard coding it in Vue instance?

I would like to link two components with each other in my Vue project.
I use two-way binding for that, so I have a parent, and two child components.
The concept:
We see a carousel in the left side of the screen, and we see an accordion in the right side. I built the carousel and the accordions with v-for from a database file.
When I click in some of the accordion it drops down, and I need a reaction from a carousel component, to slide exactly there, where I clicked in the accordion.
Like:
carousel: banana, apple, house
accordion: banana, apple house
So when im clicking in the apple accordion button, I need the slider to go to the where are the apple is displayed, and reverse.
As I said, I already bind the two components to each other, so when I'm clicking one of the accordion buttons like #click="onShowStart(index)", I get that index in the another child too, and it's changing dynamically vica-versa by sliding or clicking. So the indexes are already linked and its dynamic.
My problem is I don't know how to trigger an event, like #sliding-start from vue instance in the watch field. So I watch the "actualPosition" prop in my component, and when its changed (from 3 to 1 for example), I would like to start a sliding event to the new value of the actualPosition.
So i need something like:
this.$emit('sliding-start', actualPosition);
I've been sitting at this problem for days, but I think my whole thinking is wrong. But before i believe this, im asking you first.
Here is my code for the Parent component:
<div class="row">
<carousel :actualPosition="actualPosition" class="col bg-dark" #sendTheCarouselPosition="updateAccordion($event)"></carousel>
<accordion :actualPosition="actualPosition" class="col bg-dark" #sendTheAccordionlPosition="updateCarousel($event)"></accordion>
</div>
<script>
export default {
data() {
return {
actualPosition: null,
}
},
methods:{
updateAccordion: function (updatedAccordion){
this.actualPosition = updatedAccordion;
},
updateCarousel: function(updatedSlider){
this.actualPosition = updatedSlider
}
},
}
</script>
My Accordion component:
<template>
<div role="tablist">
<b-card no-body class="mb-1" v-for="(item, index) in dataForProject">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button block href="#" v-b-toggle="'accordion-' + index" variant="info" #click="onShowStart(index)" >{{ item.title }}</b-button>
</b-card-header>
<b-collapse :id="'accordion-' + index" visible accordion="my-accordion" role="tabpanel">
<b-card-body>
<div>
<h1>data from Carousel sibling: {{ actualPosition }}</h1>
</div>
<b-card-text>{{ item.content }}</b-card-text>
</b-card-body>
</b-collapse>
</b-card>
</div>
</template>
<script>
import myDataBase from '../data2'
export default {
props:['actualPosition'],
watch:{
actualPosition: function () {
},
},
data() {
return {
dataForProject: myDataBase,
}
},
methods:{
onShowStart: function (accordionIndex) {
this.$emit('sendTheAccordionlPosition', accordionIndex);
},
},
}
</script>
And my Carousel component:
<template>
<div>
<p class="mt-4 text-white">
data from Accordion sibling: {{ actualPosition }}
</p>
<b-carousel
id="carousel-1"
:interval="0"
controls
indicators
background="#ababab"
img-width="1024"
img-height="480"
style="text-shadow: 1px 1px 2px #333;"
ref="slider"
#sliding-start="onSlideStart"
#sliding-end="onSlideEnd"
>
<b-carousel-slide v-for="(item, index) in dataForProject" :id="index" >
<img
slot="img"
class="d-block img-fluid w-100"
width="1024"
height="480"
:src="item.image_url"
alt="image slot"
>
</b-carousel-slide>
</b-carousel>
</div>
</template>
<script>
import myDataBase from '../data2'
export default {
props:['actualPosition'],
watch: {
actualPosition: function () {
},
},
data() {
return {
//slide: 0,
dataForProject: myDataBase,
}
},
methods: {
onSlideStart(slide) {
this.$emit('sendTheCarouselPosition', slide);
},
onSlideEnd(slide) {
},
}
}
</script>
I can get this done by two ways.
1 - Global EventBus
I will create an eventBus and register events on it from any file and listen it anywhere -
import { EventBus } from '#/eventBus'
// simply import it to component which need listen the event
//Register Event where you have your methods - like In your COMP_B_TWO
EventBus.$on('changeValue', () => { this.doSomething() })
// Emit event from another component
EventBus.$emit('changeValue')// Like directly from your COMP_A_TWO
To know how to create a eventBus follow this - Global Event Bus Vue
2 - Use state management - Vuex Follow this link - Vuex
Basically, it will have centralized store for all the components in an application. Whenever you wish to update state you will update to store. And all the other component using that state will react accordingly
Okay, so my whole concept was wrong, and i was wrong about this.
I should have used the v-model for this whole thing. And nothing more.
I added the v-model to the target tags, and my problem is solved.

Vue js dynamically render router link path

I'm struggling to find a way to render the path of the router-link dynamically, for example with the test variable. I'm trying to bind :to but unsuccessfully. I don't know if I can use the ternary operator in the binding as shown below:
<template>
<div>
<router-link
:to="{ test ? '/success' : '/fail' }"
tag="button"
class="btn-next">
<span class="btn-text">BUTTON</span>
</router-link>
</div>
</template>
<script>
export default {
// ...
data: function() {
return {
test: false
};
}
}
</script>
Your solution is close (you don't need the curly brackets). It should look like this:
<router-link :to="test ? '/success' : '/fail'">
demo

Getting a value from a nested template in vue

So basically I have something like this in my home page
<template>
<div id="body">
<sideBar :menu="menu" :tableName='tableName' titleOfPage='titleOfPage'></sideBar>
<div id="menu">
HOME PAGE
</div>
</div>
</template>
In the sidebar template you can select your page. It will forward you this template
<template>
<div id="body">
<sideBar :menu="menu" :tableName='tableName' :titleOfPage='titleOfPage'></sideBar>
<div id="main">
<h1>{{ titleOfPage }}</h1>
....
I need the sidebar template to set the titleOfPage as it corresponds to the page clicked. I tried different ways but didn't manage. I appreciate the help by explaining me how to solve this issue.
The Vue docs recommend not using $router directly but instead configuring props to pass to the routed components:
const routes = [
{ path: '/', redirect: '/page-a', component: { template: `<router-view></router-view>` }, name: 'Home' },
{ path: '/page-a', component: Page, props: { titleOfPage: 'Page A' }},
{ path: '/page-b', component: Page, props: { titleOfPage: 'Page B' }},
];
If your props are dynamic and depend on the current route, you can pass a function to the props property of each route configuration:
{ path: '...', component: ..., props: (route) => ({ query: route.query.q }) }
To solve your issue, you can simply create a page component which receives the props from the router and render your sidebar component in its template:
<!-- template for page component -->
<template>
<div id="body">
<sideBar :menu="menu" :tableName='tableName' :titleOfPage='titleOfPage'></sideBar>
<div id="main">
<h1>{{ titleOfPage }}</h1>
<!-- ... -->
</div>
</div>
</template>
DEMO: stackblitz