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.
Related
I know I can catch a keydown event on an input box and return false from the handler to stop the key press from being applied to an input box. I'd like to do something on a higher level with knockout bindings, i.e., I want to stop an update that is about to happen, say, after checking that the value typed in the input box would be illegal. I don't want the illegal value be applied to my view-model and then I would have to undo it "manually". I want to stop it before the view model is changed in any way.
Another real use-case I am having is, I want to change something about the view-model before the input is being applied to it. For example, I might want to save a copy of the present state before updating it.
How can I do this within the knockout framework? I could possibly handle mounsedown or focus events to prepare to save the value and then let the update happen and then undo it if needed, but I want to stop the update itself.
Here is what I tried:
<html>
<head>
<title>Initial Write Protect Example</title>
<script src="knockout-3.5.0.debug.js" type="text/javascript"></script>
<script type="text/javascript">
const data = {
value: ko.computed({
owner: this,
read: function() { return "foo"; },
write: function(x) { console.log("writing: " + x); debugger; }
}),
};
let allowKey = true;
const changeFn = function(x) { console.log("changed: ", x, data.value()); debugger; return false; };
const beforeChangeFn = function(x) { console.log("about to change: ", x, data.value()); debugger; return false; };
const keydownFn = function(x,event) { console.log("keydown: " + event.key + " allow? " + allowKey); return allowKey; };
const keyupFn = function(x,event) { console.log("keyupn: " + event.key); return false; };
</script>
</head>
<body>
<p>
<span>Current Value:</span>
<span data-bind="text: value"></span>
<br/>
<input data-bind="value: value,
event:{
change: changeFn,
beforeChange: beforeChangeFn,
keydown: keydownFn,
keyup: keyupFn
}"></input>
</p>
<script type="text/javascript">ko.applyBindings(data);</script>
</body>
</html>
From this it is clear that while I can stop a key to take effect (setting allowKey to false in the debugger), I cannot stop the change, no beforeChange event is sent (it's mentioned once in the ko source code, so I figured I'd try it), and the change event comes after the ko.observable write. So, it's too late, the view model was already changed when the change event fires.
I guess I can go into the knockout source code to fix myself a nice beforeChange event whose return value of false would stop the further processing (and actually revert the value in the input box to what it was before.) But I wonder isn't there already some way I should go instead?
UPDATE:
I have now learned that I can subscribe to the beforeChange event for an observable:
observable.subscribe((newValue, eventName) => handle(newValue, eventName, 'beforeChange');
I made the two arguments here explicit to point out that the new value is not available in these handlers. This is a shame, because I need the new value too.
Here's how you can use a writable computed to prevent updates from propagating to your source observable.
You can use an extender to make it easy to reuse. I wrote the extender to accept a predicate function that takes a single string and returns either true or false. Depending on what you want to do with it, you might want to support a predicate that takes prevValue and nextValue. That would result in validator(target(), str).
Note: it can be a bad user experience for people to have an unresponsive input field, so make sure to provide feedback to the user typing in the input field.
ko.extenders.validate = function(target, validator) {
return ko.computed({
read: target,
write: str => {
if (!validator(str)) {
console.log(`Attempt to update ${data()} to ${str}, which is invalid.`);
target.notifySubscribers(true);
} else data(str);
}
}).extend({ notify: "always" });
}
const data = ko.observable("bdfgh");
ko.applyBindings({
data: data.extend({ validate: charsAreInAlphabeticalOrder })
});
function charsAreInAlphabeticalOrder(str) {
if (str.length <= 1) return true;
return (
str[0].localeCompare(str[1]) === -1 &&
charsAreInAlphabeticalOrder(str.slice(1))
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<label>
Enter characters alpabetically
<input type="text" data-bind="textInput: data">
</label>
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;
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/
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
I'm using the attribute committedValue of core-input element of Polymer like this:
<paper-input is="core-input" type="text" name="data_in" id="data_in" value="{{current_data_in}}" committedValue="{{committed_data_in}}"></paper-input>
It works fine, and it solves the problem of listening to some keypress + blur events to determine the input is "committed".
My problem is that I'd like to erase the contents of the input box value after the value were committed. I can't find any way to listen to this event.
Is there any built-in event that being triggered after a value is committed?
Ok, I found a way to do it and it includes observing the committedValue like this:
<script>
Polymer('chat-element', {
ready: function() {
this.committed_data_in = "";
this.current_data_in = "";
},
observe: {
'committed_data_in': 'modelUpdated'
},
modelUpdated: function(oldValue, newValue) {
console.log(oldValue, newValue);
}
});