skip to Main Content

I’m trying to create an accordion using vuejs.

I found some examples online, but what I want is different. For SEO purpose I use “is” and “inline-template”, so the accordion is kind of static not fully created in Vuejs.

I have 2 problems/questions:

1) I need to add a class “is-active” on the component based on user interaction(clicks), because of this I receive the following error.

Property or method “contentVisible” is not defined on the instance but
referenced during render. Make sure to declare reactive data
properties in the data option.

This probable because I need to set it at instance level. But “contentVisible” have a value (true or false) different for each component.

So I thought using at instance level an array of “contentVisible” and a props (pass thru instance) and custom events on child to update the instance values.

2) Could work but it is a static array. How can I make a dynamic array (not knowing the number of item components) ?

<div class="accordion">
    <div>
        <div class="accordion-item" is="item"  inline-template :class="{ 'is-active':  contentVisible}" >
            <div>
                <a @click="toggle" class="accordion-title"> Title A1</a>
                <div v-show="contentVisible" class="accordion-content">albatros</div>
            </div>
        </div>
        <div class="accordion-item" is="item"  inline-template :class="{ 'is-active': contentVisible}" >
            <div>
                <a @click="toggle" class="accordion-title"> Title A2</a>
                <div v-show="contentVisible" class="accordion-content">lorem ipsum</div>
            </div>
        </div>

    </div>

var item = {
  data: function() {
      return {
          contentVisible: true
      }
  },

  methods: {
      toggle: function(){
          this.contentVisible = !this.contentVisible
      }
  }
}

new Vue({
    el:'.accordion',
    components: {
        'item': item
    }
})

Update
I create the following code but the custom event to send the modification from component to instance is not working, tabsactive is not changing

var item = {
  props: ['active'],
  data: function() {
      return {
          contentVisible: false
      }
  },
  methods: {
      toggle: function(index){
          this.contentVisible = !this.contentVisible;
          this.active[index] = this.contentVisible;
          **this.$emit('tabisactive', this.active);**
          console.log(this.active);
      }
  }
}

new Vue({
    el:'.accordion',
    data: {
      tabsactive: [false, false]
    },
    components: {
        'item': item
    }
})

<div class="accordion" **@tabisactive="tabsactive = $event"**>
        <div class="accordion-item" is="item"  inline-template :active="tabsactive" :class="{'is-active': tabsactive[0]}">
            <div>
                <a @click="toggle(0)" class="accordion-title"> Title A1</a>
                <div v-show="contentVisible" class="accordion-content">albatros</div>
            </div>
        </div>
        <div class="accordion-item" is="item"  inline-template :active="tabsactive" :class="{'is-active': tabsactive[1]}">
            <div>
                <a @click="toggle(1)" class="accordion-title" > Title A2</a>
                <div v-show="contentVisible" class="accordion-content">lorem ipsum</div>
            </div>
        </div>
</div>

3

Answers


  1. On point 1:

    You have to define contentVisible as a vue instance variable, as you have accessed it with vue directive v-show, it searches this in vue data, watchers, methods, etc, and if it does not find any reference, it throws this error.

    As your accordion element is associated with the parent component, you may have to add contentVisible data there, like following:

    new Vue({
        el:'.accordion',
        data: {
           contentVisible: true
        }
        components: {
            'item': item
        }
    })
    

    If you have multiple items, you may use some other technique to show one of them, like have a data variable visibleItemIndex which can change from 1 to n-1, where n is number of items.

    In that case, you will have v-show="visibleItemIndex == currentIndex" in the HTML.

    You can as well have hash for saving which index are to de displayed and which to be collapsed.

    On point 2:

    You can use v-for if you have dynamic arrays. you can see the documentation here.

    Login or Signup to reply.
  2. I’m having a real hard time understanding what exactly it is you want or why you would want it, but I think this does it?

    Vue.component('accordion-item', {
      template: '#accordion-item',
      methods: {
        toggle() {
          if(this.contentVisible){
          	return
          }
          if(this.$parent.activeTab.length >= 2){
          	this.$parent.activeTab.shift()
          }
          this.$parent.activeTab.push(this)
        }
      },
      computed: {
        contentVisible() {
          return this.$parent.activeTab.some(c => c === this)
        }
      }
    })
    
    const Accordion = Vue.extend({
      data() {
        return {
          activeTab: []
        }
      },
      methods: {
        handleToggle($event) {
          this.activeTab = []
        }
      }
    })
    
    document.querySelectorAll('.accordion').forEach(el => new Accordion().$mount(el))
    <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
    
    <template id="accordion-item">
      <div class="accordion-item" :class="{ 'is-active':  contentVisible}">
          <a href="#" @click="toggle" class="accordion-title"><slot name="title"></slot></a>
          <div v-show="contentVisible" class="accordion-content" @click="$emit('toggle', $event)">
            <slot name="content"></slot>
          </div>
      </div>
    </template>
    
      <div class="accordion">
        <accordion-item @toggle="handleToggle">
          <p slot="title">a title</p>
          <p slot="content">there are words here</p>
        </accordion-item>
        <accordion-item @toggle="handleToggle">
          <p slot="title">titles are for clicking</p>
          <p slot="content">you can also click on the words</p>
        </accordion-item>
        <accordion-item @toggle="handleToggle">
          <p slot="title">and another</p>
          <p slot="content">only two open at a time!</p>
        </accordion-item>
        <accordion-item @toggle="handleToggle">
          <p slot="title">and #4</p>
          <p slot="content">amazing</p>
        </accordion-item>
      </div>
    Login or Signup to reply.
  3. This works for me:

    <template>
        <div>
            <ul>
                <li v-for="index in list" :key="index._id">
    
                    <button @click="contentVisible === index._id ? contentVisible = false : contentVisible = index._id">{{ index.title }}</button>
    
                    <p v-if='contentVisible === index._id'>{{ index.item }}</p>
    
                </li>
            </ul>
        </div>
    </template>
    
    <script>
        export default {
            name: "sameName",
            data() {
                return {
                    contentVisible: false,
                    list: [
                        {
                        _id: id1,
                        title: title1,
                        item: item1
                        },
                        {
                        _id: id2,
                        title: title2,
                        item: item2
                        }
                    ]
                };
            },
        };
    </script>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search