Caret position not keeping track? - javascript

What I'm trying to do is put key codes in an array, to do some interesting stuff with later on. So, I capture the keystrokes, get the caret position and put the key code in the array (with some help from MooTools):
var keyArray = [];
$('form').addEvent('keyup', function(event) {
var pos = document.activeElement.getCaretPosition();
keyArray[pos] = event.code;
});
Generally speaking, this works great. However, I noticed some undefined values in my array when showing the complete array in my console. Exploring this further, I found out that when typing quickly, the caret position seems to lose track, or being quick/slow to respond. I've made a jsfiddle to demonstrate this: http://jsfiddle.net/HQVR8/1/
If you type quickly in this example, you'll see a caret position sequence like
- 1 - 2 - 3 - 5 - 6 - 6.
But when typing slowly, it's
- 1 - 2 - 3 - 4 - 5 - 6.
Of course, the trouble now when typing quickly is that I have a undefined value in my array and I overwrite one array item. So the result is different.
My question is if I can somehow let the caret position keep track. I've tried using the 'native' selectionStart instead, but results are the same. I also tried capturing the caret position in a keydown event and putting it in an array in the keyup event. No difference. I'm wondering if using little pauses (ie. forcing the user to type slower) might solve it, but this feels like a hack and I would prefer a 'proper' solution. Hopefully, there is one.

you are basically making a mess by using an array instead of an object.
array indexes are dodgy and can create sparse arrays if you are not careful. for instance:
var foo = [];
foo[0] = "one!"; // foo.length = 1;
foo[2] = "two!"; // foo.length = 3, foo[1] = undefined
so if you type too fast and skip the return value, it is probably doing just that. also, pasting etc can push caret further down...
you can probably use an object though there are no guarantees on the order of keys in vs keys out in all browsers - particularly webkit, who tend to reorder and put numeric keys at top of object key loops... but if you prefix the key like "pos" + caretIndex, you ought to get FIFO
one way to solve your need to extract the actual codes w/o the undefined is via Array.filter.
eg.
retArray = Array.filter(retArray, function(el) {
return el !== undefined;
});
With an object, you can just do:
console.log(Object.values(foo));

After some more tests, it appears to be a behavior specific to keyup. When I use keydown I do get a consistent sequence: http://jsfiddle.net/HQVR8/3/
One disadvantage is that keydown is a step behind keyup when you're doing the 'value collecting' I'm doing, but in my setting, this is only a minor issue.
The difference in behavior appears odd to me: when you press four keys at a time, the keyup displays the same caret position for all of them, while keydown is showing four separate caret positions. This seems odd because you press them at once, but also depress them at once, so the caret 'readings' should be the same.

go to the JSfiddle and fiddle with this:
a) depress 'q', then depress 'w', then release 'q', then release 'w' (fast enough to avoid autorepeat). That kind of sequence happens quite often when you type 'quickly' with more than one finger :).
b) leave any key depressed long enough for the autorepeat to kick in.
The differences are in plain view
Basically, keyUp fires when the physical key is released, but
another key can be depressed before the previous one is released
keyDown is called in case of autorepeat, while keyUp isn't
Since carret position and whatever text area updates are kept in sync with keyDown, you will simply miss the intermediate steps when monitoring keyUp.
In case (a), the keyUp handler sees carret pos jump from 0 to 2 as 'q' is released because two characters have been typed before the first key was released. The release of 'w' does not change carret position, so the handler ends up storing both q and w at position 2.
In case (b), the handler misses the repeats completely, and stores only the last instance of the key.
As a conclusion, using KeyUp will never achieve the intended meaning.
keyDown could, but personnally I would rather hook the changed event.
I reckon it is no less reliable to detect a bot filling the field (after all, a legit user could want to paste the password too), and you won't have to bother with control keys like backspace or whatever other non-keyboard means of clearing the input.
You could use keyUp as an extra paranoid check, as long as you don't expect to reconstruct the exact input. For instance, you could simply check that the released key matches the character at current cursor position. But frankly I'm not sure it is worth the bother.

Related

Fire HTML input-event only once when more than one character is typed

I'm creating an input field where some data should be fetched (by AJAX) and displayed every time the user enters data into that field. However, if the user types several characters, say he types "test" to get all records who contain the string "test", it would not be necessary to do 4 queries after every character pressed, but one query after he stopped typing. I can think of several solutions with a global variable where I check if the same event has been fired again, but is there a really elegant way to do this? Maybe check if there is something in the keyboard buffer and only proceed if it is empty? Or is there an event that is only fired once the keyboard buffer is empty and all characters are in the input field?
The elegant way is to use a timeout, and to keep clearing the previous timeout with each key press
var tID;
function keyUp (e) {
if (tID) clearTimeout(tID);
tID = setTimeout(function() {
... // make web call
}, 2000);
}
This will ensure that the web call is only called after the last key is pressed (you may want to adjust the timeout value)
There are ways to achieve this that I can think of:
Use timeout, from the last keyup event. This is not always the best and not that precise with users that have low typing speed.
Use space character do regconize if the user has finished typing a word. Based on changes in length and total word count, you can decide if you would want to send AJAX or not.
Depends on the type of input you are working with, you may choose the most suitable method for you. The first one is somewhat quite rigid. The second method requires user to press space every time he finishs typing. A little bit of both could be a sweet spot perhaps. In modern day, I don't think sending request every keyup will cause huge performance effect though.

Maintaining a map of which keys are down in the presence of Undo, Copy, etc

I'm building a web application with a fullscreen canvas that needs to react to continuous and discrete keyboard input.
My current approach is similar to the one in this question: JavaScript keep track of which keys are down accurately.
I maintain a set of which keys are currently pressed using keyEvent.code to differentiate keys. I query this set in the requestAnimationFrame loop of my application. I update the set in event handlers for the 'keyup' and 'keydown' events attached to window.
The Problem
When pressing and releasing Cmd+Z to undo, the 'keydown' event fires but the 'keyup' event does not (at least in the latest Firefox, Chrome, and Edge on macOS 10.15.6).
Consequently, my set contains an entry for 'KeyZ' even though the Z key on the keyboard is not being held. Similarly, Cmd+C, Cmd+V, and many other system shortcuts seem to hijack the 'keyup' event.
Why am I not getting a 'keyup' event in this case? What can I do to ensure that my set accurately reflects the state of the currently held keys on the keyboard?
What I've Tried
This seems like it could be related to event propagation, so I tried using keyEvent.stopPropagation() and keyEvent.preventDefault() in the keydown event handler.
I've tried pressing Control+Z instead, which does fire a 'keyup' event.
Problem Reproduction
const listenerTarget = window;
const useCapture = false;
const heldKeys = new Set();
listenerTarget.addEventListener('keydown', (keyEvent) => {
const code = keyEvent.code;
if (!keyEvent.repeat) {
console.log(`${code} went down`);
heldKeys.add(code);
} else {
console.log(`${code} went down (repeat)`);
}
keyEvent.stopPropagation();
keyEvent.preventDefault();
}, useCapture);
listenerTarget.addEventListener('keyup', (keyEvent) => {
const code = keyEvent.code;
// Why does this not fire if
// the 'keydown' happened in combination with
// a Meta key?
console.log(`${code} went up`);
heldKeys.delete(code);
}, useCapture);
Workaround Update (11/03/2020)
I found When CMD key is kept pressed, keyup is not triggered for any other key and http://web.archive.org/web/20160304022453/http://bitspushedaround.com/on-a-few-things-you-may-not-know-about-the-hellish-command-key-and-javascript-events/, which indicate that this is known behavior. My current workaround is to not add codes to the set of held keys if keyEvent.metaKey is set. Doing this at least ensures that the set of held keys doesn't contain phantom entries. I'll keep this question open in case someone can think of a better approach.
For me it works just fine on the lates chrome version..
Sorry.
This isn't super helpful, but there's a bunch of good info here: https://unixpapa.com/js/key.html. You've probably already seen that in other answers though.
I say not super helpful because it doesn't really answer your particular question, but it does show that there's definite weirdness between browser versions and both modifier keys and branded keys. I suspect the answer is that the branded keys like the windows key and cmd key just act weird because they tend to trigger system level events. I would just use different keys, but hopefully someone else will know a way around this other than "use different keys".
For windows people, you can produce really similar results by doing Windows Key + Z. You'll only get up event for Z and no down events.

Get last line from a textarea on keypress

I have a textarea field and on every keypress, I would like to push the last line in the textarea to an array.
Currently, I am constructing the array on every keypress to get the last line in the textarea. Is there a way to optimize this? Meaning, get last line in the textarea without having to construct an array.
jQuery('#mytextarea').keypress(function() {
lines = jQuery('#mytextarea').text().split("\n");
lastLine = lines[lines.length - 1];
});
if(.. some condition ..) {
myArray.push(lastLine);
Indeed, there is a way to optimize this. The optimization is mostly memory usage - the actual CPU usage is also improved.
The optimized version relies on lastIndexOf(). It is as follows:
jQuery("#mytextarea").keypress(function() {
var content = this.value;
var lastLine = content.substr(content.lastIndexOf("\n")+1);
});
You will notice a couple of micro-optimizations:
this already is the DOM element. There is little point in re-invoking jQuery just to get the text content. Saves a bit on processor
using lastIndexOf allows me to get anything after the last \n
Dogbert provided a benchmark for the lastIndexOf: http://jsperf.com/splitting-large-strings

Change an input when you change another one in javascript

Hard to define the title ^^
I want to have to input fields. For example: one where you type in a color (string) and another for the code of the color (varchar).
Like this: |Green| |#657EC9| (just random color-code)
I do not want to learn how to find the color-code but how to match a value or variable with another. It was just an example.
What i wanna know is how I in the best way auto generate one of the fields when I fill in the second. When I type 'green' in the first field I want the code to automatically appear in the second and vice versa. I just want to do it for a few colors.
I am very new to PHP, HTML and Javascript and could need some good advice about how I should handle the problem.
Thank you
I would tend to just map values to an object literal, so:
var colors = {
Green:'#657EC9',
Red:'#00ffff'
}
Now you could get your value with colors[fieldInputVal] where fieldInputVal might be 'Green' or 'Red' although of course you'd have to test if there actually was a property there. Object literals are the main reason I rarely find a use for switch statements in JS.
As for the event JS, I'm going to be lazy and go with JQuery rather than explain attachEvent vs. addEventListener which would be necessary if you're supporting IE8 or below. If you want to normalize for that yourself and skip JQuery, look up 'addEvent contest' on quirksmode.org
$('.input_one').change( function(){
//note: only fires after the field loses focus - you tab out or focus another field
var inputVal = $(this).val();
if(colors[inputVal] !== undefined){
$('.input_two').val(colors[inputVal]);
}
} );
note: I did not test this code for syntax goofs.
If you want to be more flexible and accept 'green' or 'Green', I would just capitalize the first character before you use it for the lookup. To do the lookup on every character add, you'd have to look up the keyup or keydown events (I prefer keyup to avoid breaking the browsers back when somebody holds a key down).

Detect multiple keys pressed to move a "character/player"

I have an example of what I have got so far here (example 1), you'll need to click on the 'result' window so the keys will move the character (w, a, s and d keys move the character), also bear in mind that this has not been browser tested and only works in a select few modern browsers.
I can now get the image to move up/down/left/right as wanted and depending on the 'health' it moves a particular speed, and using a good example I found it rotates along with the mouse too, so it works in a birds eye view styled game.
Now, I'm a little confused about how I can get the image to move diagonally, I've tried using multiple if statements linking the possible combinations of keys, but it always takes the last key pressed.
I don't really know how to tackle the problem, do I need to put them in some sort of while loop to say while a certain key is pressed move it in that direction? Would that eliminate the need for the diagonal/multiple key press?
if ((key == key_W && key == key_D) || (key == key_w && key == key_d) || (key == key_W && key == key_d) || (key == key_w && key == key_D)) {
// Player Up/Right
}
Or do I need to set some sort of array to generate a list of keys pressed?
I understand and I'm fully aware making games in Javascript/jQuery isn't the best method to make games, it's an experiment more than anything.
EDIT
There is now have a more workable, moving character here (example 2), but there is a delay in the movement, so to expand on my question, is there anyway to reduce this?
Well this looks like a fun project so it got me curious and I started to look around. I found an article on Quirksmode that talks about using keycode vs character code. I see that you are detecting both upper and lower case letters. The following blurb seems to indicate that this is unnecessary. This article also has important docs on browser compatibility and special key compatibility between browsers and systems. For instance, the command button on a Mac keyboard. What is it? How is it detected and behaves in various browsers?
for instance, a lower case 'a' and an upper case 'A' have the same
keyCode, because the user presses the same key, but a different
charCode because the resulting character is different.
http://www.quirksmode.org/js/keys.html
So then the question becomes, how do you detect multiple key presses. I found this SO answer on it that is remarkably simple and straightforward.
Can jQuery .keypress() detect more than one key at the same time?
var keys = {};
$(document).keydown(function (e) {
keys[e.which] = true;
});
$(document).keyup(function (e) {
delete keys[e.which];
})
Update
Basically I just brought in the code from above, deleted your diagonal code and iterated over the keys array for all movement operations.
http://jsfiddle.net/mrtsherman/8NW2K/3/
Jwerty is a great library which handles this sort of thing, i suggest you check it out:
https://keithamus.github.io/jwerty/
It's compatible with jQuery too!

Categories

Resources