Pressing space bar in game will make a character shoot, pressing space bar when a confirmation box is shown will make this box disappear and pressing space bar in a highscore form will add a space in an input box. In this example there are several events for the same key, but only one is fired at a time.
Is there a general (or specific for Javascript) method or way of programming to add events to a certain key, so they are only executed under certain circumstances?
Of course it can be done like this:
var inGame = true|false;
var inConfirmationBox = true|false;
function spaceBarHandler(){
if(inGame){ /*shoot*/}
else if(inConfirmationBox){ /*remove box*/}
}
document.onkeydown = function(){ /* call space bar handler if space bar was pressed */ };
But this is a very confusing way of programming, since specific actions are mixed together in a space bar handler function, which makes maintenance hard.
What is the best way to handle multiple events for one key, such that these events are only fired under certain circumstances?
Functions are first-class objects in javascript, which makes them really powerful. Because of this, your problem can be solved very elegantly.
// the whole thing can be encapsulated
// into an object actually
function spaceBarHandler() {
var state = spaceBarHandler.state;
var actions = spaceBarHandler.actions;
// execute function if exists
if (actions[state]) {
actions[state]();
}
}
// spaceBar actions
spaceBarHandler.actions = {
shoot: function() {
// bang bang
},
removeBox: function() {
// do it...
}
};
// change current state from outside
// we are in the game
spaceBarHandler.state = "shoot";
// change current state from outside
// confirmation box is shown
spaceBarHandler.state = "removeBox";
All these cases will be handled by one function. If you want to extend with another case, you just add another function to the actions object. Notice how the whole thing is encapsulated into one object.
you could instead add and remove the event listener as needed.
let's assume you're using a javascript framework (if you're not, then you probably should be considering the amount of JS code involved in a game like this)
using PrototypeJS:
when game starts,
document.observe("keydown",shootHandler());
when the message box is created,
function createBox(text) {
...snip
document.observe("keydown",closeBox());
document.fire("game:pause");
}
and, for example
var paused = false;
function shoothandler() {
if (!paused) {
alert("pew! pew!");
}
}
function closeBox() {
$('messagebox').remove();
document.fire("game:unpaused");
document.stopObserving("keydown",closeBox());
}
document.observe("game:paused", function() { paused = true;});
document.observe("game:unpaused", function() { paused = false;});
document.observe("game:over", function() { document.stopObserving("keydown",shootHandler());});
I haven't included the high score screen but the theory is the same.
As you can see, I also used custom events to notify the pause status. The same event could also be fire by a puase button in the interface, etc...
Attach event listeners to individual elements instead of the entire document.
document.getElementById('highscore').onkeypress = function(keyEvent) {
if (is_spacebar(keyEvent)) //Do something...
};
document.getElementById('game').onkeypress = function(keyEvent) {
if (is_spacebar(keyEvent)) //Do something else...
};
This is a simplistic example. You will probably have to deal with event bubbling which can be controlled when using addEventListener() to attach functions to events. Given browser (IE) compatibility issues involving this, some JS library should be used to deal with events.
There are a few ways, typically involving code-branching for IE's ‘special’ event model.
One way is to stop keypresses handled further down from bubbling up to the document key handler:
confirmationbox.onkeydown = function(event) {
if (event === undefined) event = window.event;
// do something with event.keyCode
if ('stopPropagation' in event) // standards browsers
event.stopPropagation();
else if ('cancelBubble' in event) // IE before version 9
event.cancelBubble = true;
};
document.onkeydown = ... // will not be called for keydowns inside confirmationbox
Another way would be to check the event target element to see if it's in the box:
document.onkeydown = function(event) {
if (event === undefined) event = window.event;
var target = 'target' in event ? event.target : event.srcElement; // srcElement is for IE<9
if (target === containerbox || isDescendantOf(target, containerbox) {
// do containerbox stuff
} else {
// do other stuff
}
};
function isDescendantOf(element, ancestor) {
while (element = element.parentNode)
if (element === ancestor)
return true;
return false;
}
Related
I have a hyperlink in my page. I am trying to automate a number of clicks on the hyperlink for testing purposes. Is there any way you can simulate 50 clicks on the hyperlink using JavaScript?
MSDN
I'm looking for onClick event trigger from the JavaScript.
Performing a single click on an HTML element: Simply do element.click(). Most major browsers support this.
To repeat the click more than once: Add an ID to the element to uniquely select it:
Google Chrome
and call the .click() method in your JavaScript code via a for loop:
var link = document.getElementById('my-link');
for(var i = 0; i < 50; i++)
link.click();
You should just use click. For more advanced event firing, use dispatchEvent.
const body = document.body;
body.addEventListener('click', e => {
console.log('clicked body');
});
console.log('Using click()');
body.click();
console.log('Using dispatchEvent');
body.dispatchEvent(new Event('click'));
Original Answer - Obsolete
Here is what I use for IE9+ http://jsfiddle.net/mendesjuan/rHMCy/4/
/**
* Fire an event handler to the specified node. Event handlers can detect that the event was fired programatically
* by testing for a 'synthetic=true' property on the event object
* #param {HTMLNode} node The node to fire the event handler on.
* #param {String} eventName The name of the event without the "on" (e.g., "focus")
*/
function fireEvent(node, eventName) {
// Make sure we use the ownerDocument from the provided node to avoid cross-window problems
var doc;
if (node.ownerDocument) {
doc = node.ownerDocument;
} else if (node.nodeType == 9){
// the node may be the document itself, nodeType 9 = DOCUMENT_NODE
doc = node;
} else {
throw new Error("Invalid node passed to fireEvent: " + node.id);
}
if (node.dispatchEvent) {
// Gecko-style approach (now the standard) takes more work
var eventClass = "";
// Different events have different event classes.
// If this switch statement can't map an eventName to an eventClass,
// the event firing is going to fail.
switch (eventName) {
case "click": // Dispatching of 'click' appears to not work correctly in Safari. Use 'mousedown' or 'mouseup' instead.
case "mousedown":
case "mouseup":
eventClass = "MouseEvents";
break;
case "focus":
case "change":
case "blur":
case "select":
eventClass = "HTMLEvents";
break;
default:
throw "fireEvent: Couldn't find an event class for event '" + eventName + "'.";
break;
}
var event = doc.createEvent(eventClass);
event.initEvent(eventName, true, true); // All events created as bubbling and cancelable.
event.synthetic = true; // allow detection of synthetic events
// The second parameter says go ahead with the default action
node.dispatchEvent(event, true);
} else if (node.fireEvent) {
// IE-old school style, you can drop this if you don't need to support IE8 and lower
var event = doc.createEventObject();
event.synthetic = true; // allow detection of synthetic events
node.fireEvent("on" + eventName, event);
}
};
Note that calling fireEvent(inputField, 'change'); does not mean it will actually change the input field. The typical use case for firing a change event is when you set a field programmatically and you want event handlers to be called since calling input.value="Something" won't trigger a change event.
What
l.onclick();
does is exactly calling the onclick function of l, that is, if you have set one with l.onclick = myFunction;. If you haven't set l.onclick, it does nothing. In contrast,
l.click();
simulates a click and fires all event handlers, whether added with l.addEventHandler('click', myFunction);, in HTML, or in any other way.
I'm quite ashamed that there are so many incorrect or undisclosed partial applicability.
The easiest way to do this is through Chrome or Opera (my examples will use Chrome) using the Console. Enter the following code into the console (generally in 1 line):
var l = document.getElementById('testLink');
for(var i=0; i<5; i++){
l.click();
}
This will generate the required result
.click() does not work with Android (look at mozilla docs, at mobile section). You can trigger the click event with this method:
function fireClick(node){
if (document.createEvent) {
var evt = document.createEvent('MouseEvents');
evt.initEvent('click', true, false);
node.dispatchEvent(evt);
} else if (document.createEventObject) {
node.fireEvent('onclick') ;
} else if (typeof node.onclick == 'function') {
node.onclick();
}
}
From this post
Use a testing framework
This might be helpful - http://seleniumhq.org/ - Selenium is a web application automated testing system.
You can create tests using the Firefox plugin Selenium IDE
Manual firing of events
To manually fire events the correct way you will need to use different methods for different browsers - either el.dispatchEvent or el.fireEvent where el will be your Anchor element. I believe both of these will require constructing an Event object to pass in.
The alternative, not entirely correct, quick-and-dirty way would be this:
var el = document.getElementById('anchorelementid');
el.onclick(); // Not entirely correct because your event handler will be called
// without an Event object parameter.
IE9+
function triggerEvent(el, type){
var e = document.createEvent('HTMLEvents');
e.initEvent(type, false, true);
el.dispatchEvent(e);
}
Usage example:
var el = document.querySelector('input[type="text"]');
triggerEvent(el, 'mousedown');
Source: https://plainjs.com/javascript/events/trigger-an-event-11/
Please call trigger function any where and button will click.
<a href="#" id="myBtn" title="" >Button click </a>
function trigger(){
document.getElementById("myBtn").click();
}
Fair warning:
element.onclick() does not behave as expected. It only runs the code within onclick="" attribute, but does not trigger default behavior.
I had similar issue with radio button not setting to checked, even though onclick custom function was running fine. Had to add radio.checked = "true"; to set it. Probably the same goes and for other elements (after a.onclick() there should be also window.location.href = "url";)
I want to "listen to" a mouse event only if a checkbox is clicked. Therefore I have the following code:
HTML
<input type="checkbox" id="magicLens" onchange="magicLens()">
<label for="magicLens">Magic Lens</label>
JS
function magicLens(){
const magicLens_checked = document.getElementById('magicLens').checked;
if (magicLens_checked === true){
canvas.addEventListener('mousemove', e => {
myAnonymous = arguments.callee;
...
// draw something at the current mouse position
// and therefore use the 'e' event object
});
}
else {
canvas.removeEventListener('mousemove', myAnonymous);
}
}
Problem is, that the drawing (a lens) also occurs when the checkbox is not checked (false). I'm not even sure if removeEventListener() is the right way to deal with it (though I already tried the whole thing without it and result was the same).
Maybe you have a better idea to describe the issue in the title. Feel free to edit!
It's easier to actually always listen to the event and in there check whether the checkbox is checked.
const canvas = document.getElementById('canvas');
canvas.addEventListener('mousemove', e => {
const magicLens_checked = document.getElementById('magicLens').checked;
if (!magicLens_checked) {
return;
}
// Do something with e
console.log(e);
});
You can check it out in this Fiddle. (I added the event listener to document, to avoid creating an arbitrary canvas, but it works the same way)
The Javascript engine sees what you pass to addEventListener and removeEventListener as two different functions which is why the removeEventListener is not working. Create a named function outside of these and pass it into them.
I'm writing web application which should support both mouse and touch interactions.
For testing I use touch screen device with Windows 7. I've tried to sniff touch events in latest Firefox and Chrome canary and got the following results:
On touch Firefox fires touch and corresponding mouse event.
Chrome fires touchstart/mousedown, touchend/mouseup pairs, but mousemove fired in very strange manner: one/two times while touchmove.
All mouse events handled as always.
Is there any way to handle mouse and touch evens simultaneously on modern touch screens? If Firefox fires a pair of touch and mouse event what happens on touchmove with mousemove in Chrome? Should I translate all mouse events to touch or vice versa? I hope to find right way to create responsive interface.
You can't really predict in advance which events to listen for (eg. for all you know a USB touch screen could get plugged in after your page has loaded).
Instead, you should always listen to both the touch events and mouse events, but call preventDefault() on the touch events you handle to prevent (now redundant) mouse events from being fired for them. See http://www.html5rocks.com/en/mobile/touchandmouse/ for details.
You should rather check availability of touch interface and bind events according to that.
You can do something like this:
(function () {
if ('ontouchstart' in window) {
window.Evt = {
PUSH : 'touchstart',
MOVE : 'touchmove',
RELEASE : 'touchend'
};
} else {
window.Evt = {
PUSH : 'mousedown',
MOVE : 'mousemove',
RELEASE : 'mouseup'
};
}
}());
// and then...
document.getElementById('mydiv').addEventListener(Evt.PUSH, myStartDragHandler, false);
If you want to handle both in same time and browser does not translate well touch events into mouse events, you can catch touch events and stop them - then corresponding mouse event shouldn't be fired by browser (you won't have double events) and you can fire it yourself as mouse event or just handle it.
var mydiv = document.getElementsById('mydiv');
mydiv.addEventListener('mousemove', myMoveHandler, false);
mydiv.addEventListener('touchmove', function (e) {
// stop touch event
e.stopPropagation();
e.preventDefault();
// translate to mouse event
var clkEvt = document.createEvent('MouseEvent');
clkEvt.initMouseEvent('mousemove', true, true, window, e.detail,
e.touches[0].screenX, e.touches[0].screenY,
e.touches[0].clientX, e.touches[0].clientY,
false, false, false, false,
0, null);
mydiv.dispatchEvent(clkEvt);
// or just handle touch event
myMoveHandler(e);
}, false);
The solutions on this thread are outdated - for those (like me) who still land here in 2021, there is a new W3 specification for pointer events. These events combine mouse and touch into one.
https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events
https://www.w3.org/TR/pointerevents/
MouseEvents and TouchEvents do not technically provide exactly the same functionality, but for most purposes , they can be used interchangeably. This solution does not favor one over the other, as the user may have both a mouse and a touch screen. Instead, it allows the user to use which ever input device they wish, as long as they wait at least five seconds before changing inputs. This solution ignores mouse pointer emulation on touchscreen devices when the screen is tapped.
var lastEvent = 3 ;
var MOUSE_EVENT = 1;
var TOUCH_EVENT = 2 ;
element.addEventListener('touchstart', function(event)
{
if (lastEvent === MOUSE_EVENT )
{
var time = Date.now() - eventTime ;
if ( time > 5000 )
{
eventTime = Date.now() ;
lastEvent = TOUCH_EVENT ;
interactionStart(event) ;
}
}
else
{
lastEvent = TOUCH_EVENT ; ;
eventTime = Date.now() ;
interactionStart(event) ;
}
}) ;
element.addEventListener('mousedown', function(event)
{
if (lastEvent === TOUCH_EVENT )
{
var time = Date.now() - eventTime ;
if ( time > 5000 )
{
eventTime = Date.now() ;
lastEvent = MOUSE_EVENT ;
interactionStart(event) ;
}
}
else
{
lastEvent= MOUSE_EVENT ;
eventTime = Date.now() ;
interactionStart(event) ;
}
}) ;
function interactionStart(event) // handle interaction (touch or click ) here.
{...}
This is by no means a win all solution, I have used this a few times , and have not found problems with it, but to be fair i usually just use it to start animation when a canvas it tapped , or to provide logic to turn a div into a button. I leave it to you all to use this code , find improvements and help to improve this code.(If you do not find a better solution ).
I found this thread because I have a similar & more complex problem:
supposing we create a js enabled scrollable area with arrows NEXT/PREVIOUS which we want not only to respond to touch and mouse events but also to fire them repeatedly while the user continues to press the screen or hold down his/her mouse!
Repetition of events would make my next button to advance 2 positions instead one!
With the help of closures everything seems possible:
(1) First create a self invoking function for variable isolation:
(function(myScroll, $, window, document, undefined){
...
}(window.myScroll = window.myScroll || {}, jQuery, window, document));
(2) Then, add your private variables that will hold internal state from setTimeout():
/*
* Primary events for handlers that respond to more than one event and devices
* that produce more than one, like touch devices.
* The first event in browser's queue hinders all subsequent for the specific
* key intended to be used by a handler.
* Every key points to an object '{primary: <event type>}'.
*/
var eventLock = {};
// Process ids based on keys.
var pids = {};
// Some defaults
var defaults = {
pressDelay: 100 // ms between successive calls for continuous press by mouse or touch
}
(3) The event lock functions:
function getEventLock(evt, key){
if(typeof(eventLock[key]) == 'undefined'){
eventLock[key] = {};
eventLock[key].primary = evt.type;
return true;
}
if(evt.type == eventLock[key].primary)
return true;
else
return false;
}
function primaryEventLock(evt, key){
eventLock[key].primary = evt.type;
}
(4) Attach your event handlers:
function init(){
$('sth').off('mousedown touchstart', previousStart).on('mousedown touchstart', previousStart);
$('sth').off('mouseup touchend', previousEnd).on('mouseup touchend', previousEnd);
// similar for 'next*' handlers
}
Firing of events mousedown and touchstart will produce double calls for handlers on devices that support both (probably touch fires first). The same applies to mouseup and touchend.
We know that input devices (whole graphic environments actually) produce events sequentially so we don't care which fires first as long a special key is set at private eventLock.next.primary and eventLock.previous.primary for the first events captured from handlers next*() and previous*() respectively.
That key is the event type so that the second, third etc. event are always losers, they don't acquire the lock with the help of the lock functions eventLock() and primaryEventLock().
(5) The above can be seen at the definition of the event handlers:
function previousStart(evt){
// 'race' condition/repetition between 'mousedown' and 'touchstart'
if(!getEventLock(evt, 'previous'))
return;
// a. !!!you have to implement this!!!
previous(evt.target);
// b. emulate successive events of this type
pids.previous = setTimeout(closure, defaults.pressDelay);
// internal function repeats steps (a), (b)
function closure(){
previous(evt.target);
primaryEventLock(evt, 'previous');
pids.previous = setTimeout(closure, defaults.pressDelay);
}
};
function previousEnd(evt){
clearTimeout(pids.previous);
};
Similar for nextStart and nextEnd.
The idea is that whoever comes after the first (touch or mouse) does not acquire a lock with the help of function eventLock(evt, key) and stops there.
The only way to open this lock is to fire the termination event handlers *End() at step (4): previousEnd and nextEnd.
I also handle the problem of touch devices attached in the middle of the session with a very smart way: I noticed that a continuous press longer than defaults.pressDelay produces successive calls of the callback function only for the primary event at that time (the reason is that no end event handler terminates the callabck)!
touchstart event
closure
closure
....
touchend event
I define primary the device the user is using so, all you have to do is just press longer and immediately your device becomes primary with the help of primaryEventLock(evt, 'previous') inside the closure!
Also, note that the time it takes to execute previous(event.target) should be smaller than defaults.pressDelay.
(6) Finally, let's expose init() to the global scope:
myScroll.init = init;
You should replace the call to previous(event.target) with the problem at hand: fiddle.
Also, note that at (5b) there is a solution to another popular question how do we pass arguments to a function called from setTimeout(), i.e. setTimeout(previous, defaults.pressDelay) lacks an argument passing mechanism.
I have been using this jQuery helper to bind both touch and click events.
(function ($) {
$.fn.tclick = function (onclick) {
this.bind("touchstart", function (e) { onclick.call(this, e); e.stopPropagation(); e.preventDefault(); });
this.bind("click", function (e) { onclick.call(this, e); }); //substitute mousedown event for exact same result as touchstart
return this;
};
})(jQuery);
I have a hyperlink in my page. I am trying to automate a number of clicks on the hyperlink for testing purposes. Is there any way you can simulate 50 clicks on the hyperlink using JavaScript?
MSDN
I'm looking for onClick event trigger from the JavaScript.
Performing a single click on an HTML element: Simply do element.click(). Most major browsers support this.
To repeat the click more than once: Add an ID to the element to uniquely select it:
Google Chrome
and call the .click() method in your JavaScript code via a for loop:
var link = document.getElementById('my-link');
for(var i = 0; i < 50; i++)
link.click();
You should just use click. For more advanced event firing, use dispatchEvent.
const body = document.body;
body.addEventListener('click', e => {
console.log('clicked body');
});
console.log('Using click()');
body.click();
console.log('Using dispatchEvent');
body.dispatchEvent(new Event('click'));
Original Answer - Obsolete
Here is what I use for IE9+ http://jsfiddle.net/mendesjuan/rHMCy/4/
/**
* Fire an event handler to the specified node. Event handlers can detect that the event was fired programatically
* by testing for a 'synthetic=true' property on the event object
* #param {HTMLNode} node The node to fire the event handler on.
* #param {String} eventName The name of the event without the "on" (e.g., "focus")
*/
function fireEvent(node, eventName) {
// Make sure we use the ownerDocument from the provided node to avoid cross-window problems
var doc;
if (node.ownerDocument) {
doc = node.ownerDocument;
} else if (node.nodeType == 9){
// the node may be the document itself, nodeType 9 = DOCUMENT_NODE
doc = node;
} else {
throw new Error("Invalid node passed to fireEvent: " + node.id);
}
if (node.dispatchEvent) {
// Gecko-style approach (now the standard) takes more work
var eventClass = "";
// Different events have different event classes.
// If this switch statement can't map an eventName to an eventClass,
// the event firing is going to fail.
switch (eventName) {
case "click": // Dispatching of 'click' appears to not work correctly in Safari. Use 'mousedown' or 'mouseup' instead.
case "mousedown":
case "mouseup":
eventClass = "MouseEvents";
break;
case "focus":
case "change":
case "blur":
case "select":
eventClass = "HTMLEvents";
break;
default:
throw "fireEvent: Couldn't find an event class for event '" + eventName + "'.";
break;
}
var event = doc.createEvent(eventClass);
event.initEvent(eventName, true, true); // All events created as bubbling and cancelable.
event.synthetic = true; // allow detection of synthetic events
// The second parameter says go ahead with the default action
node.dispatchEvent(event, true);
} else if (node.fireEvent) {
// IE-old school style, you can drop this if you don't need to support IE8 and lower
var event = doc.createEventObject();
event.synthetic = true; // allow detection of synthetic events
node.fireEvent("on" + eventName, event);
}
};
Note that calling fireEvent(inputField, 'change'); does not mean it will actually change the input field. The typical use case for firing a change event is when you set a field programmatically and you want event handlers to be called since calling input.value="Something" won't trigger a change event.
What
l.onclick();
does is exactly calling the onclick function of l, that is, if you have set one with l.onclick = myFunction;. If you haven't set l.onclick, it does nothing. In contrast,
l.click();
simulates a click and fires all event handlers, whether added with l.addEventHandler('click', myFunction);, in HTML, or in any other way.
I'm quite ashamed that there are so many incorrect or undisclosed partial applicability.
The easiest way to do this is through Chrome or Opera (my examples will use Chrome) using the Console. Enter the following code into the console (generally in 1 line):
var l = document.getElementById('testLink');
for(var i=0; i<5; i++){
l.click();
}
This will generate the required result
.click() does not work with Android (look at mozilla docs, at mobile section). You can trigger the click event with this method:
function fireClick(node){
if (document.createEvent) {
var evt = document.createEvent('MouseEvents');
evt.initEvent('click', true, false);
node.dispatchEvent(evt);
} else if (document.createEventObject) {
node.fireEvent('onclick') ;
} else if (typeof node.onclick == 'function') {
node.onclick();
}
}
From this post
Use a testing framework
This might be helpful - http://seleniumhq.org/ - Selenium is a web application automated testing system.
You can create tests using the Firefox plugin Selenium IDE
Manual firing of events
To manually fire events the correct way you will need to use different methods for different browsers - either el.dispatchEvent or el.fireEvent where el will be your Anchor element. I believe both of these will require constructing an Event object to pass in.
The alternative, not entirely correct, quick-and-dirty way would be this:
var el = document.getElementById('anchorelementid');
el.onclick(); // Not entirely correct because your event handler will be called
// without an Event object parameter.
IE9+
function triggerEvent(el, type){
var e = document.createEvent('HTMLEvents');
e.initEvent(type, false, true);
el.dispatchEvent(e);
}
Usage example:
var el = document.querySelector('input[type="text"]');
triggerEvent(el, 'mousedown');
Source: https://plainjs.com/javascript/events/trigger-an-event-11/
Please call trigger function any where and button will click.
<a href="#" id="myBtn" title="" >Button click </a>
function trigger(){
document.getElementById("myBtn").click();
}
Fair warning:
element.onclick() does not behave as expected. It only runs the code within onclick="" attribute, but does not trigger default behavior.
I had similar issue with radio button not setting to checked, even though onclick custom function was running fine. Had to add radio.checked = "true"; to set it. Probably the same goes and for other elements (after a.onclick() there should be also window.location.href = "url";)
I'm attempting to build a webpage that loads depending on the input provided. I'm having some trouble wrapping my head around event handling in javascript, basically. Coming from python, if I wanted to wait for a specific keyboard input before moving on to the next object to display, I would create a while loop and put a key listener inside it.
Python:
def getInput():
while 1:
for event in pygame.event.get(): #returns a list of events from the keyboard/mouse
if event.type == KEYDOWN:
if event.key == "enter": # for example
do function()
return
elif event.key == "up":
do function2()
continue
else: continue # for clarity
In trying to find a way to implement this in DOM/javascript, I seem to just crash the page (I assume due to the While Loop), but I presume this is because my event handling is poorly written. Also, registering event handlers with "element.onkeydown = function;" difficult for me to wrap my head around, and setInterval(foo(), interval] hasn't brought me much success.
Basically, I want a "listening" loop to do a certain behavior for key X, but to break when key Y is hit.
In JavaScript, you give up control of the main loop. The browser runs the main loop and calls back down into your code when an event or timeout/interval occurs. You have to handle the event and then return so that the browser can get on with doing other things, firing events, and so on.
So you cannot have a ‘listening’ loop. The browser does that for you, giving you the event and letting you deal with it, but once you've finished handling the event you must return. You can't fall back into a different loop. This means you can't write step-by-step procedural code; if you have state that persists between event calls you must store it, eg. in a variable.
This approach cannot work:
<input type="text" readonly="readonly" value="" id="status" />
var s= document.getElementById('status');
s.value= 'Press A now';
while (true) {
var e= eventLoop.nextKeyEvent(); // THERE IS NO SUCH THING AS THIS
if (e.which=='a')
break
}
s.value= 'Press Y or N';
while (true) {
var e= eventLoop.nextKeyEvent();
if (e.which=='y') ...
Step-by-step code has to be turned inside out so that the browser calls down to you, instead of you calling up to the browser:
var state= 0;
function keypressed(event) {
var key= String.fromCharCode(event? event.which : window.event.keyCode); // IE compatibility
switch (state) {
case 0:
if (key=='a') {
s.value= 'Press Y or N';
state++;
}
break;
case 1:
if (key=='y') ...
break;
}
}
s.value= 'Press A now';
document.onkeypress= keypressed;
You can also make code look a little more linear and clean up some of the state stuff by using nested anonymous functions:
s.value= 'Press A now';
document.onkeypress= function(event) {
var key= String.fromCharCode(event? event.which : window.event.keyCode);
if (key=='a') {
s.value= 'Press Y or N';
document.onkeypress= function(event) {
var key= String.fromCharCode(event? event.which : window.event.keyCode);
if (key=='y') ...
};
}
};
you should not use such loops in javascript. basically you do not want to block the browser from doing its job. Thus you work with events (onkeyup/down).
also instead of a loop you should use setTimeout if you want to wait a little and continue if something happened
you can do sth like that:
<html>
<script>
var dataToLoad = new Array('data1', 'data2', 'data3' );
var pos = 0;
function continueData(ev) {
// do whatever checks you need about key
var ele = document.getElementById("mydata");
if (pos < dataToLoad.length)
{
ele.appendChild(document.createTextNode(dataToLoad[pos]));
pos++;
}
}
</script>
<body onkeyup="continueData()"><div id="mydata"></div></body></html>
everytime a key is released the next data field is appended
For easier implementation of event handling I recommend you to use a library such as Prototype or Jquery (Note that both links take you to their respective Event handling documentation.
In order to use them you have to keep in mind 3 things:
What DOM element you want to observe
What Event you want to capture
What action will the event trigger
This three points are mutually inclusive, meaning you need to take care of the 3 when writing the code.
So having this in mind, using Prototype, you could do this:
Event.observe($('id_of_the_element_to_observe'), 'keypress', function(ev) {
// the argument ev is the event object that has some useful information such
// as which keycode was pressed.
code_to_run;
});
Here is the code of a more useful example, a CharacterCounter (such as the one found in Twitter, but surely a lot less reliable ;) ):
var CharacterCounter = Class.create({
initialize: function(input, counter, max_chars) {
this.input = input;
this.counter = counter;
this.max_chars = max_chars;
Event.observe(this.input, 'keypress', this.keyPressHandler.bind(this));
Event.observe(this.input, 'keyup', this.keyUpHandler.bind(this));
},
keyUpHandler: function() {
words_left = this.max_chars - $F(this.input).length;
this.counter.innerHTML = words_left;
},
keyPressHandler: function(e) {
words_left = this.max_chars - $F(this.input).length;
if (words_left <= 0 && this.allowedChars(e.keyCode)) {
e.stop();
}
},
allowedChars: function(keycode) {
// 8: backspace, 37-40: arrow keys, 46: delete
allowed_keycodes = [ 8, 37, 38, 39, 40, 46 ];
if (allowed_keycodes.include(keycode)) {
return false;
}
return true
}
});
Any good browser will crash when it encounters a script that runs too long. This is to prevent malicious websites from locking up the client application.
You cannot have a infinite loop in javascript. Instead, attach an event listener to the window and point do your processing in the handler (think of it as interrupts instead of polling).
Example:
function addEventSimple(obj,evt,fn) {
if (obj.addEventListener)
obj.addEventListener(evt,fn,false);
else if (obj.attachEvent)
obj.attachEvent('on'+evt,fn);
} // method pulled from quirksmode.org for cross-browser compatibility
addEventSimple(window, "keydown", function(e) {
// check keys
});
document.onkeydown = function(e) {
//do what you need to do
}
That's all it takes in javascript. You don't need to loop to wait for the event to happen, whenever the event occurs that function will be called, which in turn can call other functions, do whatever needs to be be done. Think of it as that instead of you having to wait for the event your looking for to happen, the event your looking for will let you know when it happens.
you could attach an event listener to the window object like this
window.captureEvents(Event.KEYPRESS);
window.onkeypress = output;
function output(event) {
alert("you pressed" + event.which);
}
Check out the YUI key listener
http://developer.yahoo.com/yui/docs/YAHOO.util.KeyListener.html
using the key listener, YUI takes care of capturing any events. IN javascript, there will almost never be an instance where you must wait in a while loop for something to happen.
If you need examples of how event handling works, check out these pages.
http://developer.yahoo.com/yui/examples/event/eventsimple.html