Close menu by clicking outside item on Vue.js 3

Asked

Viewed 120 times

-1

I have a component that has a menu that opens and closes when you click on it, it works fine, but I want it to close when I click off it, n know how to do it inside See, the right way,

I call this component on my main page.

My component:

export default {
    data(){
        return{
            isOpen: true,
        }
    },
    methods:{
        toggle(){
            this.isOpen = !this.isOpen
        }
    }
    
}
.menuSusoenco{
   
    width: 200px;
    color: black;
    border: solid 1px rgb(201, 201, 201);
    box-shadow:5px 5px 10px rgb(201, 201, 201);
    border-radius: 15px;
    position: fixed;
    right: 10px;
    background-color: #fff;
    

}

.contItensMenuSuspenco{
    padding: 0px 25px 25px 25px;

}
<template>
    <div class="cont-icom-seting">
        <i @click.stop.prevent="toggle" class="fas fa-cog"></i>

        <div v-show="isOpen">
            <div class="menuSusoenco">
                <div class="contItensMenuSuspenco">
                    <div class="menuSusoenco-item">conteudo</div>
                    <div class="menuSusoenco-item">conteudo</div>
                    <div class="menuSusoenco-item">conteudo</div>
                    <hr>
                    <div class="menuSusoenco-item">Logout</div>

                </div>
            </div>
        </div>
    </div>   


</template>

What is the correct way to close this menu when the user clicks outside it?

1 answer

0

How to close a modal by clicking outside:

There are two ways to do, in your case, as you do not have a div around your modal (a staff puts to darken the screen), usually by it you could make this click outside, or as yours does not have, you can use your own body

Because there is a side effect on events, where they propagate both from parent to child elements, and vice versa, this effect is called Bubbling and capturing

You can take advantage of this if you put the event on <body> as everything is his son, he will capture any click he has given on the screen

But this can also be a problem, since clicking in the middle of the modal will also close it, and should not be what you want

And that’s where the @click.stop, the additive stop cancels this side effect on the div where it is present, so you can put it in the modal so that it stops clicking on it causes it to be closed.

Soon in your code would add the following: Add the event when creating the component, passing the function of toggle

Remembering that it is necessary to remove the event while destroying the component so that it does not attempt to run even after the modal is removed

export default {
  data(){...},
  methods(){...},
  created(){
    window.body.addEventListener("click", this.toggle)
  },
  destroy(){
    window.body.removeEventListener("click", this.toggle)
  }
}

And change the HTML to add the @click.stop on the menu/modal tag to prevent the body event from running inside the modal

<template>
  <div class="cont-icom-seting">
    <i @click.stop="toggle" class="fas fa-cog"></i>

    <div v-show="isOpen">
      <!--            Adcionado aqui ⬇️ -->
      <div class="menuSusoenco" @click.stop>
        <div class="contItensMenuSuspenco">
          <div class="menuSusoenco-item">conteudo</div>
          <div class="menuSusoenco-item">conteudo</div>
          <div class="menuSusoenco-item">conteudo</div>
          <hr />
          <div class="menuSusoenco-item">Logout</div>
        </div>
      </div>
    </div>
  </div>
</template>

However, a recommendation

Using the body, which is not directly accessible by Vue components, induces that you need to use an event that is not from Vue, and even that you should remove it after the component is destroyed, and if you forget this can cause problems. So I would recommend putting on what people call wrapper, that is nothing more than a div that stands around everything.

So with a wrapper, you can make a div occupy the entire screen and have the same function that you would be putting on the body, this would allow you to use everything that is within the scope of the component and it be totally independent, avoiding generating any kind of problem.

Soon it would be the following:

Your JS would be as it is without the need to change anything the HTML, would have to add the wrapper:

<template>
  <!--     wrapper com toggle aqui ⬇️ -->
  <div class="modal-wrapper" @click="toggle">
    <div class="cont-icom-seting">
      <i @click.stop="toggle" class="fas fa-cog"></i>

      <div v-show="isOpen">
        <!--       Adcionado stop aqui ⬇️ -->
        <div class="menuSusoenco" @click.stop>
          <div class="contItensMenuSuspenco">
            <div class="menuSusoenco-item">conteudo</div>
            <div class="menuSusoenco-item">conteudo</div>
            <div class="menuSusoenco-item">conteudo</div>
            <hr />
            <div class="menuSusoenco-item">Logout</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

But then it would be necessary to make the wrapper occupy the entire screen, so you’d need to tweak the CSS, adding:

.modal-wrapper {
  position: fixed;
  width: 100vw;
  height: 100vh;
}
And an observation

You may even be using Vue 3, but this syntax is not new, and is also valid for Vue 2

Alternatives

There are other ways of doing modals as well, another alternative that may be of interest to you is this form:

Building a Modal Component with Vue.js

Browser other questions tagged

You are not signed in. Login or sign up in order to post.