Vuejs multiple active buttons - vue.js

I'm trying to create a list where every list item contains a button and i want a user to be able to click multiple button. I'm generating my list like so:
<ul>
<li v-for="item in items" :key="item.id">
<button type="button">{{item.title}}</button>
</li>
</ul>
but the problem with my code is whenever a user click a button, it turns the rest of the buttons to "unclicked". been trying to play with the focus and active stats but even with just css i cant get to enable multiple select .
i did manage to change the look of the current selected button:
button:focus {
outline: none;
background-color: #6acddf;
color: #fff;
}
any idea how can i allow multiple buttons to be clicked?
to make things a bit clearer, i am going to create an AJAX call later and pass the item.id of each item where it's button is clicked

I would much rather avoid changing the data structure if possible
Well you have to store somewhere that you clicked on the clicked item.
If you can't edit the items array then you can always create a new one, like isClicked where you store those values.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'baz'
}
],
isClicked: []
},
beforeMount() {
// set all values to false
this.items.forEach((item, index) => this.$set(this.isClicked, index, false))
},
methods: {
clicked(index) {
// toggle the active class
this.$set(this.isClicked, index, !this.isClicked[index])
}
}
})
.active {
background: red
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="(item, index) in items" :key="index">
<button #click="clicked(index)" :class="{'active': isClicked[index]}">{{item.title}}</button>
</div>
</div>
Or you can use vuex for storing those values.
However you can just use Vues event to manipulate the classList property, like:
new Vue({
el: '#app',
data: {
items: [1, 2, 3, 4, 5, 6, 7]
}
})
.active {
color: red
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<button v-for="i in items" #click="e => e.target.classList.toggle('active')">{{ i }}</button>
</div>
But it doesn't feel right, IMHO.
Also you can use cookies or localStorage to store those states. So it's really up to you.

Use id attribute for list items to make it unique.
<ul>
<li v-for="item in items" :key="item.id" :id="item.id">
<button type="button" #click="doThis">{{item.title}}</button>
</li>
</ul>
new Vue({
el: '#app',
data: {},
methods: {
doThis() {
// Use this to access current object
}
}
});

Related

How to setup multiple dropdowns in vuetify

Someone know ho to setup multiple dropdowns in vuetify
I am new to learning in this framework.
Let me explain
In this situation im need to display all categories, subcategories and parent categories
In finish result im want get something like this
Anyway thanks for help
<ul>
<li>Category
<ul>
<li>SubCategory 1.1
<ul>
<li>
Parent Category
</li>
</ul>
</li>
<li>SubCategory 1.2</li>
</ul>
</li>
(...)
</ul>
You will need to bind each dropdown to a data member.
Add a watch to the data member, and when it changes, repopulate the other dropdowns and the default value.
Setting immediate in the watcher will call the handler when loaded, before the user selects an item, and populates the initial state.
https://codepen.io/Flamenco/pen/xovKLq
new Vue({
el: "#app",
vuetify: new Vuetify(),
data: () => ({
item: "color",
item2: "red",
items: ["color", "number"],
items2: "null"
}),
watch: {
item: {
immediate: true,
handler(value) {
if (value === "color") {
this.items2 = ["red", "blue"];
this.item2 = "red";
} else {
this.items2 = ["1", "2", "3"];
this.item2 = "1";
}
}
}
}
});
<div id="app">
<v-app>
<div style='width:3in'>
<v-select v-model='item' :items='items' label='Select 1'></v-select>
<v-select v-model='item2' :items='items2' label='Select 2'></v-select>
</div>
</v-app>
</div>

How to access a function variable as a data property in Vue

I have this accordion with three arrows. When you click on the arrow it should transform 180deg. I want to have a function that takes the parameter set in the function and tells the data property to change to zero.
I've tried
let vm = this;
vm.$data.arrowOne = 0;
And this[arrow] = 0,
And this.arrow = 0
And it's not working.
Here is my code.
<div class="uk-accordion-ekstra">
<ul uk-accordion="multiple: true">
<li>
<a #click="rotate(arrowOne)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowOne + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
<li>
<a #click="rotate(arrowTwo)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowTwo + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
<li>
<a #click="rotate(arrowThree)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowThree + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
</ul>
</div>
And VUE
new Vue({
el: '.uk-accordion-ekstra',
data: {
arrowOne: 180,
arrowTwo: 180,
arrowThree: 180
},
methods: {
arrow: function(arrow) {
if(this.arrow = 0) {
return this.arrow = 180;
}
}
}
});
I have succesfully managed to do it with a switch. But it aren't as beautiful. And im unsure of what i don't get. So help is appreciated.
You have a structural and a scope problem.
You are referencing this.arrow in a method called arrow, and you pass an argument called arrow - try to differentiate with the names, or you can get messed up
You are repeating stuff that you don't need to. Vue is great for creating components for the smallest of elements - you can use it to make your code shorter, more readable and more effective.
The snippet below makes everything easier:
you have one arrow per component, and if you solve the rotation problem in one place, then it's solved everywhere, and the arrow referenced always connects to the component you edit
you can encapsulate your arrow rotation into one component - no need to "litter" the Vue instance with such small animation
I added #click.prevent that's like preventDefault(), so there won't be a jump to the top when you click an <a></a>
Vue.component('accordion', {
props: ['title', 'text'],
template: '<div><a #click.prevent="rotateArrow()" class="uk-accordion-title" href="#">{{ title }} <i class="fa fa-arrow-circle-o-up" :style="`transform: rotate(${rotation}deg)`"></i></a><div class="uk-accordion-content"><p>{{ text }}</p></div></div>',
data() {
return {
rotation: 0
}
},
methods: {
rotateArrow() {
this.rotation = !this.rotation ? 180 : 0
}
}
})
new Vue({
el: '.uk-accordion-ekstra',
data: {
accordionItems: [{
title: 'Headline 1',
text: 'Text 1'
},
{
title: 'Headline 2',
text: 'Text 2'
},
{
title: 'Headline 3',
text: 'Text 3'
},
]
}
});
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="uk-accordion-ekstra">
<ul uk-accordion="multiple: true">
<li v-for="accordionItem in accordionItems" :key="accordionItem.title">
<accordion :title="accordionItem.title" :text="accordionItem.text"></accordion>
</li>
</ul>
</div>

Array of inputs, with last field always blank for new add

JSBin and Stackoverflow snippet are below.
I am trying to have a list of input components. If all input components are filled with a value (not blank), then there should be a "new blank field" visible at the end for the user to type into. When he types into it, it should make this field apart of the list above it, maintaining focus in it.
However the problem I'm having is, focus maintains in the new field, and never moves into the array. Here is my code:
JSBIN and stackoverflow snippet - https://jsbin.com/cudabicese/1/edit?html,js,output
const app = new Vue({
el: '#app',
data: {
inputs: [
{ id:'foo', value:'foo' },
{ id:'bar', value:'bar' }
]
},
methods: {
addRow(e) {
this.inputs.push({
id: Date.now(),
value: e.target.value
})
},
deleteRow(index) {
this.inputs.splice(index, 1)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="app">
<ul>
<li v-for="(input, index) of inputs">
<input type="text" v-model="input.value">
</li>
<li v-if="inputs.filter(input => !!input.value).length">
<input type="text" #input="addRow">
</li>
</ul>
</div>
I'd recommend you put the input for the list within a computed function vs directly using the data. The examples at https://v2.vuejs.org/v2/examples/ are a good place to start.

a simple Vue.js addition app

Im very new to Vue.js. I’m trying to build a simple app that totals a list of items. But it also, should have the option to ‘disable’ the individual items so that their value would not contribute to the total. (UPDATE: the button should toggle the disabled state)
Visually, the disabled items would simply be greyed-out, (i dont want their amounts showing zero)
I was imagining perhaps, targeting the parent item with a disabled attribute (when the btn is clicked). And then….a check would be made for the existence of this attribute, when the totalling is done (and any item without the attribute (class?) would be skipped)
But I have no idea how this would work in Vue World. Anyone willing to help out please? Or even point me in the right direction?
HTML
<div id="app">
<div id="item1">{{ item1.amt }} <button type="button">exclude me</button></div>
<div id="item2">{{ item2.amt }} <button type="button">exclude me</button></div>
<div id="item3">{{ item3.amt }} <button type="button">exclude me</button></div>
<br>
<div id="total">total: {{ item1.amt + item2.amt + item3.amt }}</div>
</div>
JS
var app = new Vue({
el: "#app",
data: {
item1: {
amt: 10
},
item2: {
amt: 20
},
item3: {
amt: 30
}
}
});
https://codepen.io/dagford/pen/oyKXYP?editors=1010
thank you
Welcome to the vuetiful world of Vue.
If you need the to see the working code, go here
But let me explain you what I actually did
JS
var app = new Vue({
el: "#app",
data: {
items:[
{
id:'item1',
included:'true',
amt: 10
},
{
id:'item2',
included:'true',
amt: 20
},
{
id:'item3',
included:'true',
amt: 30
}
]
},
computed:{
itemTotal(){
return this.items.reduce(function(sum, item){
if(item.included){
return item.amt + sum;
}
return sum;
},0)
}
}
});
It is very crucial to model your data in a correct way. As your items where having common properties, so it could come in an array. I have include the id and a flag know whether it is included or not.
I also have added a method to compute the total every time item changes.
Template:
<div id="app">
<div v-for="item in items"
:id="item.id"
:key="item.id">
<span>{{item.amt}}</span>
<button #click="item.included = false"> exclude me</button>
</div>
<br>
<div id="total">total: {{ itemTotal }}</div>
</div>
In template code I have added v-for to render the whole list, and at button click I am just changing the flag (included). Computation of the itemTotal is triggered automatically and smartly by vue. An at last I am rendering the itemTotal in separate div.
I hope this helps.
if you want to button toggles the 'included' state and exclude the value
try it use v-on:click and a unclick boolean varible
simple demo looks like:
var app = new Vue({
el: "#app",
data: {
item1: {
amt: 10,
unclick:true
},
item2: {
amt: 30,
unclick:true
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<div id="item1">{{ item1.amt }}
<button v-on:click="item1.unclick = !item1.unclick" type="button">
{{ (item1.unclick?'exclude me':'include me') }}
</button>
</div>
<div id="item2">{{ item2.amt }}
<button v-on:click="item2.unclick = !item2.unclick" type="button">
{{ (item2.unclick?'exclude me':'include me') }}
</button>
</div>
<br>
<div id="total">total: {{
(item1.unclick?item1.amt:0)
+ (item2.unclick?item2.amt:0)
}}
</div>
</div>

vuejs auto-filter out an array marked "Bar"

I am new vuejs but learning a lot. I Have an array of items that renders to a list perfectly fine. I do want to not display anything marked Bar? I have tried !Bar but it does not work. Whats the correct way to do this?
var app = new Vue({
el: "#demo",
data: {
items: [{
childMsg: 'Foo'
}, {
childMsg: 'Bar'
}]
}
});
<script src="https://unpkg.com/vue"></script>
<div id="demo">
<ul v-for="item in items">
<li>{{item.childMsg}}</li>
</ul>
</div>
As usual, there are several approaches. One most straightforward is to exclude the item directly within v-for element template, like this:
<li v-if="item.childMsg !== 'Bar'">{{item.childMsg}}</li>
An alternative would be creating a computed property: array of items that do not match the pattern. Then you can rebase your v-for onto that property. Here's how it can be done:
var app = new Vue({
el: "#demo",
data: {
exclude: '',
items: [{
childMsg: 'Foo'
}, {
childMsg: 'Bar'
}]
},
computed: {
filteredItems() {
return this.items.filter(x => x.childMsg !== this.exclude);
}
}
});
<script src="https://unpkg.com/vue"></script>
<div id="demo">
<label>Exclude word... <input type="text" v-model="exclude" /></label>
<ul v-for="item in filteredItems">
<li>{{item.childMsg}}</li>
</ul>
</div>