How do you handle click-outside of element properly in vuejs?
Asked Answered
C

5

7

Is there any proper solution for handling click-outside of elements?

there are general solutions out there, like Handling clicks outside an element without jquery :

window.onload = function() {

    // For clicks inside the element
    document.getElementById('myElement').onclick = function(e) {
            // Make sure the event doesn't bubble from your element
        if (e) { e.stopPropagation(); } 
        else { window.event.cancelBubble = true; }
            // Place the code for this element here
        alert('this was a click inside');
    };

    // For clicks elsewhere on the page
    document.onclick = function() {
        alert('this was a click outside');
    };
};

But the problem is almost all projects have multiple and different popups in different components which i should handle their click-outsides.

how should i handle click-outisde without using a global window.on?(I think it is not possible to put all components outside-case handler in window.on )

Convolvulus answered 10/2, 2020 at 5:34 Comment(2)
npmjs.com/package/vue-click-outside. Enjoy!Milksop
Something for Vue 3 + Composition API - (from fantastic "utility" library VueUse)Burress
C
12

After struggling with this and searching about this, i found how to solve this problem using vuejs directive without bleeding:

1. using libraries:

v-click-outside is a good one,

https://www.npmjs.com/package/v-click-outside

2. without a library:

```
//main.js
import '@/directives';
......

// directives.js
import Vue from "vue";
Vue.directive('click-outside', {
  bind: function (element, binding, vnode) {
    element.clickOutsideEvent = function (event) {  //  check that click was outside the el and his children
      if (!(element === event.target || element.contains(event.target))) { // and if it did, call method provided in attribute value
        vnode.context[binding.expression](event);
        // binding.value(); run the arg
      }
    };
    document.body.addEventListener('click', element.clickOutsideEvent)
  },
  unbind: function (element) {
    document.body.removeEventListener('click', element.clickOutsideEvent)
  }
});
```

use it every-where you want with v-click-outside directive like below:

//header.vue
 <div class="profileQuickAction col-lg-4 col-md-12" v-click-outside="hidePopUps">
...
</>

you can check this on

Convolvulus answered 10/2, 2020 at 10:2 Comment(1)
How does the method 2 works with iframes?Nedanedda
T
3

Using Vue3 and the recommended library doesn't work.

Here is @seyyedkhandon's great custom directive answer, adjusted for vue3.

const app = createApp(App)
app.directive('click-outside', {
    beforeMount: function (element, binding) {
        console.log({
            element,
            binding
        });

        //  check that click was outside the el and his children
        element.clickOutsideEvent = function (event) {
            // and if it did, call method provided in attribute value
            if (!(element === event.target || element.contains(event.target))) {
                binding.value(event);
            }
        };
        document.body.addEventListener('click', element.clickOutsideEvent)
    },
    unmounted: function (element) {
        document.body.removeEventListener('click', element.clickOutsideEvent)
    }
});

Vue 3 directive Migration Guide

The differences are:

  • bind → beforeMount
  • unbind -> unmounted
  • In Vue 2, the component instance had to be accessed through the vnode argument, in Vue 3, the instance is now part of the binding: binding.instance.
Tobey answered 18/5, 2023 at 12:50 Comment(0)
S
2

In Vuejs 3, or Nuxt 3 :

<template>
  <div v-if="visible" class="modal-locked">
    <div class="modal-content">
      <h2>Modal Content</h2>
    </div>
  </div>
</template>



<script>
import { onMounted, onBeforeUnmount, ref } from 'vue';

export default {
  setup() {
    const visible = ref(true);

    const handleClickOutside = (event) => {
      const modalContainer = document.querySelector('.modal-locked');

      if (modalContainer && !modalContainer.contains(event.target.parentNode)) {
        visible.value = false;
      }
    };

    onMounted(() => {
      document.addEventListener('click', handleClickOutside);
    });

    onBeforeUnmount(() => {
      document.removeEventListener('click', handleClickOutside);
    });

    return { visible };
  },
};
</script>


<style scoped>
.modal-locked {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.5);
}

.modal-content {
  background-color: white;
  padding: 20px;
}
</style>
Spradlin answered 2/7, 2023 at 14:22 Comment(0)
W
1

You can also directly use VueUse vOnClickOUtside directive.

<script setup lang="ts">
import { vOnClickOutside } from '@vueuse/components'
const modal = ref(true)
function closeModal() {
  modal.value = false
}
</script>

<template>
  <div v-if="modal" v-on-click-outside="closeModal">
    Hello World
  </div>
</template>
Wheatear answered 9/11, 2022 at 8:15 Comment(0)
A
-1

It might be little late but i ve found a clear solution

<button class="m-search-btn" @click="checkSearch" v-bind:class="{'m-nav-active':search === true}">

methods:{
checkSearch(e){
  var clicked = e.target;
  this.search = !this.search;
  var that = this;
  window.addEventListener('click',function check(e){
    if (clicked === e.target || e.target.closest('.search-input')){ //I ve used target closest to protect the input that has search bar in it
      that.search = true;
    }else {
      that.search = false;
      removeEventListener('click',check)
    }
  })
}}


 
Adige answered 19/1, 2023 at 7:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.