How to put just a value from a radio input into an array in Vuejs and Vuex - vuejs2

state: {
questions: [
{
"id": 1,
"name": "q1",
"category": "English Language",
"type": "multiple",
"question": "What is a name of any person, animal, place, thing and feeling?",
"correct_answer": "Noun",
"incorrect_answers": [
"Pronoun",
"Noun",
"Adverb",
"Adjective"
]
}
]
answer = "",
answer = []
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I'm working on a quiz app and I'm using Vuex for state management. I'm having four radio values (answers) for each question and I want to be putting just the last selected value (answer) into an array that's in my Vuex state, it's working fine but whenever the use chooses another radio input (from the same question) it enters the array too, whereas I want only the selected value from each question (no matter the number of toggle in the options).
My "questions" array of 10(in length) in the state looks like this:
state: {
questions: [
{
"id": 1,
"name": "q1",
"category": "English Language",
"type": "multiple",
"question": "What is a name of person, animal, place or thing?",
"correct_answer": "Noun",
"incorrect_answers": [
"Pronoun",
"Noun",
"Adverb",
"Adjective"
]
}
...
}
and my template looks like this:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
```
<div class="card-text ml-4" v-for="(answer, index) in question.incorrect_answers" :key="index" >
<label class="form-check-label">
<!-- <input type="radio" name="answer" class="mb-2" v-model="getAnswers[index]" :value="answer"> {{answer}} -->
<input type="radio" :name="question.name" class="mb-2" :value="answer" #change.stop="newAnswer(question.id, answer)" /> {{answer}}
</label> <!-- work on postanswer -->
</div>
```
and my mutation looks like this:
mutations:{
ANSWER(state, id, ans){
state.answer = id;
if(id === "q1"){
state.answers.push(state.answer);
} else {
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
I've been on this for weeks but I've not gotten it. How do I do it?

Logics for putting the answer into the array
Check whether the answer already inputted or not using the array index
If exists remove the old one and insert the latest choice
Here the code for the above logics
// Trying to find index of chosed answer
const indexOfExistingChoice = this.choosedAnswers.findIndex(
answer => answer.id === selectedAnswer.id
);
if (indexOfExistingChoice >= 0) {
// Choice already selected
// Removing the choice from the array
this.choosedAnswers.splice(indexOfExistingChoice, 1);
}
// Pushing into the array
this.choosedAnswers.push(selectedAnswer);

Related

Vuelidate $each: How can I validate a nested collection?

I am having a really hard time trying to grasp the likely elementary concept(s). I am passing a location in as a prop. It has a json column to store additionalAttributes. It looks something like this:
"additionalProperties": [
{
"integrations": [
{
"exampleVendor": {
"locationId": 123,
"positionId": 456
}
}
]
}
],
"createdAt": "",
"updatedAt": "",
...
The above is what I've hard-coded into my database (Postgres) to attempt to mock what the data will look like when it comes back.
I am working from the validate collections portion of the vuelidate documentation.
Here is what I am using to attempt to create the validation rule:
validations: {
location: {
additionalProperties: {
$each: {
integrations: {
$each: {
exampleVendor: {
locationId: {required},
positionId: {required},
}
}
}
}
}
}
},
In my template, I'm trying to connect the validations like this:
<select id="my-id"
name="my-id"
class="py-3 px-3 mt-1 block w-full pl-3 pr-10 py-2 text-base sm:text-sm rounded-md"
v-if="locations"
v-model.trim="$v.location.additionalProperties[0].integrations[0].exampleVendor.locationId.$model"
:class="[$v.location.additionalProperties[0].integrations[0].exampleVendor.locationId.$error ?
'focus:ring-red-500 focus:border-red-500 border-red-300' : 'focus:ring-gray-400 focus:border-gray-400 border-gray-300',]"
>
...
</select>
I've been working with this component for quite a while and have already asked a really silly question.
I am also concerned that by setting such a rigid path additionalProperties[0].integrations[0] is really bad.
I fear this one isn't too far behind but it's time to ask for some advice. Thank you for any suggestions!
EDIT
#tony19 made an excellent call about why the array if only the first value is being used. Perhaps there is a better way to do what I'm doing; here is a wider view of what the data in my database could look like. It has additional properties now beyond just integrations. For now, I'm only focused on that though.
"additionalProperties": [
{
"integrations": [
{
"exampleVendor": {
"locationId": 123,
"positionId": 456
},
"anotherVendor": {
"foo": "abc",
"bar": "def"
},
"someOtherVendor": {
"thing": "value"
}
}
],
"anotherAttribute: {
"one": "two"
},
"possibleAttributes": [...]
}
],
As you commented it's possible to have more array values in additionalProperties and integrations, it makes more sense to iterate those properties rather than hard-coding access to the first element only.
The Vuelidate docs for collections you linked shows iterating the array with $each.$iter, so I would use <template v-for="ARRAY.$each.$iter"> for each level of nesting:
<template v-for="(addtlProperty, i) in $v.location.additionalProperties.$each.$iter">
<template v-for="(integration, j) in addtlProperty.integrations.$each.$iter">
<select
:key="`${i}-${j}`"
v-model.trim="integration.exampleVendor.locationId.$model"
:class="[
integration.exampleVendor.locationId.$error
? 'focus:ring-red-500 focus:border-red-500 border-red-300'
: 'focus:ring-gray-400 focus:border-gray-400 border-gray-300',
]"
>
...
</select>
</template>
</template>
demo
There are quite a few things I've learned while working through this. One of the more important being how to troubleshoot what vuelidate thought it was getting.
I created an change handler to provide insight to what the $model value was. Here is an example:
<select #change="onChange"...">...</select>
...
// start with what I know to be true.
onChange() {
console.log($v.location.additionalProperties);
}
Using the above object structure, I'd then move into the object until I ended up with this:
console.log($v.location.additionalProperties.$each[0].integrations.$each[0]. exampleVendor.locationId.$model; // 12345
Now that I had the "path" to the model, I could update my <select> element:
<select id="my-locationId" name="my-locationId" class="py-3 px-3 mt-1 block w-full pl-3 pr-10 py-2 text-base sm:text-sm rounded-md"
v-model.trim="$v.location.additionalProperties.$each[0].integrations .$each[0].exampleVendor.locationId.$model"
:class="[
$v.location.additionalProperties.$each[0].integrations.$each[0].exampleVendor.locationId.$error
? 'focus:ring-red-500 focus:border-red-500 border-red-300'
: 'focus:ring-gray-400 focus:border-gray-400 border-gray-300',
]"
>
<option selected="selected" value="">Select</option>
<option
v-for="location in myLocations"
:key="location.id"
:value="location.id"
>
{{ location.name }}
</option>
</select>
Now that the nested path was collecting/setting the data, I could set up the validation rules:
...
data: () => ({...}),
validations: {
location: {
additionalProperties: {
$each: {
integrations: {
$each: {
exampleVendor: {
locationId: { required },
positionId: { required },
},
},
},
},
},
},
},
...
methods: {
async save() {
this.$v.$touch();
if (this.$v.$invalid) {
this.errors = true;
} else {
try {
const params = {
location: this.location, // location is passed in as props
method: this.location.id ? "PATCH" : "POST",
};
console.log('params: ', params); // {...}
// Save to vuex or ??
} catch (error) {
console.log('there was an error:', error);
}
}
},
}
Hope this helps someone else - it wasn't super straight forward & I'm sure there is a more effective way, but this ended up working for me.
EDIT 2
Please be sure to follow #tony19's suggested answer as well. The solution provided removes the "rigidity" I was speaking about in my question.

How to loop through nested objects using v-for loop

I'm working on some practice code that deals with card information, in which you can display the chosen card's detailed information by clicking one of the cards on the screen.
As demonstrated in the screenshots, if you choose one of the yellow cards, it displays more detailed information of the chosen card with green and blue background color.
I implemented this by using v-for loop, but the problem is that the detailed card information is a JSON object that contains multiple JSON objects inside, and I haven't been successful in displaying all of the members in non-JSON form.
I found some pages (like the link below) where some ways to loop through nested objects were discussed, but it was plain JavaScript code and I couldn't use the same strategy for v-for loop.
How to loop through a plain JavaScript object with the objects as members?
I understand the idea that you should just continue the loop in case the member is another object, not a primitive data type, but I don't know how to implement the same logic in v-for loop.
Could anyone tell me how to do it?
Here is my code.
(v-for loop part)
<div v-for="(obtainedCardInfo, index) in obtainedCardsInfo">
<span v-if="cardBtnChosen && card.id == selectedCard && obtainedCardInfo.id == selectedCard">
<span class="cardInfo">DETAILED CARD INFO:</span>
<div class="cardInfoDisplay">
<div v-for="(detailedInfo,index) in obtainedCardInfo" :key="index">
<p v-if="obtainedCardInfo[index]"> {{index}} : {{obtainedCardInfo[index]}} </p>
<p v-else> {{index}} : NULL </p>
</div>
</div> <br>
</span>
</div>
and the output for my current code.
DETAILED CARD INFO:
accountId : 3917674
id : 3918534
customerId : 998774
cardRole : MAIN
cardStatus : CARD_OK
truncatedCardNumber : 524804______9042
cardTemplate : MC_CARD
cardAddress : NULL
usageLimits : [ { "code": "WEEKLY", "values": null }, { "code": "DAILY", "values": [ { "code": "ATM", "singleAmount": 200, "count": 3, "sumAmount": 300 } ] }, { "code": "MONTHLY", "values": [ { "code": "ATM", "singleAmount": null, "count": 1000, "sumAmount": 1000000 } ] } ]
expiration : { "year": 2022, "month": 6 }
pinAddress : NULL
regionAndEcommBlocking : { "ecomm": false, "africa": false, "asia": false, "europe": false, "home": false, "northAmerica": false, "oceania": false, "southAmerica": false }
The v-for simply iterate through the array or the object keys.
v-for iterates through each element in the array
v-for also iterates through the keys in the object
You should also move your logic to a computed method
<template>
<p v-for:"item, index in arr" />
{{ item }}
{{ index }}
<p v-for:"item, key in obj" />
{{ item }}
{{ key }}
<br />
</template>
<script>
export default {
data() {
return {
arr:[1,2,3,4,5],
obj: { 1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e' }
}
},
computed: {
// do computation here
doSomething() {
}
}
}
</script>

VueJS - Auto create a A-Z letters list from the data

Is it possible to create a A-Z letters list (like this) from the data from a API and Vue to be able to determine if a property in a data contains a name that starts with what letter. If the data doesn't contain a specific letter name then remove/disable the href attribute from the letter anchor tag.
In the linked example, letters K, X and Z are missing coz they don't have the data
JSON
[
{
"id": 77,
"link": "http://my-site/cosmoquotes/authors/anonymous/",
"name": "Anonymous",
"slug": "anonymous"
},
{
"id": 72,
"link": "http://my-site/authors/ferdinand-marcos/",
"name": "Ferdinand Marcos",
"slug": "ferdinand-marcos"
},
{
"id": 75,
"link": "http://my-site/authors/john-f-kennedy/",
"name": "John F. Kennedy",
"slug": "john-f-kennedy"
},
{
"id": 67,
"link": "http://my-site/authors/john-maxwell/",
"name": "John Maxwell",
"slug": "john-maxwell"
}
]
Component
export default {
data() {
return {
authorsRequest: {
type: 'authors',
params: {
per_page: 100
}
},
}
},
computed: {
authors () {
return this.$store.getters.requestedItems(this.authorsRequest)
},
},
methods: {
getAuthors() {
return this.$store.dispatch('getItems', this.authorsRequest)
},
},
created() {
this.getAuthors()
}
}
So as per the returned data, only the letters 'A', 'F' and 'J' should be clickable/displayed.
I managed to do it like this,
unfortunatly it needs the authors array and the conditionnal function to be outside of the Vue component because I couldn't find how to pass argument to computed values
But since I'm new to vue (didn't even finish reading the introduction) I'm sure there has to be a better solution
EDIT: found the way to have the function in the component with methods, I could then move the data in the component too
let a = new Vue({
el: "#selector",
data: {
authors: [{"id": 77,"link": "http://my-site/cosmoquotes/authors/anonymous/","name": "Anonymous","slug": "anonymous"},{"id": 72,"link": "http://my-site/authors/ferdinand-marcos/","name": "Ferdinand Marcos","slug": "ferdinand-marcos"},{"id": 75,"link": "http://my-site/authors/john-f-kennedy/","name": "John F. Kennedy","slug": "john-f-kennedy"},{"id": 67,"link": "http://my-site/authors/john-maxwell/","name": "John Maxwell","slug": "john-maxwell"}]
},
computed: {
// there have to be a way to get this array without doing it like this but I don't know it ^^
letters() {
let letters = []
for(let i = "A".charCodeAt(0); i <= "Z".charCodeAt(0); i++) {letters.push(String.fromCharCode([i]))}
return letters
}
},
methods: {
// you may add a toUpperCase()/toLowerCase() if you're not sure of the capitalisation of you datas
isALink(letter) {
return this.authors.some(aut => aut.name.startsWith(letter))
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="selector">
<template v-for="letter in letters">
<a v-if="isALink(letter)" :href="letter">{{ letter }}</a>
<a v-else>{{ letter }}</a>
</template>
</div>
you can set the unique name as the id of dom. when you click to letter X, just get the first name start with X , and use getElementById to match the dom and scroll to the dom.

How to bind(v-model) data when a list with radio buttons in each list in Vue JS

I am fetching data from API. My data looks like -
[
{
id:1,
name:nameOfTheGroup1,
participants:[
{
id:1,
name:participant1
},
{
id:2,
name:participant2
}
]
},
{
id:2,
name:nameOfTheGroup2,
participants:[
{
id:3,
name:participant1
},
{
id:4,
name:participant2
}
]
}
]
As you can see its an array of objects. and in each object has nested array of objects. Basically i am trying to fetch all the groups for current user with its participants.
Now i am showing those in the browser using v-for like this -
<h3>Please assign an admin to given groups</h3>
<div v-for="group in groups">
{{ group.name }}
<div v-for="participant in group.participants">
<input type="radio" value="" v-model=""/>
<label>{{ participant.name }} </label>
</div>
</div>
Now, my question is how can i bind this data using v-model to get object/array with
group id and assigned user (radio checked ).
This is my best how i could explain))
Thanks.
First of all, your model doesn't have any field for the assigned participant. So, you need to add something like that:
id:1,
name:'nameOfTheGroup1',
assignedId: '',
participants:[
{
id:1,
name:'participant1'
},
{
id:2,
name:'participant2'
}
]
},
{
id:2,
name:'nameOfTheGroup2',
assignedId: 3,
participants:[
{
id:3,
name:'participant1'
},
{
id:4,
name:'participant2'
}
]
Then you need to provide binding:
<div v-for="participant in group.participants">
<input type="radio" v-model="group.assignedId" :value="participant.id" :name="group.id"/>
<label>{{ participant.name }} </label>
</div>
Do not forget to add "name" attribute to the radio.
Working example is here https://jsfiddle.net/7x46mtr1/
Try to use a property on participant.
Something like this
v-model=“participant.status”

Add dynamically data-bound text in Vue.js

My case must be weird, but I have a good for it.
Here's my situation:
I have a Vue app that renders a form based on a json.
For example, the JSON:
{
"fields": [{
"name": "firstName",
"title": "Name"
}, {
"name": "lastName",
"title": "Last Name"
}, {
"title": "Hello {{ firstName }}!"
}]
}
From that json, the final render has to be:
<input type="text" name="firstName" v-model="firstName" />
<input type="text" name="lastName" v-model="lastName" />
<p>Hello {{ firstName }}</p>
I'm able to render all of that, except for the <p> which is rendered as raw {{ firstName }} and not data-bound/reactive.
My question is:
How do I insert dynamic templates (can come from a Rest API) into the component, and make them have the full power of the mustache expressions.
The component will have something like
{...firstName field...}
<dynamic template will be added here and update whenever firstName changes>
Please let me know if I'm not too clear on this issue
Thank you!!!
Is this the sort of thing you're trying to do? I've created a dynamic component whose template is generated from a JSON string which is editable.
new Vue({
el: '#app',
data: {
componentData: {
firstName: 'Jason',
lastName: 'Bourne',
},
jsonString: `
{
"fields": [{
"name": "firstName",
"title": "Name"
}, {
"name": "lastName",
"title": "Last Name"
}, {
"title": "Hello {{ firstName }}!"
}]
}`
},
computed: {
template() {
const json = JSON.parse(this.jsonString);
return json.fields.map((s) => {
if ('name' in s) {
return `<input type="text" name="${s.name}" v-model="${s.name}">`;
}
return s.title;
}).join('\n');
},
componentSpec() {
return {
template: `<div>${this.template}</div>`,
data: () => this.componentData
};
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<textarea rows="16" cols="40" v-model.lazy="jsonString">
</textarea>
<component :is="componentSpec"></component>
</div>