views:

584

answers:

7

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.

A: 

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

Niko
If you need to use event object in a handler, that `ev` should really be an `event`, since that's the name under which it is passed to a function. IE, of course, is being "special" and assigns event object to a global `window.event` rather than passing it to event handler function under `event`.
kangax
true - you obviously need to tweak the code to act on different keypresses/releases. I was trying to demonstrate how to solfe the while-loop issue
Niko
A: 

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
});
Joel Potter
A: 
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.

tj111
A: 

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);
}
stefita
A: 

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

Zoidberg
+2  A: 

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') ...
        };
    }
};
bobince
Thanks bobince. Your comment about what step-by-step procedural code addressed my problem very well. Wish I had enough rep to upvote you.
intelligencer
A: 

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
  }

});
Lonecat