V-for clicks all buttons instead of just one - javascript

I am new to Vue.js. I am trying to render check boxes from v-for (which renders correctly) based on an array.
Then I try to render a button beside each check box which opens a multi-select based on the selected index. But every time I click the rendered button, it opens up the multi select across all the checkbox buttons.
HTML:
<div>
<label class='label'>countrys:* </label><br><br>
<div
v-for="(country, index) in countries"
class="label"
style="display: inline-block;">
<input
type='checkbox'
value="country">&nbsp
{{country.geos}} &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
<img
class='addIcon'
v-bind="country"
:key="country.index"
style="width: 26px;height: 20px;margin-right: 8px;margin-top: px;margin-left: -25px;margin-bottom:-5px"
src='../../images/createScreen/addClient#2x.png'
#click="makeMarketsActive($event, index)">
<select multiple v-if="!isHidden">
<option
v-for="(market) in country.markets"
v-bind="country">
{{ market }}
</option>
</select>
</div>
</div>
JS:
export default {
name: "Update",
components: {
},
data() {
return {
countries:[
{
"index":0,
"geos":"America",
"markets":['a','b','c','d']
},
{
"index":1,
"geos":"Canada",
"markets":['a','b']
},
"index":2,
"geos":"Africa",
"markets":['x','z']
}
],
isHidden:true
}
},
makeMarketsActive(event, index) {
this.isHidden = !this.isHidden;
}
Expected result : When clicking the image rendered for each checkbox I just want to see the market for each geo and not for all.

You also don't need the extra function
HTML
<div id="app">
<label class='label'>countries:* </label><br><br>
<div v-for="(country, index) in countries" class="label" style="display: inline-block;">
<input type='checkbox' value="country">{{country.geos}}
<img class='addIcon' v-bind="country" :key="country.index" style="margin-right: 8px;margin-top: px;margin-left: -25px;margin-bottom:-5px" src='https://via.placeholder.com/26x20' v-on:click="country.isVisible = !country.isVisible">
<select multiple v-show="country.isVisible">
<option v-for="(market) in country.markets" v-bind="country" >{{ market }}</option>
</select>
</div>
</div>
JS
new Vue({
el: '#app',
data: {
countries: [{
"index": 0,
"geos": "America",
"markets": ['a', 'b', 'c', 'd'],
"isVisible": false
},
{
"index": 1,
"geos": "Canada",
"markets": ['a', 'b'],
"isVisible": false
}, {
"index": 2,
"geos": "Africa",
"markets": ['x', 'z'],
"isVisible": false
}
]
}
})

First of all, as mentioned in comments, you are handling each button state via general property isHidden. So you need to add this property to data array:
new Vue({
el: "#app",
data: {
countries:[
{
"index":0,
"geos":"America",
"markets":['a','b','c','d'],
"isHidden":true
},
{
"index":1,
"geos":"Canada",
"markets":['a','b'],
"isHidden":true
},
{"index":2,
"geos":"Africa",
"markets":['x','z'],
"isHidden":true
}
]
},
methods: {
makeMarketsActive(event, index) {
this.countries[index].isHidden = !this.countries[index].isHidden;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<label class='label'>countrys:* </label><br><br>
<div v-for="(country, index) in countries" class="label" style="display: inline-block;">
<input type='checkbox' value="country">&nbsp{{country.geos}} &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
<img class='addIcon' v-bind="country" :key="country.index" style="width: 26px;height: 20px;margin-right: 8px;margin-top: px;margin-left: -25px;margin-bottom:-5px" src='../../images/createScreen/addClient#2x.png' #click="makeMarketsActive($event, index)">
<select multiple v-if="!country.isHidden">
<option v-for="(market) in country.markets" v-bind="country" >
{{ market }}
</option>
</select>
</div>
</div>

Related

How to sum selected values in option tag

So I have a v-for loop and 7 different documents from mongo database. Every document contains one food and for each food it has specific number of calories. And I want to sum all the selected calories. For example I got a variable food.calorie_number. Okay so I have something like this:
<tr>
<td v-for="(food) in fetch_breakfast.slice(8,15)" :key=food.id>Meal <p style="border-top: 3px solid #dddddd;">
<select class="form-select" aria-label="Default select example">
<option selected>Select your food</option>
<option v-bind:value="food.id">{{food.food}}</option>
<!-- Every meal has food.calorie_number -->
<option value="3"></option>
</select>
</p></td>
<p>Calorie sum: {{Sum}}</p>
</tr>
I wanted to do something like this: Sum = Sum + food.calorie_number but i didn't get the final solution because I don't know how to do it for a specific element generated by v-for.
If I understood you correctly try like following snippet (with computed and method for selection) :
new Vue({
el: '#demo',
data() {
return {
fetch_breakfast: [{id: 1, food: 'apple', calorie_number: 80}, {id: 2, food: 'peach', calorie_number: 70}, {id: 3, food: 'carrot', calorie_number: 90}],
selected: []
}
},
computed: {
sum() {
return this.selected.reduce((acc, curr) => acc + curr.calorie_number, 0)
}
},
methods: {
getSum(food) {
const idx = this.selected.findIndex(s => s.id === food.id)
idx > -1 ? this.selected.splice(idx, 1) : this.selected.push(food)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<table>
<tr>
<td v-for="food in fetch_breakfast" :key=food.id>Meal
<p>
<select class="form-select" #change="getSum(food)">
<option selected>Select your food</option>
<option :value="food.id">{{ food.food }}</option>
</select>
</p>
</td>
<p>Calorie sum: {{ sum }}</p>
</tr>
</table>
</div>
First you have v-for on the wrong element because that way it will return 7 select. if you want to have seven options put v-for in select but to have a default option that is not affected by the loop put in option like this:
then do your logic and fetch in either computed value
var selector = new Vue({
el: '#selector',
data: {
selected: null,
meals: [
{'id':1,
"name":"food_name_1",
"calories":"1.6g"
},
{'id':2,
"name":"food_name_g",
"calories":"1.8g"
},
{'id':3,
"name":"food_name_v",
"calories":"1.9g"
},
{'id':9,
"name":"food_name_v",
"calories":"1.66g"
},
{'id':11,
"name":"food_name_y",
"calories":"1.1g"
},
]
},
computed:{
selected_food(){
let id = this.selected
let selected_meal = this.meals.find(meal => meal.id === id)
return selected_meal
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="selector">
<select :default="0" v-model="selected">
<option selected="selected" :value=null>select meal</option>
<option v-for="(meal, key) in meals" :key=key :value="meal.id">
{{ meal.name }}
</option>
</select>
<br>
<br>
<div v-if="selected">
<span > Selected food : {{selected_food.name}}</span> </br>
<span > Selected food Calory : {{selected_food.calories}}</span>
</div>
<span v-else>Please select a meal</span>
</div>

Element deleted from the array but v-for doesn't update VUEJS

Guys so I'm trying to develop this rule component which can be spawned by my main component as many times as he wants but the problem is that when I delete an index from the list that tracks the number of rules, the vuejs layout doesn't update accordingly. What I mean by this is that if I check the array it self it deleted the correct item but when I look at the vue page (HTML) it either doesn't delete anything or only deletes the last item, and this may be caused by the v-for not updating on list change but I don't know how to solve this.
NewTask.vue (Parent)
<template>
<div class="mt-4">
<div class="container">
<div class="if-condition container-fluid d-flex flex-row ps-0">
<span class="text-center me-2 condition rounded">IF</span>
<select class="form-select form-select-sm me-2 if-select" v-model="if_condition">
<option value="ALL">ALL</option>
<option value="ANY">ANY</option>
<option value="NONE">NONE</option>
</select>
<span>of these filters match</span>
</div>
<div class="mt-2 ps-3 pt-3 pb-3 border">
<new-rule v-for="(item, index) in rules"
:key="JSON.stringify(index)" v-on:remove-rule="removeRule(index)"
:data="item" :index="index" v-on:data-changed="dataChanged"
class="mb-2"/>
<div class="mt-2 add-rule-div">
<button class="btn add-rule-btn" v-on:click="addRule">+</button>
</div>
</div>
</div>
</div>
</template>
<script>
import Rule from '#/components/rule'
export default {
name: "NewTask",
components: {
'new-rule': Rule
},
data: function () {
return {
if_condition: 'ALL',
rules: []
}
},
methods: {
dataChanged(data) {
const rules = this.rules;
const index = data.index;
delete data['index'];
rules.splice(index, 1, data)
this.rules = rules;
},
removeRule(index) {
const rules = this.rules;
rules.splice(index, 1)
this.rules = rules
},
addRule() {
const new_rule = {
type_input_text: null,
type_input_show: null,
rule_input_text: null,
rule_input_show: null,
}
this.rules.push(new_rule)
console.log(this.rules)
}
}
}
</script>
rule.vue (Child)
<template>
<div class="if-condition d-flex flex-row">
<select class="form-select form-select-sm me-2"
v-on:change="checkTypeSelect" v-model="type_select">
<option value="HTML">HTML</option>
<option value="XPATH">XPATH</option>
<option value="ID">ID</option>
<option value="CLASS">CLASS</option>
</select>
<input v-if="type_input_show" type="text" class="form-control me-2" v-model="type_input_text" v-on:change="dataChanged">
<select class="form-select form-select-sm me-2"
v-on:change="checkRuleSelect" v-model="rule_select">
<option value="CONTAINS">CONTAINS</option>
<option value="EXISTS">EXISTS</option>
</select>
<input v-if="rule_input_show" type="text" class="form-control me-2" v-model="rule_input_text" v-on:change="dataChanged">
<button class="btn remove-rule-btn pb-0 pt-0 ps-2 pe-2" v-on:click="this.$emit('remove-rule')">-</button>
</div>
</template>
<script>
export default {
name: "rule",
props: {
data: {
type: Object,
required: true
},
index: {
type: Number,
required: true
}
},
data: function () {
return {
type_select: 'HTML',
type_input_text: '',
rule_select: 'CONTAINS',
rule_input_text: '',
//
type_input_show: false,
rule_input_show: true,
}
},
beforeMount() {
if (this.data.type_select) {
this.type_select = this.data.type_select
this.checkTypeSelect()
}
if (this.data.type_input_text) {
this.type_input_text = this.data.type_input_text
}
if (this.data.rule_select) {
this.rule_select = this.data.rule_select
this.checkRuleSelect()
}
if (this.data.rule_input_text) {
this.rule_input_text = this.data.rule_input_text
}
},
methods: {
dataChanged() {
const new_data = {
index: this.index,
type_select: this.type_select,
type_input_text: this.type_input_text,
rule_select: this.rule_select,
rule_input_text: this.rule_input_text
}
this.$emit('data-changed', new_data)
},
checkTypeSelect() {
const type_select = this.type_select;
this.type_input_show = type_select !== 'HTML';
this.dataChanged()
},
checkRuleSelect() {
const rule_select = this.rule_select;
this.rule_input_show = rule_select !== 'EXISTS';
this.dataChanged()
}
}
}
</script>
Some images to demonstrate the issue:
Array Before Index delete:
Array After Index delete:
For further investigation feel free to visit the repo: https://github.com/DEADSEC-SECURITY/Easy-Scraper
THIS IS NOT PUBLICITY, I TRULY NEED HELP
You are using the index as a key. VueJS updates the DOM based on the change in key. So you have two options:
Use some distinct data from each entry or combination as a key.
Provide no key because as per Vuejs docs you need not provide a key anymore.

Dynamically generate nested inputs in form

I'm very new into frontend, so I appreciate any help.
I'm trying to build a form, where user select an option from element and then depending on condition, dynamically generates another one (and some other inputs) as a child elements.
Finally what I'm trying to get is JSON with nested structure. E.g.
fields: [{type: 'List', value: [{type: 'Map', value: [{type: 'Integer', value: 5}, {type: 'List', value: [and so on...]]]}]
I have already started to code it in native JS and this is what I have so far (snippet below).
I want to release something similar with VUE.js library (or maybe someone can tell me any other useful libraries), cuz I want to control visibility of my inputs based on some conditions and some other useful features...but I dont know how to dynamically push elements into nested into nested and so on...I appriciate any help, any ideas and any examples. Thanks!
let template = `
<select name="type" onChange="createChildElement(this)" aria-label="Select type">
<option value="List">Select type</option>
<option value="List">List</option>
<option value="Map">Map</option>
<option value="Integer">Integer</option>
</select>
<select name="method" aria-label="Метод генерации">
<option value="Static">Static</option>
<option value="Random">Random</option>
<option value="Range">Range</option>
</select>
<input name="size" type="text" placeholder="Size">
<input name="value" type="text" placeholder="Value">
`;
function createChildElement(e) {
if(e.value == "List") {
var x = document.createElement('ul');
var z = document.createElement('li');
z.insertAdjacentHTML( 'beforeend', template );
x.appendChild(z);
e.parentNode.appendChild(x);
}
if(e.value == "Map") {
var x = document.createElement('ul');
var z = document.createElement('li');
z.insertAdjacentHTML( 'beforeend', template );
x.appendChild(z);
var y = document.createElement('ul');
var n = document.createElement('li');
n.insertAdjacentHTML( 'beforeend', template );
y.appendChild(n);
e.parentNode.appendChild(x);
e.parentNode.appendChild(y);
}
}
<body>
<div id="main-container">
<ul><li><div class="singleton-card ml-2">
<select name="type" onChange="createChildElement(this)" aria-label="Select type">
<option value="List">Select type</option>
<option value="List">List</option>
<option value="Map">Map</option>
<option value="Integer">Integer</option>
</select>
<select name="method" aria-label="Метод генерации">
<option value="Static">Static</option>
<option value="Random">Random</option>
<option value="Range">Range</option>
</select>
<input name="size" type="text" placeholder="Size">
<input name="value" type="text" placeholder="Value">
</div></li></ul>
</div>
</body>
I just found this example (https://codesandbox.io/s/github/vuejs/vuejs.org/tree/master/src/v2/examples/vue-20-tree-view?from-embed), I want to build something similar, but as a form with selects and inputs (just like my snippet example).
If I understand correctly, you're trying to create dependent dropdowns. You can check the following codepen for creating a dependent dropdown in vue.js
https://codepen.io/adnanshussain/pen/KqVxXL
JS
var model_options = {
1: [{ text: "Accord", id: 1 }, { text: "Civic", id: 2 }],
2: [{ text: "Corolla", id: 3 }, { text: "Hi Ace", id: 4 }],
3: [{ text: "Altima", id: 5 }, { text: "Zuke", id: 6 }],
4: [{ text: "Alto", id: 7 }, { text: "Swift", id: 8 }]
};
var makes_options = [
{ text: "Honda", id: 1 },
{ text: "Toyota", id: 2 },
{ text: "Nissan", id: 3 },
{ text: "Suzuki", id: 4 }
];
var vm_makes = new Vue({
el: "#app",
data: {
make: null,
model: null,
makes_options: makes_options,
model_options: model_options,
},
watch: {
make: function(event) {
$('#vehicle-models').dropdown('clear');
}
}
});
$('.ui.dropdown').dropdown();
HTML
<div id="app" class="ui grid">
<div class="row">
<div class="column">
<div class="ui label">Vechicle Make</div>
<select class="ui dropdown" v-model="make" id="vehicle-makes">
<option v-for="option in makes_options" v-bind:value="option.id">
{{ option.text }}
</option>
</select>
</div>
</div>
<div class="row">
<div class="column">
<div class="ui label">Vechicle Model</div>
<select class="ui dropdown" id="vehicle-models" v-model="model">
<option
v-for="option in model_options[make]"
:value="option.id"
:key="option.id"
>
{{ option.text }}
</option>
</select>
</div>
</div>
</div>

How to use v-model with multiple checkboxes generated by v-for?

Don't know how to use v-model on input checkbox type from a loop of a nested element.
I have successfully used a select/dropdown:
<select class="custom-select custom-select-sm" v-model="slide.filterCat">
<option :value="item.category" v-for="(item,index) in categories.elCats" :key="index">{{item.category}}</option>
</select>
It returns the item and models it correctly when updated,
however I would need to offer several options that can be selected and trying to use checkboxes instead.
The loop works fine and all labels and checkboxes are being shown correctly.
But I have no idea on how I can use simple v-model here.
I have used a method that checks if value is in array and if so to return true,
which works by using binding on :checked
:checked="isInCategoryList(item.category, slide.filterCat)
and the method:
isInCategoryList(curEl, list){
console.log(curEl);
console.log(list);
return list.includes(curEl ) ? true : false
},
But it logs an insane amount, which makes me think the approach is definitely not the correct one, for every event (mouseover,...) it keeps logging data.
And this snippet simply didn't work:
<div v-for="(item,index) in categories.elCats" :key="index">
<label>{{item.category}}</label>
<input type="checkbox" :value="item.category"
v-model="slide.filterCat">
</div>
Any guidance please...
Multiple checkboxes (as well as <select multiple>) requires the v-model argument to be an array...
const vm = new Vue({
el: '#app',
data() {
return {
selected: [],
categories: [{
name: 'Category A',
value: 'A'
},
{
name: 'Category B',
value: 'B'
},
{
name: 'Category C',
value: 'C'
},
]
}
}
})
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="row around-xs">
<div>
<h4>Checkboxes:</h4>
<div v-for="(item,index) in categories" :key="index">
<label>{{item.name}}</label>
<input type="checkbox" :value="item.value" v-model="selected">
</div>
</div>
<div>
<h4>Multi-select (using same model):</h4>
<select v-model="selected" multiple>
<option :value="item.value" v-for="(item,index) in categories" :key="index">{{item.name}}</option>
</select>
</div>
</div>
<h3>
Result: {{ selected }}
</h3>
</div>

Retrieve selected text from select

I'm using VueJS 2 (with this template) and below is what I've done so far:
<template>
<select id="dropDown" v-model="mytrack">
<option value="">Select track</option>
<option v-for="track in tracksList" :value="track.circuitId">{{ track.name }}</option>
</select>
<button type="submit" #click="retrieveByTrack(track.circuitId)">
Search
</button>
</template>
<script>
export default {
data() {
return {
tracksList: []
};
},
created: {
// here I fill tracksList[] with a list of tracks
},
methods: {
retrieveByTrack(trackId){
}
}
}
</script>
I want to be able to select an option from the select element and when I click the submit button I should call retrieveByTrack(track.circuitId) method passing the option value selected in the select element.
Simple like that.
I created fiddle for you:
https://jsfiddle.net/npw7fgta/
<div class='col-xs-12'>
<hr/>
<div id="app">
<pre>{{ $data |json }}</pre>
<select v-model='result'> <option v-for="task in tasks" :value="task.id">{{ task.name }} </option> </select>
</div>
</div>
And JS:
var vm = new Vue({
el: "#app",
data: {
tasks: [
{ id: 0 , name: 'First task'},
{ id: 5, name: 'fifth task'}
]
}
});
Then you have task/track.id available in result so you can do whatever you want.

Categories

Resources