Skip to content

Conversation

@bmarkovic
Copy link

During the discussion on #916 a solution was mentioned, where attribute disabled in <router-link> would prevent the link from working. This is useful for programmatically disabling router links based on application state.

Compared to other solutions, both available and proposed, this is by far the cleanest and most idiomatic if you come from a HTML mindset, as it's available on form elements with the same purpose, and some CSS frameworks like Bootstrap promote its use for <li><a> navigation elements.

Furthermore, binding to disabled attribute is a simple, easy to style and implement, and I honestly believe that for these reasons, it is also very idiomatic to Vue.js as well.

This pull request implements that proposal.

@EvanBurbidge
Copy link

Any movement on this?

@posva
Copy link
Member

posva commented Apr 11, 2019

I saw there is a big interest in this and will take a look shortly :)

@posva
Copy link
Member

posva commented Apr 15, 2019

Coming back to this: current implementation looking for the attribute disabled doesn't look satisfactory. But at the same time, I'm afraid adding a prop named disabled could break existing behaviour with buttons and others that are disabled

After reading all the comments in the issue and asking around, I'm still unsure of what problem is being solved and what is the real use case behind.

The cleanest workaround would be a v-if but it requires duplicating the content:

<router-link v-if="active" to="/link">/link</router-link>
<span class="link-disabled" v-else>/link</span>

The other way would be to use both, a disabled and tabindex="-1" attributes (prevent keyboard-selection) and with CSS add pointer-events: none to the [disabled] selector (or something a bit more specific)

Even if we implement a disabled (or probably named differently) prop, it would feel wrong adding both or some because the expected behaviour isn't clear: do we want to prevent the click but still allow it? (if so the tabindex shouldn't be added) do we want to style things differently too?
-> A simple boolean prop wouldn't be the silver bullet people want and adding it would probably create more feature request around complicated it, which is something that I really want to avoid because disabled should be very straightforward

@bmarkovic
Copy link
Author

bmarkovic commented Apr 18, 2019

Coming back to this: current implementation looking for the attribute disabled doesn't look satisfactory

Could you please elaborate as to why? This is exactly what the users requesting the functionality (myself included) have requested.

I'm afraid adding a prop named disabled could break existing behaviour with buttons and others that are disabled

Again, I'm not sure I can follow the reasoning. A disabled form element is already disabled by the browser and behaves exactly as the rotuter-link would behave with this patch. It's highly unlikely that someone will put attribute disabled on a tag without explicitly desiring it to actually not perform any actions.

After reading all the comments in the issue and asking around, I'm still unsure of what problem is being solved and what is the real use case behind.

It's rather simple: The real use case is to be able to:

  • Programatically (by altering component or Vuex state) disable a router link from being able to be clicked or otherwise activated...
  • ..without removing the element(s) that compose the link from the DOM...
  • ..but with the ability to style it ("gray it out") using CSS, which Bootstrap et al already do for navi

The cleanest workaround would be a v-if but it requires duplicating the content:

<router-link v-if="active" to="/link">/link</router-link>
<span class="link-disabled" v-else>/link</span>

That is very, very far from clean, and does not retain implicit CSS styling in Bootstrap and other CSS libraries that share this idiom for navigation elements. Actually what people usually do is:

<router-link 
  :disabled="!whateverActivatesThisLink" 
  :event="whateverActivatesThisLink ? 'click' : ''"
  to="/link"
>/link</router-link>

Which is dry-er. Still, compare to:

<router-link :disabled="!whateverActivatesThisLink" to="/link">/link</router-link>

The other way would be to use both, a disabled and tabindex="-1" attributes (prevent keyboard-selection) and with CSS add pointer-events: none to the [disabled] selector (or something a bit more specific)

It's much easier to style by just detecting the disabled attribute, it's completely idiomatic to how DOM implementations already do it for HTML form elements. Granted, if preventing keyboard-selection (tho do note that even if keyboard selection works it has as much effect as clicking on an disabled element: none) router-link would need to include negative tabindex for disabled elements still it brings us from:

<router-link 
  :disabled="!whateverActivatesThisLink" 
  :tabindex="whateverActivatesThisLink ? undefined : -1" 
  :event="whateverActivatesThisLink ? 'click' : ''"
  to="/link"
>/link</router-link>

to

<router-link 
  :disabled="!whateverActivatesThisLink" 
  :tabindex="whateverActivatesThisLink ? undefined : -1" 
  to="/link"
>/link</router-link>

for full equivalence, but I see what you mean here.

Even if we implement a disabled (or probably named differently) prop, it would feel wrong adding both or some because the expected behaviour isn't clear: do we want to prevent the click but still allow it? (if so the tabindex shouldn't be added) do we want to style things differently too?

I'm not exactly sure what do you mean by "prevent the click but still allow" it.? If by "it"you mean "keyboard navigation" then I concur. It's a tough cookie.

Other than that, HTML form elements are visible, visibly "greyed out" and "do not work". CSS is what one uses for styling. This is exactly how router-link would behave provided you also style it with CSS, and it's quite easy to target with few lines of CSS and doesn't require adding/removing any classes or any such malarky,

Which is what Bootstrap and other CSS libraries already do for link/navi elements when they have disabled attribute.

-> A simple boolean prop wouldn't be the silver bullet people want and adding it would probably create more feature request around complicated it, which is something that I really want to avoid because disabled should be very straightforward

Again, I'm not sure I agree with this assesment: it would, and does behave exactly as the canonical disabled attribute behaves on form elements, buttons in particular which share most of the UX idioms with router-links, sans "keyboard navigation" which is extremely hard to do without keeping some form of hidden state for "tabindex" attribute for each element.

Still, even without that it's an improvement over the current state of things because:

  • In many use cases keyboard navigation over disabled <li><a> or whatever navi elements isn't important to developers and their users
  • It still removes one binding per link (:event)
  • It will work as people using Bootstrap (still the dominant CSS liib) or any CSS lib that inherited this behavior from Bootstrap would expect

With the only perhaps confusing behavior being the "keyboard navigation" case where a <li><a> would (lo and behold) behave in subtly different way than how form button does.

I firmly believe that perfect is the enemy of good, and that the scenario in which someone clever comes up with a more wholesome solution if and when you accept this patch or implement something alike is equally possible as the one where you get bombarded by issues around this from confused users. I'd even say it's more likely.

@posva posva added the group[router-link redesign] Issues regarding a redesign in current router-link implementation to make it make it more customizab label Apr 18, 2019
@posva
Copy link
Member

posva commented Apr 18, 2019

what isn't satisfactory to me is in part the naming because disabled is a form-element-only attribute. It's not possible to use on an anchor which is the default element for a router element.
On top of that, a router-link can be anything, a div, a button, an a. And the behaviour for different elements should be different. For example, a button or any other form element doesn't require anything apart from the attribute itself while others do

  • In many use cases keyboard navigation over disabled <li><a> or whatever navi elements isn't important to developers and their users

That is just not true... but if it isn't then there is no need to add anything to current implementation as using pointer-events over the [disabled] selector would work

  • It will work as people using Bootstrap (still the dominant CSS liib) or any CSS lib that inherited this behavior from Bootstrap would expect

To be fair, Bootstrap is no longer the dominant CSS (or component in the case of Vue) library

Anyway, this is still not focusing on the original problem, which is what I want to get to. Right now, the original problem seems to be that it isn't possible to prevent the navigation from happening when clicking while we still want to display the link:

<router-link to="/some-url" @click.prevent="customNavigation">go</router-link>

At least that's what I found when searching in the original issue.
If you want to style the element properly and have the disabled behaviour by doing

``vue
<router-link
:disabled="!whateverActivatesThisLink"
:tabindex="whateverActivatesThisLink ? undefined : -1"
to="/link"

/link


which is the most consistent approach to HTML

@vuejs vuejs deleted a comment from cerw Apr 18, 2019
@vuejs vuejs deleted a comment from callezenwaka Apr 18, 2019
@bmarkovic
Copy link
Author

there is no need to add anything to current implementation as using pointer-events over the [disabled] selector would work

I do agree, the original thread suggested that "the people" want it intrinsic to router-link component. I also agree that there are many ways to solve this with Vue as is, and I even agree that pointer events on [disabled] combined with automating tabindex are by far the cleanest solution.

It's a type of tradeoff in the "syntactic sugar" class. Whatever you guys choose to do is fine by me. So far design decisions of the core Vue team have "clicked" with me, and the dev experience is really one of the main reasons why I push your library on my and my employers projects.

@falstack
Copy link

falstack commented Oct 2, 2019

so, if i want disabled router-link, how can i do?

@RomainMazB
Copy link

RomainMazB commented Oct 30, 2019

I'm also interested to disable route-link.
I came here after some searches: found the @bmarkovic solution in many threads:

Actually what people usually do is:

<router-link 
  :disabled="!whateverActivatesThisLink" 
  :event="whateverActivatesThisLink ? 'click' : ''"
  to="/link"
>
  /link
</router-link>

This is working but due to href property still present on the a tag: the link still seems to be clickable for users (hand-icon on hover) so I added a style="cursor: default" on router-link when disabled.

But it's not programmer-friendly:

<router-link 
  :disabled="!whateverActivatesThisLink" 
  :event="whateverActivatesThisLink ? 'click' : ''"
  :style="!whateverActivatesThisLink ? 'cursor: default' : ''"
  to="/link"
>
  /link
</router-link>

@agm1984
Copy link

agm1984 commented Jun 2, 2020

I sometimes do stuff like this:

<component
    :is="hasSubLinks ? 'button' : 'router-link'"
    :to="hasSubLinks ? undefined : href"
    @click="hasSubLinks ? handleClick() : navigate"
>
    <!-- arbitrary markup -->
</component>

But I basically always wrap router-link, so you can gain control over disabled state with something like this:

<template>
    <router-link
        v-slot="{ href, route, navigate, isActive, isExactActive }"
        :to="to"
    >
        <a
            :class="['nav-link-white', {
                'nav-link-white-active': isActive,
                'opacity-50': isDisabled,
            }]"
            :href="isDisabled ? undefined : href"
            @click="handler => handleClick(handler, navigate)"
        >
            <slot></slot>
        </a>

    </router-link>
</template>

<script>
export default {
    name: 'top-nav-link',

    props: {
        to: {
            type: Object,
            required: true,
        },

        isDisabled: {
            type: Boolean,
            required: false,
            default: () => false,
        },
    },

    data() {
        return {};
    },

    computed: {},

    methods: {
        handleClick(handler, navigate) {
            if (this.isDisabled) return undefined;
            return navigate(handler);
        },
    },

};
</script>

In my app right now, I'm noticing that some combinations of @click="handler => handleClick(handler, navigate)" suffer significantly in performance.

For example this changes routes very slow:

@click="isDisabled ? undefined : handler => navigate(handler)"

But the pattern in my full example code above works and has no performance issue.

In general, ternary operator in @click can be very dicey, so if you get issues, don't give up right away, try many different ways to bifurcate on predicates or switch over <component :is="" based on state. navigate itself is an ornery one because it requires the implicit first parameter to work.

@posva
Copy link
Member

posva commented Jun 24, 2020

One of the ideas behind the v-slot api is to allow users to create custom tailored RouterLink components like the one above by @agm1984 . Adding a custom disabled prop fits perfectly that idea of tailored RouterLink

@posva posva closed this Jun 24, 2020
@sylvainpolletvillard
Copy link

For anyone looking for an easy fix:

<component :is="isDisabled ? 'span' : 'router-link'" :to="yourUrl" />

is the most straight forward option currently available

@ramwin
Copy link

ramwin commented Aug 14, 2020

I think add a disabled attribute is great. Thanks you for your pull request.
@posva :

But at the same time, I'm afraid adding a prop named disabled could break existing behaviour with buttons and others that are disabled

Can you show us some more example to convince us?

@posva :

After reading all the comments in the issue and asking around, I'm still unsure of what problem is being solved and what is the real use case behind.

@bmarkovic is trying to change the ugly code

<router-link v-if="active" to="/link">/link</router-link>
<span class="link-disabled" v-else>/link</span>

to more beautiful and simple code:

<router-link :disabled="!active" to="/link">/link</router-link>

@joaquinwojcik
Copy link

I think is more natural pass disabled as a router-link prop rather than use a vue-slot.
So, +1 to this.

@posva
Copy link
Member

posva commented Aug 31, 2020

Docs to show how to extend custom needs for router link are on the way. Keep in mind there is no way to support every single use case for router-link out of the box and it's fine to extend it to support custom cases in your application like navbars, content links, external links, etc

Example: disabled has to be added in user land because there are different ways to implement it. For example, disabled would only make sense semantically on a button element as it doesn't exist on a elements. But as some people pointed out in the thread, they would want to use a span with no click event attached to it as a disabled mechanism.

@vuejs vuejs locked as resolved and limited conversation to collaborators Aug 31, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

feature request group[router-link redesign] Issues regarding a redesign in current router-link implementation to make it make it more customizab

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants