Struggling with how to use .sync when you render components from a list. How do I handle the event emitted in my component to update the parent?
Trying to update the categorySet.gradeCategory.predictionWeight in the input.
<category-set v-for="cat in categories" v-bind:key="cat.id" v-bind:category-set="cat"></category-set>
Vue.component('category-set', {
props: ['categorySet'],
template: ' <div class="form-group">\n' +
' <label for="gradeRange" class="col-sm-2 control-label">{{ categorySet.gradeCategory.gradeCategoryName }}</label>\n' +
' <div class="col-sm-1">\n' +
' <input id="gradeRange" class="form-control" type="number" v-bind:value.number="categorySet.gradeCategory.predictionWeight" \n' +
' step="0.5" v-on:input="$emit(\'input\', $event.target.value)" > \n' +
' </div>\n' +
' </div>'
});
Fiddle: https://jsfiddle.net/rhmiller/aq9Laaew/10971/
Personally, I would do it like the following:
The component is passed the array index and the item (cat), with the item you define the item within the component, then bind the input event which then emits the complete object back to the parent with its index, then the parent sets the item back into the data.
As the Final Exam item is nulled the gradeCategory property you need to handle/recover from that as your using it in the view. Also the label is the same in the parent, so prefer to use that else it would be null if you used the gradeCategory one.
Vue.component('categorySet', {
template: '#category-set',
props: ['data', 'index'],
data() {
return {
item: {
label: this.data.label,
showInSummary: this.data.showInSummary,
gradeCategory: Object.assign({
"gradeCategoryName": null,
"groupGradeWeight": 0.0,
"predictionWeight": null,
"id": this.data.id
}, this.data.gradeCategory),
id: this.data.id
}
}
},
methods: {
inputOccurred(e) {
this.$emit('on-change', this.item, this.index)
}
}
});
//
var vm = new Vue({
el: '#app',
data() {
return {
categories: [
{
"label": "Assignments",
"showInSummary": true,
"gradeCategory": {
"gradeCategoryName": "Assignments",
"groupGradeWeight": 0.0,
"predictionWeight": null,
"id": 81
},
"id": 81
}, {
"label": "Reflections",
"showInSummary": true,
"gradeCategory": {
"gradeCategoryName": "Reflections",
"groupGradeWeight": 10.0,
"predictionWeight": null,
"id": 82
},
"id": 82
}, {
"label": "Quizzes",
"showInSummary": true,
"gradeCategory": {
"gradeCategoryName": "Quizzes",
"groupGradeWeight": 10.0,
"predictionWeight": 10.0,
"id": 83
},
"id": 83
}, {
"label": "Attendance \u0026 Participation",
"showInSummary": true,
"gradeCategory": {
"gradeCategoryName": "Attendance \u0026 Participation",
"groupGradeWeight": 0.0,
"predictionWeight": null,
"id": 84
},
"id": 84
}, {
"label": "Final Exam",
"showInSummary": true,
"gradeCategory": null,
"id": 92
}
]
}
},
methods: {
syncCategorie(value, index) {
this.categories[index] = Object.assign(this.categories[index], value);
}
}
});
<div id="app">
<category-set v-for="(cat, index) in categories" :key="cat.id" :data="cat" :index="index" #on-change="syncCategorie"></category-set>
<pre>{{ categories }}</pre>
</div>
<template id="category-set">
<div class="form-group">
<label for="gradeRange" class="col-sm-3 control-label">{{ item.label }}</label>
<div class="col-sm-1">
<input id="gradeRange" class="form-control" type="number" v-model="item.gradeCategory.predictionWeight" step="0.5" #input="inputOccurred">
</div>
</div>
</template>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.min.js"></script>
Run the snippet your see it updates the parent fine.
You can just omit the v-on:input part when you add a .sync modifier.
:prop.sync="binding"
will effectively expands into:
:prop="binding" #update:prop="value => binding = value"
( : is just an abbreviation for v-bind: and # for v-on: )
Related
Here is my code
<template>
<div class="w-full bg-white mt-13">
<div class="flex flex-col mx-12 mt-8">
<div>
<table class="w-full h-auto my-12 border border-collapse table-auto">
<tr class="border">
<th v-for="i in columns" :key="i.name" class="border">
{{ i.title }}
</th>
</tr>
<tr v-for="p in getParticipants" :key="p.id">
<td class="border"></td>
<td class="border">{{ p.fullName }}</td>
<td class="border">{{ p.phone }}</td>
<td class="border">{{ p.participantId }}</td>
<td
v-for="(btn, index) in buttonCheckAtt"
:key="index"
class="border select-attendance"
>
<div>
<button
:unique-key="true"
class="w-full h-16 text-transparent"
:class="[
btn.className,
{ selected: selectedIndex === index },
]"
#click="selectedIndex = index"
>
{{ btn.btnText }}
</button>
</div>
</td>
<td class="w-16 border">
<input
ref="p.participantId"
#change="updateRemarks()"
v-model="note"
/>
</td>
</tr>
<tr></tr>
</table>
</div>
</div>
</div>
</template>
<script>
import Button from "#/components/form/Button.vue";
import { mapGetters } from "vuex";
const columns = [
{
title: "#",
},
{
title: "Name",
},
{
title: "Phone Number",
},
{
title: "ID",
},
{
title: "P",
},
{
title: "A",
},
{
title: "AP",
},
{
title: "L",
},
{
title: "Remarks",
},
];
const data = [];
export default {
components: { Button },
data() {
return {
note: null,
participantId: "abc-1",
};
},
methods: {
updateRemarks() {
let data = {
participants: [
{
participantId: this.participantId,
attendance: {
status: this.status,
note: this.note,
markBy: "organizer-id",
markMethod: "manual",
},
},
],
};
this.$store.dispatch(
"$_studyGroup/addRemarks",
{ participantData: data },
{ root: true }
);
},
},
computed: {
...mapGetters({
getParticipants: "$_studyGroup/getParticipants",
}),
},
mounted() {
this.$store.dispatch("$_studyGroup/getParticipants", {}, { root: true });
},
};
</script>
I want to use this input to this input to make patch request into my getParticipants api however it requires me to pass the participantId into the participants data in order to make patch request and I have no idea how to retrieve that participantId from the v-for loop
<input
ref="p.participantId"
#change="updateRemarks()"
v-model="note"
/>
and down below is what my getParticipants api looks like I want to pass the abc-1 and abc-2 id into the attendance in order to make patch request
getParticipants = [
{
"status": "join",
"createdAt": "2022-09-20T07:30:06.753Z",
"calendarId": "6553c8ea-0139-4802-b5d6-127e44b95412",
"email": "john#gmail.com",
"fullName": "John",
"participantId": "abc-1",
"attendance": {
"participantId" : {{participantId}},
"markBy": "organizer-id",
"markMethod": "manual",
"note": "Because of traffic jam",
"status": "Present"
},
{
"status": "join",
"createdAt": "2022-09-20T07:30:06.753Z",
"calendarId": "6553c8ea-0139-4802-b5d6-127e44b95412",
"email": "chris#gmail.com",
"fullName": "Chris",
"participantId": "abc-2",
"attendance": {
"participantId" : {{participantId}},
"markBy": "organizer-id",
"markMethod": "manual",
"note": "Because of traffic jam",
"status": "Late"
},]
My first column (Test1) is active, so the background turns orange. Why does the active class not change to the second column (Test2) when I click the radio button?
I'd like it to change the active class when the radio button is clicked.
I have the following template:
<template v-if="columns" v-for="(item, index) in columns" v-bind="item">
<div class="card-container"
:style="{'height': item.height+'%'}"
:class="{ active: item.active}">
<div class="card-inner">
<input type="radio" v-if="item.selectedDefault" checked v-model="currentInput" name="cardRadioGroup"
:value="item.id" v-on:change="updateDetails(item, index)"/>
<input type="radio" v-else v-model="currentInput" name="cardRadioGroup" :value="item.id"
v-on:change="updateDetails(item, index)"/>
<template v-if="item.gbAmount === null">
Onbeperkt
</template>
<template v-else>
{{ item.gbAmount }} GB
</template>
{{ currentInput }}
</div>
</div>
</template>
With the following Vue code:
import {onMounted} from "vue";
export default {
name: 'Card',
components: {Details},
data() {
const columns =
{
"Test1": {
"id": 1,
"gbAmount": 0,
"price": 5,
"selectedDefault": false,
"informationGet": ["bla", "bla"],
"informationUsing": "Boop.",
"recommended": false,
"height": 16.66,
"active": true,
},
"Test2": {
"id": 2,
"gbAmount": 1,
"price": 10,
"selectedDefault": false,
"informationGet": ["beh", "beh"],
"informationUsing": "Beep",
"recommended": false,
"height": 33.33,
"active": false,
},
}
return {
columns,
currentColumn: {},
checkedCard: [],
currentInput: null,
};
},
methods: {
updateDetails(item, index) {
this.currentColumn = {
...item,
name: index,
active: true,
}
console.log($(this.currentColumn));
},
},
setup() {
return {};
},
};
And CSS:
.active {
background: orange;
}
You update this.currentColumn but in template use this.columns. Correct will be:
updateDetails(item, index) {
Object.keys(this.columns).forEach(key => this.columns[key].active = false);
this.columns[index].active = true;
console.log(this.columns);
}
<template>
<div v-for="column in columns" :key="column.id">
<div
class="card-container"
:style="{ height: column.height + '%' }"
:class="{ active: column.id === currentInput }"
>
<div class="card-inner">
<input
type="radio"
name="cardRadioGroup"
:value="column.id"
v-model="currentInput"
/>
<template v-if="column.gbAmount === null"> Onbeperkt </template>
<template v-else> {{ column.gbAmount }} GB </template>
{{ currentInput }}
</div>
</div>
</div>
</template>
<script lang="ts">
import { ref } from 'vue'
const columns = [
{
id: 1,
gbAmount: 0,
price: 5,
selectedDefault: false,
informationGet: ['bla', 'bla'],
informationUsing: 'Boop.',
recommended: false,
height: 16.66,
},
{
id: 2,
gbAmount: 1,
price: 10,
selectedDefault: false,
informationGet: ['beh', 'beh'],
informationUsing: 'Beep',
recommended: false,
height: 33.33,
},
]
export default {
name: 'Card',
setup() {
const currentInput = ref(columns[0].id)
return {
columns,
currentInput,
}
},
}
</script>
I am facing an issue where I need to display one select field two times in the form and while saving the form it will save the data in an array.
What I have done is created a form and added a select form and I want it to display two times (two select form) and it will be able to select different values for two select displays
I have created a sandbox here
Any ideas are much appreciated.
You could create new variable to second value with same options as first select input and save it as array.
<template>
<div>
<b-form-select
class="mb-2 mr-sm-2 mb-sm-0"
:options="optQuality"
v-model="slcQuality"
#input="changeQuality"
>
</b-form-select>
<div>slcQuality: {{ slcQuality }}</div>
<b-form-select
class="mb-2 mr-sm-2 mb-sm-0"
:options="optQuality"
v-model="slcQuality2"
#input="changeQuality"
>
</b-form-select>
<div>slcQuality: {{ slcQuality2 }}</div>
<div>
<button #click="submit">Submit</button>
</div>
<div>submitted Data: {{ JSON.stringify(submittedData) }}</div>
</div>
</template>
<script>
export default {
data() {
return {
optQuality: [
{ value: 1, text: "Original" },
{ value: 2, text: "Kw-1" },
{ value: 3, text: "Kw-2" },
],
slcQuality: null,
slcQuality2: null, // new variable
submittedData: [],
};
},
methods: {
changeQuality() {
console.log("test");
console.log(this.slcQuality);
},
submit() {
const data = [this.slcQuality, this.slcQuality2]; //save data as array
this.submittedData = data;
console.log(data);
},
},
};
</script>
EDIT
To avoid massive code you could use an array of objects as variable or nested array like this, then loop twice in template (nested v-for).
<template>
<div>
<div v-for="(quality, i) in slcQualities" :key="i">
<div v-for="(selection, j) in quality.values" :key="j">
<div>{{ selection.name }}</div>
<b-form-select
class="mb-2 mr-sm-2 mb-sm-0"
:options="quality.options"
v-model="selection.value"
#input="changeQuality"
/>
<div>slcQuality: {{ quality.value }}</div>
</div>
</div>
<div>
<button #click="submit">Submit</button>
</div>
<div>submitted Data: {{ JSON.stringify(submittedData) }}</div>
</div>
</template>
<script>
// array of data
const qualities = [
{
options: [
{ value: 1, text: "Original" },
{ value: 2, text: "Kw-1" },
{ value: 3, text: "Kw-2" },
],
values: [
{ name: "Select 1-1", value: null },
{ name: "Select 1-2", value: null },
],
},
{
options: [
{ value: 1, text: "Original" },
{ value: 2, text: "Kw-3" },
{ value: 3, text: "Kw-4" },
],
values: [
{ name: "Select 2-1", value: null },
{ name: "Select 2-2", value: null },
],
},
];
export default {
data() {
return {
slcQualities: qualities,
submittedData: [],
};
},
methods: {
changeQuality() {
console.log("test");
console.log(this.slcQuality);
},
submit() {
const data = this.slcQualities.map((i) => i.values.map((j) => j.value)); //map the values
this.submittedData = data;
console.log(data);
},
},
};
</script>
Here's the sandbox
I try to filter an object of array in Vue.js. have products collection in this vue component. I would like to filter this collection with select buttons. These products are food products and as default I would like to show all products but if I select the lactosefree button the I would like to show only products are lactosefree. In my database these options true or false. so for example if I have a cheese that lactose free then in the database I have a field lactosefree with value true.
I have tried to filter the array with computed property but I don't really know how to do it.
<div class="col-12 justify-content-between row filterbtn">
<label class="btn btn-primary">
<input v-model="selected" value="gluteinfree" type="checkbox" class="check2">GLUTEIN FREE
</label>
<label class="btn btn-primary">
<input v-model="selected" value="lactosefree" type="checkbox" class="check2">LAKTOZ FREE
</label>
</div>
<script>
export default{
data(){
return{
products: [
{ "id": 1, "productName": "Majomkenyérfa kivonat", "gluteinfree": true, "lactosefree": false, },
{ "id": 2, "productName": "Kókuszolaj", "gluteinfree": false, "lactosefree": true,},
{ "id": 3, "productName": "C-vitamin 80mg", "gluteinfree": true, "lactosefree": true, },
],
selected: [],
}
},
computed: {
//
},
}
</script>
As default I would like to show all the products. but when i click the gluteinfree select button I would like to show only the First and the last products where the gluteinfree is true.
Here is the code you can use for your computed. This will loop over all the products and compare each against a list of selected options
return this.products.filter(product => this.selected.every(selection => product[selection] === true));
note that it's using filter and every which for old browsers may require polyfills. You can can also convert to a more verbose for loop though.
Code:
new Vue({
el: '#app',
data() {
return {
products: [{
"id": 1,
"productName": "Majomkenyérfa kivonat",
"gluteinfree": true,
"lactosefree": false,
},
{
"id": 2,
"productName": "Kókuszolaj",
"gluteinfree": false,
"lactosefree": true,
},
{
"id": 3,
"productName": "C-vitamin 80mg",
"gluteinfree": true,
"lactosefree": true,
},
],
selected: [],
}
},
computed: {
zsir() {
return this.products.filter(prod => this.selected.every(sel => prod[sel] === true));
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="col-12 justify-content-between row filterbtn">
<label class="btn btn-primary">
<input v-model="selected" value="gluteinfree" type="checkbox" class="check2">GLUTEIN FREE</label>
<label class="btn btn-primary"><input v-model="selected" value="lactosefree" type="checkbox" class="check2">LAKTOZ FREE</label>
</div>
<ul>
<li v-for="prod in zsir" :key="prod.id">{{prod.productName}}</li>
</ul>
</div>
I have form and select components.
In fact things are simple: I need two binding model.
The parent component:
Vue.component('some-form', {
template: '#some-form',
data: function() {
return {
countryNameParent: ''
}
}
});
The child component with items:
Vue.component('countries', {
template: '#countries',
data: function () {
return {
items: {
"0": {
"id": 3,
"name": "Afghanistan"
},
"1": {
"id": 4,
"name": "Afghanistan2"
},
"2": {
"id": 5,
"name": "Afghanistan3"
}
},
countryName: ''
}
},
props: ['countryNameParent'],
created: function() {
var that = this;
this.countryName = this.countryNameParent;
},
methods: {
onChange: function (e) {
this.countryNameParent = this.countryName;
}
}
});
I'm using v-model to incorporate components above.
Templates like this:
<template id="some-form">
{{ countryNameParent }}
<countries v-model="countryNameParent"></countries>
</template>
<template id="countries">
<label for="">
<select name="name" #change="onChange" v-model="countryName" id="">
<option value="0">Select the country!</option>
<option v-for="item in items" v-bind:value="item.name">{{ item.name }}</option>
</select>
</label>
</template>
My target is getting data in parent component to send it to server (real form is much bigger), however I can't get the value of the countryName in countryNameParent. Moreover, Parent should setting data in successor if not empty.
Here you go link where I've been attempting to do it several ways (see commented part of it).
I know that I need to use $emit to set data correctly, I've even implemented model where I get image as base64 to send it by dint of the same form, hence I think solution is approaching!
Also: reference where I've built sample with image.
Here is your countries component updated to support v-model.
Vue.component('countries', {
template: `
<label for="">
<select v-model="countryName">
<option value="0">Select the country!</option>
<option v-for="item in items" v-bind:value="item.name">{{ item.name }}</option>
</select>
</label>
`,
data: function () {
return {
items: {
"0": {
"id": 3,
"name": "Afghanistan"
},
"1": {
"id": 4,
"name": "Afghanistan2"
},
"2": {
"id": 5,
"name": "Afghanistan3"
}
},
}
},
props: ['value'],
computed:{
countryName: {
get() { return this.value },
set(v) { this.$emit("input", v) }
}
},
});
v-model is just sugar for setting a value property and listening to the input event. So to support it in any component, the component needs to accept a value property, and emit an input event. Which property and event are used is configurable (documented here).