I've been developing a simple system that is supposed to change between two different scenes when you press a button.
gameOne();
var game = 1;
function gameOne() {
game = 1;
console.log("Game 1");
$( "body" ).keyup(function( event ) {
if ( event.which == 49 && game == 1) { // Number 1 key
gameTwo();
}
});
}
function gameTwo() {
game = 2;
console.log("Game 2");
$( "body" ).keyup(function( event ) {
if ( event.which == 49 && game == 2) { // Number 1 key
gameOne();
}
});
}
Expected behaviour - I want it to say Game 1, after after pressing the 1 key and then Game 2 after pressing the 1 key again, and then repeat this as I press 1.
Actual behaviour - It does the expected behaviour a few times, and then it starts repeating 1 and 2 over and over again, to the point it lags the browser out.
JSFiddle: https://jsfiddle.net/a0npotm8/10/
I'm really sorry if this is a basic question or anything, I'm still fairly new to Javascript and JQuery and this is really confusing me currently.
All help is appreciated.
Thank you :)
The problem here is that you are rebinding the keyup event recuresively inside the keyup callback, so it ends up by breaking the browser.
What you need to do is to get the keyup binding code out of the two functions:
gameOne();
var game = 1;
$("body").keyup(function(event) {
if (event.which == 49 && game == 1) { // Number 1 key
gameTwo();
} else if (event.which == 49 && game == 2) { // Number 1 key
gameOne();
}
});
function gameOne() {
game = 1;
console.log("Game 1");
}
function gameTwo() {
game = 2;
console.log("Game 2");
}
what about something like:
let game = 1;
document.onkeyup = ev => {
if (ev.which === 49) {
console.log(`Game ${game}`);
game = game === 1 ? 2 : 1;
}
};
will it solve your issue?
You can use a delegate event handler to control actions like this, so you do not have to juggle event bindings around.
var $container = $('#container').focus();
$(document.body)
.on('keyup', '#container.game1', function(e){
if (e.which == 49) {
console.log('Game 1');
$container.removeClass('game1').addClass('game2');
}
})
.on('keyup', '#container.game2', function(e){
if (e.which == 49) {
console.log('Game 2');
$container.removeClass('game2').addClass('game1');
}
});
#container {
min-width: 100vw;
min-height: 100vh;
background-color: rgb(128, 128, 128);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container" class="game1" tabindex="0">
</div>
This logic creates two different delegate event handlers for the body. Both filter out events for the #container child element of the body, but also filter based on an additional class on the container; game1 and game2. Depending on which class the element has, only one of the event handlers will process.
Whenever you call keyup on an element, you attach another event handler. To catch events, you only need to call it once. The callback functions that handle the event will fire every time the event happens.
Related
one function can be triggered by two events but dont let it trigger by second event after it already occured by one event for example a function creates a box and it can be done by 2 methods
press enter
focus out
so once the box is created dont let it create by the other event.(please tell multiple ways to do it).
<textarea onfocusout="createFolder()>
or
$("textarea").keypress(function (e) {
if (e.which == 13 && !e.shiftKey) {
// $('textarea').replaceWith('<p class="text-center bd-title">' + $("textarea").val() + '</p>')
createFolder();
}
});
you can maintain a flag
let isFolderCreated = false;
$("textarea").keypress(function (e) {
if (!isFolderCreated && (e.which == 13 && !e.shiftKey)) {
isFolderCreated = true;
createFolder();
}
});
all its doing here is checking if the folder is not created and creating the folder
I am trying to create a hangman game, but my event listener doesn't seem as though it's working? When I press a key, there's no response from the webpage.
document.onkeydown = function (event) {
// If the game is finished, one keystroke will reset the game
if (hasFinished) {
resetGame();
hasFinished = false;
console.log("eventListenerWorking16");
} else {
// Make sure A-Z was actually pressed
if (event.keyCode >= 65 && event.keycode <= 90) {
makeGuess(event.key.toLowerCase());
updateDisplay();
checkWin();
checkLoss();
console.log("eventListenerWorking16");
}
}
};
Oh great .. One typo can takes hrs to resolve
One typo in this line
if (event.keyCode >= 65 && event.keycode <= 90) {
It has to be event.keyCode Caps not small
IMHO the event should fire correctly.
I think to make a better debug you should place a console.log right after the anonymous function call (see code below).
This way it will be possible to understand if there is something wrong after (that concerns the logic applied later).
document.onkeydown = function (event) {
console.log("keydown event fired");
// If the game is finished, one keystroke will reset the game
if (hasFinished) {
resetGame();
hasFinished = false;
console.log("eventListenerWorking16 hasFinished");
} else {
// Make sure A-Z was actually pressed
if (event.keyCode >= 65 && event.keycode <= 90) {
makeGuess(event.key.toLowerCase());
updateDisplay();
checkWin();
checkLoss();
console.log("eventListenerWorking16 makeGuess");
}
}
};
Try using this code:
document.addEventListener("keydown", myScript);
I have created two functions. To keep it simple lets take for an example the following:
I got functions firing different events for the same objects. You can activate them using your keyboard arrows
$("body").keydown(function(e) {
if (event.which == 39) open_second_layer();
});
$("body").keydown(function(e) {
if (event.which == 37) open_first_layer();
});
As soon as I have fired one function and press the same key again it fires the animation one more time (unnecessarily).
Because of that as soon as the function open_second_layer has been fired, it should not be able to be fired again, until open_first_layer is fired again. The same should be the case the other way round.
I found .bind and .when as possible solutions, but can't figure out how to use them the right way for that case. I appreciate every suggestions or keywords to google.
You can keep a state variable and track when changes are made to it:
var state_changed = (function() {
var current = null;
return function(state) {
if (state == current) {
return false;
}
current = state;
return true;
};
}());
function open_first_layer()
{
if (!state_changed(1)) {
return;
}
// rest of code
}
function open_second_layer()
{
if (!state_changed(2)) {
return;
}
// rest of code
}
$("body").keydown(function(e) {
if (event.which == 39) {
open_second_layer();
} else if (event.which == 37) {
open_first_layer();
}
});
You can use jQuery's one().
In your first click handler, you bind the second one.
In your second click handler, you bind the first one.
sample
<div id=activate-first>first</div>
<div id=activate-second style="display:none;">second</div>
$(document).ready(function () {
function slide_first(){
$('#activate-first').show();
$('#activate-second').hide();
$('#activate-second').one('click', slide_first);
};
function slide_second(){
$('#activate-first').hide();
$('#activate-second').show();
$('#activate-first').one('click', slide_second);
};
$('#activate-first').one('click', slide_second);
$('#activate-second').one('click', slide_first);
});
Put the other function inside slide_first, like:
function slide_first(){
// other code
$('#activate_second').one('click', slide_second);
}
$('#activate_first').one('click', slide_first);
or use an Anonymous function to do the same:
$('#activate_first').one('click', function(){
// slide_first code here
$('#activate_second').one('click', function(){
// slide_second code here
});
});
Maybe your really want:
function recursiveSlider(){
$('#activate_first').one('click', function(){
// slide_first code here
$('#activate_second').one('click', function(){
// slide_second code here
recursiveSlider();
});
});
}
recursiveSlider();
This is a perfect use case for delegation. You have a single click event, and whenever the event happens, you determine what has been clicked, and you take action accordingly:
$(document.body).on("click", function(ev) {
var $targ = $(ev.target);
if ($targ.is('#button_1')) {
// someone clicked #button_1
}
if ($targ.is('.page-2 *')) {
// something inside of .page-2 was clicked!!
}
});
UPDATE: now the OP has included more code, I'm not sure the issue is - there's no need to bind and unbind events...
http://jsfiddle.net/ryanwheale/uh63rzbp/1/
function open_first_layer() {
$('#first_panel').addClass('active');
$('#second_panel').removeClass('active');
}
function open_second_layer() {
$('#first_panel').removeClass('active');
$('#second_panel').addClass('active');
}
// one event === good
$("body").keydown(function(e) {
if (event.which == 39) open_second_layer();
if (event.which == 37) open_first_layer();
});
... or if you're trying to build a slider, I suggest changing your naming convention:
http://jsfiddle.net/ryanwheale/uh63rzbp/2/
var current_layer = 1,
$all_layers = $('[id^="panel_"]'),
total_layers = $all_layers.length;
function move_layer (dir) {
current_layer += dir;
if (current_layer < 1) current_layer = total_layers;
else if (current_layer > total_layers) current_layer = 1;
$all_layers.removeClass('active');
$('#panel_' + current_layer).addClass('active');
}
// one event === good
$("body").keydown(function(e) {
if (event.which == 39) move_layer(1);
if (event.which == 37) move_layer(-1);
});
move_layer(0);
When i press spacebar, the function shoot executes.
window.onkeydown=function(e){
var which = e.keyCode;
if(which == 32){
shoot();
}
}
If you hold space down, shoot calls many times in a row. I only want the function to execute once every 500ms.
(function($){
var lazerCharging = false,
lazerChargeTime = 500; // Charge time in ms
function handleKeyPress(e){
if(e.keyCode == 32){
shoot(lazerChargeTime);
}
}
function shoot(chargeTime){
if(!lazerCharging){
lazerCharging = true;
$("body").append("pew<br/>");
setTimeout(function(){
lazerCharging = false;
}, chargeTime)
}
}
$(window).on("keydown", handleKeyPress);
})($);
Here's a jsfiddle
You'll want to "debounce"
Using jQuery throttle / debounce, you can pass a delay and function to
$.debounce to get a new function, that when called repetitively,
executes the original function just once per "bunch" of calls.
This can be especially useful for rate limiting execution of handlers
on events that will trigger AJAX requests. Just take a look at this
example to see for yourself!
Ben Alman did the hard work for you here: http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/
Essentially a debounce as MattC suggested. Store the time the function was called last and make sure 500 ms has passed. Also you probably should be using .addEventListener instead of window.onkeydown
(function() {
var lastCallTime = 0;
window.onkeydown = function(e){
var now = Date.now();
if(e.keyCode == 32 && now - lastCallTime > 500) {
shoot();
lastCallTime = now;
}
}
});
I doubt it's guaranteed that keydown/keypress events are always fired continuously. It may depend on the browser, operating system settings, etc. Even if they are, "fire rate" may fluctate. You probably don't want this.
I think a better idea would be to create a timer that's started when the first keydown event is fired, and stopped on keyup event.
http://jsfiddle.net/kPbLH/
var fireTimer = null;
function fire() {
// do whatever you need
}
document.addEventListener("keydown", function(e) {
if (e.keyCode == 32 && fireTimer === null) {
fire(); // fire immediately...
fireTimer = setInterval(fire, 500); // ...and 500ms, 1000ms and so on after
}
});
document.addEventListener("keyup", function(e) {
if (e.keyCode == 32 && fireTimer !== null) {
clearInterval(fireTimer);
fireTimer = null;
}
});
(edited some spelling errors)
So here's a piece of code that repeats itself with every answer the user gives to an MPC-question:
It fires the question, generates 4 answers, binds 'click' and 'mouseover' to the answers, and waits for the user to actually click on one of them.
When he does, it checks if it was right or wrong, displays that to the user, and then waits for another input (anywhere in the document, this time) before it proceeds and repeats.
Now, the idea is that a user can either click with the cursor, OR use QWER to answer. And as I'm am quite new to manipulation of an event and its data, I found a (flawed, apparently) way to separate the two, as you'll find below.
However, when a user 'answers' by clicking, and 'proceeds' by QWER, it waltzes through the whole thing too fast, by actually firing the question as intended, and then immediately answering it AND firing another question AGAIN. (And after extracting the time it takes for this into an array, I found that it sometimes does this 3-6 times in a row, with no apparent reason for this how many times at all...)
Now, as mentioned, I am new when it comes to events, so there may be redundancies and/or wrong uses of (e), but bear with me. I expect the bug to be related to this, of course. Also, some functions are irrelevant here, because they lead back to whatever function their call is in (right(); for example, does not fire anything after itself).
It's the loop that's the point here.
Thanks in advance!
Here's the code:
function keyAns() {
answered="no";
draw(0);
}
function keyNotAns (A) {
B = $(A).children('.answer');
if ($(B).hasClass('right')) {
// do something
}
else if ($(B).hasClass('wrong')) {
// do something
}
answered = "yes";
}
function waitForInput() {
// MOUSE CLICK
$(document).click(function() {
if (answered == "yes") {
answered = "no";
draw(0);
}
});
$('.answer').click(function(e) {
$('.answer').unbind('click');
if (answered == "no") {
calcTime(1);
e.stopPropagation();
if ($(this).hasClass('right')) {
// do something
}
else if ($(this).hasClass('wrong')) {
// do something
}
answered = "yes";
}
});
// KEYPRESSES
$(document).bind('keyup', function(e){
$(document).unbind('keyup');
if (answered == "yes") {
e.stopPropagation();
keyAns();
}
else if ( answered == "no") {
calcTime(1); // irrelevant
if(e.which == 81 || e.keyCode == 81) { // Q
AAA = '#ansQ';
e.stopPropagation();
keyNotAns(AAA);
}
else if(e.which == 87 || e.keyCode == 87) { // W
AAA = '#ansW';
e.stopPropagation();
keyNotAns(AAA);
}
else if(e.which == 69 || e.keyCode == 69) { // E
AAA = '#ansE';
e.stopPropagation();
keyNotAns(AAA);
}
else if(e.which == 82 || e.keyCode == 82) { // R
AAA = '#ansR';
e.stopPropagation();
keyNotAns(AAA);
}
else {
}
waitForInput();
}
});
}
Every time that there's a keypress event, you rebind everything.
This means that if you push 10 keys, you will have 10 onclick listeners. So when a user then clicks, the callback will run 10 times in a row.
The code that you posted doesn't include the original call to the waitForInput function, but you only need to call it once, so you can delete it from this code.
A quick introduction to jquery events:
When you bind, every time that event occurs (on the element you put it to), the callback function you provided will run.
Another tip is that in more recent versions of jQuery, there in an alternative to bind named one. It does the same, but it will only run the first time. Although in this situation, you don't need it.
Your problem is that inside your keybind, you're calling waitForInput.
Asynchronous programming takes some getting used to, but what the functions do inside waitForInput is set up event listeners, and any time the event happens, those listeners fire.
The problem you're seeing is that after handling an event, you're adding more event listeners, and next time the event fires, the listener will fire multiple times.
Simply take the waitForInput() line out of the function, and put it at the bottom of your code. Then it will run only once, and you'll be fine. (It wouldn't hurt to rename it to something like setupEventListeners, to avoid confusion.)
Found it!
All thanks to Scott Mermelstein and ColBeseder, both equally invaluable to my solution! Not only did they find the flaws, they made me understand how events and binds work. So thanks a million, you two!
Indeed:
binds were being stacked, therefore IF it fired, it would fire excessively, also;
the self-call of waitForinput() was indeed unneeded, however;
one e.stopPropagation() was also needed, to prevent the loop from working with wrong event-data, which lead the function to interpret it as the next answer and fire itself again.
Plus: due to laziness - I didn't feel like too much trial and error - I wanted to stay on the safe side, so I added all unbind()'s as first-thing when the function fires.
The result, which works flawlessly, for those who're interested:
function keyNotAns (A) {
B = $(A).children('.answer');
if ($(B).hasClass('right')) {
// do something
}
else if ($(B).hasClass('wrong')) {
// do something
}
answered = "yes";
}
function waitForInput() {
$(document).unbind('keyup');
$(document).unbind('click');
$('.answer').unbind('click');
// MOUSE CLICKS
$(document).click(function() {
if (answered == "yes") {
answered = "no";
draw(0);
}
});
$('.answer').click(function(e) {
if (answered == "no") {
calcTime(1);
if ($(this).hasClass('right')) {
// do something
}
else if ($(this).hasClass('wrong')) {
// do something
}
e.stopPropagation();
answered = "yes";
}
});
// KEYPRESSES
$(document).bind('keyup', function(e){
if (answered == "yes") {
answered="no";
draw(0);
}
else if (answered == "no") {
if(e.which == 81 || e.keyCode == 81) { // Q
keyNotAns('#ansQ');
}
else if(e.which == 87 || e.keyCode == 87) { // W
keyNotAns('#ansW');
}
else if(e.which == 69 || e.keyCode == 69) { // E
keyNotAns('#ansE');
}
else if(e.which == 82 || e.keyCode == 82) { // R
keyNotAns('#ansR');
}
}
});
}
There are two ways to find such bugs. The first one is using the debugger - which can be pretty tedious when you have loops.
The second approach is logging: Write a report / log of all the important actions that you can read later to find out why it failed at some point in the past.
Have a look at console.log() or a JavaScript logging framework.