for me NuxtJS application i've got multiple components thats come together in one form. Here the code:
AppButton.vue
<template>
<button
class="button"
:class="btnStyle"
:disabled="disabled"
v-bind="$attrs"
v-on="$listeners"><slot /></button>
</template>
<script>
export default {
name: 'AppButton',
props: {
btnStyle: {
type: String,
default: ''
},
disabled: {
type: String
}
}
}
</script>
AppFormInput.vue
<template>
<div class="form-input">
<input
v-bind="$attrs"
:name="name"
:value="value"
:type="type"
:placeholder="placeholder"
:max="max"
:min="min"
:pattern="pattern"
:required="required"
:disabled="disabled"
#input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
name: 'AppFormInput',
props: {
controlType: {
type: String,
default: 'input'
},
name: {
type: String
},
value: {
type: String,
default: ''
},
type: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
max: {
type: String
},
min: {
type: String
},
pattern: {
type: String
},
required: {
type: String,
},
disabled: {
type: String
}
}
}
</script>
FormGroup.vue
<template lang="html">
<div class="form-group">
<AppLabel :label="label"/>
<AppFormInput :v-bind="$attrs"/>
</div>
</template>
<script>
import AppLabel from '~/components/atoms/AppLabel'
import AppFormInput from '~/components/atoms/AppFormInput'
export default {
components: {
AppLabel,
AppFormInput
},
props: {
label: {
type: String,
default: 'Form Label'
}
}
}
</script>
Form.vue
<template lang="html">
<form #submit.prevent="onSave">
<FormGroup label="Form Input" v-model="formPosts.forminput"/>
<FormGroup label="Form Input Disabled" disabled="disabled" v-model="formPosts.forminputdisabled"/>
<FormGroup label="Form Input With Placeholder" placeholder="Set placeholder" v-model="formPosts.forminputplaceholder"/>
<FormGroup label="Form Input Required" required="required" v-model="formPosts.forminputrequired"/>
<FormGroup label="Form Email" type="email" v-model="formPosts.forminputemail"/>
<FormGroup label="Form Date" type="date" v-model="formPosts.forminputdate"/>
<FormGroup label="Form Number" type="number" value="1" min="1" max="5" v-model="formPosts.forminputnumber"/>
<FormGroup label="Form Tel" type="tel" pattern="\d{3}-\d{3}-\d{4}" placeholder="XXX-XXXX-XXXX" v-model="formPosts.forminputtel"/>
<!--Add Select normal and disabled-->
<FormGroup label="Form Radio" type="radio" value="1" v-model="formPosts.forminputradio"/>
<FormGroup label="Form Checkbox" type="checkbox" value="2" v-model="formPosts.forminputcheckbox"/>
<AppButton type="submit" btn-style="btn-brand">Save</AppButton>
</form>
</template>
<script>
import FormGroup from '~/components/molecules/FormGroup'
import AppButton from '~/components/atoms/AppButton'
export default {
components: {
FormGroup,
AppButton
},
data() {
return{
formPosts: {
forminput: '',
forminputdisabled: '',
forminputplaceholder: '',
forminputrequired: '',
forminputemail: '',
forminputdate: '',
forminputnumber: '',
forminputtel: '',
forminputradio: '',
forminputcheckbox: '',
}
}
},
methods: {
onSave() {
console.log(this.formPosts);
this.$emit('submit', this.formPosts)
},
}
}
</script>
At least, the index.vue
<template lang="html">
<div class="container">
<h1>Forms</h1>
<Form #submit="onSubmitted"/>
</div>
</template>
<script>
import Form from '~/components/organism/Form'
export default {
components: {
Form
},
methods: {
onSubmitted(data){
console.log(data);
}
}
}
</script>
when the form is submitted the fields stays empty. The required field must have a value for example but it stays empty. I think that the some components not have access through the value of the field. Does anyone have tips?
Thanx for any help
$attrs (in v-bind="$attrs") will only bind attributes, not props (bold is mine):
vm.$attrs: Contains parent-scope attribute bindings (except for class and style) that are not recognized (and extracted) as props. When a component doesn't have any declared props, this essentially contains all parent-scope bindings (except for class and style), and can be passed down to an inner component via v-bind="$attrs" - useful when creating higher-order components.
You need to set the props yourself in FormGroup.vue.
In FormGroup.vue, declare the value prop so you can use in the template:
<script>
import AppLabel from '~/components/atoms/AppLabel'
import AppFormInput from '~/components/atoms/AppFormInput'
export default {
components: {
AppLabel,
AppFormInput
},
props: {
label: {
type: String,
default: 'Form Label'
},
value: { // added this
type: String // added this
} // added this
}
}
</script>
In FormGroup.vue, Add :value="value" #input="$emit('input', $event)" to the template.
<template lang="html">
<div class="form-group">
<AppLabel :label="label"/>
<AppFormInput :v-bind="$attrs" :value="value" #input="$emit('input', $event)" />
</div>
</template>
The code above will set <AppFormInput>'s value and will propagate (up) it's input event. Not the v-models in Form.vue should work.
Related
this question is related to Two way data binding with Vuex-ORM
i tried using a watch with deep to handle a user form like this.
<template>
<div id="app">
<div style="display: inline-grid">
<label for="text-1">Text-1: </label>
<input name="text-1" type="text" v-model="user.name" />
<label for="text-2">Text-2: </label>
<input name="text-2" type="text" v-model="user.lastName" />
<label for="text-3">Text-3: </label>
<input name="text-3" type="text" v-model="user.birth" />
<label for="text-4">Text-4: </label>
<input name="text-4" type="text" v-model="user.hobby" />
</div>
<div>
<h5>Result</h5>
{{ userFromStore }}
</div>
</div>
</template>
<script>
import { mapGetters, mapMutations, mapActions } from "vuex";
export default {
name: "App",
computed: {
...mapGetters({
userFromStore: "getUserFromStore",
messageFromStore: "getMessage",
}),
user: function () {
return this.userFromStore ?? {}; // basically "User.find(this.userId)" inside store getters
},
},
watch: {
user: {
handler(value) {
console.log('called')
// this.updateUser(value);
},
deep: true,
},
},
methods: {
...mapActions({
fetchUser: "fetchUser",
}),
...mapMutations({
updateUser: "updateUser",
}),
},
created() {
this.fetchUser();
},
};
</script>
problem is my watcher is not watching, no matter what i try. as soon as the data came from Vuex-ORM my component is not able to watch on the getters user
Anyone idea why?
User.find(...) returns a model. The properties of that model are not reactive i.e. you cannot perform two-way data binding on items that are not being tracked. Hence your watcher will not trigger.
My advice would be to push your user data as props to a component that can handle the data programmatically.
Or, by way of example, you can simply handle two-way binding manually:
Vue.use(Vuex)
class User extends VuexORM.Model {
static entity = 'users'
static fields() {
return {
id: this.number(null),
name: this.string(''),
lastName: this.string(''),
birth: this.string(''),
hobby: this.string('')
}
}
}
const db = new VuexORM.Database()
db.register(User)
const store = new Vuex.Store({
plugins: [VuexORM.install(db)]
})
User.insert({
data: {
id: 1,
name: 'John',
lastName: 'Doe',
birth: '12/12/2012',
hobby: 'This, that, the other'
}
})
Vue.component('user-input', {
props: {
value: { type: String, required: true }
},
template: `<input type="text" :value="value" #input="$emit('input', $event.target.value)" placeholder="Enter text here...">`
})
new Vue({
el: '#app',
computed: {
user() {
return User.find(1)
}
},
methods: {
update(prop, value) {
this.user.$update({
[prop]: value
})
}
}
})
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex#3.6.2/dist/vuex.min.js"></script>
<script src="https://unpkg.com/#vuex-orm/core#0.36.4/dist/vuex-orm.global.prod.js"></script>
<div id="app">
<div v-if="user" style="display: inline-grid">
<label for="text-1">Name: </label>
<user-input
id="text-1"
:value="user.name"
#input="update('name', $event)"
></user-input>
<label for="text-2">Last name: </label>
<user-input
id="text-2"
:value="user.lastName"
#input="update('lastName', $event)"
></user-input>
<label for="text-3">D.O.B: </label>
<user-input
id="text-3"
:value="user.birth"
#input="update('birth', $event)"
></user-input>
<label for="text-4">Hobby: </label>
<user-input
id="text-4"
:value="user.hobby"
#input="update('hobby', $event)"
></user-input>
</div>
<pre>User in store: {{ user }}</pre>
</div>
I want to create a formRow component that have a slot for the input field and vee-validate for validation
this is my markup
//form
<vFormRow validate="required" v-slot="{ onInput, errors }">
<vTextfield #input="onInput" :errors="errors"/>
</vFormRow>
I tried to emit the value to the parent and get the value with #input="onInput" on the slot, but this doesn't work
//formrow
<template>
<div class="mb-4">
<v-validation-provider
v-slot="{ errors }"
:rules="validate"
>
<label>{{ label }}</label>
<slot
:onInput="onInput"
:errors="errors"
/>
<vFormError
:message="errors[0]"
/>
</v-validation-provider>
</div>
</template>
<script>
import { ValidationProvider as vValidationProvider } from 'vee-validate'
import vRadioButton from '#primitives/textfield'
import vFormError from '#primitives/formError'
export default {
components: {
vTextfield,
vFormError,
vValidationProvider
},
props: {
value: {
type: String,
default: '',
},
validate: {
type: String,
required: true,
}
},
methods: {
onInput(value) {
console.log(value)
}
}
}
</script>
//textfield
<template>
<div>
<input :value="value" #input="onInput"/>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '',
},
errors: {
type: Array,
default: () => {}
}
},
data: {
return {
// local_value: ''
}
}
methods: {
onInput(e) {
// this.local_value = e.target.value
this.$emit('input', e.target.value)
}
}
}
</script>
What I'm doing wrong?
You're not actually passing the slot props to <vTextfield>. I think you might be assuming that data bindings and event handlers on the <slot> are automatically applied to the slot's children, but that's not true.
To use the value slot prop, destructure it from v-slot, and bind it to the <vTextfield> child:
<vFormRow validate="required" v-slot="{ value }">
<vTextfield :value="value" />
</vFormRow>
You could also pass the onInput method as a slot prop:
// vFormRow.vue
<slot :onInput="onInput" />
Then bind the event handler to <vTextfield>:
<vFormRow validate="required" v-slot="{ onInput }">
<vTextfield #input="onInput" />
</vFormRow>
I found some code on the internet to develop an input component. The component works great. However, since there is no two-way binding between the parent and the child. I'm wondering what this.$emit("change", value); does. How does this update the parent? I'm not putting 1 and 2 together here. Thank you in advance.
<template>
<div>
<div class="form-control-label" v-if="label">{{ label }}</div>
<div v-for="(option, index) in options" :key="option.text">
<div class="custom-control custom-radio">
<input
:id="id + index"
:name="id"
type="radio"
:value="option.value"
:checked="option.value === value"
:class="inputClass"
class="custom-control-input"
:disabled="disabled"
:required="required"
#change="updateValue(option.value)"
/>
<label :for="id + index" class="custom-control-label">{{
option.text
}}</label>
<slot v-if="option.value === value" :name="option.value" />
</div>
</div>
<validation-provider v-slot="{ errors }" :name="label" rules="required">
<input type="hidden" v-model="selected" />
<slot name="error">
<div v-if="errors[0]" class="invalid-feedback" style="display: block;">
{{ errors[0] }}
</div>
</slot>
</validation-provider>
</div>
</template>
<script>
export default {
model: {
event: "change",
selected: null,
},
props: {
rules: {
type: String,
required: false,
},
id: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
value: {
type: [String, Number, Boolean, Object],
default: null,
},
options: {
type: [Array],
required: true,
},
required: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
inputClass: {
type: [String, Object],
default: "",
},
},
methods: {
updateValue(value) {
this.selected = value;
this.$emit("change", value);
},
},
};
</script>
Normally, v-model works by the component receiving a value prop and emitting an input event that contains the updated value.
Your component implements v-model by taking a value prop, and emitting a change event (instead of input event), as specified by the model option. Whenever the component emits the change event in updateValue(), the component's consumer sets the v-model variable to the event's value.
In the following example, selectedOption is set to "my value" when the radio option in MyOptions is selected:
// App.vue
<MyOptions v-model="selectedOption" />
// MyOptions.vue
<input type="radio" #change="$emit('change', 'my value')">
I am trying to learn some vuejs and am struggling to understand how to pass data from a parent component to its child component. I know more is required but i'm not sure which way to go. How do pass the name in an input field in the parent component when the submit button is pressed to display in the child component?
I have tried using v-model because from what i have read and understand it is supposed to do what i need but it updates it without me even needing to press the button.
//Parent component
<template>
<div id="app">
<form #submit.prevent="handleSubmit">
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
<input type="submit" value="Submit Name">
</form>
<Name lname="lname" fname="fname"></Name>
</div>
</template>
<script>
import Name from './components/fullName.vue'
export default {
name: 'app',
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
components: {
Name
},
methods: {
handleSubmit() {
submittedFname = fname,
submittedLname = lname
}
}
}
</script>
//child component
<template>
<div id="my-name">
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>
</template>
<script>
export default {
name: 'my-name',
data () {
return {
}
},
props: {
submittedFname: String,
submittedLname: String
}
}
</script>
I am expecting to display the full name on the child component when the button is pressed but instead it is displayed as i am typing it.
//Parent component
<template>
<div id="app">
<form>
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
</form>
<button #click="handleSubmit(fname,lname)">submit</button>
<Name :submittedFname="submittedFname" :submittedLname="submittedLname" ></Name>
</div>
</template>
<script>
import Name from './components/fullName.vue'
export default {
name: 'app',
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
components: {
Name
},
methods: {
handleSubmit(fname,lname) {
this.submittedFname = fname,
this.submittedLname = lname
}
}
}
</script>
//child component
<template>
<div id="my-name">
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>
</template>
<script>
export default {
name: 'my-name',
data () {
return {
}
},
props: {
submittedFname: String,
submittedLname: String
}
}
</script>
in case I forgot some things here are screenshots:
Parent component
child component
v-model means that the fname and lname instance data properties are updated each time the value of their respective input elements changes (it uses the input event behind the scenes). You then pass fname and lname directly as props to the child component. These props are reactive so it behaves as you see and the name is updated as you type.
To only change the name when submit is pressed, you can do this:
Add 2 more data properties in the parent component (e.g. submittedfname and submittedlname)
Add an #submit event listener on the form that copies the values from fname and lname to submittedfname and submittedlname
Use submittedfname and submittedlname as props for the child component.
Working code:
//Parent component
Vue.component('app', {
template: `
<div>
<form #submit.prevent="handleSubmit">
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
<input type="submit" value="Submit Name">
</form>
<name-comp :submittedFname="submittedFname" :submittedLname="submittedLname"></Name>
</div>`,
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
methods: {
handleSubmit() {
this.submittedFname = this.fname;
this.submittedLname = this.lname;
}
}
});
//child component
Vue.component('name-comp', {
template: `
<div>
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>`,
props: {
submittedFname: String,
submittedLname: String
}
});
var vapp = new Vue({
el: '#app',
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<app />
</div>
You were missing ":" in front of your props given to the Name component. Also you didn't use this like in this.lname.
I currently have a custom component with v-validate on the parent. I would like to abstract out the "fields.email && fields.email.touched ? errors.first('email') : ''" into it's own method, preferably on a mixin. I have tried creating a method that takes in a name and returns the result of the above, this does not seem to work due to methods not being reactive. I have also tried creating a computed property, but cannot seem to make one that is dynamic. A solution that may work is dynamically creating computed properties, however: i believe this is not possible.
Here is my code:
InputField.vue:
<template>
<label :for='name' :class="{error: error}">
{{ label }}
<input
class='input'
:value="value"
:disabled="disabled"
:name="name"
:type="type"
:placeholder="placeholder"
#input="$emit('input', $event.target.value)"
#change="$emit('change', $event.target.value)"
#blur="$emit('blur')"
/>
<p v-if="error">{{ error }}</p>
</label>
</template>
<script>
export default {
props: {
label: String,
name: String,
type: String,
value: [String, Number],
placeholder: [String, Number],
disabled: {
type: Boolean,
default: false
},
error: String
}
};
</script>
LoginModal.vue
<template>
<div class='login-modal-inner'>
<div class='left-box form-wrapper'>
<h3>Login</h3>
<form #submit.prevent="submitForm('login')">
<input-field
v-model.trim="login.email"
name='email'
label='Email'
type='email'
v-validate="'required|email'"
:error="fields.email && fields.email.touched ? errors.first('email') : ''"
/>
<input-field
v-model="login.password"
name='password'
label='Password'
type='password'
v-validate="'required'"
/>
<app-button class='submit'>Log In</app-button>
</form>
</div>
...
</div>
</template>
<script>
import InputField from "../UI/input/InputField.vue";
import Button from "../UI/input/Button.vue";
import InputValidationError from "../UI/input/Button.vue";
export default {
data() {
return {
register: {
email: "",
password: "",
confirmPassword: ""
},
login: {
email: "",
password: ""
}
};
},
methods: {
submitForm(type) {
this.submitClicked = true;
this.$validator.validate();
this.$emit(`${type}-submit`, this[type]);
},
},
components: {
InputField,
appButton: Button
}
};
</script>