Here is a simple Vue 2.0 form component. It consists of a number input and a button, e.g.:
Note that the value of the input is tied to the component's data using v-model. buttonText is passed in as a prop.
What's the best way to pass a default value into the form, so that it initially renders with a value other than 10?
Using props doesn't seem to be the right way to do it because then v-model no longer works properly.
However, data can't be passed in the way props can, as far as I can tell from Vue documentation.
.
<template>
<form v-on:submit.prevent="onSubmit">
<input v-model="amount" type="number" min="1" max="20"></input>
<button type="submit">{{ buttonText }}</button>
</form>
</template>
<script>
export default {
props: [ 'buttonText' ],
data: function() {
return {
amount: 10
}
},
methods: {
onSubmit: function() {
this.$emit("submit", parseInt(this.amount) );
}
}
}
</script>
You can pass in a prop (say initialAmount) and reference that when initializing the amount value in the data function:
export default {
props: {
buttonText: { type: String },
initialAmount: { type: Number, default: 10 },
},
data: function() {
return {
amount: this.initialAmount
}
},
methods: {
onSubmit: function() {
this.$emit("submit", parseInt(this.amount) );
}
}
}
Related
So I'm trying to build a component in Vue 3 that acts as a form, and in order for the data to be processed by the parent I want it to emit an object with all the inputs on change. The issue I'm having is that I don't seem to be able to call $emit from within watch() (probably because of the context, but I also don't see why the component-wide context isn't passed by default, and it doesn't accept this). I also cannot call any method because of the same reason.
I do see some people using the watch: {} syntax but as I understand it that is deprecated and it doesn't make a whole lot of sense to me either.
Here's a minimal example of what I'm trying to accomplish. Whenever the input date is changed, I want the component to emit a custom event.
<template>
<input
v-model="date"
name="dateInput"
type="date"
>
</template>
<script>
import { watch, ref } from "vue";
export default {
name: 'Demo',
props: {
},
emits: ["date-updated"],
setup() {
const date = ref("")
watch([date], (newVal) => {
// $emit is undefined
console.log(newVal);
$emit("date-updated", {newVal})
// watchHandler is undefined
watchHandler(newVal)
})
return {
date
}
},
data() {
return {
}
},
mounted() {
},
methods: {
watchHandler(newVal) {
console.log(newVal);
$emit("date-updated", {newVal})
}
},
}
</script>
Don't mix between option and composition api in order to keep the component consistent, the emit function is available in the context parameter of the setup hook::
<template>
<input
v-model="date"
name="dateInput"
type="date"
>
</template>
<script>
import { watch, ref } from "vue";
export default {
name: 'Demo',
props: {},
emits: ["date-updated"],
setup(props,context) {// or setup(props,{emit}) then use emit directly
const date = ref("")
watch(date, (newVal) => {
context.emit("date-updated", {newVal})
})
return {
date
}
},
}
</script>
if you want to add the method watchHandler you could define it a plain js function like :
...
watch(date, (newVal) => {
context.emit("date-updated", {newVal})
})
function watchHandler(newVal) {
console.log(newVal);
context.emit("date-updated", {newVal})
}
...
I feel like I am about to go down a path of extreme inefficiency when trying to keep data correctly mapped between a Parent and Child component.
If I have a simple Child Vue element like below
common/InputText.vue
<template>
<input v-bind:id="name" v-bind:value="value" v-on:input="changed($event, $event.target.value)">
</template>
<script>
props: ['name', 'value'],
methods: {
changed(event, value) { this.$emit('emitChanged', event, value); }
}
</script>
If I have a Parent Vue element like below, it is binding data to the Child elements. The problem is that it seems to be only binding from the Parent to the Child, the Parent data is not updating
Parent.vue
<input-text name="field01" v-bind:value="field01" #emitChanged="changed"></input-text>
<input-text name="field02" v-bind:value="field02" #emitChanged="changed"></input-text>
<script>
import inputText from "./common/InputText.vue";
export default {
data() {
return() {
field01: '',
field02: ''
}
},
components: {
input-text: inputText
},
changed(event, newValue) {
console.log(newValue);
}
}
</script>
I am able to update the Parent data with whatever the data the Child returns by changing the changed method to the below
changed(event, newValue) {
console.log(newValue);
if( event.target.id == 'field01' ) {
this.field01 = newValue;
}
if( event.target.id == 'field02' ) {
this.field02 = newValue;
}
}
This feels like a hack though and will become unmanageable should there be many input fields. What is the correct way to reupdate the Parent data?
This is why the v-model is useful, you can change your code in following way to overcome your problem without using v-model. but I would recommend try to implement v-model way.
<template>
<input v-bind:id="name" v-bind:value="value" v-on:input="changed($event, $event.target.value)">
</template>
<script>
props: ['name', 'value'],
methods: {
changed(event) { this.$emit('emitChanged', event); }
}
</script>
<input-text name="field01" v-bind:value="field01" #emitChanged="changed($event, 'field01')"></input-text>
<input-text name="field02" v-bind:value="field02" #emitChanged="changed($event, 'field02'"></input-text>
<script>
import inputText from "./common/InputText.vue";
export default {
data() {
return() {
field01: '',
field02: ''
}
},
components: {
input-text: inputText
},
changed(event, field) {
this[field] = event.target.value
}
}
</script>
Currently we have an text input whose value is stored in our vuex store, passed in as props and retrieved via computed property. In that same vuex store we have a selection range (array of two numbers), which will allow text in the input to be selected/highlighted.
Here's a simplified version of the code:
<template>
<input type="text" v-model="value" #select="selectionHandler" />
</template>
<script>
export default {
props: ['props'],
computed: {
value: function() {
return this.props.value // some string
},
selectionRange: {
get: function () { return this.props.selectionRange }, // [number, number]
set: function (range) { /* dispatch store action to update, which will update props */ }
}
},
methods: {
selectionHandler(e) {
this.selectionRange = [e.currentTarget.selectionStart, e.currentTarget.selectionEnd]
}
}
}
</script>
The goal is to select/highlight the text (from the store) and update the store based on what the user selects. I'm planning to utilize setSelectionRange (docs). I'm thinking I could use refs and watchers, but that just seems like overkill.
I ended up creating a select function and using refs to select the input/textarea text. Initially I called select using Vue life cycle methods: mounted and updated. So the simplified version looked like this:
<template>
<input ref="someRefName" type="text" v-model="value" #select="selectionHandler" />
</template>
<script>
export default {
props: ['props'],
computed: {
value: function() {
return this.props.value // some string
},
selectionRange: {
get: function () { return this.props.selectionRange }, // [number, number]
set: function (range) { /* dispatch store action to update, which will update props */ }
}
},
methods: {
selectionHandler(e) {
this.selectionRange = [e.currentTarget.selectionStart, e.currentTarget.selectionEnd]
},
select() {
this.$refs['someRefName'].setSelectionRange(this.selectionRange[0], this.selectionRange[1])
}
},
mounted: function () {
if (this.selectionRange[1] !== 0) { this.select() }
},
updated: function () {
if (this.selectionRange[1] !== 0) { this.select() }
}
}
</script>
That seemed like a pretty good solution... until I realized "oh, that's not how browsers work." The selected/highlighted text gets cleared every time the input loses focus. So I changed it to use focus and blur handler functions. Now the text is only selected when the element is in focus. Something like this:
<template>
<input ref="someRefName" type="text" v-model="value" #focus="focusHandler" #select="selectionHandler" #blur="blurHandler" />
</template>
<script>
export default {
props: ['props'],
computed: {
value: function() {
return this.props.value // some string
},
selectionRange: {
get: function () { return this.props.selectionRange }, // [number, number]
set: function (range) { /* dispatch store action to update, which will update props */ }
}
},
methods: {
focusHandler() {
if (this.selectionRange[1] !== 0) { this.select() }
},
selectionHandler(e) {
this.selectionRange = [e.currentTarget.selectionStart, e.currentTarget.selectionEnd]
},
blurHandler() {
this.selectionRange = [0, 0]
},
select() {
this.$refs['someRefName'].setSelectionRange(this.selectionRange[0], this.selectionRange[1])
}
}
}
</script>
https://github.com/euvl/vue-js-modal#readme
I have searched through the docs and can't find an example that fits my needs.
I have also searched through stack overflow without finding an example that matches mine.
I am strictly asking about the interactions between the parent and modal component here. You can assume all of my imports are correct. I have omitted some code for brevity ( for example the template is redundant on Component A ). You can assume open is being triggered via a click handler.
I have a component: A
{
data() {
return {
data: ''
}
},
methods: {
open() {
this.$modal.show(ComponentB, { data: this.data }, {}, { 'before-close': update })
},
update(e) {
// e = event
// this.data is not changed
}
}
}
Component B
<template>
<div>
<input v-model="$attrs.data" />
<button #click="$emit('close')">close</button>
</div>
</tempate>
As you can see Component A passes its data prop into the ComponentB via vue-js-modal, the input is in turn is bound via v-model back to that attribute.
Why when I change this attribute does it not change on the parent component.
What is the correct way to pass data down and back up through the modal component.
Also. My requirement is to use dynamic modals in this style. NOT to use the template syntax with JSX.
I got around this by passing in a function that does the assignment into the Modal, This is probably not best practice but it works.
{
data() {
return {
data: ''
}
},
methods: {
open() {
this.$modal.show(ComponentB, { update: this.update }, {}, {})
},
update(data) {
this.data = data
}
}
}
Component B
<template>
<div>
<input v-model="data" #change="update" />
<button #click="$emit('close')">close</button>
</div>
</tempate>
export default {
data() {
{
data: ''
}
},
methods: {
update() {
this.$attrs.update(this.data)
}
}
}
I got a set of nested components as follows in Vue 2.2.1:
<Root>
<VForm>
<Accordion>
<Panel>
<Stripe ref="stripe">
And I need to call a method getToken on the Stripe component when the form is submitted. On my <VForm> component I have the following template.
<template>
<form :method="method" :action="action" :class="classes" :autocomplete="autocomplete" #submit.prevent="submit">
<slot></slot>
</form>
</template>
<script>
export default {
props: {
method: {
type: String,
default: 'POST'
},
action: {
required: true,
type: String
},
classes: {
type: String
},
autocomplete: {
type: String,
default: 'on'
}
},
methods: {
submit(){
this.$refs.stripe.getToken
}
}
}
</script>
But I get Uncaught TypeError: Cannot read property 'getToken' of undefined. I also tried it by emitting an event at the <v-form> level but, if i'm not mistaken, it is my understanding that the events flow from child to parent, so that didn't work.
How can I trigger stripe.getToken on <v-form> submit?
You could use a bus.
const bus = new Vue();
Vue.component("parent", {
methods:{
triggerStripe(){
bus.$emit('get-token');
}
}
})
Vue.component("stripe",{
mounted(){
bus.$on('get-token', () => this.getToken());
}
})