简体   繁体   中英

Open only one accordion panel vue.js / bootstrap vue

Building out a bootstrap-vue accordion and everything is working as expected however, i just added expand all / collapse all buttons which also function as I expected but now my issue is when I click on any of the accordion panels directly they ALL open. Is there a way to have them only open the clicked panel?

html:

   <div class="container container-accordion-one">
      <!--expand all / collapse all row -->
      <div class="row row-expand-collapse">
        <div class="offset-md-1 col-expand-collapse">
          <ul class="list-expand-collapse">
            <li><a href="#/" @click="showCollapse = true" class="font__card-body">Expand All</a></li>
            <li><a href="#/" @click="showCollapse = false" class="font__card-body">Collapse All</a></li>
          </ul>
        </div>
      </div>
      <!--end: expand all / collapse all row -->
      <div class="row">
        <div class="offset-md-1 accordion-style-one">
          <div role="tablist">
            <b-card no-body class="">
              <b-card-header href="#" v-b-toggle.accordion-1 header-tag="header" class="accordion-header" role="tab">
                <p class="font__accordion-header">Accordion 1</p>
                <i class="fal fa-plus accordionClosed" />
                <i class="fal fa-minus accordionOpen" />
              </b-card-header>
              <b-collapse id="accordion-1" v-model="showCollapse" role="tabpanel">
                <b-card-body>
                  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto distinctio enim fugit, inventore odio odit perferendis quas quo veritatis voluptate.</p>
                </b-card-body>
              </b-collapse>
            </b-card>

            <b-card no-body class="">
              <b-card-header href="#" v-b-toggle.accordion-2 header-tag="header" class="accordion-header" role="tab">
                <p class=" font__accordion-header">Accordion 2</p>
                <i class="fal fa-plus accordionClosed" />
                <i class="fal fa-minus accordionOpen" />
              </b-card-header>
              <b-collapse id="accordion-2" v-model="showCollapse" role="tabpanel">
                <b-card-body>
                  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto distinctio enim fugit, inventore odio odit perferendis quas quo veritatis voluptate.</p>
                </b-card-body>
              </b-collapse>
            </b-card>

            <b-card no-body class="">
              <b-card-header href="#" v-b-toggle.accordion-3 header-tag="header" class="accordion-header" role="tab">
                <p class=" font__accordion-header">Accordion 3</p>
                <i class="fal fa-plus accordionClosed" />
                <i class="fal fa-minus accordionOpen" />
              </b-card-header>
              <b-collapse id="accordion-3" v-model="showCollapse" role="tabpanel">
                <b-card-body>
                  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto distinctio enim fugit, inventore odio odit perferendis quas quo veritatis voluptate.</p>
                </b-card-body>
              </b-collapse>
            </b-card>
          </div>
        </div>
      </div>
    </div>

js:

export default {
  name: 'm',
  components: {
   LinksTo
  },
  data() {
    return {
      showCollapse: false
    };
  }
};

Since you want each collapse to be able to be individually opened or closed, you need to have a v-model variable for each collapse. In the following I am using an array to store the collapse states:

<template>
  <div>
    <b-button @click="expandAll">Expand all</b-button>
    <b-button @click="collapseAll">Collapse all</b-button>

    <b-button block v-b-toggle.accordion-1 class="mt-2">Accordion 1</b-button>
    <b-collapse id="accordion-1" v-model="collapseStates[0]">
      <div>Lorem ipsum dolor sit amet.</div>
    </b-collapse>

    <b-button block v-b-toggle.accordion-2 class="mt-2">Accordion 2</b-button>
    <b-collapse id="accordion-2" v-model="collapseStates[1]">
      <div>Lorem ipsum dolor sit amet.</div>
    </b-collapse>

    <b-button block v-b-toggle.accordion-3 class="mt-2">Accordion 3</b-button>
    <b-collapse id="accordion-3" v-model="collapseStates[2]">
      <div>Lorem ipsum dolor sit amet.</div>
    </b-collapse>
  </div>
</template>

<script>
export default {
  data() {
    return {
      collapseStates: [false, false, false]
    }
  },
  methods: {
    expandAll() {
      this.collapseStates = this.collapseStates.map(x => true)
    },
    collapseAll() {
      this.collapseStates = this.collapseStates.map(x => false)
    }
  }
}
</script>

See a working fiddle at: https://jsfiddle.net/p60zktLs/

You have to declare a separate showCollapse flags for each tab. And in shwo/collapse all turn on/off all of them.

EDIT:

Using DRY principals you are better breaking the accordion content out into data and only writing the markup once, then looping through your data. You can then track the open/close state on each accordion.

In your code, every accordion is modeled on the same variable, so they can only be all open or all closed. You need to have a separate state for each accordion.

Example: (simplified so it's easier to read, but you can do the same with bootstrap)

 new Vue({ el: "#app", data: { allClosed: true, items: [{ title: 'title 1', description: 'description one', open: false }, { title: 'title 2', description: 'description two', open: false }, { title: 'title 3', description: 'description three', open: false } ], }, methods: { openCloseAll() { this.allClosed = !this.allClosed if (this.allClosed) this.items.map(x => x.open = false) else this.items.map(x => x.open = true) } } })
 .accordian { margin: 8px 0; cursor: pointer; background: pink; padding: 0.25em; }
 <div id="app"> <div> <button @click="openCloseAll()"> <template v-if="allClosed">Open</template> <template v-else>Close</template> All </button> </div> <div v-for="(item, i) in items" class="accordian" @click="item.open = !item.open"> <span>{{ item.title }}</span> <br /> <span v-if="item.open">{{ item.description }}</span> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script>

Previous suggestion:

This is one way you could achieve such functionality:

 new Vue({ el: "#app", data: { items: ['one', 'two', 'three', 'four', 'five'], openItems: [], openAll: true }, methods: { toggle(i) { const index = this.openItems.findIndex(x => x === i) if (index !== -1) this.openItems.splice(index, 1) else this.openItems.push(i) }, openCloseAll() { if (this.openAll) this.items.forEach((x, i) => this.openItems.push(i)) else this.openItems = [] this.openAll = !this.openAll } } })
 .accordian { margin: 8px 0; cursor: pointer; background: pink; padding: 0.25em; }
 <div id="app"> <div> <button @click="openCloseAll()"> <template v-if="openAll">Open</template> <template v-else>Close</template> All </button> </div> <div v-for="(item, i) in items" class="accordian" @click="toggle(i)"> {{ item }} <span v-if="openItems.includes(i)">opened</span> <span v-else>closed</span> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM