Functional key handling in Opera TV Store applications
Update history:
- 8 June 2012: added requirements for Back/Return button handling
- 24 August 2012: changes to key handling in line with DOM Level 3 Events, additional information on repeating key events.
- 4 September 2012: alternative and refined approach for handling repeating key events, clarification of extra <ELEMENT onkeydown="…"> complications to get event object, inclusion of HTML5 history API trick to circumvent automatic app closure.
- Spatial navigation and functional buttons
- List of available functional buttons
- Handling
keydown
events - Repeating key events
- Requirements for the Back/Return button
- Preventing default spatial navigation
- Determining support for a specific functional key
Spatial navigation and functional buttons
The Opera TV Store is designed to use the standard four-way directional keys on a remote control for spatial navigation. Authors should test that their applications work correctly using the default spatial navigation built into the Opera TV Store browser.
Opera's spatial navigation works in a similar way to traditional TAB based keyboard access in most browsers, allowing users to move between focusable elements (links, form controls, image map areas). In addition, spatial navigation also employs heuristics that make arbitrary elements with attached click
and mouseover
JavaScript events focusable as well. Lastly, as the name implies, spatial navigation in Opera allows the user to move between those elements based on their spatial relationship on screen, rather than in source order (as with TAB navigation).
In most cases, authors can simply rely on Opera's spatial navigation to handle their application's controls. There are simple mechanisms to further tweak spatial navigation for TV browsing using CSS3.
For maximum control, authors may also choose to handle the navigation of their application themselves by intercepting key presses from the remote control. This makes it possible to not only react to the basic directional buttons (UP, RIGHT, DOWN, LEFT), but to further bind functionality to the various shortcut and functional keys (such as BACK, INFO, OPTIONS or the RED button). As the exact key codes for remote control keys vary between different devices, the Opera TV Store browser provides built-in global constants mapped to the hardware-specific codes used by the current device.
List of available functional buttons
Hardware key | Key code constant | Comment |
---|---|---|
↑ | VK_UP | Always available* |
→ | VK_RIGHT | Always available* |
↓ | VK_DOWN | Always available* |
← | VK_LEFT | Always available* |
Confirm/Select/OK | VK_ENTER | Always available* |
Exit | N/A | Optional but recommended (handled by native firmware) |
Back/Return | VK_BACK_SPACE | Always available |
BLUE | VK_BLUE | Optional but recommended |
RED | VK_RED | Optional but recommended |
GREEN | VK_GREEN | Optional but recommended |
YELLOW | VK_YELLOW | Optional but recommended |
Menu | VK_MENU | Optional |
0 | VK_0 | Optional |
1 | VK_1 | Optional |
2 | VK_2 | Optional |
3 | VK_3 | Optional |
4 | VK_4 | Optional |
5 | VK_5 | Optional |
6 | VK_6 | Optional |
7 | VK_7 | Optional |
8 | VK_8 | Optional |
9 | VK_9 | Optional |
PLAY | VK_PLAY | Optional |
PAUSE | VK_PAUSE | Optional |
STOP | VK_STOP | Optional |
NEXT | VK_TRACK_NEXT | Optional |
PREV | VK_TRACK_PREV | Optional |
FF (Fast-Forward) | VK_FAST_FWD | Optional |
REWIND | VK_REWIND | Optional |
SUBTITLE | VK_SUBTITLE | Optional |
INFORMATION | VK_INFO | Optional |
Note: CONFIRM, EXIT and directional buttons are mandatory for device manufacturers to implement, so they are always available for the end user via the remote control of any device that the Opera TV Store is integrated with. The EXIT key is handled by the Opera TV Store browser itself, to ensure that each application can be closed. For this reason VK_EXIT will not be sent to the application.
Handling keydown
events
Previously, authors were encouraged to handle keypress
events. However, starting with the Opera Device SDK 3.4, the Opera TV Store is aligned with the DOM Level 3 Events model.
The most notable change here is that the keypress
event is now only fired for keys which produce a character value. From the list of functional buttons above, this means that only the number keys 0-9 and the ENTER (Confirm/Select/Ok) buttons can be detected via keypress
.
Additionally, this specification deprecates the keypress
event, meaning that future versions of the specification – and, as a result, future versions of conformant browsers – should not fire this event anymore. For compatibility with existing content, it is unlikely that browsers will drop legacy support for this event, but we would still recommend using keydown
instead of keypress
going forward.
The simplest, but least elegant, way to add key events is to directly add an onkeydown
attribute to an element. When that element has focus, the key event code will be fired. Note, though, that this old-school method requires extra work to determine the event (and the related properties, like keyCode
) that caused the handler to be called, and doesn't necessarily work cross-platform.
<ELEMENT onkeydown="handler()">
function handler() {
// extra hoop to jump through to get event
if (!event) { event=window.event; }
…
}
A much cleaner and flexible way would be to do this directly via JavaScript, either by attaching the handler function directly to the onkeydown
property of the element or using addEventListener
. This automatically passes on the event object associated with the call, avoiding any ugly window.event
hacks:
object.onkeydown = handler;
object.addEventListener("keydown", handler, useCapture);
In the handler
function, you can then compare the event.keyCode
to the set of global constants for functional keys provided in the Opera TV Store.
function handler(event){
…
if (VK_RED == event.keyCode){
/* VK_RED was pressed … do something useful */
}
…
}
Although the DOM Level 3 Events model normatively uses event.key
and event.char
, it still retains information on legacy key attributes such as event.keyCode
. For compatibility with existing content, it is likely that event.keyCode
will continue to be available for the time being. As the new event properties are not backwards-compatible, we recommend still using the current event.keyCode
property.
Depending on the application, it is advisable not to include a large number of separate event handlers to various elements in the page, but to instead take advantage of event capture / bubbling and use an event delegation mechanism, hooking the keydown
handler on a top-level element (for instance, the body
) or object (window
or similar):
window.addEventListener("keydown", handler, useCapture);
In your handler
function, you may need to determine the element where the event originated. A reference to this can be easily obtained from the event.target
:
function handler(event){
…
var target = event.target;
…
}
Repeating key events
What happens when a user keeps a functional button on their remote control pressed is dependant on their specific device. Some devices will only send a single keydown
event until the button is released. Others may send a series of keydown
(and keypress
, if it's a key that produces a character value) and keyup
events (as if the button was manually being pressed and released multiple times). Lastly, platforms that do support proper key repeats will send a continuous stream of keydown
(and keypress
, if it's a key that produces a character value) events, and only fire keyup
once the user releases the button.
In general, since it cannot be guaranteed that a device has full key repeat support, we'd recommend not making an application reliant on this behaviour.
If your applications does need to handle repeating / long-press button events, the switch to the DOM Level 3 Events model in the Opera Device SDK 3.4 may require some additional work in order to ensure backwards- and forwards-compatibility.
Previously, repeating keys (on supporting platforms) used to fire:
keydown
> [multiplekeypress
] >keyup
New versions of the Opera TV Store, in accordance with the DOM Level 3 Events model, will instead fire:
- [multiple
keydown
andkeypress
] >keyup
(for keys that produce a character value) - [multiple
keydown
] >keyup
(for all other keys)
If for previous versions of the TV Store your code listened to repeating keypress
events, the best way to remain compatible is to register your handlers for both keydown
and keypress
. To avoid having functionality being triggered twice (for the first button press in the old SDK, and for repeating character value keys in the new SDK), you can take advantage of the event.repeat
property introduced in DOM Level 3 Events to filter out unwanted duplicate events:
// example using event delegation
window.addEventListener("keydown", handler, useCapture);
window.addEventListener("keypress", handler, useCapture);
function handler(event){
if ((event.type=='keydown' && !('repeat' in event)) ||
(event.type=='keypress' && ('repeat' in event))) return;
…
}
Alternatively, if you're binding event handlers via JavaScript already, you can use the new window.KeyboardEvent
interface as an indicator for DOM 3 support, and only bind your event handler to either keydown
or keypress
.
// example using event delegation
if (window.KeyboardEvent){
window.addEventListener('keydown', handler, useCapture);
} else {
window.addEventListener('keypress', handler, useCapture);
}
function handler(event){
// no need to de-dupe events
…
}
Requirements for the Back/Return button
Most remote controllers have a Back or Return button. In the Opera TV emulator, this is equivalent to pressing the BACKSPACE key. The Opera TV Store requires that the Back/Return button works consistently in each application as follows:
- Pressing Back/Return must return the user to the previous page or screen.
- If the user is at the first page of the application, the application should close.
- If the user is at an "Exit app" confirmation dialog, the application should close.
In other words, if the user presses Back/Return repeatedly, they will eventually exit the application and return to the TV Store menu.
If your application consists of regular pages loaded one after another, the Back/Return button should work without any extra effort – the correct behaviour is handled automatically by Opera. If your application is using AJAX, overlays or history modifications, however, then Back/Return must be handled by your application. Here are examples of how such behaviour can be coded:
// To close overlays with Back/Return
function handler(event) {
// assuming there's a global boolean overlay_opened
if (overlay_opened && event.keyCode == VK_BACK_SPACE) {
event.preventDefault();
overlay_opened = false;
closeOverlay();
}
}
// To close the application with Back/Return
function handler(event) {
// assuming there's a global boolean main_page
if (main_page && event.keyCode == VK_BACK_SPACE) {
event.preventDefault();
main_page = false;
window.close();
}
}
Currently, the Opera TV Store has implemented some additional hardcoded behavior which automatically closes an application if the user presses Back/Return and the browser history is empty, without the possibility to preventDefault();
the event. The idea is to avoid faulty applications from inadvertently trapping users.
To circumvent this behavior, a slightly hacky workaround is to user the HTML5 history management API to inject entries into the browser history.
window.history.pushState({}, document.title, '#dummy_url');
After this, the preceding code snippets to manually handle the Back/Return key using preventDefault();
will work as expected.
Preventing default spatial navigation
When handling key events directly, you will probably want to stop the Opera TV Store browser from carrying out its normal spatial navigation and element activation behaviours. This can simply be suppressed in the handler
function:
function handler(event){
…
event.preventDefault();
…
}
Determining support for a specific functional key
Authors can check if a specific functional key has been defined on the current device using a simple JavaScript check. If a button is supported, the constant will contain the device-specific key code of the button; otherwise, the constant will return a null
value. For example, to test for the VK_RED key:
if (VK_RED !== null) {
/* VK_RED is supported */
…
}
Although all devices running the Opera TV Store should have all the global constants listed above defined (though their value may be null
, if the device's default remote control doesn't have a particular button), it is still advisable to also check for the existence of the constant before using it, to avoid any Unhandled Error: Undefined variable
JavaScript errors.
if (('VK_RED' in window)&&(VK_RED !== null)) {
/* VK_RED is supported */
…
}
This precaution should also be taken when checking event.keyCode
values against those constants:
function handler(event){
…
if (('VK_RED' in window)&&(VK_RED == event.keyCode)){
/* VK_RED was pressed … do something useful */
}
…
}
The list of VK_*
global constants is currently set in a user.js
file, which OEMs include as part of their Opera TV Store installation at integration time. Device manufacturers will include the buttons and their respective key codes based on the default remote controls that ship with their devices. With this approach, however, any third-party or alternative remote controls may not match the standard remote's set of functional buttons – the constants may be defined and present, but the actual remote doesn't have those physical keys. For this reason, it's still advisable to use caution and to make applications work with the minimal set of Always available keys.
This article is licensed under a Creative Commons Attribution 3.0 Unported license.
Comments