views:

91

answers:

4

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?

A: 

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.

MooGoo
This isn't a bad idea, but the general problem is that whilst `document` always has focus, `#game` won't have unless you deliberately move focus to inside it (and then it might lose focus if the user clicks away and back to the window).
bobince
Hmm yea I see what you are saying. Web App mindset here. This is probably similar to implamenting the ability to drag or resize an element, the 'mousemove' event listener must be attached to window or document else the event would cease to fire once the cursor is not hovering over said element before its position is updated by the event function. Ah well
MooGoo
+4  A: 

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.

galambalazs
I guess this is the best answer, thanks! The idea is great and I will definitely use it; also I'll add a method `addState(name, fn)`, which makes this object complete. Thanks :]
Harmen
A: 

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;
}
bobince
except what happens when confirmation box doesn't have focus (e.g. focus not set, or focus not reset when the user clicks on the game area) in which case the confirmation box is no longer capturing input
Jonathan Fingland
btw, is there any reason not to use the default operator: `event = event || window.event` ?
Jonathan Fingland
That's what's usually wanted; I'm not sure which behaviour the OP is after though. If the source of the key event is unimportant and only the timing matters then yes, the only way to do that would be the external variable.
bobince
`event || window.event` is fine, I just prefer to be as explicit as possible about what I'm checking for.
bobince
ah, thanks (padding).
Jonathan Fingland
A: 

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...

Jonathan Fingland