7

I am looking for a way to close a component when there it a click outisde of the element.

I tried an addEventListener. This closes the component but after being closed it will not open again.

window.addEventListener('click', function(e){

if (document.getElementById('shopcartpreview').contains(e.target)){
console.log("Clicked in Box");


} else{
console.log("Clicked outside Box");
$('#shopcartpreview').hide();
 }
 })

Is there a way to accomplish this?

<template>
    <div id="shopcartpreview"  v-if="carthover">
        <div class="cartitem" v-for="item in cartitems">
            <div class="cartitempic"><img class="productImg" width="80px" height="80px" v-bind:src="'assets/products/' + item.image"></div>
            <div class="cartitemdetails">
                <div class="cartitemname">{{item.name}}</div>
                <div class="cartitemqty">1 X </div>
                <div class="cartitemprice">€{{item.unit_price}}</div>
            </div>
            <div class="cartitemdelete">
                <img src="assets/images/icon-bin.png" width="15px" height="15px">
            </div>
        </div>

        <div class="carttotal">
            <div class="carttotaltext">TOTAL:</div>
            <div class="carttotalprice">€2,860.00</div>
        </div>
        <div class="cartcheckouttbn">PROCEED TO CHECKOUT</div>
        <div class="viewcart">VIEW CART</div>



    </div>    
</template>
<script>
    module.exports = {
        data: function () {
                return{ 
                    cartitems: 0,
                    carthover: false,
                }
            },
            created(){
            EventBus.$on('addToCart', (payload) =>{
                this.cartitems = payload
            }),
            EventBus.$on('mouseover', (carthover) =>{
            this.carthover = carthover
            })
        }
    }
</script>
MK01111000
  • 770
  • 2
  • 9
  • 16

3 Answers3

16

I created a div element at the end of the component like that:

<div v-if="isPopup" class="outside" v-on:click="away()"></div>

Where .outside class is specified in CSS as follows:

.outside {
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0px;
  left: 0px;
}

And away() is a method in Vue instance as:

away() {
 this.isPopup = false;
}

Easy, works well.

Kate Orlova
  • 3,225
  • 5
  • 11
  • 35
Arnaud LiDz
  • 291
  • 3
  • 10
  • Great answer! This is way simpler than setting up the directive. Thanks! – Clifton Labrum Apr 12 '20 at 05:39
  • 3
    'outside' class sometimes need z-index: -1 – Ahmed Aboud Sep 01 '20 at 14:43
  • this doesn't work IF you have any buttons or other clickeable items in your view, as the click on this div will override their click. While adding z-index: -1 will take care of that issue, it will also result in you not being able to click this div everywhere, just on spaces where it doesn't 'overlap' with other divs. – Axelle Oct 12 '22 at 09:34
4

2020.11.10 Update

I find the previous solution I answered has so many errors, so I have to update it.

There are multiple solutions to close a component by clicking outside of it.

Firstly, there are some libraries which handle this problem, for example simplesmiler/vue-clickaway, the nuxt also use this script if you have read the source code.

Secondly, if you want to implement it manually, here is code:

onClickOutside ( event: Event ) {

    const path = event.path || (event.composedPath ? event.composedPath() : undefined)
    // check if the MouseClick occurs inside the component
    if (path && !path.includes(this.em) && !this.em.contains(event.target as HTMLElement)) {
      this.closeThisComponent() // whatever method which close your component
    }
  }

Then, you must bind this eventHandler onClickOutside to the document.documentElement after open your component and remove this eventHandler from document.documentElement after you close your component.

Please notice the timing and refers to the event loop of JavaScript, you must understand the difference between MicroTask and MacroTask.

For example, open the component

openThisComponent () {
    this.showThisCompoennt = true // whatever codes which open your component

    // You can also use Vue.$nextTick or setTimeout
    requestAnimationFrame(() => {
      document.documentElement.addEventListener('click', this.onClickOutside, false)
    })

  }
closeThisComponent () {
    this.showComponent = false // whatever codes which close your component
    document.documentElement.removeEventListener('click', this.onClickOutside, false)
  }
Reyshawn
  • 131
  • 3
  • 5
3

Demo Fiddle : https://jsfiddle.net/bq8m4fhe/

Create a clickoutside directive ... Detect click outside element

module.exports = {
    data: function() {
        return {
            cartitems: 0,
            carthover: false
        };
    },
    directives: {
        clickoutside: {
            bind: function(el, binding, vnode) {
                el.clickOutsideEvent = function(event) {
                    // here I check that click was outside the el and his childrens
                    if (!(el == event.target || el.contains(event.target))) {
                        // and if it did, call method provided in attribute value
                        vnode.context[binding.expression](event);
                    }
                };
                document.body.addEventListener("click", el.clickOutsideEvent);
                document.body.addEventListener("touchstart", el.clickOutsideEvent);
            },
            unbind: function(el) {
                document.body.removeEventListener("click", el.clickOutsideEvent);
                document.body.removeEventListener("touchstart", el.clickOutsideEvent);
            },
            stopProp(event) {
                event.stopPropagation();
            }
        }
    },
    created() {
        EventBus.$on("addToCart", payload => {
            this.cartitems = payload;
        }),
        EventBus.$on("mouseover", carthover => {
            this.carthover = carthover;
        });
    }
};

Use that directive like this.

<div id="shopcartpreview"  v-if="carthover" v-clickoutside="SHOPPING_CART_HIDE_FUNCTION">
staffan
  • 5,641
  • 3
  • 32
  • 28
dagalti
  • 1,868
  • 1
  • 13
  • 15