Hotkeys

The users of your Finsemble application will want hotkeys for convenience and expedience. The Hotkey Client provides a convenient way to create local and global hotkeys. This tutorial will go through an example of each, and discuss why you might use our Hotkey Client to create your local hotkeys instead of window.addEventListener.

Global hotkeys

Global hotkeys are where the true power of the Hotkey Client lies. Global hotkeys can activate components even when that component is not in focus.

To add a global hotkey:

HotkeyClient.addGlobalHotkey(keys, onHotkeyTriggered, onHotkeyRegistered);

While the above code works perfectly well in a normal component, global hotkeys are best kept in microservices, as explained below. In Finsemble, microservices are loaded before components. However, they do not have access to the FSBL Object.

Adding a global hotkey in a service would look something more like this:

const HotkeyClient = Finsemble.Clients.HotkeyClient
HotkeyClient.addGlobalHotkeys(keys, onHotkeyTriggered, onHotkeyRegistered);

Local hotkeys

Adding a hotkey to a single-page application is simple. If you've ever built a single-page application, you are undoubtedly familiar with the block of code below:

window.addEventListener("keydown", e => {
    if(e.ctrlKey)
    ...
    // Do some stuff if CTRL is pressed.
});

If your application already has hotkeys, you don't necessarily have to change from this method to our API. If you do, however, there are a couple of advantages:

  1. API consistency with global hotkeys.
  2. The ability to detect which side of the keyboard that the key was pressed (e.g., left or right shift).

If those advantages aren't necessary for your application, you can leave them as they are. If you do want to use our API for local hotkeys, an example follows:

const keyMap = FSBL.Clients.HotKeyClient.keyMap,
    keys = [keyMap.ctrl, keyMap.shift, keyMap.up];

// If CTRL+SHIFT+UP is pressed inside of this component, all windows will be brought to the front of the stack.
function onHotkeyTriggered (err, response) {
    if(err){
        return console.error(err);
    }
    FSBL.Clients.LauncherClient.bringWindowsToFront();
}
// If there's a problem registering, it will come through this function. (e.g., you pass in a bad key).
function onHotkeyRegistered (err, response) {
    if(err){
        return console.error(err);
    }
}

function onHotkeyUnregistered(err){
    if(err){
        return console.error(err);
    }
    console.log("Success!");
}

HotkeyClient.addLocalHotkey(keys, onHotkeyTriggered, onHotkeyRegistered);

Removing hotkeys

Removing a hotkey mirrors the add functionality. To remove the global hotkey that we added earlier, you would do the following:

HotkeyClient.removeGlobalHotkey(keys, onHotkeyTriggered, onHotkeyUnRegistered);

Removing a local hotkey is similar:

HotkeyClient.removeLocalHotkey(keys, onHotkeyTriggered, onHotkeyUnRegistered);

Best Practices

Use named functions for event handlers

We recommend using named functions for your handlers. If, when you created the hotkey, you passed in an anonymous function, you will be unable to remove that handler (e.g., when the component is closed). This is common to all event handlers, but we wanted to make it clear that we recommend using named event handlers so that they can be removed.

Specify global hotkeys in microservices

Global hotkeys are best stored inside of microservices. Microservices are singletons, so you don't need to worry that a key combination will be registered multiple times. Also, because they are singletons, they are loaded before components and are intended to serve multiple components.

If you put the hotkeys in a component, you'll need to handle cases where multiple copies of the component are spawned and the hotkeys are registered in multiple instances. For example, let's say we wanted to add a global hotkey to the Finsemble toolbar. The problem with this scenario is that there can be more than one toolbar. So we need to make sure that only one of the toolbars registered can launch components via the hotkey—otherwise the hotkey would launch N charts. That's definitely not the expected behavior!

For that reason, we recommend that you exercise caution putting global hotkeys inside of components that could be launched more than once. For safety's sake, keep them in your microservices.

The key map

Let's go over how Finsemble figures out which keys you want to trigger an event handler. Parsing strings is messy. For example, "esc," "escape," "ESC," and "ESCAPE" are all valid ways to describe the key in the top-left of most keyboards. The Hotkey Client will try to parse your strings. ["ctrl", "up", "d"] is a totally valid argument for the Hotkey Client. However, in order to reduce conflicts and ensure consistency across calls, we've provided a key map for you to use. So instead of ["ctrl", "up", "d"], we recommend you use [keyMap.ctrl, keyMap.up, keyMap.d]. The key map is a top-level property of the Hotkey Client. The properties below can be used in your method calls to set hotkeys. See the examples below for more.

{
    "backspace": "backspace",
    "bs": "backspace",
    "bksp": "backspace",
    "tab": "tab",
    "escape": "escape",
    "esc": "escape",
    "clear": "clear",
    "enter": "enter",
    "return": "enter",
    "shift": "shift",
    "shft": "shift",
    "lshift": "shift",
    "lshft": "shift",
    "left shift": "shift",
    "leftshift": "shift",
    "rshift": "shift",
    "rshft": "shift",
    "right shift": "shift",
    "rightshift": "shift",
    "control": "control",
    "ctrl": "control",
    "alt": "alt",
    "alternate": "alt",
    "pause": "pause",
    "caps lock": "caps lock",
    "capslock": "caps lock",
    "spacebar": "spacebar",
    "space": "spacebar",
    "space bar": "space",
    "page up": "page up",
    "pgup": "page up",
    "pg up": "page up",
    "page down": "page down",
    "pgdn": "page down",
    "pg dn": "page down",
    "end": "end",
    "home": "home",
    "left arrow": "left arrow",
    "left": "left arrow",
    "up arrow": "up arrow",
    "up": "up arrow",
    "right arrow": "right arrow",
    "right": "right arrow",
    "down arrow": "down arrow",
    "down": "down arrow",
    "select": "select",
    "slct": "select",
    "print": "print",
    "prnt": "print",
    "execute": "execute",
    "print screen": "print screen",
    "printscreen": "print screen",
    "print scrn": "print screen",
    "printscrn": "print screen",
    "prnt scrn": "print screen",
    "prntscrn": "print screen",
    "prt scrn": "print screen",
    "prtscrn": "print screen",
    "prt scn": "print screen",
    "prtscn": "print screen",
    "prt scr": "print screen",
    "prtscr": "print screen",
    "prt sc": "print screen",
    "prtsc": "print screen",
    "pr sc": "print screen",
    "prsc": "print screen",
    "insert": "insert",
    "ins": "insert",
    "delete": "delete",
    "del": "delete",
    "help": "help",
    "0": "0",
    "1": "1",
    "2": "2",
    "3": "3",
    "4": "4",
    "5": "5",
    "6": "6",
    "7": "7",
    "8": "8",
    "9": "9",
    "a": "a",
    "b": "b",
    "c": "c",
    "d": "d",
    "e": "e",
    "f": "f",
    "g": "g",
    "h": "h",
    "i": "i",
    "j": "j",
    "k": "k",
    "l": "l",
    "m": "m",
    "n": "n",
    "o": "o",
    "p": "p",
    "q": "q",
    "r": "r",
    "s": "s",
    "t": "t",
    "u": "u",
    "v": "v",
    "w": "w",
    "x": "x",
    "y": "y",
    "z": "z",
    "windows": "windows",
    "left windows": "windows",
    "right windows": "windows",
    "applications": "applications",
    "computer sleep": "computer sleep",
    "sleep": "computer sleep",
    "numpad 0": "0",
    "numpad 1": "1",
    "numpad 2": "2",
    "numpad 3": "3",
    "numpad 4": "4",
    "numpad 5": "5",
    "numpad 6": "6",
    "numpad 7": "7",
    "numpad 8": "8",
    "numpad 9": "9",
    "f1": "f1",
    "fn1": "f1",
    "function 1": "f1",
    "f2": "f2",
    "fn2": "f2",
    "function 2": "f2",
    "f3": "f3",
    "fn3": "f3",
    "function 3": "f3",
    "f4": "f4",
    "fn4": "f4",
    "function 4": "f4",
    "f5": "f5",
    "fn5": "f5",
    "function 5": "f5",
    "f6": "f6",
    "fn6": "f6",
    "function 6": "f6",
    "f7": "f7",
    "fn7": "f7",
    "function 7": "f7",
    "f8": "f8",
    "fn8": "f8",
    "function 8": "f8",
    "f9": "f9",
    "fn9": "f9",
    "function 9": "f9",
    "f10": "f10",
    "fn10": "f10",
    "function 10": "f10",
    "f11": "f11",
    "fn11": "f11",
    "function 11": "f11",
    "f12": "f12",
    "fn12": "f12",
    "function 12": "f12",
    "f13": "f13",
    "fn": "f13",
    "function 13": "f13",
    "f14": "f14",
    "fn14": "f14",
    "function 14": "f14",
    "f15": "f15",
    "fn15": "f15",
    "function 15": "f15",
    "f16": "f16",
    "fn16": "f16",
    "function 16": "f16",
    "num lock": "num lock",
    "numlock": "num lock",
    "number lock": "num lock",
    "numeric lock": "num lock",
    "scroll lock": "scroll lock",
    "sclk": "scroll lock",
    "scrlk": "scroll lock",
    "slk": "scroll lock",
    "menu": "menu"
}

Further reading

The documentation of the Hotkey Client provides additional information.