Vue.js blur with enter event - triggers both of them - javascript

I am trying to make it so that when I press enter OR if I lose focus from an element it triggers a function but when I press enter it then triggers the blur event also. So the function is called twice. It should only be called once.
<input v-on:blur="saveField('name')" keyup.enter="saveField('name')">
The problem is that my saveField() function hides the element, triggering also the blur event.
I guess the other issue is how not to have to write the same function call twice. (DRY).

You can use some kind of condition to check if the value needs to be updated. It seems complicated to avoid the two events from being triggered:
<div id="app">
<input v-model="inputValue" type="text" #blur="save()" #keyup.enter="save()">
<div v-for="saving in savings">
{{ saving }}
</div>
</div>
new Vue({
el: '#app',
data: {
inputValue: '',
savedValue: '',
savings: []
},
methods: {
save () {
if (this.inputValue !== this.savedValue) {
this.savings.push('Saving value ' + this.inputValue)
this.savedValue = this.inputValue
}
}
}
})
Here is a working JsFiddle: https://jsfiddle.net/L6kfz48m/

Related

Blur event cancels click event in Vue component

I have a search component in Vue.js. When you type into the text input, a list of search results is fetched from the server, then displayed in a list beneath the search field. When you click one of the results, the submit event fires.
However, now I tried adding a blur event to the text input, which should hide the list of results when the user clicks away from the input. This works fine, except for one crucial situation - clicking on a result no longer fires the submit event.
I understand why this is - the blur event apparently fires before the click event, and hides the results list before the click can be registered on one of the results. My question is, how do I get around this? I need the results list to close when clicking outside the text input, but I obviously also need the submit method to function.
Here is the component in full:
<template>
<div class="search basic-search">
<input type="text" v-model="search_string" v-on:keyup="search" v-on:focus="activate" v-on:blur="inactivate" class="form-control search" placeholder="Search stocks" />
<div :class="['search-results', active === true ? 'active' : '']">
<div class="search-result" v-for="result in search_results" v-on:click="submit(result.id)">
{{ result.name }} ({{ result.ticker }})
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
search_string : '',
search_results : [],
active : false
};
},
methods : {
search : function() {
const axios_data = {
_token : $('meta[name="csrf-token"]').attr('content'),
str : this.search_string
};
axios.post('/stock-search', axios_data).then(response => {
if(response.data.success){
this.search_results = response.data.stocks;
this.active = true;
}
});
},
activate : function() {
if(this.search_string !== '')
this.active = true;
},
inactivate : function() {
this.active = false;
},
submit : function(stock_id) {
document.location = "/graphs/" + stock_id;
}
}
}
</script>
You could delay the hiding of the box until click fires
inactivate : function() {
setTimeout( () => this.active = false, 100)
},
You may also try to use mousedown instead of click
<div class="search-result" v-for="result in search_results" v-on:mousedown="submit(result.id)">
I don't know if the order of the events is determined, but mousedown should be triggered before blur.

vue.js element selected by focus is not reactive

I have a listener to check what input was selected at last to add some kind of string/variable later into it.
created: function () {
document.addEventListener('focusin', this.focusChanged);
}
focusChanged(event) {
if (event.target.id !== 'variable-search') {
this.lastElement = event.target;
}
}
This seems to work fine, and when I click on an input field this.lastElement gets updated with the focused element. All these inputs have a v-model which can be a string in an object or just a plain string.
Now the thing is when I try to update the value by:
this.lastElement.value += variable;
Vue won't detect its changes, also in the Vue Developer tools the string won't get updated. But in the input field it does get updated. So this should be a reactivity thing.
When I add a new character into the input field (v-model) it does update again. So it's just when I update the string by this.lastElement it won't register its changes.
The thing is that the input fields are dynamic, so I don't know how many input fields are here and how many lists etc. So I need Vue to re-render the variable after the value of lastElement is updated.
Edit
I just tried it with an #focus here an example
<input v-model="testVar" #focus="lastElement = testVar">
If I update lastElement later on it doesn't update it for testVar but just for lastElement.
Changing values in DOM elements programmatically does not cause DOM events to fire. v-model relies on input (or change when using .lazy) events to update its bound variable. If you dispatch those events when you update the value in an input, the variable will react.
new Vue({
el: '#app',
data: {
items: ['one','two','three']
},
methods: {
addAddress() {
this.lastElement.value += 'address';
this.lastElement.dispatchEvent(new Event('input'));
this.lastElement.dispatchEvent(new Event('change'));
},
focusChanged(event) {
this.lastElement = event.target;
}
}
})
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div v-for="item, index in items">
<input v-model="items[index]" #focus="focusChanged">
{{item}}
</div>
<button type="button" #click="addAddress">+address</button>
</div>
You could add a ref attribute to each of the inputs and use the ref to update their values. For example, an input element could be:
<input v-model="testVar" ref="input1" id="input1" #focus="focusChanged">
In your methods:
methods: {
focusChanged(event) {
if (event.target.id !== 'variable-search') {
this.lastElement = event.target.id;
}
},
}
And where you want to update the value: this.$refs[this.lastElement].value += variable;

Vuejs click event from checkbox?

I have a v-model on checkbox which values are assigned from a loop.
I want the click event to invoke a funtion where I need to access the data of the checked ones. When a click is trigerred, If I log the state it doesnot print the current clicked data of the checkbox. It prints previous clicked checkbox data. Should a event must be passed and accessed data in the function?
<div id="demo">
<ul>
<li v-for="mainCat in mainCategories">
<input
type="checkbox"
:value="mainCat.merchantId"
id="mainCat.merchantId"
v-model="checkedCategories"
#click="check(checkedCategories)"
>
{{mainCat.merchantId}}
</li>
</ul>
{{ checkedCategories }}
</div>
script:
var demo = new Vue({
el: '#demo',
data: {
checkedCategories: [],
mainCategories: [{
merchantId: '1'
}, {
merchantId: '2'
}] //testing with data use: [{done:false,content:'testing'}]
},
methods: {
check: function(e) {
console.log(this.checkedCategories,e)
}
}
})
Js fiddle:http://jsfiddle.net/bmpfs2w2/
Use #change instead of #click. Click event is triggered before value is really changed.
<input type="checkbox"
:value="mainCat.merchantId"
id="mainCat.merchantId"
v-model="checkedCategories"
#change="check($event)"
>
http://jsfiddle.net/eLzavj5f/
If you'd like the function to be called after the value has been changed, you can wrap your handler functionality inside of this.$nextTick(). You can read about $nextTick, but the gist is
Defer the callback to be executed after the next DOM update cycle. Use it immediately after you’ve changed some data to wait for the DOM update.
So your handler will get called after the DOM gets updated, aka after your state has changed and the effects have been reflected in the DOM.
<input
type="checkbox"
:value="mainCat.merchantId"
id="mainCat.merchantId"
v-model="checkedCategories"
#change="check($event)"
>
// ...
check (e) {
this.$nextTick(() => {
console.log(checkedCategories, e)
})
}
Altered Grant's solution
<input
type="checkbox"
:value="mainCat.merchantId"
id="mainCat.merchantId"
v-model="checkedCategories"
#change="check($event)"
/>
// ...
check (e) {
this.$nextTick(() => {
console.log(e.target.id)
})
}
Thanks Grant

vue.js keyup, keydown events one character behind

I'm using the keydown/keyup events which call a javascript function that prints the value of input box to the console (and also the value of the currentTarget field of the event), and I am noticing it is a character late. For example, if I type hello into the input box, I only see hell in the console, until I press another key and then I see hello, even though by this point I've typed hello1. Why is this? And is there anyway around it?
Here's the HTML:
<input type="text" class="form__field" v-model="keywords" v-on:keyup.enter="queryForKeywords" v-on:keydown="queryForKeywords">
And the JS:
queryForKeywords: function(event) {
var self = this;
if (this.keywords.length > 2) {
console.log("keywords value: " + this.keywords);
console.log("event value: " + event.currentTarget.value);
}
Because you are depending on the input's v-model to update the keywords property, the value won't update until the Vue component has re-rendered.
You can access the updated value of keywords in a callback passed to this.$nextTick like in this example:
new Vue({
el: '#app',
data() {
return { keywords: '' }
},
methods: {
queryForKeywords: function(event) {
this.$nextTick(() => {
if (this.keywords.length > 2) {
console.log("keywords value: " + this.keywords);
}
});
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<input type="text" class="form__field" v-model="keywords" v-on:keyup.enter="queryForKeywords" v-on:keydown="queryForKeywords">
</div>
The real problem doesn't has to do with vue.js at all
The problem hides behind the keydown event!
So when the event fires, the input value is NOT updated yet. Fiddle example
MDN - keydown event
In general, keydown it is used for informing you which key is pressed. And you can access it like this:
document.addEventListener('keydown', logKey);
function logKey(e) {
console.log(e.key)
}
As solution, you can use the keyup event: Fiddle
My recommendation is to use a custom v-model using :value and the #input event.
<input type="text" :value="keywords" #input="queryForKeywords">
And the script:
data: {
keywords: ''
},
methods: {
queryForKeywords(event) {
const value = event.target.value
this.keywords = value
if (value.length > 2) {
console.log("keywords value: " + this.keywords);
}
}
}
See it in action
The currently accepted answer is for an old version of vue, in the latest versions should be used #input instead of keypress or keyup.

Enter with #keyup event not working in Vue

I am trying to call method on pressing enter key but it's not working. Code is as below.
<template>
<div>
<button #click="callEvent" #keyup.enter="callEvent"> Click </button>
</div>
</template>
<script>
export default{
methods:{
callEvent(){
console.log("Event called");
}
}
}
</script>
The click event already triggers with the ENTER key (it also triggers with Space in some browsers, like Chrome for desktop). So, your code only needs a #click="callEvent" and everything works well since the focus is already on the button:
var app = new Vue({
el: "#app",
methods: {
callEvent() {
console.log("Event called");
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<button #click="callEvent">Enter</button>
</div>
If you want that any ENTER triggers the button even if it isn't with focus, you should bind the event to the window object, which can be made inside the mounted handler:
var app = new Vue({
el: "#app",
methods: {
callEvent() {
console.log("Event called");
}
},
mounted() {
window.addEventListener('keyup', function(event) {
if (event.keyCode === 13) {
app.callEvent();
}
});
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<button>Enter</button>
</div>
Remember that if you're using Single File Components, the instance is exposed by the this keyword, which can be used to call component methods inside the desired handler:
export default {
methods: {
callEvent() {
console.log('Event called')
}
},
mounted() {
window.addEventListener('keyup', event => {
if (event.keyCode === 13) {
this.callEvent()
}
})
}
}
Buttons don't have keyup event on them. Even when you have focus on the button, and hit enter, it will be considered a click event, instead of keyup.enter.
Try binding the event to an input and it'd work.
Alternatively, you could use jQuery (or Plain JS) to bind for keydown event on the body element, and trigger the Vue method by calling app.callEvent().
var app = new Vue({
el: "#app",
methods: {
callEvent() {
console.log("Event called");
}
},
mounted() {
var self = this;
window.addEventListener('keyup', function(event) {
if (event.keyCode === 13) {
self.callEvent();
}
});
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<template>
<div>
<button #click="callEvent"> Click </button>
</div>
<input type="text" #keyup.enter="callEvent" />
</template>
</div>
Updated to use mounted instead of relying on jQuery - as per Erick Petrucelli's answer as it allows referring to the Vue component without the global variable.
I experienced inconsistent results when using native JS with window.addEventListener. VueJS natively supports modifying behavior for keyboard events https://v2.vuejs.org/v2/guide/events.html#Key-Modifiers
This also worked a lot better in my case due to needing separate behavior for the tab key.
Your input can look like this with custom modifiers on each key up|down
<input
type="text"
class="form-control"
placeholder="Start typing to search..."
v-model="search_text"
#focus="searchFocus"
#blur="searchFocusOut"
v-on:keyup.enter="nextItem"
v-on:keyup.arrow-down="nextItem"
v-on:keyup.arrow-up="nextItem"
v-on:keydown.tab="nextItem"
>
Then inside NextItem you can reference the event, and get each key.. or write a separate function for each key modifier.
#keyup.enter="callEvent"
change to
#keypress.enter.prevent="callEvent"
<template>
<div>
<button #click="callEvent" #keypress.enter.prevent="callEvent"> Click </button>
</div>
</template>
Ref: https://github.com/vuejs/vue/issues/5171

Categories

Resources