VueJS Input Binding for dynamic radio buttons - javascript

I am trying to render several radio buttons with dynamic data.
Users can create markets and then products and unlock these products for each market and give more properties. Among other things, radio buttons should be available for this purpose.
I've tried:
<div v-for="market in markets" :key="market.id">
<div>
<span>{{ market.name }}</span>
</div>
<div>
<div v-for="(field, index) in market.market_fields" :key="index">
<label :for="field.name">{{field.label}}</label>
<div v-if="field.type != 'radio'"><input :type="field.type" v-model="field.value"></div>
<div>
<input type="radio" :name="field.name" :value="true" v-model="field.value">
<label :for="field.name">ja</label><br>
<input type="radio" :name="field.name" :value="false" v-model="field.value">
<label :for="field.name">nein</label><br>
</div>
</div>
</div>
</div>
The problem seems to be with the v-model because the selection of a radio button is only ever for one market.
for example:
I click a radio button for market1, then the radio button is checked, but if I select the same radio button for market2, it is no longer checked for market1.
EDIT:
For every market the radio buttons had the same name attribute (field.name). So i changed it to market.name_field_name

Try to add index to input id :id="field.name + index":
const { ref } = Vue
const app = Vue.createApp({
data() {
const markets = ref(
[{market_fields: [{id:1, name:'market1', label: 'aaa', value: '', type: 'radio'}, {id:2, name:'market1', label: 'bbb', value: '', type: 'radio'}, {id:3, name:'market2', label: 'ccc', value: '', type: 'radio'}]}]
)
return {
markets
}
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="market in markets" :key="market.id">
<div>
<span>{{ market.name }}</span>
</div>
<div>
<div v-for="(field, index) in market.market_fields" :key="index">
<label :for="field.name">{{field.label}}</label>
<div v-if="field.type != 'radio'">
<input :type="field.type" v-model="field.value">
</div>
<div>
<input type="radio" :id="field.name + index + 'ja'" :value="true" v-model="field.value">
<label :for="field.name + index + 'ja'">ja</label><br>
<input type="radio" :id="field.name + index + 'nein'" :value="false" v-model="field.value">
<label :for="field.name + index + 'nein'">nein</label><br>
</div>
</div>
</div>
</div>
{{ markets }}
</div>

Related

How to pass data to method from a vue element iteration

I am developing an invoice system in laravel vuejs. I am trying to pass data to the method from a form. In this form, I have used iteration to add/remove input text/select fields. In order to Iterate I have used v-model of these inputs. Now these v-models fail to pass data. When I console, I see they are empty.
I am sharing codes of that form :
For Iteration
<div class="form-group" v-for="(item,k) in inputs" :key="k">
<div class="row mb-2">
<div class="col-md-3">
<select class="form-control" v-model="item.product_id"
#change="getProductCost()">
<option value="">Select Product</option>
<option v-for="(product, i) in products" :key="i" :value="product.id">{{
product.product_name }}</option>
</select>
</div>
<div class="col-md-3">
<input type="text" class="form-control" placeholder="Quantity" v-
model="item.quantity" #keyup="getProductCost">
</div>
<div class="col-md-3">
<input type="text" class="form-control" placeholder="Total" v-
model="item.total">
</div>
<div class="col-md-3">
<span>
<i class="fa fa-minus-circle text-danger" #click="removeElement(k)" v-
show="k || ( !k && inputs.length > 1)"></i>
<i class="fa fa-plus-circle text-success ml-4" #click="addElement(k)" v-
show="k == inputs.length-1"></i>
</span>
</div>
</div>
</div>
From script, I need to pass data (item : {}) to getProductCost() method
export default {
data() {
return {
allerrors : [],
inputs : [{
product_id : '',
quantity : '',
total : '',
}],
item : {
product_id : '',
quantity : '',
total : '',
}
}
},
getProductCost() {
axios.get('/api/product-cost?
product_id='+this.item.product_id+'&&quantity='+this.item.quantity,
this.data).then(response => {
this.data.total_product_price = response.data
})
},
addElement() {
this.inputs.push({
product_id : '',
quantity : '',
total : ''
})
},
removeElement (index) {
this.inputs.splice(index, 1)
},
Well, actually you are overwriting the scope of item in the forloop.
I could imagine, that you just have to rename this line
<div class="form-group" v-for="(item,k) in inputs" :key="k">
to something like this:
<div class="form-group" v-for="(input,k) in inputs" :key="k">
and check for renaming the coresponding references aswell.
Edit 1:
You can look here for the vuejs way to handle dynamic data bindings and adjust it to your case: https://blog.logrocket.com/how-to-make-form-elements-dynamic-in-vue-js/

Get the value of inputs and convert them to a certain array format

I'm trying to get the value of all input that is located in a table header created using ng-repeat upon clicking a button.
This is my html code for creating all the inputs
<th ng-repeat="(index, header) in filterTbl.gatVisibleFilterHeaders track by $index"
style="display: {{header.is_visible ? 'block' : 'none'}};
width: {{filterTbl.gatFilterHeadersWidth}}px !important;">
<div>
<span class="field-{{ header.filter_type }} th-title">{{ header.name }}</span>
<span class="fa fa-filter header-filter spanHF-{{index}}" title="Filter"
ng-click="filterTbl.showFilter(index, $event)" role="button"
tabindex="0"></span>
</div>
<div class="actual-filter divAF-{{index}} filter_input">
<input type="hidden" value="{{ header.col }}" class="header_col"
name="filter_header_col" />
<div ng-if="header.filter_type == 'text' || header.filter_type == ''"
ng-click="filterTbl.getFilterRecords(index, header)">
<div class="filter_select_container_{{ index }}"
style="display: block">
<select class="form-control select2 filter_{{ index }}"
name="filter_select" multiple="multiple">
</select>
</div>
</div>
<div ng-if="header.filter_type == 'numeric'">
<div class="number-range">
<div style="display: none;" class="filter-numeric-error"></div>
<input type="text" class="form-control froms "
onclick="event.stopPropagation();"
placeholder="From"
pattern="[0-9]{1,14}\.[0-9]{2}"/>
<div class="clearfix"></div>
</div>
<div class="number-range">
<div style="display: none;" class="filter-numeric-error"></div>
<input type="text" class="form-control to"
onclick="event.stopPropagation();"
placeholder="To" pattern="[0-9]{1,14}\.[0-9]{2}"/>
<div class="clearfix"></div>
</div>
</div>
</div>
</th>
I successfully created all the select tag and inputs but being able to pass them properly on javascript side.
This is my function when i click a button to get all the value of created input
var filterSelect = [];
$('#filterTable').find('table input,table select,table textarea').each(function(item, value) {
// console.log(item, value);
console.log(this.value);
});
I'm successfully receiving the value of a select but I need to convert it to a result something like this
['name of input': value, 'name of input': value]
example : [col:1, filter_select: "sample"]
Does anybody has an idea how can I accomplish this. been stuck for 1 week already.
Did you try to have array in which you push the values selected from select ? Your modified function will be as follows
filterSelect = [];
$('#filterTable').find('table input,table select,table
textarea').each(function(item, value) {
var obj={item:value};
this.filterSelect.push(obj);
//to verify the array items
angular.forEach(this.filterSelect, function(value, key) {
console.log(key + ': ' + value);
});
});

[Vue warn]: Error in nextTick: "TypeError: Cannot read property 'key' of undefined"

I am fairly new to Vue (and using Vue components in particular), and I can't figure out what i'm doing wrong here. Essentially I have a signup wizard (using vue-good-wizard) which if the user never clicks the back button works fine, but if at some point they click back I get the error message described in the title. I think it has something to do with the following template being rendered a second time:
<template>
<div class="playerSelectionBox">
<table style="width:100%">
<tr v-for="i in 10" track-by="$index">
<td>Row: {{i}}</td>
<td><player-entry-box></player-entry-box></td>
</tr>
</table>
</div>
</template>
Here is the code for the template from the player-entry-box component:
<template>
<div class="PlayerEntryBox">
<input class="numberBox" type="text" #blur="sendData" v-model="number" placeholder="#">
<input class="nameBox" type="text" #blur="sendData" v-model="playerName" placeholder="Player Name"><br>
</div>
</template>
My best guess is that something is happening when the code is rendered where the player-entry-box components inside of the v-for have something which conflicts with the others, but it works as expected the first time it is rendered. Is there something here I am doing wrong?
Any advice or insights into what I am missing would be great, thanks!
Other information:
All of the components are currently registered globally in the main.js file.
Here is the code for the wizard:
<div id="TeamWizard" v-if="isCreatingTeam" style="padding-top=100px;">
<vue-good-wizard
:steps="teamSteps"
:onNext="nextClickedTeam"
:onBack="backClickedTeam">
<div slot="teamPage1">
<label class="myLabel">Team Name:</label>
<input type="text" v-model="teamName" placeholder="Team Name"><br>
<br>
<label class="myLabel">Sport:</label>
<br>
<select-sport :initialSport="tkdSport" style="display: inline-block;" #sportWasSelected="tkdSport=$event"></select-sport>
<h1></h1>
</div>
<div slot="teamPage2">
<h4>Add Players</h4>
<h1></h1>
<player-selection-box></player-selection-box>
</div>
<div slot="teamPage3">
<h4>Step 3</h4>
<label class="myLabel">Team ID:</label>
<input type="text" v-model="teamID" placeholder="Team ID"><br>
<br>
<label class="myLabel">Team Token:</label>
<input type="text" v-model="teamToken" placeholder="Team Token"><br>
<br>
</div>
</vue-good-wizard>
</div>
Here is where I called this.$nextTick():
mounted () {
this.$nextTick(() => {
this.loggedInUser = firebase.auth().currentUser;
});
}
The following is from the main component :
<template>
<div class="hello">
<h1>{{ tkdSport }}</h1>
<div id="mainPage" v-if="!(isCreatingTeam || isCreatingPlayer)">
<div id="mySidenav" class="sidenav">
×
<img src="../assets/images/testUser.png" width="100px" height="100px">
About
Services
Clients
Contact
<div style="scroll">
<div v-for="i in 50">
<div style="margin=10px; color=red;"><p>Test {{ i }}</p></div>
</div>
</div>
</div>
<h1>{{ loggedInUser.email }}</h1>
<button #click="logout">Logout</button>
<button #click="openNav">Open Nav</button>
<br>
<br>
<br>
<br>
<br>
<button #click="showTeam">New Team Account</button>
<br>
<br>
<br>
<button #click="showPlayer">New Player Account</button>
</div>
<div v-else>
<div id="createTeam" v-if="isCreatingTeam">
<h1>TEAM</h1>
<br>
</div>
<div id="createPlayer" v-if="isCreatingPlayer">
<h1>PLAYER</h1>
</div>
<div id="PlayerWizard" v-if="isCreatingPlayer" style="padding-top=100px;">
<div>
<vue-good-wizard
:steps="playerSteps"
:onNext="nextClickedPlayer"
:onBack="backClickedPlayer">
<div slot="playerPage1">
<label class="myLabel">Team Name:</label>
<input type="text" v-model="teamName" placeholder="Team Name"><br>
<br>
<label class="myLabel">Sport:</label>
<br>
<select-sport :initialSport="tkdSport" style="display: inline-block;" #sportWasSelected="tkdSport=$event"></select-sport>
<h1></h1>
</div>
<div slot="playerPage2">
<h4>Add Players</h4>
<h1>Player Selection will go here</h1>
</div>
</vue-good-wizard>
</div>
</div>
<div id="TeamWizard" v-if="isCreatingTeam" style="padding-top=100px;">
<vue-good-wizard
:steps="teamSteps"
:onNext="nextClickedTeam"
:onBack="backClickedTeam">
<div slot="teamPage1">
<label class="myLabel">Team Name:</label>
<input type="text" v-model="teamName" placeholder="Team Name"><br>
<br>
<label class="myLabel">Sport:</label>
<br>
<select-sport :initialSport="tkdSport" style="display: inline-block;" #sportWasSelected="tkdSport=$event"></select-sport>
<h1></h1>
</div>
<div slot="teamPage2">
<h4>Add Players</h4>
<h1></h1>
<player-selection-box></player-selection-box>
</div>
<div slot="teamPage3">
<h4>Step 3</h4>
<label class="myLabel">Team ID:</label>
<input type="text" v-model="teamID" placeholder="Team ID"><br>
<br>
<label class="myLabel">Team Token:</label>
<input type="text" v-model="teamToken" placeholder="Team Token"><br>
<br>
</div>
</vue-good-wizard>
</div>
</div>
</div>
</template>
<script>
import firebase from 'firebase';
export default {
name: 'HelloWorld',
data () {
return {
msg: 'Welcome to Your Vue.js App',
loggedInUser: '',
teamName: '',
teamToken: '',
teamID: '',
playerName: '',
tkdSport: 'basketball',
isCreatingPlayer: false,
isCreatingTeam: false,
playerSteps: [
{
label: 'Name and Sport',
slot: 'playerPage1',
},
{
label: 'Link (OPTIONAL)',
slot: 'playerPage2',
}
],
teamSteps: [
{
label: 'Name and Sport',
slot: 'teamPage1',
},
{
label: 'Add Players',
slot: 'teamPage2',
},
{
label: 'Setup Linking (OPTIONAL)',
slot: 'teamPage3',
}
]
}
},
mounted () {
this.$nextTick(() => {
this.loggedInUser = firebase.auth().currentUser;
});
},
methods: {
logout: function() {
firebase.auth().signOut().then(() => {
this.$router.replace('login')
})
},
openNav: function() {
document.getElementById("mySidenav").style.width = "250px";
},
closeNav: function() {
document.getElementById("mySidenav").style.width = "0";
},
showTeam: function() {
this.isCreatingTeam = true
this.isCreatingPlayer = false
},
hideCreating: function() {
this.isCreatingTeam = false
this.isCreatingPlayer = false
},
showPlayer: function() {
this.isCreatingPlayer = true
this.isCreatingTeam = false
},
onComplete: function() {
alert('Yay. Done!');
},
nextClickedPlayer(currentPage) {
console.log('next clicked', currentPage)
if(currentPage==1){
this.hideCreating()
}
return true; //return false if you want to prevent moving to next page
},
backClickedPlayer(currentPage) {
console.log('back clicked', currentPage);
return true; //return false if you want to prevent moving to previous page
},
nextClickedTeam(currentPage) {
console.log('next clicked', currentPage)
if(currentPage==2){
this.hideCreating()
}
return true; //return false if you want to prevent moving to next page
},
backClickedTeam(currentPage) {
console.log('back clicked', currentPage);
return true; //return false if you want to prevent moving to previous page
}
}
}
</script>
The user logs in from a separate Login Component, and then gets routed to this component, I just need to have access to the user object within this component.
Here is the stack trace:
Error stack trace

#click on checkbox add/remove data

I currently have the following scenario:
I have multiple checkboxes, once any is clicked, the value of it will get added to an array. If the checkbox is unchecked then the item needs to be removed out of the array again.
selectAddOn(addOnId) {
if (! this.selectedAddOns.includes(addOnId)) {
this.selectedAddOns.push(addOnId);
}
}
The following works and it adds them to my selectedAddOns[]. But when the checkbox is checked again, it is not removed. Sure, I could just use else, but...
Unfortunately, the browser behavior is when you click on a <label>, a click event will automatically be triggered on the <input>, so the outer div receives 2 events, one from label, one from input. I am aware that I can work around this by adding #click.prevent on the <label>, but this then will not add my custom checkbox styles.
<div #click="selectAddOn(index)" class="col-6" v-for="(addOn, index) in categories[categoryId].addOns">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input">
<span class="custom-control-indicator"></span>
<span class="custom-control-description">{{ addOn.name }} (+ ${{ addOn.price }})</span>
</label>
</div>
Any idea on how I can work around this scenario?
This is a built-in behavior of v-model when used with an array on multiple checkboxes. You don't need a click handler. (Code shamelessly lifted from Bert's answer.)
console.clear()
new Vue({
el: "#app",
data:{
selectedAddOns:[],
categories:[
{
addOns:[
{name: "AddOn One", price: 10},
{name: "AddOn two", price: 20},
{name: "AddOn Three", price: 30},
]
},
],
categoryId: 0
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
Selected Addons: {{selectedAddOns}}
<div class="col-6" v-for="addOn, index in categories[categoryId].addOns">
<label class="custom-control custom-checkbox" >
<input type="checkbox" class="custom-control-input" :value="index" v-model="selectedAddOns" >
<span class="custom-control-indicator"></span>
<span class="custom-control-description">{{ addOn.name }} (+ ${{ addOn.price }})</span>
</label>
</div>
</div>
Put the click event handler on the input.
console.clear()
new Vue({
el: "#app",
data:{
selectedAddOns:[],
categories:[
{
addOns:[
{name: "AddOn One", price: 10},
{name: "AddOn two", price: 20},
{name: "AddOn Three", price: 30},
]
},
],
categoryId: 0
},
methods:{
selectAddOn(addOnId) {
let index = this.selectedAddOns.findIndex(a => a === addOnId)
if (index >= 0)
this.selectedAddOns.splice(index, 1)
else
this.selectedAddOns.push(addOnId);
}
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
Selected Addons: {{selectedAddOns}}
<div class="col-6" v-for="(addOn, index) in categories[categoryId].addOns">
<label class="custom-control custom-checkbox" >
<input type="checkbox" class="custom-control-input" #click="selectAddOn(index)" >
<span class="custom-control-indicator"></span>
<span class="custom-control-description">{{ addOn.name }} (+ ${{ addOn.price }})</span>
</label>
</div>
</div>

Usng Vue.js to push dynamically created inputs to an array

What would the best process be to push dynamically created inputs to an array. Ideally I would like an array in the data like.
customer_answers: [1,4,5,6,8]
The items in the array being the values of the answers. I understand that each answer would need to be pushed and probably use on:change to run a a method.
However how would I identify each question where the questions and answers and dynamic?
The questions code is below:
<div v-for="(question, item) in questions.questions">
{{question.question}}
<div v-if="grouped_answers">
<div v-for="a in grouped_answers[item].answers">
<label>{{a.answer}}</label>
<div v-if= "question.type === 'radio'">
<input type="radio" v-bind:value="a.id">
</div>
<div v-if= "question.type === 'checkbox'">
<input type="checkbox" v-bind:value="a.id">
</div>
</div>
</div>
</div>
The full code if it helps is below:
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div v-for="app in appliances">
<input type="radio" id="one" v-model="appliance" v-bind:value="app.id" v-on:change="getQuestions">
<label for="one">{{app.appliance}}</label>
</div>
<br>
<span>Picked: {{appliance}}</span>
</br>
</br>
<div v-for="co in brands">
<input type="radio" id="two" v-model="brand" v-bind:value="co.id">
<label for="one">{{co.brand}}</label>
</div>
<span>Picked: {{ brand }}</span>
</br>
</br>
<input type="radio" id="one" value=1 v-model="age">
<label for="one">1 Year</label>
<br>
<input type="radio" id="two" value=2 v-model="age">
<label for="two">2 Years</label>
<br>
<input type="radio" id="two" value=3 v-model="age">
<label for="two">3 Years</label>
<br>
<input type="radio" id="two" value=4 v-model="age">
<label for="two">4 Years</label>
<br>
<input type="radio" id="two" value=5 v-model="age">
<label for="two">5 Years</label>
<br>
<input type="radio" id="two" value=6 v-model="age">
<label for="two">6 Years</label>
<br>
<input type="radio" id="two" value=7+ v-model="age">
<label for="two">7+ Years</label>
<br>
<span>Picked: {{ age }}</span>
<br>
<br>
<div v-for="(question, item) in questions.questions">
{{question.question}}
<div v-if="grouped_answers">
<div v-for="a in grouped_answers[item].answers">
<label>{{a.answer}}</label>
<div v-if= "question.type === 'radio'">
<input type="radio" v-bind:value="a.id">
</div>
<div v-if= "question.type === 'checkbox'">
<input type="checkbox" v-bind:value="a.id">
</div>
</div>
</div>
</div>
<br>
<br>
<br>
<br>
<input v-model="first_name" placeholder="First Name">
<p>First Name is: {{ first_name }}</p>
<input v-model="last_name" placeholder="Last Name">
<p>Last Name is: {{ last_name }}</p>
<input v-model="phone_number" placeholder="Phone Number">
<p>Phone Number is: {{ phone_number }}</p>
<input v-model="email" placeholder="Email">
<p>Email is: {{ email }}</p>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
mounted() {
console.log('Component ready.');
console.log(JSON.parse(this.a));
console.log(JSON.parse(this.b));
this.appliances = JSON.parse(this.a);
this.brands = JSON.parse(this.b);
},
props: ['a','b'],
data: function() {
return {
appliances: '',
appliance: '',
brands: '',
brand: '',
age: '',
first_name: '',
last_name: '',
phone_number: '',
email: '',
questions: '',
answers: '',
result: '',
grouped_answers:'',
customer_answers: [],
}
},
methods: {
incrementItem: function(item) {return item + 1},
getQuestions: function (){
console.log(this.appliance);
var self = this;
axios.get('/get_questions/' + this.appliance, {
})
.then(function(response) {
console.log(response.data);
self.questions = response.data;
self.getAnswers();
})
.catch(function(error) {
console.log(error);
});
},
getAnswers: function (){
console.log(this.appliance);
var self = this;
axios.get('/get_answers/' + this.appliance, {
})
.then(function(response) {
console.log(response.data);
self.answers = response.data;
self.putAnswers();
})
.catch(function(error) {
console.log(error);
});
},
putAnswers: function (){
var result = {};
for (var i = 0; i < this.answers.answers.length; i++) {
var question_id = this.answers.answers[i].question_id;
console.log(question_id);
if(!result[question_id]) {
result[question_id] = {question_id: question_id, answers: []};
}
result[question_id].answers.push({
id: this.answers.answers[i].id,
question_id: question_id,
answer: this.answers.answers[i].answer})
}
result = Object.keys(result).map(function (key) { return result[key]; });
console.log(result);
this.grouped_answers = result;
console.log(this.grouped_answers[0].answers);
},
},
}
</script>
UPDATE
nextQuestion: function (){
this.holding_answers = [];
},
saveAnswer (question, groupedAnswerItem, value, type, event) {
console.log(value);
console.log(type);
if(type === "radio"){
this.holding_answers = [];
this.holding_answers.push(value);
};
if(type === "checkbox"){
this.holding_answers.push(value);
};
},
<div v-for="(question, item) in questions.questions">
{{question.question}}
<div v-bind:id="item">
<div v-if="grouped_answers">
<div v-for="a in grouped_answers[item].answers">
<label>{{a.answer}}
<input #change="saveAnswer(question, grouped_answers[item], a.id, question.type)" :type="question.type" :value="a.id" :name="a.question_id">
</div>
</div>
</div>
<button #click="nextQuestion()">Next</button>
</div>
I simply use on:change="findAnswers" and then a Javascript event
methods: {
findAnswers: function(e) {
console.log(e.target.value);
},
}
Before pushing to a data array.
A couple of notes first:
You can remove the v-if for checkbox or radio and use a dynamic type like, :type="question.type"
Try to set the initial data as the possible real type, like grouped_answers shouldn't be an Array instead of String ?
Ok, so if I understand correctly, you want to save the the correct answer in the right question ? You should consider use the '#change' or '#click' with a function that gets the data that you need as parameter.
For example:
<input #change="saveAnswer(question, grouped_answers[item], a.id)" :type="question.type" :value="a.id">
saveAnswer:
saveAnswer (question, groupedAnswerItem, value, event) {...}
That's a general idea, not the implementation, the point is that you can pass the dynamically data as parameter and change as you want. Usually this is the way to go, pass all the information necessary in your function to achieve the change with that data. Inside the function you have access to your data, that should make things easier.

Categories

Resources