toggle active class on list menu items - vue js - javascript

I want that, clicking on a menu item, the active class is triggered only for that specific item and removed for the others, until now I wrote this:
<template>
<nav class="navbar">
<div class="navbar__brand">
<router-link to="/">Stock Trader</router-link>
</div>
<div class="navbar__menu">
<ul class="navbar__menu--list">
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
<router-link to="/portfolio">Portfolio</router-link>
</li>
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
<router-link to="/stocks">Stocks</router-link>
</li>
</ul>
</div>
<div class="navbar__menu--second">
<ul class="navbar__menu--list">
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
End Day
</li>
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
Save / Load
</li>
</ul>
</div>
</nav>
</template>
<script>
export default {
data(){
return{
isActive: false
}
}
}
</script>
now of course, when I click on one item the active class is inserted/removed for all the items, what is the best solution for making that only a specific item, on clicking on it, receives the active class?

You'll want some sort of identifier for each clickable item and set that to your data property. For example
data() {
return { active: null }
}
and in your list items (for example)
<li #click="active = 'portfolio'"
class="navbar__menu--item"
:class="{active:active === 'portfolio'}">
In this example, the identifier is "portfolio" but this could be anything, as long as you use a unique value per item.

You could keep an object of links you have and handle a click on each of items. E.g.
data() {
return {
links: [
{
title : 'Portfolio',
to : '/portfolio',
isActive : false,
location : 'first',
},
{
title : 'Stocks',
to : '/stocks',
isActive : false,
location : 'first',
},
{
title : 'End Day',
to : '#',
isActive : false,
location : 'second',
},
{
title : 'Save / Load',
to : '#',
isActive : false,
location : 'second',
},
]
};
},
methods: {
handleNavClick(item) {
this.links.forEach(el, () => {
el.isActive = false;
});
item.isActive = true;
}
},

I do this in vue3.
the template is:
<li
v-for="(title, index) in titles"
class="title"
:key="index"
:class="{ active: active === index }"
#click="updateActive(index)"
>
{{ title }}
</li>
and the script is
<script lang="ts" setup>
import { ref } from "vue"
const titles = ["title1","title2","title3"]
const active = ref(-1)
function updateActive(val: number) {
active.value = val
}
</script>

If you have several ul => use title instead of index. For example :
<ul>
<div>
Applicants
</div>
<li
v-for="(title, index) in applicantMenuTitles"
:key="index"
:class="{ active: active === title }"
#click="updateActive(title)"
>
{{ title }}
<div
v-if=" active === title "
class="cursor"
/>
</li>
</ul>
<ul>
<div>
Offices
</div>
<li
v-for="(title, index) in officeMenuTitles"
:key="index"
:class="{ active: active === title }"
#click="updateActive(title)"
>
{{ title }}
<div
v-if=" active === title "
class="cursor"
/>
</li>
</ul>
And on script :
...
const active = ref('')
function updateActive(title: string) {
active.value = title
}

Related

Vue class binding attribute and multiple tailwind classes

I'm trying to bind an attribute and multiple tailwind classes to an html element. This is for a tab menu, so the idea is that for the "active" tab I take the title dynamically and inject also some tailwind classes to change the look and feel of the "active" tabs.
<li
:class="{
selected: title === selectedTitle,
selected ? ['border-b-2', 'border-blue-700' ]
}"
#click.stop.prevent="selectedTitle = title"
v-for="title in tabTitles"
:key="title"
role="presentation"
>
At the moment the previous code is not working for me.
From my point of view following line can't work:
selected ? ['border-b-2', 'border-blue-700' ]
This is a short if else without an else case - I think you ment writing something like this:
<li :class="{
'selected border-b-2 border-blue-700': title === selectedTitle,
}">
Try like following:
new Vue({
el: "#demo",
data() {
return {
tabTitles: ['aaa', 'bbb', 'ccc'],
selectedTitle: ''
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" integrity="sha512-wnea99uKIC3TJF7v4eKk4Y+lMz2Mklv18+r4na2Gn1abDRPPOeef95xTzdwGD9e6zXJBteMIhZ1+68QC5byJZw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<div id="demo">
<li :class=" selectedTitle === title ? 'border-b-2 border-blue-700' : '' "
#click.stop.prevent="selectedTitle = title"
v-for="title in tabTitles"
:key="title"
role="presentation"
>{{title}}</li>
</div>

VueJS How to show/hide closest hidden element in list of items

How can I show/hide the closest div by clicking a button in vue?
lets say I have a list of items, each with some hidden details
<ul>
<li v-for="item in items" :key="item.id">
<div>
<p>{{item.text}}</p>
<button #click="showDetails(item)">Show details</div>
<div class="details" :class="isVisible ? activeClass : 'hidden'">Some hidden details</div>
</div>
</li>
</ul>
Then I do
data() {
return {
items: [ // a bunch of item objects here]
isVisible: false,
activeClass: 'is-visible'
}
},
methods: {
showDetails(item) {
this.isVisible = item;
}
}
Right now, when I click on on of the "showDetails" buttons, all divs with class .details opens and get the .is-visible-class, but I just want the closest div to the item to be displayed. For some reason I think this is pretty simple, but I can't make it work.
How can I achieve that?
try this
<template>
<ul>
<li v-for="(item, i) in items" :key="item.id">
<div>
<p>{{item.text}}</p>
<button #click="showDetails(i)">Show details</button>
<div class="details" :class="i == active ? activeClass : 'hidden'">Some hidden details</div>
</div>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
items: [],
activeClass: 'is-visible',
active: null
};
},
methods: {
showDetails(i) {
this.active = i;
}
}
};
</script>
It would be clearer to create a new component for list item which would contain all logic itself. Something like:
// ListItem.vue
<template>
<div>
<p>{{text}}</p>
<button #click="toggleVisibility">Show details</button>
<div class="details" v-show="isVisible">Some hidden details</div>
</div>
</template>
<script>
props: {
text: String
},
data() {
return {
isVisible: false
}
},
methods: {
toggleVisibility() {
this.isVisible = !this.isVisible
}
}
</script>
and in your parent component:
<ul>
<li v-for="item in items" :text="item.text" :key="item.id" is="list-item" /></li>
</ul>
data() {
return {
items: [ // a bunch of item objects here]
}
}
Just store "isVisible" variable inside the "item"
<ul>
<li v-for="item in items" :key="item.id">
<div>
<p>{{item.text}}</p>
<button #click="showDetails(item)">Show details</div>
<div class="details" :class="item.isVisible ? activeClass : 'hidden'">Some hidden details</div>
</div>
</li>
</ul>
data() {
return {
items: [ // a bunch of item objects here]
isVisible: false,
activeClass: 'is-visible'
}
},
methods: {
showDetails(item) {
item.isVisible = !item.isVisible;
this.$forceUpdate();
}
}

Run functions with checkbox interactions

I am trying to create checkboxes that will add an object to an array when checked, but will remove that object when unchecked. I am not sure if this is the correct way, but will show what I have below. Using Angular 2 by the way.
Original Code:
<div>
<ul>
<a *ngFor="let perkResult of perkList.results" (click)="onAddPerk(perkResult)">
<li>{{ perkResult.perk }}</li>
</a>
</ul>
</div>
<div *ngFor="let perk of company.perks;>
{{ perk.perk }}
<a (click)="onDeletePerk(i)"></a>
</div>
Functions:
onAddPerk(perkResult) {
// Adds a new perk to the array
this.company.perks.push({perk: (perkResult.perk)});
}
onDeletePerk(i: number) {
// Delete the perk in the selected index
this.company.perks.splice(i, 1);
}
And I want to do something like this:
<div *ngFor="let benefitResult of benefitList.results" >
<a (click)="onAddBenefit(benefitResult)">
<input type="checkbox" />
//Basically if checked then run add function, if not checked then run delete function
</a> {{ benefitResult.benefit }}
</div>
EDIT:
Got to this point, but cannot reference my other scope for the delete.
<ul *ngIf="benefitList">
<div *ngFor="let benefitResult of benefitList.results; let i = index" >
<input type="checkbox" (change)="updateBenefits($event, benefitResult, i)" >
<li>
{{ benefitResult.benefit }}
</li>
</div>
</ul>
//Original delete
<div *ngFor="let benefit of company.benefits; trackBy: customTrackBy; let i = index">
{{benefit.benefit}}
<a (click)="onDeleteBenefit(i)"></a>
</div>
//function
updateBenefits(event, benefitResult, i) {
if(event.srcElement.checked) {
this.company.benefits.push({benefit: (benefitResult.benefit)});
} else {
this.company.benefits.splice(i, 1);
}
}
You could listen to the change event on the checkboxes. Then pass the event along with the object and index to a method that will look at the status of the checkbox element.
In the component:
optionsList = {
results: [{
perk: 'free coffee',
checked: false
}, {
perk: 'car',
checked: false
}, {
perk: 'pastry',
checked: false
}, {
perk: 'free post-it notes',
checked: false
}]
};
updatePerks(event, perkResult) {
perkResult.checked = event.srcElement.checked;
}
In your HTML template:
<ul>
<li *ngFor="let listItem of optionsList.results">
<label><input type="checkbox" (change)="updatePerks($event, listItem)">
{{ listItem.perk }}</label>
</li>
</ul>
Note: I don't know your data structure, so make the necessary adjustment or post a data sample if you need more help.

How to set active menu option with Vue

I have a two column layout. The left column has a stacked menu, and when the user selects an option the content displays in the right. What's the best way to set the active class on the selected menu link?
I have come up with a solution below, but it seems kind of verbose. Each menu item has a class assignment which is conditional upon data.activeTab. A click event handler updates the value of data.activeTab.
<template>
<div>
<div class="row">
<div class="col-md-3">
<ul class="nav nav-pills nav-stacked">
<li role="presentation" :class="{active : activeTab == 'option1'}">
<a :href="'/#/sales/' + uuid + '/option1'" #click="updateMenu">Option 1</a>
</li>
<li role="presentation" :class="{active : activeTab == 'option2'}">
<a :href="'/#/sales/' + uuid + '/option2'" #click="updateMenu">Option 2</a>
</li>
</ul>
</div>
<div class="col-md-9">
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
export default {
data : function () {
return {
activeTab : 'option1'
}
},
props : [
'uuid'
],
methods : {
updateMenu : function (event) {
var str = event.target.toString();
this.activeTab = str.substr(str.lastIndexOf('/') + 1);
}
}
}
</script>
Use router-link instead of your manual anchor tags and set the linkActiveClass in your router construction options.
I can't see your routes object, so this is a best guess based on what you have above. Refer to the router link documentation I linked above to determine the best way to set up your to attributes.
<router-link tag="li" :to="{ path: 'sales', params: { uuid : uuid, option: "option1" }}">
<a>Option 1</a>
</router-link>
<router-link tag="li" :to="{ path: 'sales', params: { uuid : uuid, option: "option2" }}>
<a>Option 2</a>
</router-link>
Then, wherever you initialized your router,
new VueRouter({
routes,
linkActiveClass: "active"
});

Vuejs - Accordion

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>
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>
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.
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}">
<slot name="title"></slot>
<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>

Categories

Resources