I'm writing a script for the following task:
The task is for participants to alternately press the a and b keys on the keyboard as quickly as possible for 10 minutes. Every time a participant successfully press the a key followed by the b key, they should receive a point. Points should only be awarded for alternating key presses, pressing the a key or the b key without alternating between the two should not result in points.
The part of the problem I am asking about is the detection of alternating key events. I attempted this myself and ended up with the code below, but it does not achieve the desired result and I'm getting the following error:
Uncaught ReferenceError: x is not defined
... but I just don't understand what I'm doing wrong.
How can I fix my code and achieve the desired result?
var points = 0;
document.addEventListener('keydown', function(event) {
var x = event.code;
});
document.addEventListener('keydown', function(event) {
if (x == 'KeyA' && event.code == 'KeyB') {
points = points + 1;
document.getElementById("points").innerHTML = points;
}
});
<p>Points: <span id="points">0</span></p>
You only need one event listener, and you need to define the a key press state variable (x) outside of the listener function so that it can be referenced by subsequent executions of the listener function.
You also need to make sure that you reset the a key press variable after the b key press.
it is also generally a good idea to cache your references to elements, rather than selecting the element from the DOM each time your listener function runs, and using textContent instead of innerHTML bypasses the HTML parser.
const target = document.getElementById('points');
var points = 0, x;
document.addEventListener('keydown', function(event) {
if(event.key === 'a') x = true; // If this is an `a` key event, set x to true
if(event.key === 'b') {
// if this is a `b` key event and a was not pressed, return early
if(!x) return;
// otherwise increment the points variable and assign the result to
// the textContent property of the target element
target.textContent = ++points;
// remember to set x to false again
x = false;
}
});
<p>Points: <span id="points">0</span></p>
Related
I'm writing some jQuery which intercepts the value entered in an input field and determines whether to allow or to prevent the typed value.
I need to get the next value, i.e., what the value will be were I to permit the key-press, and I need to know this value at a point before it is displayed, so I'm using the keypress event.
My question is: inside the keypress event, how can I tell what the resultant value would be were I to permit the key-press? What is the 'potential value'?
If I write out the key-press event object to the console and inspect the properties, I can see that currentTarget.value shows this 'potential value'. However, if I use this property inside the keypress event then it returns only the value prior to the context key-press.
For example, if the user types "a" into an empty text box bound to the following jQuery
$(":input").on("keypress", function(e) {
console.log(e);
console.log(e.currentTarget.value);
});
Digging down through the first console output (e) shows that currentTarget.value = "a".
But the second console output (e.currentTarget.value) will show "".
If the user was then to type "b" into that same text box then:
Manually inspectng e and locating currentTarget.value displays "ab"; Dumping e.currentTarget.value from inside the event displays "a".
Can anyone explain how I can get this 'potential value' while inside the keypress event?
Not the prettiest of solutions (you can see the would be result just before it's reverted), but to save the trouble of discerning between arrow/control and input keys etc, you could store the original value in keypress and revert to that in keyup if needed (also storing the selection positions for complete reversion)
$(":input").keypress(function(e) {
$(this).data('orgValue', {value: this.value, pos: this.selectionStart, selend:this.selectionEnd});
}).keyup(function(e){
var val = this.value;
if(!somevalidation(val)){
var org =$(this).data('orgValue');
this.value = org.value;
this.selectionStart = org.pos;
this.selectionEnd = org.selend;
}
});
Example fiddle
Edit
Did some testing, but jquery makes predicting the outcome relatively easy. Not only does it fill the key property, it also fills other properties on its event on which the type of key can be checked. While testing charCode seems to be 0 for 'non input' keys.
The straight forward would be:
$(":input").keypress(function(e) {
if(!e.charCode)return; //is 0 for non input
var cur = this.value; //current value
var val = cur.substring(0,this.selectionStart)
+ e.key
+ cur.substring(this.selectionEnd);
return cur=== val //unchanged
|| somevalidation(val);
});
But that would not include deletes/backspaces, to handle those as well:
$(":input").keypress(function(e) {
var key,start = this.selectionStart ,end = this.selectionEnd;
if(e.charCode)
key = e.key;
else{
if(e.keyCode===8 || e.keyCode===46){
key = '';
if(end===start){
if(e.keyCode===8)
start--;
else
end++;
}
}
else
return true; //charCode is 0 for non input 46 = delete 8 = backspace
}
var cur = this.value; //current value
var val = cur.substring(0, start) + key + cur.substring(end);
return cur=== val //unchanged
|| somevalidation(val);
});
Fiddle
While testing this seemed to behave as expected. An other way might be have a hidden input field, send the keys there and examine its results, but the above should do the trick.
Is it possible to know, what 2 last keys was pressed on keyboard?
How can I do it?
I need to compare this keys, and if they are the same - make some function.
As example, if someone press Enter - this is a first function, if after Enter he press a SPACE- this is a second function. If after the ENTER he press a Ctrl - this is a third function.
So, I hink that only onw way to do it - is make a 2 var with current and previous key value, and make a IF ELSE IF function
:-)
This is a way, how I get a current key value
$('#text').keydown(function(event) {
$('#show').text(event.keyCode);
});
Or, the BETTER question! (I saw it right now)
Directly in this editor, after I press doublespace - it jump to anothe line in live-preview.
How it works? I'm not sure, but I thinks that I need is almost the same.
Thank you very much!
I think storing the last key pressed and checking both the old and new keyCode in the event handler is a good way to do this.
$('#text').keydown((function() {
var lastKey = undefined;
return function(event) {
// here you have both the old and new keyCode
lastKey = event.keyCode;
};
})());
Also, the two spaces thing is Markdown interpretation.
You could start with a very simple jQuery plugin:
(function($) {
$.fn.keyWatcher = function() {
return this.keydown(function(evt){
var $this = $(this);
var prevEvt = $this.data('prevEvt');
$this.trigger('multikeydown',[prevEvt,evt]);
$this.data('prevEvt',evt);
});
};
})(jQuery);
This will raise a new event (multikeydown) whenever a keydown event is usually raised. This will either provide just the keydown event from the current key press, or if there is a previous one it will provide that too.
Usage could be something like:
$(document).keyWatcher().bind('multikeydown',function(evt,prevKey,currKey){
alert('prevKey = ' + ((prevKey) ? String.fromCharCode(prevKey.keyCode) : '(none)'));
alert('currKey = ' + String.fromCharCode(currKey.keyCode));
});
Live example (press keys on the lower right hand pane in jsfiddle): http://jsfiddle.net/9VMUy/1/
The event provided in prevKey and currKey parameters to the event handler contain the original keydown events, so you have full access to the keyCode, the ctrlKey and shiftKey properties etc.
This is probably not the best solution but see if you can use it or parts of it
jsFiddle
var clicks = 0
$("#text").one("keypress", function () {
clicks = 1;
var KeyCode1 = event.keyCode;
$('#result').text(KeyCode1);
$("#text").one("keypress", function () {
clicks = 2;
var KeyCode2 = event.keyCode;
$('#result').text(KeyCode1 + "\n" + KeyCode2);
});
});
This script is giving me problems. I've re written it several times but I'm missing something.
'questions' array refers to my objects. These objects are different questions with 'a' always being the correct answer.
The script works fine up to this point. The idea is to increment 'n' from 0 to scroll through my array, and provide the next question when a correct answer is clicked. 'x' is always the correct answer (it always holds 'a').
So I can increment 'n' just fine on correct guess; however, when I call the function 'a()' again, (after my alert 'hi' part), it is causing problems. I want to increment n, and call a() so I get the next question. Then I want to place the correct guess (x) in a random place (ie position 0, 1 or 2.)
Grateful for any help.
var questions = [q0,q1,q2,q3,q4,q5,q6,q7];
var n = 0;
function a(){
var y;
var z;
var x = Math.floor((Math.random() * 3))
if(x == 0){y = 1; z = 2}else if(x == 1){y = 0; z = 2}else{y = 0; z = 1}
$("#question_holder").text(questions[n].q);
$(".answer_holder").eq(x).text(questions[n].a);
$(".answer_holder").eq(y).text(questions[n].b);
$(".answer_holder").eq(z).text(questions[n].c);
$(document).ready(function(){
$(".answer_holder").eq(x).click(function(){
alert("hi");
n++;
a();
/*this area needs to get the next question by incrementing
n, then generate a different value to x, to place it
in a different position. which it does. however,
in subsequent questions, you can click the wrong answer as if
it's correct, ie not 'a' or 'x'.*/
});
});
};
Your logic is a bit strange here.. what you are trying to do is register a new click event every time a() runs. I think you want to register one click event for all answer_holder elements, and in the event handler check which element this is and handle it accordingly
Notice the following:
$(document).ready(function(){ - the function defined in this handler is supposed to run once your page is loaded.. I don't think you want to use it inside a(). It is usually only used in global scope (outside all functions)
$(".answer_holder").eq(x).click(function(){ - this event handler registers your function depending on the value of x. I suggest you register an event handler for all $(".answer_holder"), without depending on x (in the global scope). And inside that event handler (in the function), you check which element triggered the event (using $(this) - it returns the element that was clicked)
You have the $(document).ready() in the wrong place. Try something like this (caveat: this is completely untested):
function setupQuestion(n) {
var x,y,z;
x = Math.floor((Math.random() * 3))
if(x == 0){y = 1; z = 2}else if(x == 1){y = 0; z = 2}else{y = 0; z = 1}
$("#question_holder").text(questions[n].q);
$(".answer_holder").eq(x).text(questions[n].a).data('answer', 'a');
$(".answer_holder").eq(y).text(questions[n].b).data('answer', 'b');
$(".answer_holder").eq(z).text(questions[n].c).data('answer', 'c');
}
$(document).ready(function() {
var n = 0;
$('.answer_holder').click(function() {
if ($(this).data('answer') === 'a') { // Or whatever is the correct answer
n++;
if (n < questions.length) {
setupQuestion(n);
} else {
// Do something else, the test is finished
}
}
return false;
});
setupQuestion(n);
});
Note that I am not comparing on the text() function but rather the data() function. This is because the text as displayed to the user might be decorated in some way, making comparisons difficult. Simply using the answer index 'a' 'b' or 'c' is clearer.
Can I know the number of keys pressed at the same time in Javascript?
If so, how can I have an array of their keyCode?
You can listen for keydown and keyup events.
var keys = { length: 0 };
document.onkeydown = function(e){
if(!keys[e.keyCode]) {
keys[e.keyCode] = true;
keys.length++;
}
}
document.onkeyup = function(e){
if(keys[e.keyCode]) {
keys[e.keyCode] = false;
keys.length--;
}
}
Then all the keys that are true are the ones that are pressed currently.
Fiddle demo thanks to #Esailija: http://jsfiddle.net/maniator/Gc54D/
This should do the trick. It is similar to Neal's, but should fix a few issues, including the leaving-the-window bug and the negative-numbers-of-keys bug. I also streamlined the message writing code a bit. I replaced the timer loop for writing the number of keys message with an on-demand system, added a safety mechanism for decrementing the length index, and added the clearKeys to switch all keys to up when the user leaves the window. The code still has two bugs: it will not recognize keys that are still held down after a new window has been opened and closed (they must be released and re-pushed), and I can't get it to recognize more then six keys (I suspect this isn't related to this code, but the computer/browser...).
var keys = {
length: 0
};
window.onkeydown = function(e) {
if (!keys[e.keyCode]) {
keys[e.keyCode] = true;
keys.length++;
document.body.innerHTML = "You are pressing " + keys.length + " keys at the same time.";
}
}
window.onkeyup = function(e) {
if (keys[e.keyCode]) {
keys[e.keyCode] = false;
if (keys.length) {
keys.length--;
}
document.body.innerHTML = "You are pressing " + keys.length + " keys at the same time.";
}
}
function clearKeys() {
for (n in keys) {
n = false
};
keys.length = 0;
document.body.innerHTML = "You are pressing " + 0 + " keys at the same time.";
}
document.body.innerHTML = "You are pressing 0 keys at the same time.";
window.onblur = clearKeys;
Since you want the number of keys pressed at the same time and an array of their key codes I suggest you use the following function:
var getKeys = function () {
var keys = [];
window.addEventListener("blur", blur, false);
window.addEventListener("keyup", keyup, false);
window.addEventListener("keydown", keydown, false);
return function () {
return keys.slice(0);
};
function blur() {
keys.length = 0;
}
function keyup(event) {
var index = keys.indexOf(event.keyCode);
if (index >= 0) keys.splice(index, 1);
}
function keydown(event) {
var keyCode = event.keyCode;
if (keys.indexOf(keyCode) < 0)
keys.push(keyCode);
}
}();
When you call getKeys it will return an array of all the keys pressed at the same time. You can use the length property of that array to find the number of keys pressed at the same time. Since it uses addEventListener it works cooperatively with other code on the page as well.
I tested the above function and it always returns the correct keys pressed even when you switch to another window while holding down a key (it removes that key from the array). If you hold a key and switch back then it recognizes that a key is pressed and it pushes in onto the array. Hence I can testify that there are no bugs in the above code. At least not on the browser I tested it on (Opera 12.00).
I was able to press 8 keys at the same time (A, S, D, F, J, K, L and ;). This number seems OS specific as I can only press 4 left hand keys and 4 right hand keys at the same time. For example after I press A, S, D and F, and then I press another left hand (let's say G) then it won't recognize the last key. This is probably because the OS knows how humans type and so it only allows four interrupts for each of the left and right hand keys. The OS I am using is Ubuntu 12.04.
You can see the code in action on this fiddle. I used a Delta Timer instead of setInterval to display the results after every 50 ms. You may also wish to read the following answer as well.
I'm curious how i, with the following jQuery plugin code im writing at the bottom of this question, could implement key combos. How it's working so far is it allows a user to create key commands simply by doing a normal jQuery like syntax and provide an event for the key command, like so:
$(window).jkey('a',function(){
alert('you pressed the a key!');
});
or
$(window).jkey('b c d',function(){
alert('you pressed either the b, c, or d key!');
});
and lastly what i want is the ability to do, but can't figure out:
$(window).jkey('alt+n',function(){
alert('you pressed alt+n!');
});
I know how to do this outside of the plugin (on keyup set a var false and on keydown set the var true and check if the var is true when you press the other key), but i don't know how to do this when you dont know what keys are going to be pressed and how many. How do I add this support? I want to be able to allow them to do things like alt+shift+a or a+s+d+f if they wanted. I just can't get my head around how to implement this. Any ideas?
I'm going to release this as an open source plugin and i'd love to give whoever gives me the right, working, answer some credit on the blog post and in the code it's self. Thanks in advance!
(function($) {
$.fn.jkey = function(keyCombo,callback) {
if(keyCombo.indexOf(' ') > -1){ //If multiple keys are selected
var keySplit = keyCombo.split(' ');
}
else{ //Else just store this single key
var keySplit = [keyCombo];
}
for(x in keySplit){ //For each key in the array...
if(keySplit[x].indexOf('+') > -1){
//Key selection by user is a key combo... what now?
}
else{
//Otherwise, it's just a normal, single key command
}
switch(keySplit[x]){
case 'a':
keySplit[x] = 65;
break;
case 'b':
keySplit[x] = 66;
break;
case 'c':
keySplit[x] = 67;
break;
//And so on for all the rest of the keys
}
}
return this.each(function() {
$this = $(this);
$this.keydown(function(e){
if($.inArray(e.keyCode, keySplit) > -1){ //If the key the user pressed is matched with any key the developer set a key code with...
if(typeof callback == 'function'){ //and they provided a callback function
callback(); //trigger call back and...
e.preventDefault(); //cancel the normal
}
}
});
});
}
})(jQuery);
Use keypress instead of keyup/keydown because the latter two do not accurately protray the keycode (reference, see last paragraph). You can reference the altKey ctrlKey and shiftKey boolean properties of the event object in this case...
$(document).keypress(function(e) {
var key = String.fromCharCode(e.which);
var alt = e.altKey;
var ctrl = e.ctrlKey
var shift = e.shiftKey;
alert("Key:" + key + "\nAlt:" + alt + "\nCtrl:" + ctrl + "\nShift:" + shift);
});
Also, you can use String.fromCharCode to translate the key code to an actual letter.
You can't trap multiple keys aside from combinations with Ctrl, Alt, and Shift. You simply can't do it in a single event. So throw the a+s+d+f idea out the window.
Note: Obviously there are certain key combinations that are used by the browser. For instance, Alt + F usually brings up the File menu in Windows. Ctrl + N usually launches a new window/tab. Do not attempt to override any of these combinations.
Here's a live demo for your testing pleasure.
Here's what I came up with. Essentially what I did was created a JSON object that stores all the key codes. I then replace all the provided keys with the codes. If the keys are using the '+' to make a key combo, I then create an array of the codes out of it.
We then create another array that stores all the keys that are being pressed (keyDown add the item, keyUp removes it). On keyDown, we check if it's a single key command or combo. If it's a combo, we check it against all the currently active key presses. If they all match, we execute the callback.
This will work with any number of key combos. Only time I saw that it wasn't working is when you use the 'alert()' to display a message on the key combo because it will no longer remove the items from the active key press array.
(function($) {
$.fn.jkey = function(keyCombo,callback) {
// Save the key codes to JSON object
var keyCodes = {
'a' : 65,
'b' : 66,
'c' : 67,
'alt' : 18
};
var x = '';
var y = '';
if(keyCombo.indexOf(' ') > -1){ //If multiple keys are selected
var keySplit = keyCombo.split(' ');
}
else{ //Else just store this single key
var keySplit = [keyCombo];
}
for(x in keySplit){ //For each key in the array...
if(keySplit[x].indexOf('+') > -1){
//Key selection by user is a key combo
// Create a combo array and split the key combo
var combo = Array();
var comboSplit = keySplit[x].split('+');
// Save the key codes for each element in the key combo
for(y in comboSplit){
combo[y] = keyCodes[ comboSplit[y] ];
}
keySplit[x] = combo;
} else {
//Otherwise, it's just a normal, single key command
keySplit[x] = keyCodes[ keySplit[x] ];
}
}
return this.each(function() {
$this = $(this);
// Create active keys array
// This array will store all the keys that are currently being pressed
var activeKeys = Array();
$this.keydown(function(e){
// Save the current key press
activeKeys[ e.keyCode ] = e.keyCode;
if($.inArray(e.keyCode, keySplit) > -1){ // If the key the user pressed is matched with any key the developer set a key code with...
if(typeof callback == 'function'){ //and they provided a callback function
callback(); //trigger call back and...
e.preventDefault(); //cancel the normal
}
} else { // Else, the key did not match which means it's either a key combo or just dosn't exist
// Check if the individual items in the key combo match what was pressed
for(x in keySplit){
if($.inArray(e.keyCode, keySplit[x]) > -1){
// Initiate the active variable
var active = 'unchecked';
// All the individual keys in the combo with the keys that are currently being pressed
for(y in keySplit[x]) {
if(active != false) {
if($.inArray(keySplit[x][y], activeKeys) > -1){
active = true;
} else {
active = false;
}
}
}
// If all the keys in the combo are being pressed, active will equal true
if(active === true){
if(typeof callback == 'function'){ //and they provided a callback function
callback(); //trigger call back and...
e.preventDefault(); //cancel the normal
}
}
}
}
} // end of if in array
}).keyup(function(e) {
// Remove the current key press
activeKeys[ e.keyCode ] = '';
});
});
}
})(jQuery);
This is just a shot in the dark but maybe it'll help you down the right path.
If it's possible to have that function recognize a hexadecimal value for the key that you entered instead of the literal key (such as 0x6E for the letter 'n'), you could derive what "alt+n" translates to in hex and have the function look out for that value.
If you're looking for something that will let a user easily enter and define key combos using a plain input box, I wrote a plugin that does it for you: http://suan.github.com/jquery-keycombinator/