Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/piranha/keymage

Yet Another JS Keybinding library
https://github.com/piranha/keymage

hotkey hotkeys javascript shortcut shortcuts

Last synced: about 2 months ago
JSON representation

Yet Another JS Keybinding library

Awesome Lists containing this project

README

        

# keymage.js

Keymage is a small (1.6kb after Closure Compiler and gzip) library for handling
key bindings in JavaScript. It supports nested application scopes, has a simple
DSL for defining keys and can handle key chords.

[![Build Status](https://travis-ci.org/piranha/keymage.svg?branch=master)](https://travis-ci.org/piranha/keymage) - or [check tests](https://rawgit.com/piranha/keymage/master/test/test.html) in browser

## Features

- Simple language for defining bindings
- Key sequences (a-la Emacs chords)
- Nested scopes
- Default modifier (`defmod` key which is `command` on OS X and `control`
elsewhere)
- Ability to prevent defaults for whole sequence

## Usage

Include `keymage.min.js` in your page:

```html

```

There are no dependencies. It is possible to use library as a simple JS module,
as an AMD module or as CommonJS module.

It worth to note that [Keymage is on cdnjs](http://cdnjs.com/libraries/keymage/)
which enables you to use it without downloading.

Plus, of course, it's on [NPM](https://www.npmjs.org/package/keymage).

## Defining shortcuts

Keymage exposes a single function, `keymage`:

```javascript
// bind on 'a'
keymage('a', function() { alert("You pressed 'a'"); });

// returning false prevents default browser reaction (you can always use
// e.preventDefault(), of course)
keymage('ctrl-e', function() { return false; });

// binding on 'defmod' binds on Command key on OS X and on Control key in other
// systems
keymage('defmod-j', function() { alert("I am fired"); });
```

Handler function receives two arguments: the original event and the context so
you can understand what and why was fired.

The context contains those properties:

- `shortcut` is a string you've originally provided for binding
- `scope` is a scope which is currently active
- `definitionScope` is a scope where this shortcut was defined

```javascript
keymage('alt-c', function(e, ctx) {
console.log(ctx.shortcut, ctx.scope, ctx.definitionScope);
});

// -> "alt-c", "", ""
```

## Sequences

Keymage supports key sequences:

```javascript
keymage('ctrl-k j', function() { alert("Nice!"); });
```

For this to fire you have to first press both `ctrl` and `k`, and then
`j`. This will fire an alert.

There is something to remember: browsers have their own shortcuts, for example
`ctrl-j` in most browsers means "open downloads". And while you can always call
`e.preventDefault()` in usual situation, if `ctrl-j` is part of a sequence, it's
not that easy - you'll get control over what's happening only when the whole
sequence is pressed.

So keymage provides you with a means to support this use case. I do not
encourage you to override browser hotkeys, but let's imagine you want to do
that. For this, you can pass an option object as last parameter, having
`preventDefault` property set to `true`:

```javascript
keymage('ctrl-t ctrl-j k',
function() { alert("wow"); },
{preventDefault: true});
```

This option will prevent default on *every* key press which is a valid part of a
bound sequence, including the one triggering your handler. Note that pressing
only `ctrl-j` (without `ctrl-t`) will still open downloads, keymage looks for
sequence of `ctrl-t ctrl-j`.

## Scopes

Keymage support nested scopes. This means that your application can have few
areas where you can gradually have more and more specific shortcuts. It works
like this:

```javascript
// You can skip scope argument if you want global work-always shortcut
keymage('ctrl-j q', function() { alert("Default scope"); });

// This will fire after "keymage.setScope('chat')"
keymage('chat', 'ctrl-j w', function() { alert("Chat scope"); });

// This will fire after "keymage.setScope('chat.input')"
keymage('chat.input', 'ctrl-j e', function() { alert("Chat.input scope"); });
```

You can control scopes with helpful `pushScope` and `popScope` methods. This way
your nested view (or whatever is enabling nested scope) doesn't need to know
about parent scope:

```javascript
keymage.pushScope('chat') // scope is 'chat'

keymage.pushScope('input') // scope is 'chat.input'

keymage.popScope() // scope is 'chat'

keymage.pushScope('deep')
keymage.pushScope('deeper') // scope is 'chat.deep.deeper'

// way to jump out of deep scoping
keymage.popScope('chat') // scope is ''
```

`pushScope` returns resulting scope and `popScope` returns topmost scope it
removed (so with parameters it's the one you've asked to remove).

Note that calling `popScope` with name of a scope which is repeated few times
will pop topmost one, i.e.:

```javascript
keymage.setScope('this.scope.is.deep.scope')
keymage.popScope('scope') // scope is 'this'
```

## Options

Last and optional argument to `keymage` function is an option object. Here is a
list of possible options:

- `preventDefault`: when `true`, calls `event.preventDefault()` on every key
press which looks like a part of defined sequence.

- `context`: binding handler will be called with provided object as a context.

## Unbinding

And if you ever need to unbind a handler, use this:

```javascript
keymage.unbind('ctrl-j k', your_handler_function);
```

Also, `keymage(...)` returns a function, which unbinds this shortcut when called:

```javascript
var unbinder = keymage('ctrl-j k', your_handler_function);
unbinder();
```