This is the sixth article of an ongoing series about game development for the Web.
In the previous article, we learned that the main loop (also called game loop or event loop) runs continuously during gameplay. Every iteration, it processes user input, updates the game state and then it renders the game.
We, also, learned that the target platform might condition the way the main loop is implemented, which is why, when targeting the Web, we rely on callbacks handled by the browser. Otherwise, we would block the thread that the browser uses to render its own window. In addition, we talked about how important is to control the rate at which the loop iterates to ensure consistency across different hardware.
In this delivery, we will discuss how to handle events in Web documents & the browser, this will help us transform our simulations into immersive experiences.
Handling user interaction when doing game development for the Web is no different than any other low-level native game sub-system. There are no built-in abstractions to notify when the user has interacted with a specific object that has been rendered on the canvas
.
Although, they can be easily binded using element attributes, events are not coupled to the canvas
element rendering API's nor any other HTML element. This allows us to have a great degree of control over the implementation of event driven systems such as input handling.
Let's dive right in!
JavaScript Event Model
An event is is an action or occurrence detected and handled by a program. Events are one of the key building blocks of client side programming with JavaScript.
In order to react to events, JavaScript code must register a callback subroutine which is triggered when a particular action occurs. These subroutines are called event handlers (or listeners). If you are familiar with design patterns you can tell that this is an obvious implementation of the Observer Pattern.
You could classify JavaScript events into two main categories:
DOM Events, these are events that affect the elements that comprise the HTML document. For example, clicking a button on a form.
Interactive User Agent Events (or Web Browser events), these are events tied directly to the browser. For example, when the browser window is resized.
Mozilla's Developers Network has a detailed list of the supported events at the Event Reference page.
Each event is an instance of a specific event type. Events themselves are just data structures that contain information about the action that occurred. Usually, Web browsers pass this single data structure as an argument to the registered handler.
Historically, like the Document Object Model (DOM), the event model used by various Web browsers had significant differences. This caused compatibility issues and substantial platform specific code to be produced. To combat this, the event model was standardized by the World Wide Web Consortium (W3C) in DOM Level 2. As of the writing of this article, a draft of the DOM Level 3 Event Model is ready and many browser vendors implement it as an "experimental" feature.
In this event model, the browser is in charge of signalling that something has happened by implementing the EventTarget interface. It is worth mentioning that standard conforming browsers may support features not found in the specification and they should be avoided to ensure consistency and compatibility across devices, which is what the standard itself tries to overcome.
DOM Level 2 events have a propagation path through the HTML document and each triggered event must reflect a hierarchical tree structure. The tree's child with the highest depth is the event target.
The specification defines that all events go through three phases in the following order:
Capture phase: The event object must propagate through the target's ancestors from the document's browsing context's Window Proxy object to the target's parent. Here, registered Event listeners must handle the event before it reaches its target.
Target phase: The event object must arrive at the event object's event target. Event listeners registered for this phase must handle the event once it has reached its target. If the event type indicates that the event must not bubble, the event object must halt after completion of this phase.
Bubble phase: The event object propagates through the target's ancestors in reverse order, starting with the target's parent and ending with the document's browsing context's Window Proxy object. Event listeners registered for this phase must handle the event after it has reached its target.
JavaScript event handling is single-threaded, therefore events are executed sequentially. This means that if two events happen at the same time they will be executed one after the other.
Event Handlers
The way events are handled, in JavaScript, has evolved with time. Prior to the adoption of the DOM Level 2 Event Model by all major browser vendors, event handling was a gray area. UI programmers had to jump through many hurdles in order to achieve cross-browser compatibility.
Up to this date there are 3 official event handling models (excluding the current DOM Level 3 Event Model draft):
- DOM Level 0 Event Model
- Inline
- Traditional
- DOM Level 2 Event Model
- Microsoft Event Model
DOM Level 0 Event Model
This model was first introduced by Netscape Navigator 2 with the DOM manipulation specification. One particular characteristic of this model is the fact that only one handler can be registered per event. There are two model types: inline model and traditional model.
Inline: In the inline model, event handlers are added as attributes of HTML elements. The default click behavior is cancelled by returning false in the event handler.
<a onclick="EventHandler()">Click me!</a>
<script>
function EventHandler() {
alert('This is an event handler!');
// return false if you want to cancel the default behavior.
}
</script>
A common misconception is the belief that this model allows the usage of custom arguments, i.e. EventHandler('Custom Argument')
. While this will work, the JavaScript engine will really create an anonymous function containing the function call with the argument and will replace the event handler binding use the function.
function() {
EventHandler('Custom Argument');
return false;
}
Traditional Model: In the traditional model, we use DOM objects to bind the handlers to the events.
<a id="clickable">Click me!</a>
<script>
var element = document.getElementById('clickable');
element.onclick = EventHandler;
// To remove the event handler binding just set the event to null
//
element.onclick = null;
function EventHandler() {
alert('This is an event handler!');
}
</script>
Notice that when doing the binding EventHandler
is missing the parenthesis. This is intentional, adding the parenthesis would mean that we are assigning to the onclick event the return value of the EventHandler
function (which in this case is undefined) instead of the function itself.
Even though, all the browsers out there support this model in both of its forms, we should stay away from it. It does not align with current code practices, re-usability, maintainability, and will, also, taint the HTML document code.
DOM Level 2 Event Model
The DOM Level 2 Event Model is the most current stable model by the W3C. It is designed with two main goals:
The first goal is the design of a generic event system which allows registration of event handlers, describes event flow through a tree structure, and provides basic contextual information for each event.
The second goal of the event model is to provide a common subset of the current event systems used in DOM Level 0 browsers. This is intended to foster interoperability of existing scripts and content. It is not expected that this goal will be met with full backwards compatibility. However, the specification attempts to achieve this when possible.
This model introduces the EventTarget interface which allows registration and removal of EventListeners (as in plural, a big change in contrast with the DOM Level 0 Model) and dispatch of events to a specific EventTarget.
/* This definition was taken from the W3C official specification.
* It can be found here: http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration
*/
interface EventTarget {
void addEventListener(in DOMString type, in EventListener listener,
in boolean useCapture);
void removeEventListener(in DOMString type, in EventListener listener,
in boolean useCapture);
boolean dispatchEvent(in Event evt)
raises(EventException);
};
A naive implementation of this model would look like this:
<a id="clickable">Click me!</a>
<script>
var element = document.getElementById('clickable');
element.addEventListener('click', EventHandler, false);
function EventHandler() {
alert('This is an event handler!');
}
// Dispatching is simple
// Create the event object
//
var event = new Event('MouseEvents');
// Set the event object properties
//
event.initMouseEvent(
'click' // event type
,true // can bubble?
,true // cancelable?
);
// Dispatch the event
//
element.dispatchEvent( event );
// To remove the listener you would do it like this.
//
element.removeEventListener('click', EventHandler, false);
</script>
Microsoft Event Model
Prior to adopting the W3C event model, Microsoft implemented its own one. It supports binding multiple handlers to the same event and in some ways it feels very similar to the DOM Level 2 Event Model.
/*
* Taken from Internet Explorer's legacy event model definition.
* http://msdn.microsoft.com/en-us/library/ie/jj853328(v=vs.85).aspx
*/
void attachEvent(in string DHTMLEvent,
in Pointer EventHandlerFunctionPointer);
void detachEvent(in string DHTMLEvent,
in Pointer EventHandlerFunctionPointer);
boolean fireEvent(in string DHTMLEvent,
in EventObject evt, out boolean returnValue);
Despite of the similarities between this model and the DOM Level 2 Event Model, there are some important drawbacks:
- The event handling is referenced, not copied, which makes the
this
keyword always refer to the window object. - The capture phase of the event cannot be accessed because of the way bubbling works.
- It's only compatible with Trident (Internet Explorer's layout engine) Enabled browsers.
This model was the way event handling work up until Internet Explorer 8. Latest versions of the browser are compliant with the W3C standard. In addition, Microsoft announced that legacy support will be retired in Internet Explorer 11.
Here's an implementation of the model:
<a id="clickable">Click me!</a>
<script>
var element =
document.getElementById('clickable');
// Event Handler callback definition
//
function EventHandler() {
alert('This is an event handler!');
}
// Attaching the event
//
element.attachEvent('onclick', EventHandler);
// Dettaching the event
//
element.detachEvent('onclick', Eventhandler);
</script>
Custom Event Handlers
The ability of handling custom events has been an essential part of client side programming using JavaScript. At the beginning, JavaScript borrowed a lot of concepts from Java (including part of its name), the custom event API was no exception. The initial version resulted in a cumbersome implementation which required wizard (level 70) skills.
The newer versions of the JavaScript Event model provide a simple API to handle and trigger custom events. This is useful in cases where we want to retrieve extra information of pre-defined events, like mouseclick, keypress, etc. In addition, we could trigger custom behaviors like initializations or finalizations in our own custom code base.
Here's an example of an implementation of the new CustomEvent API:
var customEvent = new CustomEvent('init', { 'detail': 'Custom data' });
// Event Handler callback definition
//
function EventHandler() {
alert('This is an event handler!');
}
// Listen for the event.
//
element.addEventListener('init', EventHandler, false);
// Dispatch the event.
//
element.dispatchEvent(customEvent);
If you plan on supporting older browsers I'd recommend using the following polyfill. In case you want to implement a custom branch to use the older API, which I strongly advice against, you can see an example here of how it's done.
Conclusion
What should I use?
I know, I know, there are so many options. What should I do to ensure compatibility across the browser universe?
The W3C is the authority of the Web, so it is safe to follow their proposed standard, DOM Level 2 Event Model.
Mozilla Developer Network justify's the use of the DOM Level 2 Model as follows:
- It allows adding more than a single handler for an event.
- It gives you finer-grained control of the phase when the listener gets activated (capturing vs. bubbling)
- It works on any DOM element, not just HTML elements.
Compatibility
To ensure compatibility with legacy browsers, like Internet Explorer 8, you must add the EventTarget
interface definition.
What's next?
On the couple of following articles we will demonstrate how to create a keyboard and mouse input system using JavaScript's event handling.
Stay tuned!
References
P.-P. Koch, "Advanced event registration models," [Online]. Available: http://www.quirksmode.org/js/events_advanced.html. [Accessed 10 May 2014].
P.-P. Koch, "Event compatibility tables," [Online]. Available: http://www.quirksmode.org/dom/events/index.html. [Accessed 10 May 2014].
Mozilla Developers Network, "Event," [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/API/Event. [Accessed 10 May 2014].
Mozilla Developers Network, "Event reference," [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/Reference/Events. [Accessed 10 May 2014].
Mozilla Developers Network, "EventTarget.addEventListener," [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener. [Accessed 10 May 2014].