using a watcher to dynamically change form data - vue.js

OK, so I am tired and I know something I am doing is wrong (more than it doesn't work LOL) but I cannot see it. So, this is the situation.
I have 3 fields a user fills in
Day
Month
Year
That needs to then be combined into a formal date that is in Y-m-d format and stored inside form.birthdate
I THOUGHT using a Vue Watcher would be the solution but... when someone fills out the form, it doesnt seem to update the form.birthdate which then causes an error of "birthdate not found" - HOWEVER, if I change watch to computed on dev, in Vue DevTools, if I open it up, it instantly populates the data.
What am I missing that should be updating the specific form data object/string while the user types.
watch: {
getBirthdate: function () {
let birthdate = `${this.birthdate.year}-${this.birthdate.month}-${this.birthdate.day}`;
this.form.birthdate = birthdate;
},
},

As v-model is a two-way binding which means if you change the input value, the bound data will be changed. Hence, Values will automatically get updated but as you want to combine those day, month and year without any explicit method call, you can achieve that by using computed property.
Live Demo :
new Vue({
el: '#app',
data: {
birthdate: {
day: '',
month: '',
year: ''
}
},
computed: {
combinedBirthDate: function() {
let birthDate = `${this.birthdate.year}-${this.birthdate.month}-${this.birthdate.day}`;
return birthDate;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form>
<label>Day</label>
<input type="text" name="day" v-model="birthdate.day"><br>
<label>Month</label>
<input type="text" name="month" required v-model="birthdate.month"><br>
<label>Year</label>
<input type="text" name="year" v-model="birthdate.year">
</form>
<pre>{{ birthdate }}</pre>
<pre>{{ combinedBirthDate }}</pre>
</div>

Related

Vuejs why all the methods are called?

In the following code first tag p renders name and it is also binded to input event of input field.
There is method called random and it generates random number. And it is rendered in the last p tag. Wondering why random method is being called for every character input in the input box? Shouldn't that be executed only one?
I know I can add Vuejs directive v-once to the last p tag and it stays the same.
Can anyone help me understand this better?
Vue.config.devtools = false
Vue.config.productionTip = false
new Vue({
el:"#exercise",
data: {
name: "Tokyo",
},
methods: {
changeName: function(event){
this.name = event.target.value;
},
random: function(){
return Math.random();
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="exercise">
<p>VueJS is pretty cool - {{ name }}</p>
<br/>
<input type="text" v-bind:value="name" v-on:input="changeName">
<br/>
<p>{{random()}}</p>
</div>
It updateds because its binded to your vue instance. Whenever name changes it will automatically change in the DOM.
With every key press you trigger the method changeName. Now changeName changes name. Vue.js detect this change and update your DOM.

input box automatically changed to original value

When I change the input box, it will automatically change to original value by itself. It seems the reason is btnDisabled has been changed, because if I remove the line of this.btnDisabled = !this.btnDisabled; the input box will no more be automatically changed. I want to know why btnDisabled will affect the input box's value?
const vm = new Vue({
el: '#app',
data: {
btnDisabled: false,
total: 25,
},
created() {
setInterval(() => {
this.btnDisabled = !this.btnDisabled;
}, 500);
},
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<input type="text" v-bind:value="total">
<button v-bind:disabled="btnDisabled">test</button>
</div>
This is because Vue is rerendering the "component" and the value is still technically 25. You can use v-model or #input to update the data or you can use v-once to prevent Vue from rerendering the input text box.
const vm = new Vue({
el: '#app',
data: {
btnDisabled: false,
total: 25,
},
created() {
setInterval(() => {
this.btnDisabled = !this.btnDisabled;
}, 500);
},
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<input type="text" v-bind:value="total" v-once>
<button v-bind:disabled="btnDisabled">test</button>
</div>
There is an answer by Steven B which might solve your problem but I would like to add a little bit about the problem identifying the cause. The problem is in the one-way-binding, what I mean is that, when you are using the following:
<input type="text" v-bind:value="total">
You are introducing a new separate state in DOM by allowing the user typing in the input. So, when the user types into the input, the data.total property is not being updating, it's still 25 but the DOM input has new value. In that case, when setInterval fires and data.btnDisabled is updated, the Application's state is changed and then VUE just force the component to be re-render to keep the data and the DOM in sync. I would prefer v-model instead of :value.

Vue.js not respecting text input v-bind value, allowing modifications in text input value when it shouldn't (controlled component)

Given the following, simple 'controlled component' in Vue.js, I expected the input value to be fixed, impossible to change, since it's bound (using v-bind) by Vue:
<body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<input type="text" v-bind:value="fixedValue">
</div>
<script>
var app = new Vue({
el: '#app',
computed: {
fixedValue: function () {
return 'This should NOT change'
}
}
})
</script>
</body>
But, in reality, the input text only respects this on initial load. I can click on the input field and type anything and it WILL change. Why is that and how to prevent that?
Here's the fiddle: https://jsfiddle.net/6w74yj28/
EDIT:
To compare with React (here's the fiddle: https://jsfiddle.net/ko7duw5x/), if you create a text input and bind it's value, it's impossible to change the text input value by typing inside (which is the behavior I'm trying to achieve with Vue).

Vue: computed vs data(), for vuex bindings?

(Note: I not am not asking about how to use watch).
I have this form template and want to bind it to some variables, for example objectvalue3, that are tracked in a Vuex store (note: different forms may be present on one page, so props.formname tells the form where to look).
<template>
<div>
Tracking formname_:{{formname_}}:
<form method="post" autocomplete="off">
<input type="text" name="objectvalue3" :value="objectvalue3" />
<input type="submit" class="btn btn-primary btn-success" value="Track" #click.prevent="write">
</form>
</div>
</template>
....
props: {
formname: {
type: String,
default: "main"
}
},
Tracking it in data does not work - i.e. the form does not get updated - it just keeps the value the vuex was initialized to :
data: function() {
return {
formname_: this.formname
,objectvalue3: this.$store.state.tracker[this.formname].objectvalue3
},
But using computed works.
computed: {
objectvalue3: function() {
return this.$store.state.tracker[this.formname].objectvalue3
}
I know I have to use computed when I need to do calculations. But there is not real calculation going on here. Unless, could it be the hash lookup via this.formname which tells the form which tracker attribute to look at that is causing a straight data to fail? Is this specific to vuex?
Try this instead:
data: function() {
return {
formname_: this.formname,
tracker: this.$store.state.tracker[this.formname]
},
Then:
<input type="text" name="objectvalue3" :value="tracker.objectvalue3" />
This should work because the data's tracker object is pointing to the one in the store, then whenever the value changes there, it'll also change in the tracker object.
computed works because it listens to changes in the variables used within it, while data doesn't because it applies the first value when executed and doesn't track changes.

Vue.js Input value gets deleted on first char only

Based on the official example Custom Input Component example:
HTML:
<div id="app" >
<div :class="{'has-value' : hasValue}">
<input type="text"
ref="input"
v-bind:value="value"
#input="onInput" >
</div>
</div>
JS:
new Vue({
el: '#app',
data: function() {
return {
hasValue: false
}
},
props: ['value'],
methods:{
onInput: function(){
val= this.$refs.input.value
if(val && val.length > 0){
this.hasValue = true
} else {
this.hasValue = false
}
}
}
})
JS Fiddle to play
Expected:
Dynamically set hasValue data for input and bind a Css Class to this data
Unexpected:
Only after initialization and typing the first Character, the typed character gets deleted from the input. Press JS-Fiddle->Run to see it again.
After first character everything works as expected.
If I remove v-bind:value="value" or comment \\this.hasValue = true it works as expected. Binding value prop is recommended for custom Input component.
Is this intended and why? Or is this a bug I should report?
This is expected behaviour:
When the first character is inserted, hasValue is changed from false to true, causing the component to re-render
During re-render, Vue sees that the input has a value (the character you just typed in), but the property you have bound to it (the value prop) is empty.
Therefore, Vue updates the input to match the bound prop - and thus, it empties the input.
After that, hasValue doesn't change anymore, so there's no re-rendering happening, and thus, Vue doesn't reset the input field's value anymore.
So how to fix this?
First you have to understand that Vue is data-driven - the data determines the HTML, not the other way around. So if you want your input to have a value, you have to reflect that value in the property bound to it.
This would be trivial if you worked with a local data property:
https://jsfiddle.net/Linusborg/jwok2jsx/2/
But since you used a prop, and I assume you want to continue using it, the sittuation is a bit different - Vue follows a pattern of "data down - events up". You can't change props from within a child, you have to tell the parent from which you got it that you want to change it with an event. The responsibility to change it would be the parent's. Here's your example with a proper parent-child relationship:
https://jsfiddle.net/Linusborg/jwok2jsx/4/
I'm guessing that when you modified hasValue, Vue re-renders the component (to apply the has-value class to the div) which causes the v-bind:value to be re-evaluated. value is a prop which is unassigned and never changes, so the value of the input element gets cleared.
You really should use v-model on the input element and control has-value based on the value in the model, rather than interacting directly with the DOM. (You won't be able to use v-model with value though since value is a prop and cannot be assigned from within the component.)
I'm also guessing you want to make a custom input component which applies a certain style when it has a value. Try this:
Vue.component('my-input', {
props: ['value'],
template: `<input class="my-input" :class="{ 'has-value': value }" type="text" :value="value" #input="$emit('input', $event.target.value)">`,
});
new Vue({
el: '#app',
data: {
value: '',
},
});
.my-input.has-value {
background-color: yellow;
}
<script src="https://unpkg.com/vue#2.5.6/dist/vue.js"></script>
<div id="app">
<my-input v-model="value"></my-input>
</div>
Are you trying to achieve something like this?
new Vue({
el: '#app',
data: {
input: ''
},
computed: {
hasValue () {
return this.input.length ? true : false
}
}
})
.has-value {
background-color: red;
}
<div id="app">
<div :class="{'has-value' : hasValue}">
<input type="text" v-model="input">
</div>
</div>
<script src="https://unpkg.com/vue"></script>