How to run a function in Vue.js when state changes - vue.js

I'm looking to run a function when the state changes in my Vue app.
In my component I'm able to get the boolean state of isOpen. I'm looking to run a function that adds focus to my form input when the modal opens and isOpen is set to true. I've tried using a watcher but with no luck. I'm opening my modal by calling :class="{ 'is-open': search.isOpen }" in the html and showing it via css. Any help would be most appreciated.
data() {
return {
isFocussed: this.$store.state.search,
//checks the state of isOpen?
}
},
computed: {
search() { return this.$store.state.search },
},
watch: {
isFocussed() {
this.formfocus()
},
},
methods: {
formfocus() {
document.getElementById('search').focus()
},

please check my snippet which shows the good way to work in Vue.js, you can work with refs which is very helpful instead of document.getElementById()
new Vue({
el: '#app',
data: {
isOpen: false,
},
computed: {
},
watch: {
isOpen(){
if(this.isOpen){
this.$nextTick( function () {
this.formfocus();
}
);
}
}
},
methods: {
formfocus(){
this.$refs.search.focus();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.js"></script>
<div id="app">
<button v-on:click="isOpen = !isOpen">show modal</button>
<div v-if="isOpen">
<input ref="search" type="text" placeholder="search">
</div>
</div>
EDIT: i have added a conditional if on the watch, i hope this solves the problem

I am not sure what your template looks like but here is how I set focus on a conditional element.
element
<input type="text" class="search-input" v-model="search" :class="{'collapsed-search': hideInput}" placeholder="Enter keyword" ref="search">
notice the ref="search" on the input.
here is the method when the input condition is true
toggleSearch() {
this.hideInput = !this.hideInput
this.search = ''
setTimeout(() => {
this.$refs.search.focus()
}, 1000)
}
this.$refs.search.focus() is called after the element has been fully created which is the purpose of the setTimeout

Related

When data is changed within the watch function, dom does not update

Below is the data in a component
data: function () {
return {
sltAreaStyle: {
paddingTop: "3%",
},
checkedTypes: ["krw", "btc", "usdt"],
};
},
Below is watch function of checkedTypes data
watch: {
checkedTypes: {
handler: function (newVal, oldVal) {
if (newVal.length < 1) {
alert("Choose one or more.");
var last = oldVal[0];
this.$data.checkedTypes = [last];
}
},
},
},
Below is my html template
<div class="ckbxArea">
<input type="checkbox" value="krw" v-model="checkedTypes">KRW</input>
<input type="checkbox" value="btc" v-model="checkedTypes">BTC</input>
<input type="checkbox" value="usdt" v-model="checkedTypes">USDT</input>
</div>
I want to change the last value to checkedTypes data when all the check boxes are unchecked.
If the first checkbox was finally unchecked, the checkedTypes would be 'krw' like checkedTypes = ['krw'] The checkedTypes data is ['krw'], but all checkbox tags are unchecked. That is, dom has not been updated. I don't think I understand Vue's life cycle well. I think this problem is related to the life cycle of v-model and components, but I don't know what the problem is. Please explain why this problem occurs and tell me how to solve it.
Well this is more about Vue rendering mechanisms for v-modeleld input controls.
Check this:
Only one last checkbox is checked so model value is ['krw']
Uncheck last checkbox
Watcher is executed - new model value is [] BUT the watcher immediately sets it to same value as before ... ['krw']
Vue re renders the template (see the message in the console) BUT as the v-model value is same as during last render, it does not update the checkbox
Simple solution to situations like this is to postpone the update to next rendering cycle using nextTick
this.$nextTick(() => {
this.checkedTypes = [last];
})
new Vue({
el: "#app",
data: function () {
return {
checkedTypes: ["krw", "btc", "usdt"],
};
},
updated() {
console.log("Component updated")
},
watch: {
checkedTypes: {
handler: function (newVal, oldVal) {
if (newVal.length < 1) {
alert("Choose one or more.");
//console.log("Choose one or more.");
var last = oldVal[0];
// this.checkedTypes = [last];
this.$nextTick(() => {
this.checkedTypes = [last];
})
}
},
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<div id="app">
<input type="checkbox" value="krw" v-model="checkedTypes"/> KRW
<input type="checkbox" value="btc" v-model="checkedTypes"/> BTC
<input type="checkbox" value="usdt" v-model="checkedTypes"/> USDT
<pre>{{ checkedTypes }}</pre>
</div>

VueJS: how to trigger 'change' on <input> changed programmatically

I'm going to build a customized virtual keyboard, so that's the first problem I've encountered.
I have an input element, whose value is changed from outside, in my case by pressing a button. The problem is that there seems to be no way to trigger the normal 'change' event.
Neither clicking outside the input, nor pressing Enter gives any result. What might be the correct way of solving this problem?
<template>
<div class="app-input">
<input #change="onChange" type="text" v-model="value" ref="input">
<button #click="onClick">A</button>
</div>
</template>
<script>
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
</script>
The whole pen can be found here.
v-on:change would only trigger on a direct change on the input element from a user action.
What you are looking for is a wathcer for your data property, whenever your value changes, watcher will execute your desired function or task.
watch: {
value: function() {
this.onChange();
}
}
The watch syntax is elaborated on the provided official vuejs docs link. use your data property as the key and provide a function as a value.
Check the snippet.
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
// this one:
watch: {
value: function() {
this.onChange();
}
},
// --- rest of your code;
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
When I build any new vue application, I like to use these events for a search input or for other inputs where I don't want to fire any functions on #change
<div class="class">
<input v-model="searchText" #keyup.esc="clearAll()" #keyup.enter="getData()" autofocus type="text" placeholder="Start Typing ..."/>
<button #click="getData()"><i class="fas fa-search fa-lg"></i></button>
</div>
These will provide a better user experience in my opinion.

get two datepicker input values using one mounted function with vue

I'm trying to get two input values using one mounted function for the date picker. But not getting how do I do that?
Here is the code:
HTML:
<div id="app">
<form v-on:submit.prevent="saveFleetJob">
<input type="text" class="fleet-date" v-model="item.from">
<input type="text" class="fleet-date" v-model="item.to">
<button class="btn btn-primary">Click</button>
</form>
</div>
Vue code:
new Vue({
el: "#app",
data: {
item: {
from:'',
to:''
},
},
mounted() {
$(".fleet-date").datepicker().on(
"changeDate", () => {
this.item.from = $('.fleet-date').val()
}
);
},
methods: {
saveFleetJob() {
alert(JSON.stringify(this.item));
},
}
})
js fiddle demo link here
Any suggestions please?
As Sphinx pointed there is some context mess, but it's not the only issue.
See this updated fiddle: https://jsfiddle.net/pb91fk6o/
You have to add
elem.dispatchEvent(new Event('input'))
to make this work.
So mounted should look like:
mounted() {
$(".fleet-date")
.datepicker()
.on("changeDate", (e) => {
e.target.dispatchEvent(new Event('input'))
})
},
See this issue for more details: https://github.com/vuejs/vue/issues/2804

Is it possible to use a v-model from inside a component template?

Can the v-model syntax be used from inside a Vue component template?
The following works as expected when included directly in an .html
<input type="text" v-model="selected_service_shortname">
Putting the following stuff into a component template does not work.
var service_details = {
template: `
...
<input type="text" v-model="selected_service_shortname">
...
`
};
vm = new Vue({
el: "#app",
components: {
'service-details': service_details
},
Results in vue.min.js:6 ReferenceError: selected_service_shortname is not defined
Changing the template syntax to
<input type="text" v-model="this.$parent.selected_service_shortname">
Seems to halfway work -- changes applied externally to selected_service_shortname appear in the input box as expected. But making changes to the input box directly results in Uncaught TypeError: Cannot convert undefined or null to object
Is what I'm trying to do a supported use case? If so, are there working examples somewhere?
You can implement support for v-model in your component. This is covered in the documentation here.
Here is an example.
var service_details = {
props: ["value"],
template: `
<input type="text" v-model="internalValue">
`,
computed: {
internalValue: {
get() {
return this.value
},
set(v) {
this.$emit("input", v)
}
}
}
};
Basically, v-model, by default, is simply sugar for passing a value property and listening for the input event. So all you need to do is add a value property to your component, and emit an input event. This can also be customized as described in the documentation.
console.clear()
var service_details = {
props: ["value"],
template: `
<input type="text" v-model="internalValue">
`,
computed: {
internalValue: {
get() {
return this.value
},
set(v) {
this.$emit("input", v)
}
}
}
};
new Vue({
el: "#app",
data: {
selected_service_shortname: "some service name"
},
components: {
'service-details': service_details
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<service-details v-model="selected_service_shortname"></service-details>
<hr>
Selected Service Shortname: {{selected_service_shortname}}
</div>
Used in the parent like this:
<service-details v-model="selected_service_shortname"></service-details>

How to defer form input binding until user clicks the submit button?

I wanted to make a two-way data binding on my form input in Vue.js 2.3. However, I cannot use the v-model directive, because I want the data to be updated only on clicking the submit button. Meanwhile, the input value may be updated from another Vue method, so it should be bound to the data property text. I made up something like this jsFiddle:
<div id="demo">
<input :value="text" ref="input">
<button #click="update">OK</button>
<p id="result">{{text}}</p>
</div>
new Vue({
el: '#demo',
data: function() {
return {
text: ''
};
},
methods: {
update: function () {
this.text = this.$refs.input.value;
}
}
});
It works, but it does not scale well when there are more inputs. Is there a simpler way to accomplish this, without using $refs?
You can use an object and bind its properties to the inputs. Then, in your update method, you can copy the properties over to another object for display purposes. Then, you can set a deep watcher to update the values for the inputs whenever that object changes. You'll need to use this.$set when copying the properties so that the change will register with Vue.
new Vue({
el: '#demo',
data: function() {
return {
inputVals: {
text: '',
number: 0
},
displayVals: {}
};
},
methods: {
update() {
this.copyObject(this.displayVals, this.inputVals);
},
copyObject(toSet, toGet) {
Object.keys(toGet).forEach((key) => {
this.$set(toSet, key, toGet[key]);
});
}
},
watch: {
displayVals: {
deep: true,
handler() {
this.copyObject(this.inputVals, this.displayVals);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="demo">
<input v-model="inputVals.text">
<input v-model="inputVals.number">
<button #click="update">OK</button>
<input v-for="val, key in displayVals" v-model="displayVals[key]">
</div>
If you're using ES2015, you can copy objects directly, so this isn't as verbose:
new Vue({
el: '#demo',
data() {
return {
inputVals: { text: '', number: 0 },
displayVals: {}
};
},
methods: {
update() {
this.displayVals = {...this.inputVals};
},
},
watch: {
displayVals: {
deep: true,
handler() {
this.inputVals = {...this.displayVals};
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="demo">
<input v-model="inputVals.text">
<input v-model="inputVals.number">
<button #click="update">OK</button>
<input v-for="val, key in displayVals" v-model="displayVals[key]">
</div>
You can use two separate data properties, one for the <input>'s value, the other for the committed value after the OK button is clicked.
<div id="demo">
<input v-model="editText">
<button #click="update">OK</button>
<p id="result">{{text}}</p>
</div>
new Vue({
el: '#demo',
data: function() {
return {
editText: '',
text: ''
};
},
methods: {
update: function () {
this.text = this.editText;
}
}
});
Updated fiddle
With a slightly different approach than the other answers I think you can achieve something that is easily scalable.
This is a first pass, but using components, you could build your own input elements that submitted precisely when you wanted. Here is an example of an input element that works like a regular input element when it is outside of a t-form component, but only updates v-model on submit when inside a t-form.
Vue.component("t-input", {
props:["value"],
template:`
<input type="text" v-model="internalValue" #input="onInput">
`,
data(){
return {
internalValue: this.value,
wrapped: false
}
},
watch:{
value(newVal){
this.internalValue = newVal
}
},
methods:{
update(){
this.$emit('input', this.internalValue)
},
onInput(){
if (!this.wrapped)
this.$emit('input', this.internalValue)
}
},
mounted(){
if(this.$parent.isTriggeredForm){
this.$parent.register(this)
this.wrapped = true
}
}
})
Here is an example of t-form.
Vue.component("t-form",{
template:`
<form #submit.prevent="submit">
<slot></slot>
</form>
`,
data(){
return {
isTriggeredForm: true,
inputs:[]
}
},
methods:{
submit(){
for(let input of this.inputs)
input.update()
},
register(input){
this.inputs.push(input)
}
}
})
Having those in place, your job becomes very simple.
<t-form>
<t-input v-model="text"></t-input><br>
<t-input v-model="text2"></t-input><br>
<t-input v-model="text3"></t-input><br>
<t-input v-model="text4"></t-input><br>
<button>Submit</button>
</t-form>
This template will only update the bound expressions when the button is clicked. You can have as many t-inputs as you want.
Here is a working example. I included t-input elements both inside and outside the form so you can see that inside the form, the model is only updated on submit, and outside the form the elements work like a typical input.
console.clear()
//
Vue.component("t-input", {
props: ["value"],
template: `
<input type="text" v-model="internalValue" #input="onInput">
`,
data() {
return {
internalValue: this.value,
wrapped: false
}
},
watch: {
value(newVal) {
this.internalValue = newVal
}
},
methods: {
update() {
this.$emit('input', this.internalValue)
},
onInput() {
if (!this.wrapped)
this.$emit('input', this.internalValue)
}
},
mounted() {
if (this.$parent.isTriggeredForm) {
this.$parent.register(this)
this.wrapped = true
}
}
})
Vue.component("t-form", {
template: `
<form #submit.prevent="submit">
<slot></slot>
</form>
`,
data() {
return {
isTriggeredForm: true,
inputs: []
}
},
methods: {
submit() {
for (let input of this.inputs)
input.update()
},
register(input) {
this.inputs.push(input)
}
}
})
new Vue({
el: "#app",
data: {
text: "bob",
text2: "mary",
text3: "jane",
text4: "billy"
},
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<t-form>
<t-input v-model="text"></t-input><br>
<t-input v-model="text2"></t-input><br>
<t-input v-model="text3"></t-input><br>
<t-input v-model="text4"></t-input><br>
<button>Submit</button>
</t-form>
Non-wrapped:
<t-input v-model="text"></t-input>
<h4>Data</h4>
{{$data}}
<h4>Update Data</h4>
<button type="button" #click="text='jerome'">Change Text</button>
</div>