A custom directive similar to v-if in vuejs
Asked Answered
C

3

31

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.

Cannabis answered 24/3, 2017 at 15:54 Comment(0)
A
30

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.

Akel answered 21/4, 2017 at 13:18 Comment(7)
It's a good answer, but it will show 'Cannot set property '$el' of undefined' in console.Capelin
It works great, when I remove vnode.componentInstance.$el = comment; Becuase the componentInstance is undefined in my case. Thank youCapelin
Anyone else have an issue where an empty html tag is created when component data changes with this?Incompetence
This won’t pass any unit tests assuming you are looking for the functionality similar to v-if, when using vue-test-utilsDogwood
el.parentElement is always null. Yet when inspected it is not... which makes this unworkableExtravasation
While SSR is enabled, it will show The client-side rendered virtual DOM tree is not matching server-rendered content. in front-end.Midwest
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')Policyholder
P
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/

Policyholder answered 20/11, 2022 at 6:54 Comment(0)
R
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>
Rubber answered 24/7, 2023 at 11:29 Comment(1)
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?Breuer

© 2022 - 2024 — McMap. All rights reserved.