B0BBY
B0BBY

Reputation: 1109

make all <b-collapse> non visible in child.vue when button is clicked in parent

I'm working with BootstrapVue.

I have following situation: I have a parent.vue and a child.vue. In my parent.vue I have a v-for where I can create multiple Buttons. Each of these is triggering a b-collapse in my child.vue and each of this has multiple b-collapse as well. (see Code)

Now I need to do following: I want to close all of my b-collapse inside my child.vue when my b-collapse in my parent.vue will be closed. But I could not figure out how to do that.. (they should be closed as well when I reopen my parent.vue-collapse)

I have reduced my code to the minimum. But just for additional info I will do this.inputs.push[{id: this.id +=1}] each time adding a new Item or Element. So each of them has an unique id.

Hopefully someone can help me out!

CODE

parent.vue

<div v-for="item in inputs" :key="item.id">
  <b-button v-b-toggle="'NewItem'+item.id"></b-button>
</div>

<Child/>

<b-button @click="addNewItem()"></b-button>

child.vue

<b-collapse visible :id="'NewItem' + item.id">  //Here i need a solution
  <div v-for="element in inputs" :key="element.id">
    <b-button v-b-toggle="'Element' + element.id"></b-button>
    <b-collapse :id="'Element' + element.id>
      <div>Here is Element {{element.id}}</div>
    </b-collapse>
  </div>

  <b-button @click="addElement()"></b-button>
</b-collapse>

EDIT - Full Code:

Parent.vue

<template>
<div class="container">
  <div v-for="(item, index) in inputs" :key="item.id">
    <b-button v-b-toggle="'NewItem'+item.id" @click="closeAll()">Item {{index + 1}}</b-button>
  </div>

  <Child :idParent="item.id" :closeAllProducts="closeAllProducts" />

  <b-button @click="addNewItem()">Add new Item</b-button>
</div>
</template>

<script>

import Child from "./components/child.vue"

export default {

  components: {
    Child,
  },

  data() {
    return {
      closeAllProducts: true,
      id: 1,
      inputs: [{
        id: 1,
      }]
    }
  },

  methods: {
    addNewItem() {
      this.inputs.push({id: this.id += 1})
    },

    closeAll() {
      this.closeAllProducts = false;
    }
  }
}
</script>

Child.vue

<template>
  <b-collapse :visible="closeAllProducts" :id="'NewItem'+item.id">  
    <div v-for="(element, index) in inputs" :key="element.id">
      <b-button v-b-toggle="'Element' + element.id"></b-button>
      <b-collapse :id="'Element' + element.id">
        <div>Here is Element {{index + 1}}</div>
      </b-collapse>
    </div>

    <b-button @click="addElement()">Add new Element</b-button>
  </b-collapse>
</template>

<script>
export default {
  props: ["idParent", "closeAllProducts"],

  data() {
    return {
      id: 1,
      inputs: [{
        id: 1,
      }]
    }
  },

  methods: {
    addElement() {
      this.inputs.push({id: this.id += 1})
    }
  }
}
</script>

NEW EDIT: Added closeAllProducts - If I'm clicking my button in my parent.vue it should trigger the function to change the boolean to **false**. But when I use it like this all elements in every item will be non visible.. I need to pass a parameter with it but I could not figure out how..

Upvotes: 6

Views: 904

Answers (1)

tony19
tony19

Reputation: 138526

One solution is to create a child prop that collapses its b-collapse elements, and have the parent control that prop:

  1. Create a Boolean prop named collapsed in the child:

    // child.vue
    export default {
      props: {
        collapsed: Boolean
      }
    }
    
  2. In addElement(), insert a visible prop to match the b-collapse's visible prop. We'll bind each item's visible to the corresponding b-collapse prop. Note we use b-collapse's v-model to bind its visible prop, which keeps the item's visible prop in sync with the actual visibility state.

    <!-- child.vue -->
    <script>
    export default {
      data() {
        return {
          inputs: [
            {
              id: 1,
              visible: false,
            },  👆
          ],
        }
      },
      methods: {
        addElement() {
          this.inputs.push({ id: ++this.id, visible: false })
        }                                      👆
      }
    }
    </script>
    
    <template>
      ⋮                              👇
      <b-collapse v-model="element.visible" ⋯>
         <div>Here is Element {{ index + 1 }}</div>
      </b-collapse>
    </template>
    
  3. Add a watcher on the collapsed prop in the child. This watcher will set each element's visible prop to false only when collapsed is true:

    // child.vue
    export default {
      watcher: {
        collapsed(collapsed) {
          if (collapsed) {
            this.inputs.forEach(input => input.visible = false)
          }
        },
      }
    }
    
  4. To ensure each element's ID is globally unique in the context of the parents, incorporate the parent ID into the child IDs:

    <!-- child.vue -->
    <div v-for="(element, index) in inputs" :key="element.id">
      <b-button v-b-toggle="'Element' + idParent + '.' + element.id" ⋯>
        ⋯                                   👆
      </b-button>
      <b-collapse :id="'Element' + idParent + '.' + element.id" ⋯>
        ⋯                             👆
      </b-collapse>
    </div>
    
  5. In the parent's addNewItem(), add a collapsed property. We'll bind each child's collapsed prop to this new property, and toggle it upon a button-click:

    <!-- parent.vue -->
    <script>
    export default {
      data() {
        return {
          inputs: [
            {
              id: 1,
              collapsed: false,
            },  👆
          ],
        }
      },
      methods: {
        addNewItem() {
          this.inputs.push({ id: ++this.id, collapsed: false })
        }                                      👆
      }
    }
    </script>
    
    <template>
      â‹®
      <div v-for="(item, index) in inputs" :key="item.id">
        <b-button @click="item.collapsed = !item.collapsed">
          ⋯                       👆
        </b-button>
        <Child :idParent="item.id" :collapsed="item.collapsed" />
      </div>                                          👆
    </template>
    

demo

Upvotes: 6

Related Questions