Home > other >  Custom Accordion Toggle one Item at a Time in Vue
Custom Accordion Toggle one Item at a Time in Vue

Time:04-29

I am using Vue with Nuxt 2 and having Problem in my Custom Accordion component.

i want to toggle one accordion at a time. But what actually happening is that if i click on first accordion Link, the body of first Link(accordion) is opening. And without closing the first one, if i open on second link(accordion), the second accordion body also opening. Which i want to avoid. If i wanna open other accordion, the opened ones should first close. So how do i achieve this in vue way, without using any library.

Accordion Toggling Process: I am using ACTIVE value to toggle the accordion by adding aria-expended = "true" on my anchor Tag whenever it get clicked. At the same time i am showing or hiding the accordion-item-body based on ACTIVE value.

My component Accordion:

<template>
  <div id="accordion" ref="accordion" >
    <slot />
  </div>
</template>

<script lang="ts">
import Vue from "vue"
import { props } from "./props"

export default Vue.extend({
  name: "Accordion",
  props,
  created() {
    this.$nuxt.$off("eventName")
    this.$nuxt.$on("eventName", ($event: any) => this.toggleActive($event))
  },
  methods: {
    toggleActive(e: any) {
      console.log(e)
    },
  },
})
</script>

My AccordionItem component:

<template>
  <div  role="tab">
    <div >
      <a
        :aria-expanded="active"
        @click.prevent=";(active = !active), $nuxt.$emit('eventName', active)"
      >
        {{ title }}

        <span
          
        >
          expand_more
        </span>
      </a>
    </div>
    <transition name="accordion">
      <div v-show="active"  role="tabpanel">
        <slot />
      </div>
    </transition>
  </div>
</template>

<script lang="ts">
import Vue from "vue"
export default Vue.extend({
  name: "AccordionItem",
  props: {
    title: {
      type: String,
      required: true,
    },
    id: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      active: false,
    }
  },
})
</script>

Usage of Accordion and AccordionItem in Parent:

<Accordion :items="items">
  <AccordionItem
    v-for="item in items"
    :id="item.id"
    :key="item.id"
    :title="item.title"
  >
    <div v-html="item.description"></div>
  </AccordionItem>
</Accordion>

CodePudding user response:

Here is a simple yet working solution (I didn't implemented the styling of course, but the functionality is working fine).

For the wrapper

<template>
  <div>
    <accordion
      v-for="(item, index) in list"
      :key="item.id"
      :item="item"
      :active-index="currentlyActiveIndex"
      :item-index="index"
      @update:itemIndex="currentlyActiveIndex = $event"
    ></accordion>
  </div>
</template>

<script>
export default {
  name: 'AccordionList',
  data() {
    return {
      list: [
        { id: 1, name: 'hello' },
        { id: 2, name: 'world' },
        { id: 3, name: 'swag' },
      ],
      currentlyActiveIndex: null,
    }
  },
}
</script>

Accordion.vue

<template>
  <div>
    <button @click="updateIndex">get this index: {{ itemIndex }}</button>
    <span v-show="activeIndex === itemIndex">{{ item.name }}</span>
  </div>
</template>

<script>
export default {
  name: 'Accordion',
  props: {
    item: {
      type: Object,
      default: () => {},
    },
    activeIndex: {
      type: Number,
      default: null,
    },
    itemIndex: {
      type: Number,
      default: null,
    },
  },
  methods: {
    updateIndex() {
      console.log('index updated:', this.itemIndex)
      this.$emit('update:itemIndex', this.itemIndex)
    },
  },
}
</script>

This could also be achieved with something like { id: 1, name: 'hello', isActive: false }, a bit cleaner but both methods are valid here.

PS: I don't know how to use TS neither, but I'm sure you can figure this part yourself (not the blocker here I guess).

  • Related