Why Vue.js #input event isn't fired when button is clicked? - vue.js

Why Vue.js #input event isn't fired when button is clicked?
When I change count value directly (click on input element and type new value) alert method is fired.
new Vue({
el: "#app",
data: {
count: 0
},
methods: {
add: function() {
this.count += 1;
},
alert: function() {
alert('Count has been changed!');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model.number="count" #input="alert">
<button #click="add">+1</button>
</div>

You can invoke alert method from inside add method:
add: function() {
this.count += 1;
// invoke alert()
this.alert();
}

I am sure its #change not #input:
<input type="text" v-model.number="count" #change="alert">
Alternatively:
watch : {
number: function (newVal) {
alert();
}
}

#input isn't for any changes to an input tag; it's specifically triggered when a user directly modifies the input tag. You want something like watch:
new Vue({
el: "#app",
data: {
count: 0
},
methods: {
add: function() {
this.count += 1
}
},
watch: {
count: function() {
alert('Count has been changed!')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model.number="count" />
<button #click="add">+1</button>
</div>

Related

VueJS Trying to capture enter press on child component before parent component handles enter

I have an auto complete input that when I press enter in this input, it is to emit its value to the parent component and then the parent submit action should be handled. However it appears that the parent is first receiving the enter key submiting the form and then the child component will finally emit the value meaning the data doesnt get updated until after it is needed.
I have an example code pen I made up
codepen
Vue.component('child', {
data () {
return {
someData: ""
}
},
template: `
<div>
<input #keyup.enter.capture="enterPressed" v-model="someData" />
</div>
`,
methods: {
enterPressed(){
this.$emit('updateData',this.someData)
console.log('CHILD: enter pressed')
}
}
});
Vue.component('parent', {
data () {
return {
lastGo: null,
parentData: "init"
}
},
template: `
<form v-on:submit.prevent="go">
<child #updateData="updateData"></child>
<button #click="go">Go</button>
<p>Parent data: <b>{{parentData}}</b></p>
<p>Last go: <b>{{lastGo}}</b></p>
</form>
`,
methods: {
updateData(data){
this.parentData = data;
},
go(){
this.lastGo = this.parentData;
console.log("go: "+this.parentData)
}
}
});
new Vue({
el: '#app'
});
I'm not sure how to resolve this, I feel maybe that my pattern just isn't going to work, is there a better way?
There is a way to work around is using #input event in child component
Vue.component('child', {
data () {
return {
someData: ""
}
},
template: `
<div>
<input #input="onInput" v-model="someData" />
</div>
`,
methods: {
onInput(){
console.log('CHILD: enter pressed')
this.$emit('updateData',this.someData)
}
}
});
Demo

Vue JS: Setting computed property not invoking v-if

When a method sets a computed property, v-ifs are not getting invoked. I thought a computed property logically worked just like a 'regular' property.
// theState can't be moved into Vue object, just using for this example
var theState = false;
var app = new Vue({
el: '#demo',
data: {
},
methods: {
show: function() {
this.foo = true;
},
hide: function() {
this.foo = false;
}
},
computed: {
foo: {
get: function() {
return theState;
},
set: function(x) {
theState = x;
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="demo">
<input type=button value="Show" #click="show()">
<input type=button value="Hide" #click="hide()">
<div v-if="foo">Hello</div>
</div>
Am I doing something wrong?
Vue doesn't observe changes in variables outside the component; you need to import that value into the component itself in order for the reactivity to work.
var theState = false; // <-- external variable Vue doesn't know about
var app = new Vue({
el: '#demo',
data: {
myState: theState // <-- now Vue knows to watch myState for changes
},
methods: {
show: function() {
this.foo = true;
theState = true; // <-- this won't affect the component, but will keep your external variable in synch
},
hide: function() {
this.foo = false;
theState = false; // <-- this won't affect the component, but will keep your external variable in synch
}
},
computed: {
foo: {
get: function() {
return this.myState;
},
set: function(x) {
this.myState = x;
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="demo">
<input type=button value="Show" #click="show()">
<input type=button value="Hide" #click="hide()">
<div v-if="foo">Hello</div>
</div>
(Edited to remove incorrect info; I forgot computed property setters existed for a while there)
You need to move theState into data. Otherwise it wont be reactive, so vue wont know when its changed, so v-if or any other reactivity wont work.
var app = new Vue({
el: '#demo',
data: {
foo2: false,
theState: false
// 1
},
methods: {
show: function() {
this.foo = true;
},
hide: function() {
this.foo = false;
}
},
computed: {
foo: { // 2
get: function() {
return this.theState
},
set: function(x) {
this.theState = x;
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="demo">
<input type=button value="Show" #click="show()">
<input type=button value="Hide" #click="hide()">
<div v-if="foo">Hello</div>
</div>

VueJS 2 update contenteditable in component from parent method

I have editable element updated by component method, but i have also json import and i want to update element my parent method. I can update model, but editable element doesn´t bind it. If i insert content to component template, it will bind updated model, but then i can´t really edit it.
Here´s my example: https://jsfiddle.net/kuwf9auc/1/
Vue.component('editable', {
template: '<div contenteditable="true" #input="update"></div>', /* if i insert {{content}} into this div, it wil update, but editing behave weird */
props: ['content'],
mounted: function () {
this.$el.innerText = this.content;
},
methods: {
update: function (event) {
console.log(this.content);
console.log(event.target.innerText);
this.$emit('update', event.target.innerText);
}
}
})
var app = new Vue({
el: '#myapp',
data: {
herobanner: {
headline: 'I can be edited by typing, but not updated with JSON upload.'
}
},
methods: {
uploadJSON: function (event) {
var input = event.target;
input.src = URL.createObjectURL(event.target.files[0]);
var data = input.src;
$.get(data, function(data) {
importdata = $.parseJSON(data);
this.$data.herobanner = importdata.herobanner;
}.bind(this));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<main id="myapp" class="container-fluid">
<input type="file" id="file" name="file" #change="uploadJSON" style="display: none; width: 1px; height: 1px"/>
<a href="" onclick="document.getElementById('file').click(); return false" title="Import settings from JSON file">
upload JSON
</a>
<h1>
<editable :content="herobanner.headline" #update="herobanner.headline = $event"></editable>
</h1>
Real value of model:
<br>
<h2>{{herobanner.headline}}</h2>
</main>
Working example:
Vue.component('editable', {
template: `
<div contenteditable="true" #blur="emitChange">
{{ content }}
</div>
`,
props: ['content'],
methods: {
emitChange (ev) {
this.$emit('update', ev.target.textContent)
}
}
})
new Vue({
el: '#app',
data: {
herobanner: {
headline: 'Parent is updated on blur event, so click outside this text to update it.'
}
},
methods: {
async loadJson () {
var response = await fetch('https://swapi.co/api/people/1')
var hero = await response.json()
this.herobanner.headline = hero.name
},
updateHeadline (content) {
this.herobanner.headline = content
}
}
})
<main id="app">
<button #click="loadJson">Load JSON data</button>
<h1>
<editable
:content="herobanner.headline"
v-on:update="updateHeadline"
>
</editable>
</h1>
<h2>{{herobanner.headline}}</h2>
</main>
<script src="https://unpkg.com/vue#2.5.3/dist/vue.min.js"></script>

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>

Focus input of freshly added item

So I have a list of items and list of inputs linked to each item via v-for and v-model.
I click a button and add new item to that list. I want to focus input which is linked to newly added item.
Can't figure out how to achieve this goal.
<div id="app">
<div v-for="item in sortedItems">
<input v-model="item">
</div>
<button #click="addItem">
add
</button>
</div>
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
Here's fiddle with sorted list
https://jsfiddle.net/sfL91r95/1/
Thanks
Update: inspired by pkawiak's comment, a directive-based solution. I found that calling focus in the bind section didn't work; I had to use nextTick to delay it.
Vue.directive('focus-on-create', {
// Note: using Vue 1. In Vue 2, el would be a parameter
bind: function() {
Vue.nextTick(() => {
this.el.focus();
})
}
})
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app">
<div v-for="item in sortedItems">
<input v-focus-on-create v-model="item">
</div>
<button #click="addItem">
add
</button>
</div>
Original answer:
Make your input a component so that you can give it a ready hook.
const autofocus = Vue.extend({
template: '<input v-model="item" />',
props: ['item'],
ready: function() {
this.$el.focus();
}
})
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
components: {
autofocus
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app">
<div v-for="item in sortedItems">
<autofocus :item="item"></autofocus>
</div>
<button #click="addItem">
add
</button>
</div>
I Like to extend #Roy's answer.
if you are using any UI framework then it will create DIV and within the DIV input tag will be created so this Snippet will handle that case.
Vue.directive('focus-on-create', {
bind: function(el) {
Vue.nextTick(() => {
el.querySelector('input').focus()
})
}
})