2

I am using bootstrap 4.3.1 and [email protected]

I have this menu (is using collapse - and I don`t want to use JQuery):

 <li class="nav-item">
     <a class="nav-link" href="#sidebar-products" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="sidebar-products">
         <i class="ni ni-single-copy-04 text-primary"></i>
         <span class="nav-link-text">Products</span>
     </a>
     <div class="collapse" id="sidebar-products">
         <ul class="nav nav-sm flex-column">
             <li class="nav-item">
                 <a href="#" class="nav-link">Item List 1</a>
             </li>
             <li class="nav-item">
                 <a href="#" class="nav-link">Item List 2</a>
             </li>
         </ul>
     </div>
 </li>

This is only a single block that contains 2 sub-items.

What I saw using JQuery, when click on "Products" the #sidebar-products receives the .show class and aria-expanded="true".

When having multiple blocks - when click on a block to close (if there are collapsed) the others blocks.

How can I make it work the collapse with vue?

UPDATE 1

I created a click event that do the job:

<a class="nav-link" href="javascript:void(0)" @click="navItemCollapse('sidebar-products', $event)" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="sidebar-products">

and the event:

 navItemCollapse(id, event) {
     let expanded = event.target.getAttribute('aria-expanded').toLocaleLowerCase() == 'true' ? true : false;
     let el = document.getElementById(id);
     expanded ? el.classList.remove('show') : el.classList.add('show');
                event.target.setAttribute('aria-expanded', !expanded);
 }

But what if I have more blocks ? When click to open the current collapse on a block to close the others ???

4
  • Do you have the entire component code? what toggles the collapse? Commented Nov 8, 2019 at 12:54
  • I have only the html code .... what toggles the collapse? click on "Products" toggle the collapse (Item List 1, Item List 2).... Commented Nov 8, 2019 at 12:57
  • You can try taking a look at bootstrap-vue which is an implementation of Bootstrap for Vue that doesn't require jQuery Commented Nov 8, 2019 at 13:04
  • I forgot to mention in the post....I don't want to use the bootstrap-vue :) .... I want the project to keep it simple as possible Commented Nov 8, 2019 at 13:11

4 Answers 4

4

This is the implementation of no jquery

new Vue({
  el: '#app',
  data() {
    return {
      menuList: [{
          name: 'Products',
          expand: false,
          items: [{
              name: 'Item List 1',
              link: ''
            },
            {
              name: 'Item List 2',
              link: ''
            }
          ]
        },
        {
          name: 'Others',
          expand: false,
          items: [{
              name: 'Other Item 1',
              link: ''
            },
            {
              name: 'Other Item 2',
              link: ''
            }
          ]
        }
      ]
    }
  },
  methods: {
    navItemCollapse(index) {
      this.menuList = this.menuList.map((item, i) => {
        item.expand = !item.expand
        if (i !== index) {
          item.expand = false
        }
        return item
      })
    }
  }
})
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>

<ul id="app">
  <li v-for="(navItem,i) in menuList" :key="i" class="nav-item">
    <a class="nav-link" href="javascript:;" data-toggle="collapse" role="button" :aria-expanded="navItem.expand" aria-controls="sidebar-products" @click.prevent="navItemCollapse(i)">
      <i class="ni ni-single-copy-04 text-primary"></i>
      <span class="nav-link-text">{{navItem.name}}</span>
    </a>
    <div v-if="navItem.items.length>0" class="collapse" :class="{show: navItem.expand}">
      <ul class="nav nav-sm flex-column">
        <li v-for="(subItem,j) in navItem.items" :key="j" class="nav-item">
          <a href="#" class="nav-link">{{subItem.name}}</a>
        </li>
      </ul>
    </div>
  </li>
</ul>

I integrate the menu data into an array of objects. Each object has an expand flag to determine whether it is currently expanded. When you click on the menu, switch the expand flag of the current menu.

Note: You don't need to care about the id of the <a> tag.

Sign up to request clarification or add additional context in comments.

Comments

1

No jQuery or bootstrap-vue ...

Create a function in the Component to handle the normal Bootstrap class and timing logic...

  data() {
    return {
      classArr: ['collapse'],
      styleObj: {}
    };
  },
  methods: {
    toggleCollapse(ref) {
        let show = this.classArr.indexOf('show')>-1?false:'show'
        this.classArr = ['collapsing']
        setTimeout(() => {
            if (show){
                let height = this.$refs[ref].firstChild.clientHeight + 'px';
                this.styleObj = { height }
            }
            else {
                this.styleObj = {}  
            }
        }, 10)
        setTimeout(() => {
            this.classArr = ['collapse', show]
        }, 340)
    }
  }

In the component template, bind the class and style attrs to the data manipulated by the method. The ref of the specific collapse is passed in to the method...

  <li class="nav-item">
        <a class="nav-link" href="#sidebar-products" role="button" @click="toggleCollapse('sidebar')">
            <i class="ni ni-single-copy-04 text-primary"></i>
            <span class="nav-link-text">Products</span>
        </a>
        <div :class="classArr" :style="styleObj" id="sidebar-products" ref="sidebar">
            <ul class="nav nav-sm flex-column">
                <li class="nav-item">
                    <a href="#" class="nav-link">Item List 1</a>
                </li>
                <li class="nav-item">
                    <a href="#" class="nav-link">Item List 2</a>
                </li>
            </ul>
        </div>
  </li>

https://www.codeply.com/p/GA5CaNMzmc

EDIT: I updated the demo to make it scaleable for multiple collapses

Comments

1

This is a fully working version using bootstrap-vue:

 <div class="accordion" role="tablist">
            <b-card v-for="(value, key) in this.jobs" :key="key" no-body class="mb-1">
              <b-card-header header-tag="header" class="p-1" role="tab">
                <b-button block v-b-toggle="'accordion-'+key" variant="primary">{{ value.title }}</b-button>
              </b-card-header>
              <b-collapse :id="'accordion-'+key.toString()" accordion="my-accordion" role="tabpanel">
                <b-card-body>
                  <b-card-text>{{ value.specs }}</b-card-text>
                </b-card-body>
              </b-collapse>
            </b-card>
          </div>

Data object:

data() {
    return {
     jobs: [
        {
          title: 'Design artist',
          specs: 'Have an eye for web beauty'
        },
        {
          title: 'Backend guru',
          specs: 'Do stuff that don\'t break'
        },
        {
          title: 'Frontend master',
          specs: 'Create an UI that works'
        }
      ]
}
}

Comments

0

I like the @sugars approach :)

So...the final version is this:

<li v-for="(navItem, i) in sidenavItems" class="nav-item">
                            <router-link v-if="!navItem.isCollapsible" class="nav-link" @click.native="navItemCollapse(i)" active-class="active" :to="{name: navItem.route}" exact>
                                <i :class="navItem.class"></i>
                                <span class="nav-link-text">{{ navItem.name }}</span>
                            </router-link>

                            <a v-if="navItem.isCollapsible" class="nav-link" href="javascript:void(0)" @click="navItemCollapse(i)" data-toggle="collapse" :aria-expanded="navItem.expanded">
                                <i :class="navItem.class"></i>
                                <span class="nav-link-text">{{ navItem.name }}</span>
                            </a>
                            <div v-if="navItem.isCollapsible" class="collapse" :class="navItem.expanded ? 'show' : ''">
                                <ul class="nav nav-sm flex-column">
                                    <li v-for="subItem in navItem.items" class="nav-item">
                                        <router-link class="nav-link" :to="{name: subItem.route}">{{ subItem.name }}</router-link>
                                    </li>
                                </ul>
                            </div>
                        </li>

the sidenavItems:

 sidenavItems: [
                    {name: 'Dashboard', isCollapsible: false, route: 'dashboard', class: 'class1'},
                    {name: 'Categories', isCollapsible: false, route: 'category', class: 'class2'},
                    {name: 'Brands', isCollapsible: false, route: 'brand', class: 'class3'},
                    {name: 'Products', isCollapsible: true, expanded: false, class: 'class4', items: [{name: 'List', route: 'product'}]},
                    {name: 'Orders', isCollapsible: false, route: 'order', class: 'class5'},
                    {name: 'Blog', isCollapsible: true, expanded: false, class: 'class6', items: [{name: 'List', route: ''}]},
                ],

and the navItemCollapse method:

 navItemCollapse(index) {
     this.sidenavItems = this.sidenavItems.map( (item, i) => {
                item.expanded = !item.expanded;
                if(i !== index) {
                    item.expanded = false;
                }
                return item;
            })
        }

1 Comment

If it helps you, I hope to adopt my answer. Have a nice day :)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.