Spagheeti
Spagheeti

Reputation: 79

Display dropdowns dynamically in one component

I want to have multiple dropdowns in one component using one variable to display or not and also clicking away from their div to close them:

<div class="dropdown">
  <button @click.prevent="isOpen = !isOpen"></button>
    <div v-show="isOpen">Content</div>
</div>
// second dropdown in same component
<div class="dropdown">
  <button @click.prevent="isOpen = !isOpen"></button>
    <div v-show="isOpen">Content</div>
</div>
data() {
  return {
    isOpen: false
  }
},
watch: {
  isOpen(isOpen) {
    if(isOpen) {
      document.addEventListener('click', this.closeIfClickedOutside)
    }
  }
},
methods: {
  closeIfClickedOutside(event){
    if(! event.target.closest('.dropdown')){
      this.isOpen = false;
    }
  }
}

But now when I click one dropdown menu it displays both of them. I am kinda new to vue and cant find way to solve this

Upvotes: 2

Views: 61

Answers (2)

Irakli Kandelaki
Irakli Kandelaki

Reputation: 106

Make an array and loop through it, much easier that way.

<template>     
<div id="app">
        <div class="dropdown" v-for="(drop, index) in dropData" :key="index">
      <button @click="openDropdown(index);">{{ drop.title }}</button>
        <div v-show="isOpen === index">{{ drop.content }}</div>
    </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          isOpen: null,
          dropData: [
            {
              title: "Hey",
              content: "Hey it's content 1"
            },
              {
              title: "Hey 2",
              content: "Hey it's content 2"
            },
              {
              title: "Hey 3",
              content: "Hey it's content 3"
            },
          ]
        };
      },
     
    methods: {
        openDropdown(idx){
         if (this.isOpen === idx) {
              this.isOpen = null;
            } else {
            this.isOpen = idx;
            }
    
        }
    
    }
    };
    </script>

Upvotes: 1

tony19
tony19

Reputation: 138276

To use just one variable for this, the variable needs to identify which dropdown is open, so it can't be a Boolean. I suggest storing the index (e.g., a number) in the variable, and conditionally render the selected dropdown by the index:

  1. Declare a data property to store the selected index:

    export default {
      data() {
        return {
          selectedIndex: null
        }
      }
    }
    
  2. Update closeIfClickedOutside() to clear the selected index, thereby closing the dropdowns:

    export default {
      methods: {
        closeIfClickedOutside() {
          this.selectedIndex = null
        }
      }
    }
    
  3. In the template, update the click-handlers to set the selected index:

    <button @click.stop="selectedIndex = 1">Open 1</button>
    <button @click.stop="selectedIndex = 2">Open 2</button>
    
  4. Also, update the v-show condition to render based on the index:

    <div v-show="selectedIndex === 1">Content 1</div>
    <div v-show="selectedIndex === 2">Content 2</div>
    

Also, don't use a watcher to install a click-handler on the document because we want to know about the outside-clicks when this component is rendered. It would be more appropriate to add the handler in the mounted hook, and then remove in the beforeDestroy hook:

export default {
  mounted() {
    document.addEventListener('click', this.closeIfClickedOutside)
  },
  beforeDestroy() {
    document.removeEventListener('click', this.closeIfClickedOutside)
  },
}

demo

Upvotes: 1

Related Questions