input box automatically changed to original value - vue.js

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.

Related

Vue v-on:click change it to load or mouse over event

I am trying to get v-bind:value on to function reservationBy() as load event instead of v_on:clickevent. Right now it passes the value when I click on it only.
Is there a way to make it load automatically or use mouse over event? I even try to use v-on:load and v-on:focus event but it did not work.
View
<div id="app">
<input v-bind:value="2" v-on:click="reservationBy"/>
</div>
Script
new Vue({
el: "#app",
data: {
},
methods: {
reservationBy: function(e) {
var peopleBookedId = e.target.value;
console.log(peopleBookedId);
}
}
})
Here is example on JSFIDDLE
https://jsfiddle.net/ujjumaki/yz0p1vqL/4/
#mouseover and #mouseleave will do the job.
<input v-bind:value="2" #mouseover="reservationBy"/>
or
<input v-bind:value="2" #mouseleave="reservationBy"/>
If using v-model is not an option (that would be the easiest way), and you want to execute the function when component is rendered, you can use ref and access value in mounted hook:
<input ref="myInputRef" :value="2" />
Script:
mounted: function() {
console.log(this.$refs.myInputRef.value);
}

Vue - Change in the state does not re render the template?

I'm trying to understand the basics of Vue and so far what I understand is every time any of the states in the data property changes, the template or the component should re render. Here is the code snippet I'm working with.
index.html
<div id="app">
<h3>Generator</h3>
<div>
Input:
<input #input="onInput"/>
</div>
<div>
Output:
{{test()}}
</div>
</div>
main.js
new Vue({
el:'#app',
data: {
textInput: ''
},
methods: {
onInput(event){
this.textInput = event.target.value
},
test(){
console.log("Test running")
}
}
})
What I expected to happen?
Since I'm updating the textInput data property with every keystroke, I thought that since the template would re render itself, I would see the Test running message in the console every time I hit a key and since the page would re render every time, I would see the input field as blank.
What currently happens
I see the test function run only once when I run the code.
I don't see a blank input field with every key stroke
The DOM does not depend on textInput, so changes to it do not cause a re-render. If the render function uses the variable, you will get a re-render when the variable changes.
new Vue({
el:'#app',
data: {
textInput: ''
},
methods: {
onInput(event){
this.textInput = event.target.value;
},
test(){
console.log(this.textInput);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h3>Generator</h3>
<div>
Input:
<input #input="onInput"/>
</div>
<div>
Output:
{{textInput.length}}
{{test()}}
</div>
</div>

Vue component communication

I'm looking for a concise example of two Vue components. The first component should contain a text input or textarea. The second component displays a character counter. I would like the first component to emit change events, and the second component should listen for those events and display its computed values (character count). I'm new to Vue and trying to wrap my head around the best way to implement this functionality. It seems rather straightforward in pure JavaScript but doing it the Vue way is not as clear to me. Thanks.
Here is how I'd do it in JavaScript:
Here's the textarea:
<textarea id="pagetext" name="pagetext"
onChange="characterCount();"
onKeyup="characterCount();">Type here</textarea>
Here's the JavaScript:
function characterCount()
{
var characters=document.myForm.pagetext.value.length;
document.getElementById('charcounter').innerHTML=characters+"";
}
My concern with Vue is passing the entire value around... for performance reasons this seems less than ideal. I may want my text editing Vue component to self-contain the value and emit the stats, ie the value for character count which would then be observed by a text stats component.
You can create a "Model" for value of textarea and provide this model to second component by using following way https://v2.vuejs.org/v2/guide/components-props.html
I've written up a snippet with four examples: your original, a simple Vue app (no components) that does the same thing, and two apps with two components that are coordinated by the parent.
The simple Vue app is actually more concise than the pure JavaScript app, and I think it shows off the reason for having a framework: your view doesn't act as a store for your program data, from which you have to pull it out.
In the final example, the parent still owns pageText, but passes it down to the my-textarea component. I like to hide the emitting behind the abstraction of a settable computed, so that the element can use v-model. Any changes are emitted up to the parent, which changes pageText, which propagates back down to the component.
I think your performance concerns fall into the realm of premature optimization, but it is possible not to use the text content as data at all, and only be concerned with the length. The fourth example does that. emitLength could have used event.target.value.length, but I wanted to use it in the mounted to initialize the length properly, so I used a ref.
function characterCount() {
var characters = document.myForm.pagetext.value.length;
document.getElementById('charcounter').innerHTML = characters + "";
}
new Vue({
el: '#app',
data: {
pageText: 'Type here'
}
});
new Vue({
el: '#app2',
data: {
pageText: 'Type here'
},
components: {
myTextarea: {
props: ['value'],
template: '<textarea name="pagetext" v-model="proxyValue"></textarea>',
computed: {
proxyValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue);
}
}
}
},
textLength: {
props: ['value'],
template: '<div>{{value}}</div>'
}
}
});
new Vue({
el: '#app3',
data: {
textLength: null
},
components: {
myTextarea: {
template: '<textarea ref="ta" name="pagetext" #input="emitLength">Type here</textarea>',
methods: {
emitLength() {
this.$emit('change', this.$refs.ta.value.length);
}
},
mounted() {
this.emitLength();
}
},
textLength: {
props: ['value'],
template: '<div>{{value}}</div>'
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<form name="myForm">
<textarea id="pagetext" name="pagetext" onChange="characterCount();" onKeyup="characterCount();">Type here</textarea>
</form>
<div id="charcounter"></div>
<div id="app">
<h1>Vue (simple)</h1>
<form>
<textarea name="pagetext" v-model="pageText"></textarea>
</form>
<div>{{pageText.length}}</div>
</div>
<div id="app2">
<h1>Vue (with components)</h1>
<form>
<my-textarea v-model="pageText"></my-textarea>
</form>
<text-length :value="pageText.length"></text-length>
</div>
<div id="app3">
<h1>Vue emitting stats</h1>
<form>
<my-textarea #change="(v) => textLength=v"></my-textarea>
</form>
<text-length :value="textLength"></text-length>
</div>

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>

Is it possible to globally bind to a change event?

I would like to trigger a method everytime the user switches to another field in a form. This does the job:
new Vue({
el: "#root",
data: {
theContent1: "",
theContent2: ""
},
methods: {
changeFun() {
console.log('change')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="root">
<form>
<input v-model="theContent1" #change="changeFun()">
<input v-model="theContent2" #change="changeFun()">
</form>
</div>
It is however repetitive when there are many fields. Is there a way to globally bind to a method for any change?
Note: I do not want to set a watch on the content of data - I need the method to be trigerred when the edited element changes, not when its content does. A practical example would be a submit once a field is completed and the user leaves it (but wihout submitting at each change of the value of the field while it is edited).
Just put your event listener at the root of your div. All Dom events traverse the Dom down from the root, to the element that generated the event, then back up again ! You can listen for them at any level. Use event.target and event.currentTarget to find out what generated the event and what captured it.
Note that, for this reason, it's super agressive to stop the propagation of an event. All kinds of things above your element might have an interest in the events it generates.
new Vue({
el: "#root",
data: {
theContent1: "",
theContent2: ""
},
methods: {
changeFun(event) {
console.log('change from '+event.target.id)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="root" #change="changeFun">
<form>
<input id="one" v-model="theContent1">
<input id="two" v-model="theContent2">
</form>
</div>
You could use watchers:
new Vue({
el: "#root",
data: {
theContent1: "",
theContent2: ""
},
watch: {
theContent1(newVal) {
console.log('change from theContent1: ', newVal)
},
...
}
})
With this you'll have to set watchers for each too...