Reputation: 981
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 ???
Upvotes: 2
Views: 7989
Reputation: 31
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'
}
]
}
}
Upvotes: 1
Reputation: 981
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;
})
}
Upvotes: 0
Reputation: 362410
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
Upvotes: 1
Reputation: 1493
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.
Upvotes: 4