1

I use a component menu and this is displayed for each item but it all stays open if I don't click on close out I would like it to close when I click on another item with a menu.

to open and close I use a toggle on click the component has several styles

i on vuejs 3

I have the impression that each menu is independent so I don't know what to do

  <nav role="navigation" :id="uuid">
    <div class="app-menu">
      <div class="menu-princ" @click.stop="showModal = !showModal">
        <span
          @click.stop="showModal = !showModal"
          v-if="ellipsis"
          :class="{ ellipsis: ellipsis, cells: cells }"
        ></span>
        <span @click.stop="showModal = !showModal" v-if="ellipsis === false"
          ><b></b
        ></span>
      </div>
      <div
        id="menu-princ"
        class="menu-modal"
        :class="[{ open: showModal }, { menuEllipsisPosition: ellipsis }]"
        v-if="showModal"
      >
        <div class="wrap">
          <header class="menuHeader" :class="{ ellipsisMenu: ellipsis }">
            <h6
              :class="{ ellipsisTitle: ellipsis, ellipsisTitleAfter: ellipsis }"
            >
              {{ titleMenu }}
            </h6>

            <u :class="{ ellipsisSubTitle: ellipsis }">{{ subTitleMenu }}</u>
            <button
              class="detailBtn"
              :class="{ ellipsisDetailBtn: ellipsis }"
              @click.stop="showModal = !showModal"
            ></button>
          </header>
          <section>
            <ul v-if="disableMenuLi">
              <li
                @click.stop="showModal = !showModal"
                v-for="([key, value], j) in Object.entries(items)"
                :key="`item${j}`"
                :data-label="value.label"
                class="menuLiContent"
                :class="{
                  ellipsisBorderBottom: ellipsis,
                  ellipsisMenuLiContent: ellipsis,
                  ellipsisliHover: ellipsis,
                  disable: disable
                }"
              >
                <slot @click.stop="showModal = !showModal" :item="value">{{
                  item[key]
                }}</slot>
              </li>
            </ul>
          </section>
        </div>
      </div>
    </div>
  </nav>
</template>

<script>
import { uuid } from 'vue-uuid';

export default {
  name: 'Menu',
  data() {
    return {
      showModal: false,
      uuid: uuid.v4()
    };
  }
};
</script>

<style lang="scss" src="./style.scss" scoped></style>
  • Have a look at these two links: https://laracasts.com/discuss/channels/vue/close-dropdown-when-click-another-element https://stackoverflow.com/questions/62601164/vue-question-how-would-i-close-the-menu-when-i-click-a-link – shahidiqbal Apr 23 '21 at 13:34

1 Answers1

0

This was occurring for me with PrimeVue because when you click a menu, it does detect click outside to close, but if you click the same button on a different card, it allows you to open the menu in every card.

I would say normally that's how you solve it. When you have a menu open and you click anywhere outside it, you can close the menu. Here is a good resource for a Vue3 directive: Migrating "detect click outside" custom directive from Vue 2 to Vue 3.

The problem for me using PrimeVue is that they internally use approximately this logic, but the problem seems to stem from el.target being the same when you click the same button in a different card component. That's my best guess. It doesn't trigger the click-outside-to-close.

To solve it, I introduced two things: a composable that tracks the currently-open menu's unique id, and a watcher in each component that renders a menu.

./src/composables/useSingleMenu.js

import { ref } from 'vue';

const lastOpenedId = ref(null);

export const useSingleMenu = () => {
    const open = (id) => {
        lastOpenedId.value = id;
    };

    return {
        lastOpenedId,
        open,
        close,
    };
};

./src/components/SandwichCard.vue

<script setup>
import { ref, computed, watch } from 'vue';
import { useSingleMenu } from '@/composables/useSingleMenu';

const singleMenu = useSingleMenu();

const menu = ref();
const menuId = computed(() => `sandwich-options-${props.sandwich.id}`);

const toggleMenu = (event) => {
    singleMenu.open(menuId.value);
    menu.value.toggle(event);
};

watch(() => singleMenu.lastOpenedId.value, () => {
    if (singleMenu.lastOpenedId.value !== menuId.value) menu.value.hide();
});
</script>

If you want to attempt this, just take the composable as is, then you need to do two things:

  1. Call singleMenu.open('some-unique-id-1337'); when your menu opens
  2. Add the watcher: every time singleMenu.lastOpenedId.value changes, if its current value doesn't match the menu's id, close the menu.

This effectively makes a global piece of state for the currently-opened menu id, and every menu tracks its unique id and whether or not its id matches the currently open one. If they don't match, every menu tries to close itself and thus, only one menu can be open at any given time, and its the menu where the menu id matches the currently open menu id.

agm1984
  • 15,500
  • 6
  • 89
  • 113