12

Based on this question Detect click outside element and this answer https://stackoverflow.com/a/42389266, I'm trying to migrate the directive from Vue 2 to Vue 3. It seems that binding.expression and vnode.context not exists more. How can I make it work?

app.directive('click-outside', {
    beforeMount (el, binding, vnode) {
        el.clickOutsideEvent = function (event) {
            if (!(el === event.target || el.contains(event.target))) {
                vnode.context[binding.expression](event);
            }
        };
        document.body.addEventListener('click', el.clickOutsideEvent);
    },
    unmounted (el) {
        document.body.removeEventListener('click', el.clickOutsideEvent);
    }
});
Michal Levý
  • 24,601
  • 3
  • 44
  • 68
Sauron
  • 1,816
  • 3
  • 16
  • 19
  • I don't know your specific use case, but many time people are wanting to use a click outside event they are not taking accessibility into account in that a keyboard only user may need the same functionality to happen when a blur event or "focus" outside happens. If you don't want to reinvent the wheel and you're able to add another library, this is a great library I've used in the past to achieve both click outside and blur outside. https://www.npmjs.com/package/vue-outside-events – maxshuty Sep 13 '20 at 11:08
  • 2
    @maxshuty thanks for your suggestion, however the library shown is for vue 2 – Sauron Sep 13 '20 at 12:31

3 Answers3

17

You can use binding.value instead like this:

const { createApp } = Vue;

const highlightEl = (color ) => (event, el) => {
  if (el) {
    el.style.background = color;
  } else {
    event.target.style.background = color;
  }
}
const clearHighlightEl = (event, el) => {
  if (el) {
    el.style.background = '';
  } else {
    event.target.style.background = '';
  }
}

const app = Vue.createApp({
  setup() {
    return {
      highlightEl,
      clearHighlightEl
    }
  }
})

app.directive('click-outside', {
  mounted(el, binding, vnode) {
    el.clickOutsideEvent = function(event) {
      if (!(el === event.target || el.contains(event.target))) {
        binding.value(event, el);
      }
    };
    document.body.addEventListener('click', el.clickOutsideEvent);
  },
  unmounted(el) {
    document.body.removeEventListener('click', el.clickOutsideEvent);
  }
});

app.mount('#app')
<script src="https://unpkg.com/vue@3.0.0-rc.11/dist/vue.global.prod.js"></script>

<div id="app">
  <h1 v-click-outside="highlightEl('yellow')" @click="clearHighlightEl">Element 1</h1>
  <p v-click-outside="highlightEl('#FFCC77')" @click="clearHighlightEl">Element 2</p>
</div>
Daniel
  • 28,755
  • 15
  • 87
  • 134
14

out of the context, there's an easier way in vue3 with composition.

Link to Vueuse ClickOutside (Vue 3)

Link to Vueuse ClickOutside(Vue 2)

<template>
  <div ref="target">
    Hello world
  </div>
  <div>
    Outside element
  </div>
</template>

<script>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'

export default {
  setup() {
    const target = ref(null)

    onClickOutside(target, (event) => console.log(event))

    return { target }
  }
}
</script>
Min Somai
  • 427
  • 5
  • 10
0

you can use ref to find out if the element contains the element clicked

<template>
    <div ref="myref">
        Hello world
    </div>
    <div>
        Outside element
    </div>
</template>

<script>

export default {

    data() {
        return {
            show=false
        }
    },
    mounted(){
        let self = this;
        document.addEventListener('click', (e)=> {
            if (self.$refs.myref !==undefined && self.$refs.myref.contains(e.target)===false) {
                //click outside!
                self.show = false;
            }
        })
    }
}
</script>
Miguel
  • 1