I have a slot containing radio buttons in a parent Vue3 component, I'm passing a v-model attribute to these radio buttons and the data model exists in the parent component. However when I change the selected radio button in the slot, the data in the parent component doesn't change.
parent template:
<template>
<div class="card">
<div class="card-body">
<slot
:type="type"
/>
</div>
</div>
</template>
parent vue:
<script>
export default {
data() {
return {
type: 'standard',
}
},
}
</script>
slot content:
<parent v-slot="slotProps">
<div class="row">
<label>
<span class="required">Type</span>
</label>
<label>
Standard Model
<input v-model="slotProps.type" type="radio" name="type" value="standard" required/>
</label>
<label>
Touch Model
<input v-model="slotProps.type" type="radio" name="type" value="touch" required/>
</label>
<label>
Display Model
<input v-model="slotProps.type" type="radio" name="type" value="display" required/>
</label>
</div>
</parent>
I do not think this is a good idea and this is also not recommended by the Vue team.
Anyway, if you really need to do it, as stated by posva from the Vue team, you can pass a method instead to change the value or you can pass an object and modify a property (keep in mind this is not recommended).Here is the object way to do it:
Parent:
<template>
<div class="card">
<div class="card-body">
<slot :myObject="myObject" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
myObject: {
type: "standard",
},
};
},
};
</script>
Slot content:
<parent v-slot="slotProps">
<div class="row">
<label>
<span class="required">Type</span>
</label>
<label>
Standard Model
<input
v-model="slotProps.myObject.type"
type="radio"
name="type"
value="standard"
required
/>
</label>
<label>
Touch Model
<input
v-model="slotProps.myObject.type"
type="radio"
name="type"
value="touch"
required
/>
</label>
<label>
Display Model
<input
v-model="slotProps.myObject.type"
type="radio"
name="type"
value="display"
required
/>
</label>
</div>
</parent>
Here is the method way to do it:
Parent:
<template>
<div>
<div>
<slot :type="type" :onTypeChange="onTypeChange" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
type: "touch",
};
},
methods: {
onTypeChange(event) {
this.type = event.target.value;
},
},
};
</script>
Slot content:
<parent v-slot="slotProps">
<div class="row">
<label>
<span class="required">Type</span>
</label>
<label>
Standard Model
<input
v-model="slotProps.type"
type="radio"
name="type"
value="standard"
required
#change="slotProps.onTypeChange"
/>
</label>
<label>
Touch Model
<input
v-model="slotProps.type"
type="radio"
name="type"
value="touch"
required
#change="slotProps.onTypeChange"
/>
</label>
<label>
Display Model
<input
v-model="slotProps.type"
type="radio"
name="type"
value="display"
required
#change="slotProps.onTypeChange"
/>
</label>
</div>
</parent>
Related
I am using custom page layouts for my nuxt3 app. When user selects a radio option, the URL changes to reflect the change (ie, 'solved', 'unsolved' radio options). This works good, however, the radio buttons are not shown for the checked item. For example, when user presses "solved tickets" radio option, the radio button is not shown for that item. What am I doing wrong?
components/AppFilter.vue
<template>
<section>
<div class="list-group">
<NuxtLink to="/" class="list-group-item list-group-item-action">
<input
class="form-check-input me-1"
type="radio"
name="listGroupRadio"
value="all"
id="firstRadio"
/>
<label class="form-check-label" for="firstRadio"
>All tickets/index page</label
>
</NuxtLink>
<NuxtLink to="/solved" class="list-group-item list-group-item-action">
<input
class="form-check-input me-1"
type="radio"
name="listGroupRadio"
value="solved"
id="secondRadio"
/>
<label class="form-check-label" for="secondRadio">Solved Tickets</label>
</NuxtLink>
<NuxtLink to="/unsolved" class="list-group-item list-group-item-action">
<input
class="form-check-input me-1"
type="radio"
name="listGroupRadio"
value="unsolved"
id="thirdRadio"
/>
<label class="form-check-label" for="thirdRadio"
>Unsolved Tickets</label
>
</NuxtLink>
</div>
{{ picked }}
</section>
</template>
Stackblitz reproduction: https://stackblitz.com/edit/nuxt-starter-dmx5zu?file=components/AppFilter.vue
Pretty funky structure IMO but that one works fine
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const picked = ref([])
const updateRadioAndMove = ({ radio, newPath }) => {
picked.value = radio.target.value
router.push(newPath)
}
</script>
<template>
<section>
<br/>
picked radio button >> {{ picked }}
<br/>
<br/>
<div class="list-group">
<input
#change="updateRadioAndMove({ radio: $event, newPath: '/' })"
class="form-check-input me-1"
type="radio"
name="listGroupRadio"
value="all"
id="firstRadio"
/>
<label class="form-check-label" for="firstRadio"
>All tickets/index page</label
>
<input
#change="updateRadioAndMove({ radio: $event, newPath: '/solved' })"
class="form-check-input me-1"
type="radio"
name="listGroupRadio"
value="solved"
id="secondRadio"
/>
<label class="form-check-label" for="secondRadio">Solved Tickets</label>
<input
#change="updateRadioAndMove({ radio: $event, newPath: 'unsolved' })"
class="form-check-input me-1"
type="radio"
name="listGroupRadio"
value="unsolved"
id="thirdRadio"
/>
<label class="form-check-label" for="thirdRadio"
>Unsolved Tickets</label
>
</div>
</section>
</template>
VueJS emit doesn't work, I'm trying to change the value of a boolean, but it doesn't want to emit the change
here is the first component:
<template>
<div id="reg">
<div id="modal">
<edu></edu>
</div>
<div :plus="plus" v-if="plus" #open="plus = !plus">
abc
</div>
</div>
</template>
the script:
<script>
import edu from './education/edu.vue'
export default {
components: {
edu
},
data() {
return {
plus: false
}
}
}
</script>
the second component with the emit:
<template>
<div id="container">
<h2>Education</h2>
<form action="">
<input type="text" class="input" placeholder="preparatory/bachelor/engineering..">
<input type="text" class="input" placeholder="University..">
<input type="text" class="input" placeholder="Where?..">
<h6>Start date</h6>
<input type="date" class="input" value="2017-09-15" min="2017-09-15" max="2021-09-15">
<div class="end-date">
<h6>End date</h6>
<div class="flexend">
<h6>present</h6><input type="checkbox" name="" id="checkbox" #click="checked">
</div>
</div>
<input type="date" class="input" v-if="show" value="2017-09-15" min="2017-09-15">
<div class="flex-end">
<button class="btn-plus" #click="$emit('open')"><i class="fas fa-plus"></i></button>
</div>
<div class="flex-end">
<button class="btn">next <i class="fas fa-arrow-right"></i></button>
</div>
</form>
</div>
</template>
the script:
<script>
export default {
props:{
plus: Boolean
},
data(){
return{
show: true,
}
},
I thought this is working, but it seems like it's not
You should use the component name instead of the div element :
<edu :plus="plus" v-if="plus" #open="plus = !plus">
abc
</edu>
So, currently, I have a set of radio buttons on my home.vue page and binding to a child component like so -
<div class="radio-toolbar prev-selector">
<input type="radio" id="one" value="Default" v-model="preview" />
<label for="one">Default</label>
<input type="radio" id="two" value="Padded" v-model="preview" />
<label for="two">Padded</label>
<input type="radio" id="three" value="Full" v-model="preview" />
<label for="three">Full</label>
<span>Picked: {{ preview }}</span>
</div>
<div class="media-wrapper">
<CardStyle
v-bind:card="this.preview"
/>
</div>
Which is then passing that "preview" data to the CardStyle Prop.
The prop is receiving it as ['card'] and I can use the data within my component.
However, am wondering how I can use the potential value of "this.preview" (which could be 'default', 'padded' or 'full') as a dynamic class to my child component without having to convert them to a true/false outcome and use -
:class="{ default: isDefault, padded: isPadded, full: isFull }"
(I have classes called .default, .padded and .full ready to use if it is as simple as passing the data in somehow.)
As long as this.preview has a value of the class name you want to use, just use :class="this.preview".
I built a couple of sample components that demonstrate how to solve your problem.
Parent.vue
<template>
<div class="parent">
<h5>Select Card Style</h5>
<div class="row">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="radio" id="one" value="default" v-model="preview" />
<label class="form-check-label" for="one">Default</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="two" value="padded" v-model="preview" />
<label class="form-check-label" for="two">Padded</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="three" value="full" v-model="preview" />
<label class="form-check-label" for="three">Full</label>
</div>
</div>
</div>
<card-style :preview="preview" />
</div>
</template>
<script>
import CardStyle from './CardStyle.vue'
export default {
components: {
CardStyle
},
data() {
return {
preview: 'default'
}
}
}
</script>
CardStyle.vue
<template>
<div class="card-style">
<hr>
<div class="row">
<div class="col-md-6">
<span :class="preview">{{ preview }} Card Style</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
preview: {
type: String,
required: true
}
},
}
</script>
<style scoped>
.default {
background-color: burlywood;
}
.padded {
background-color:lightskyblue
}
.full {
background-color:lightcoral
}
</style>
You can build a computed variable for your field class in your component. This will allow you to select or define what classes to use based on your conditions or case.
computed: {
myclasses() {
return [
'class1',
'class2',
[this.condition ? 'class3' : 'class4'],
{ class5: this.othercondition },
];
},
},
Then, just use it your template:
<div :class="myclasses"></div>
I have a form which contains name and address components. In the parent's page I have a submit button. I can send data from the parent to the children using props.
Now I am trying to get the children's values from the parent's form. And I want to validate child fields from the parent's form.
How to acheive this?
Here is my form structure.
parent.vue
<form #submit.prevent="handleSubmit">
<name-fields :user='user'></name-fields>
<address-fields :address='address'></address-fields>
<button>Register</button>
</form>
<script>
export default {
data () {
return {
user: {
firstName: 'Raja',
lastName: 'Roja',
},
address: {
doorNo: '11',
state: 'KL',
country: 'IN',
},
submitted: false
}
},
components: {
'name-fields': cNameFields,
'address-fields': cAddressFields,
},
}
</script>
cNameFields.vue
<template>
<div>
<div class="form-group">
<label for="firstName">First Name</label>
<input type="text" v-model="user.firstName" v-validate="'required'" name="firstName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('firstName') }" />
<div v-if="submitted && errors.has('firstName')" class="invalid-feedback">{{ errors.first('firstName') }}</div>
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input type="text" v-model="user.lastName" v-validate="'required'" name="lastName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('lastName') }" />
<div v-if="submitted && errors.has('lastName')" class="invalid-feedback">{{ errors.first('lastName') }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'name',
props: {
user: Object,
submitted : Boolean
},
</script>
Currently getting this output:
What I want to do:
Use this.$emit https://v2.vuejs.org/v2/api/#vm-emit
and also use watch https://v2.vuejs.org/v2/api/#vm-watch
So in child component you should watch for changes in user.firstName and user.lastName. Call emit in the watch and get the value in parent. Don't forget to also emit the this.errors bag which comes from vee-validate.
Hope this will help you :)
You are passing objects as props to your children, which are passed by reference in JavaScript. From the Vue docs ..
Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child component will affect parent state.
This means that you're already getting the children's values in the parent and you can access them in the parent through this.user.firstName,this.user.lastName, this.address.doorNo, etc. If this isn't the intended behavior and you want to keep your parent's data isolated then you should look into deep cloning your objects.
If you want to expose your validation errors from your child components to the parent you can look into Scoped Slots. So you may do something like this ..
parent.vue
<form #submit.prevent="handleSubmit">
<name-fields :user='user'>
<span slot="firstName" slot-scope="{validationErrors}" style="color:red">
{{validationErrors.first('firstName')}}
</span>
<span slot="lastName" slot-scope="{validationErrors}" style="color:red">
{{validationErrors.first('lastName')}}
</span>
</name-fields>
<address-fields :address='address'>
<span slot="doorNo" slot-scope="{validationErrors}" style="color:red">
{{validationErrors.first('doorNo')}}
</span>
<span slot="state" slot-scope="{validationErrors}" style="color:red">
{{validationErrors.first('state')}}
</span>
<span slot="country" slot-scope="{validationErrors}" style="color:red">
{{validationErrors.first('country')}}
</span>
</address-fields>
<button>Register</button>
</form>
cNameFields.vue
<template>
<div>
<div class="form-group">
<label for="firstName">First Name</label>
<input type="text" v-model="user.firstName" v-validate="'required'" name="firstName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('firstName') }" />
<slot name="firstName" :validationErrors="errors"></slot>
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input type="text" v-model="user.lastName" v-validate="'required'" name="lastName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('lastName') }" />
<slot name="lastName" :validationErrors="errors"></slot>
</div>
</div>
</template>
This is a great video that explains how scoped slots work.
I am trying to check by default the Mrs radio button... but it does not work ... I also tried to add a checked attribute wo any success //
what could be wrong with my coding ?
<div class="col-lg-9">
<form>
<!-- Full name -->
<div class="input-group input-group-lg mb-3">
<div class="input-group-prepend">
<div class="input-group-text pb-0">
<label class="form-group-label active"><input v-model="gender" type="radio" value="Mrs"> Mrs</label>
</div>
<div class="input-group-text pb-0">
<label><input v-model="gender" type="radio" name="gender" value="Mr"> Mr</label>
</div>
</div>
<input v-model="username" #input="$v.username.$touch" v-bind:class="{ error: $v.username.$error, valid: $v.username.$dirty && !$v.username.$invalid }" type="text" class="form-control" placeholder="Indiquez votre prénom et votre nom">
</div>
</form>
</div>
This is how you might do it. I've added the toggle button to show how this binding works:
new Vue({
el: '#app',
data: {
radio: 'mrs',
},
methods: {
toggle() {
this.radio = this.radio === 'mrs' ? 'mr' : 'mrs';
}
},
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<input v-model="radio" type="radio" value="mrs">
<label>Mrs</label>
<input v-model="radio" type="radio" value="mr">
<label>Mr</label>
<button #click="toggle">Toggle</button>
</div>
EDIT: Snippet fixed and updated
You need have the value for gender set in the data model.
https://www.codeply.com/go/KDKbm4PTBO
<label class="form-group-label active">
<input v-model="gender" type="radio" value="Mrs"> Mrs
</label>