30

I'm writing a custom directive in vue.

I want it to work like v-if but it will have a little logic going inside it. Let me explain with an example:

<button v-permission="PermissionFoo">Do Foo</button>

It will check the permission and will show or hide the component.

Currently I'm doing this via CSS styles:

var processPermissionDirective = function (el, binding, vnode) {
    if (SOME_LOGIC_HERE) {
        el.style.display = el._display;
    }
    else {
        el.style.display = 'none';
    }
}

export default {
    bind: function (el, binding, vnode) {
        el._display = el.style.display;
        processPermissionDirective(el, binding, vnode);
    },
    update: function (el, binding, vnode) {
        processPermissionDirective(el, binding, vnode);
    }
}

But I don't want this element to stay in the document. So I'm looking for another way other than CSS because it must be also removed from DOM like v-if does.

Yves
  • 3,752
  • 4
  • 29
  • 43

3 Answers3

29

Try to use this hack:

Vue.directive('permission', (el, binding, vnode) => {
  if (!isUserGranted(binding.value)) {
    // replace HTMLElement with comment node
    const comment = document.createComment(' ');
    Object.defineProperty(comment, 'setAttribute', {
      value: () => undefined,
    });
    vnode.elm = comment;
    vnode.text = ' ';
    vnode.isComment = true;
    vnode.context = undefined;
    vnode.tag = undefined;
    vnode.data.directives = undefined;

    if (vnode.componentInstance) {
      vnode.componentInstance.$el = comment;
    }

    if (el.parentNode) {
      el.parentNode.replaceChild(comment, el);
    }
  }
});

UPD 05-19-2017: My latest code. I define setAttribute() and check for vnode.componentInstance to prevent js errors when using with both html elements and Vue components.

Sergey Tsapenko
  • 306
  • 3
  • 4
  • It's a good answer, but it will show 'Cannot set property '$el' of undefined' in console. – EasonBlack May 18 '17 at 07:00
  • It works great, when I remove vnode.componentInstance.$el = comment; Becuase the componentInstance is undefined in my case. Thank you – EasonBlack May 18 '17 at 07:11
  • Anyone else have an issue where an empty html tag is created when component data changes with this? – bertmaclin Mar 12 '18 at 18:09
  • 1
    This won’t pass any unit tests assuming you are looking for the functionality similar to v-if, when using vue-test-utils – ierdna Aug 22 '19 at 19:21
  • `el.parentElement` is always null. Yet when inspected it is not... which makes this unworkable – Douglas Gaskell Aug 26 '20 at 01:14
  • While SSR is enabled, it will show `The client-side rendered virtual DOM tree is not matching server-rendered content.` in front-end. – Omid Feb 10 '22 at 16:04
  • is there a solution for Vue 3? I wanted to try this and but it throws errors in the js console: Uncaught (in promise) TypeError: Cannot set properties of undefined (setting 'height') – Ivan Cabrera Nov 20 '22 at 06:22
0

For Vue 3, it's much simpler:

import { DirectiveBinding, VNode } from 'vue';
import { Meteor } from 'meteor/meteor';

export const VCan = function(el: HTMLElement, binding: DirectiveBinding, vNode: VNode) {
    const behaviour = binding.modifiers.disable ? 'disable' : 'hide';
    // @ts-ignore
    const hasPermission = Roles.userIsInRole(Meteor.userId(), `${ binding.value }-${ binding.arg }`,
        Meteor.user()?.profile.profile);
    if (!hasPermission) {
        if (behaviour === 'hide') {
            // @ts-ignore
            vNode.el.hidden = true;
        } else if (behaviour === 'disable') {
            // @ts-ignore
            el.disabled = true;
        }
    }
};

Reference: https://www.linkedin.com/pulse/vue-3-custom-directives-links-showhide-via-shahzad-ahmed/

Ivan Cabrera
  • 337
  • 4
  • 9
0

For Vue 3

const savedPlaces = new WeakMap()

export default (el, binding) => {
    if (binding.value !== binding.oldValue) {
        if (!binding.value) {
            const replacer = document.createComment(' ')
            savedPlaces.set(el, { parentNode: el.parentNode, replacer })
            el.parentNode?.replaceChild(replacer, el)
        } else {
            const ctx = savedPlaces.get(el)
            if (ctx.replacer) {
                ctx.parentNode?.replaceChild(el, ctx.replacer)
            }
        }
    }
}

main.js

//...
import permission from '@/path/to/directives'

const app = createApp(App)

app.directive('permission', permission)

use templates:

<button v-permission="PermissionFoo">Do Foo</button>
  • Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. **Would you kindly [edit] your answer to include additional details for the benefit of the community?** – Jeremy Caney Jul 31 '23 at 00:47