I'm building a component which takes in multiple input fields using a slot, when the user submit's the form inside the parent component, I want to output the values of all the inputs.
index.html
<filter-form>
<input type="text" name="email" :value="form.email" />
</filter-form>
FilterForm.vue
<template>
<div>
<form
#submit.prevent="onSubmit"
>
<slot />
<div>
<button>
Submit
</button>
</div>
</form>
</div>
</template>
<script>
export default {
data () {
return {
form: {
email: 'test#email.com'
}
}
}
}
</script>
As you can see I'm referencing "form.email" inside index.html, which should populate the input with the data in the FilterForm component, however that's throwing this error
Property or method "form" is not defined on the instance but referenced during render
Which makes sense since the form data isn't available in the index.html file.
How can I use this system so that the data for the form is kept in the FilterForm component, but I can add any numbe of form inputs to the index.html file.
As #User 28 mentioned, you can take advantage of slots and scoped-slots to get this to work with an arbitrary number of fields like so:
<template>
<div id="app">
<FormWrapper :names="['name', 'country']" #submit="process($event)">
<template #default="{ form }">
<input name="name" type="text" v-model="form.name">
<input name="country" type="text" v-model="form.country">
</template>
</FormWrapper>
</div>
</template>
<script>
import FormWrapper from "./components/FormWrapper";
export default {
name: "App",
components: {
FormWrapper
},
methods: {
process(values) {
console.log(values);
}
}
};
</script>
Here, FormWrapper can nest arbitrary input fields. Some key points:
You need to provide names prop with some unique IDs for fields you care about.
When form inside FormWrapper is submitted, a submit event will be triggered with the values sent as an object.
Also note that we pass data for our slots in a form object and bind it to our inputs via v-model.
Here is the FormWrapper component:
<template>
<div class="form-wrapper">
<slot :form="form"></slot>
<div class="controls">
<button #click="submit()">Submit</button>
</div>
</div>
</template>
<script>
export default {
props: {
names: Array
},
data() {
return {
form: {}
};
},
created() {
for (const name of this.names) {
this.$set(this.form, name, "");
}
},
methods: {
submit() {
this.$emit("submit", this.form);
}
}
};
</script>
You can check https://codesandbox.io/s/so-form-wrapper-yxz0i for a working example.
Vue has a feature called Scoped Slots which might solve your problem.
<filter-form>
<template v-slot='slotProps'>
<input :value='slotProps.form.email' />
</template>
</filter-form>
and
<div>
<form #submit.prevent=''>
<slot :form='form'/>
<div>
<button>Submit</button>
</div>
</form>
</div>
Example
Related
I'm using the latest version of Vue 3, with no other vendors.
It may be a bug, or it is an expected behaviour, but I lost a couple of hours finding why my form component was firing the submit event twice.
It fact, if you listen to #submit on your form, and then $emit('submit') so you can listen to this event on the parent component, the event will be fired twice.
More than words, here is a pen to show this: https://codepen.io/rekam/pen/dyeqxyy
const { createApp } = Vue
const TestForm = {
template: `
<form #submit.prevent.stop="onSubmit">
<input type="text" />
<input type="submit" value="in component" />
</form>
`,
props: {
eventName: { type: String }
},
methods: {
onSubmit() {
this.$emit(this.eventName)
}
}
}
createApp({
template: `
<div>
<TestForm event-name="submit" #submit="() => onSubmit('with event name [submit]')" />
<TestForm event-name="other" #other="() => onSubmit('with event name [other]')" />
<blockquote>
<p v-for="(log, i) in logs" :key="i">{{ log }}</p>
</blockquote>
<input type="button" value="clear" #click="logs = []" />
</div>
`,
components: { TestForm },
data: () => ({
logs: []
}),
methods: {
onSubmit(msg) {
console.log(msg)
this.logs.push(msg)
}
}
}).mount('#app')
I can live with that, I just need to give a specific name to my own submit event. My question is: why is this happening and is it expected?
EDIT
I just found that wrapping the form tag in a div get rid of the second submit event. To have the submit event fired twice, you need:
your form beign the top level dom element in your component
emit an event named "submit"
You dont need to add the event #submit.prevent.stop="onSubmit"
in you're child component because it will be inherited from the parent component';
template: `
<form>
<input type="text" />
<input type="submit" value="in component" />
</form>
`,
template: `
<div>
<TestForm event-name="submit" #submit.prevent="() => onSubmit('with event name [submit]')" />
<TestForm event-name="other" #submit.prevent="() => onSubmit('with event name [other]')" />
<blockquote>
<p v-for="(log, i) in logs" :key="i">{{ log }}</p>
</blockquote>
<input type="button" value="clear" #click="logs = []" />
</div>
`,
https://codepen.io/keyXpert/pen/dyegYQB
you call submit twice with an eventlistener you created from emit and another from #submit you added to your form tag. you should use one of them to submit your form.
The main problem is in the title. My guess would be that the ref() and reactive() are somehow messing with each other, I just don't know how and what I could do about it.
The component using the store:
<script setup>
import { ref } from "vue";
import { store } from "#/store.js";
const game = ref({
date: null,
location: null,
length: null,
});
</script>
<template>
<form #submit.prevent="store.addGame(game)">
<input v-model="game.date" type="date" required />
<input v-model="game.location" type="text" placeholder="Location" required />
<input v-model="game.length" type="number" placeholder="Length (in minutes)" required />
<button class="button" type="submit">Submit ✔</button>
</form>
</template>
The problem occurs in the store, in the games array, after I add a second or more items...
store.js:
import { reactive } from "vue";
export const store = reactive({
games: [],
addGame(game) {
this.games.push(game);
console.log(this.games);
}
});
The reason is because all the objects that you push inside games share the same reference. To fix this, do this change in store.js
import { reactive } from "vue";
export const store = reactive({
games: [],
addGame(game) {
this.games.push({...game}); // line changed
console.log(this.games);
}
});
It's not "replace", all the game reference to the same reactive variable defined in const game = ref().
Whenever you call store.addGame(game), you added the same game to the store. Because the game is reactive, is will automatic implement the latest input value by v-model.
If you put another button to check the value in store. Change the input value, and then click Test button, you can see the store.games changes too.
<template>
<form #submit.prevent="store.addGame(game)">
<input v-model="game.date" type="date" required />
<input v-model="game.location" type="text" placeholder="Location" required />
<input v-model="game.length" type="number" placeholder="Length (in minutes)" required />
<button class="button" type="submit">Submit ✔</button>
</form>
<button #click="console.log(store.games)">Test</button>
</template>
I want to pass two Id's alongside a users input. I'm putting those Id's in a hidden input. I already understand that the v-model does not work with hidden inputs. That's why my hidden input looks like this
<input type="hidden" ref="property_id" :name="form.property_id" :value="property_id"><
The following is my full block of code:
<template>
<div>
<form #submit.prevent="submit()">
<div v-for="(property, propIndex) in properties" :key="propIndex">
{{ property.name }}
<div v-for="(house_type, typeIndex) in property.house_type.data" :key="typeIndex">
{{ house_type.type }}<br>
<input type="text" v-model="rent[propIndex][typeIndex]">Rent<br>
<input type="text" v-model="house_quantity[propIndex][typeIndex]">How many<br>
<input type="hidden" ref="property_id" :name="form.property_id" :value="property_id"><br>
<input type="hidden" ref="house_type_id" :name="form.house_type" :value="house_type.type"><br>
</div>
<br>
</div>
<button>Submit</button>
</form>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
data() {
return {
rent:[{}, {}, {}, {}, {}, {}],
house_quantity:[{}, {}, {},{}, {}, {},{}, {}, {}],
form:{
property_id: [],
house_type: [],
}
}
},
}
</script>
How do I get the values of the hidden inputs so that I can submit them alongside the rest of the data.
methods: {
async submit(){
this.form.rent = this.rent
this.form.house_quantity = this.house_quantity
await axios.post('/api/landlord/set/up/store/part/3', this.form)
}
},
If I got it right, you can use v-bind to bring the values to the input and make a method or computed property that gets the values back to this.form.property_id and this.form.house_type when input #blur or #change, for example.
This would update data back even without having the v-model interactivity you need.
Is it possible to add a vue input component to a standard html form? I know this is not the ideal way to handle vue forms, but I'm curious about the possibility as a "quick and dirty" way for adding custom elements into pre existing html forms. Here's a hypothetical example:
<form action="/users" accept-charset="UTF-8" method="post">
<input type="email" name="user[email]" />
<input type="password" name="user[password]" />
<my-custom-fancy-vue-component />
<input type="submit"value="Sign up">
</form>
I'm wondering if the browser can read the value exposed by an input element withen the vue component and send that as a param when the user submit the form. Is there any other way to tell the browser how to access the value from a vue component if for example it doesn't use a native input internally, perhaps using a web component as a wrapper or using shadow dom?
Any <input> elements within the form should be included by the browser when the form is submitted. The browser won't care that the <input> is inside a Vue component.
For components that don't already have an <input> (or other suitable form element) you can add a hidden input, <input type="hidden">, to hold the value.
If the component you want to include is a third-party component then you won't be able to add the hidden input directly. However, you could still add it by using a wrapper component. The example below illustrates how you could handle that scenario.
const thirdPartyComponent = {
template: `
<button
#click="onClick"
type="button"
>
Increment {{ value }}
</button>
`,
props: ['value'],
methods: {
onClick () {
this.$emit('input', this.value + 1)
}
}
}
const myCustomFancyVueComponent = {
template: `
<div>
<third-party-component v-model="counter" />
<input type="hidden" :value="counter">
</div>
`,
components: {
thirdPartyComponent
},
data () {
return {
counter: 4
}
}
}
new Vue({
el: 'form',
components: {
myCustomFancyVueComponent
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<form action="/users" accept-charset="UTF-8" method="post">
<input type="email" name="user[email]">
<input type="password" name="user[password]">
<my-custom-fancy-vue-component></my-custom-fancy-vue-component>
<input type="submit" value="Sign up">
</form>
I'm building a component in Vue.js. I have an input on the page where the user can request a certain credit amount. Currently, I'm trying to make a function that will log the input amount to the console, as I type it in. (Eventually, I'm going to show/hide the requested documents based on the user input. I don't want them to have to click a submit button.)
The code below logs it when I tab/click out of the input field. Here's my component.vue:
<template>
<div class="col s12 m4">
<div class="card large">
<div class="card-content">
<span class="card-title">Credit Limit Request</span>
<form action="">
<div class="input-field">
<input v-model="creditLimit" v-on:change="logCreditLimit" id="credit-limit-input" type="text">
<label for="credit-limit-input">Credit Limit Amount</label>
</div>
</form>
<p>1. If requesting $50,000 or more, please attach Current Balance Sheet (less than 1 yr old).</p>
<p>2. If requesting $250,000 or more, also attach Current Income Statement and Latest Income Tax Return.</p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'licenserow',
data: () => ({
creditLimit: ""
}),
methods: {
logCreditLimit: function (){
console.log(this.creditLimit)
}
}
}
</script>
If I change methods to computed, it works - but I get an error saying Invalid handler for event: change every time it logs the value.
Use the input event.
<input v-model="creditLimit" v-on:input="logCreditLimit" id="credit-limit-input" type="text">
change only fires when the element loses focus for input elements. input fires each time the text changes.
Binding #input event alongside with v-model is unnecessary. Just bind v-model and thats all. Model is automatically updated on input event.
new Vue({
el: '#app',
data: {
message: ''
}
})
<script src="https://unpkg.com/vue#2.4.4/dist/vue.min.js"></script>
<div id="app">
<input type="text" v-model="message"><br>
Output: <span>{{ message }}</span>
</div>
And if you need log it on change to console, create particular watcher:
new Vue({
el: '#app',
data: {
message: ''
},
watch: {
message: function (value) {
console.log(value) // log it BEFORE model is changed
}
}
})
<script src="https://unpkg.com/vue#2.4.4/dist/vue.min.js"></script>
<div id="app">
<input type="text" v-model="message"><br>
Output: <span>{{ message }}</span>
</div>
<input v-model="yourVariableName" #input="yourFunctinNameToBeCall" id="test" type="text">
You can use #change to it trigger the function when is done
Ex- Select a value from drop down
But here you need to call the function when pressing keys (enter values). So use #click in such cases