A tiny, powerful and declarative wrapper around keyboard bindings in JavaScript
A gorgeous, simple, tiny JavaScript package to add keyboard bindings into your application.
then
group
function, making your code more readable and powerful.Whenipress is available via npm: npm i whenipress
.
You should then require it as a module in your main JavaScript file.
import whenipress from 'whenipress/whenipress';
whenipress('a', 'b', 'c').then(e => console.log('Nice key combo!'));
But you can equally use it via a CDN:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/whenipress.js"></script>
<script>
whenipress('a', 'b', 'c').then(e => console.log('Nice key combo!'));
</script>
Keyboard shortcuts are often an add-on in most web applications. Why? Usually, because it can be pretty complicated to
add them in JavaScript. The keydown
and keyup
events are pretty low level stuff, and require a fair bit of abstraction
to make adding shortcuts a simple task.
Say hello to whenipress. We’ve done all the abstraction for you, and provided the functionality you’ll need in a
much simpler, easier to manage way. Getting started is as simple as calling the global whenipress
method, passing
in the key-combo you want to listen for. Check out our guide below…
What follows is an in depth look at all the juicy functionality offered to you out of the box by whenipress. Enjoy!
So, how do you get started? After you’ve installed the package using one of the methods described in the ‘getting started’
section, you can get to registering your first keyboard shortcut. Let’s imagine we want to register a shortcut on the ‘/’
key that will focus the global search bar in our web application. We’ve already set up a method, focusGlobalSearchBar()
,
that will actually focus the input for us. We just need to wire it up to our shortcut. Check it out:
whenipress('/').then(event => focusGlobalSearchBar())
And we’re done. Yeah, it’s that easy. However, that’s also pretty easy to set up in vanilla JavaScript, right? What isn’t
so easy to wire up are key combinations. There is no way in native JavaScript to listen for multiple keys at the same time.
Fret not, we have you covered here too. Let’s imagine that, when the ‘left control’ key is pressed in combination with
the ‘n’ key, we want to redirect to a page where a new CRUD entry can be added. Once again, we’ve already set up a method,
redirectToCreateForm()
, that will do the redirecting. Here’s how we wire it up:
whenipress('ControlLeft', 'n').then(event => redirectToCreateForm())
Pretty nice, right? We can pass any number of keys or key codes into the whenipress
method to set up complex and
powerful shortcuts.
then
Because then
is used in JavaScript promises, some of you may wish to use a different syntax to avoid any confusion.
Whenipress aliases then
to do
and run
, so you can use those instead if you prefer.
// This...
whenipress('a').then(e => alert('e pressed!'))
// Is the same as this...
whenipress('a').do(e => alert('e pressed!'))
// And this...
whenipress('a').run(e => alert('e pressed!'))
Sometimes, you’ll want to disable a key binding. No problem! When you create the key binding, you’ll be returned a
reference to it. You can call the stop
method on that reference at any time to stop listening.
var nKeyPress = whenipress('n').then(e => console.log('You pressed n'));
nKeyPress.stop();
Even better, the related event listener will be completely removed from the DOM, keeping performance snappy.
If you wish to stop listening for all registered key bindings, you can call the stopAll
method on the global
whenipress
instance.
whenipress('A', 'B', 'C').then(e => console.log('Foo'));
whenipress('T', 'A', 'N').then(e => console.log('Bar'));
whenipress().stopAll();
Because all key bindings are stored in a single location, it is possible to retrieve them programmatically at any time.
This is super useful in whenipress plugins, where you can’t be sure which key bindings have been registered.
whenipress('n', 'e', 's').then(e => console.log('Foo'));
whenipress('l', 'i', 'h').then(e => console.log('Bar'));
whenipress().bindings() // Will return [['n', 'e', 's'], ['l', 'i', 'h']]
Only want to register a key binding for a single press? Just add the once
modifier!
whenipress('z').then(e => console.log("z was pressed")).once();
The event listener will be removed the first time it is fired. You can place the once
modifier before the then
call if you wish.
Whenipress supports key groups for easily adding modifiers without having to repeat yourself over and over.
whenipress().group('Shift', () => {
whenipress('b').then(e => console.log('Shift + b pressed'));
whenipress('c').then(e => console.log('Shift + c pressed'));
});
Want to listen for keys pressed twice in quick succession? We have you covered. You can even alter the timeout between
key presses.
whenipress('a').twiceRapidly().then(e => console.log('You just double pressed the a key'));
// Use a 300ms timeout
whenipress('a').twiceRapidly(300).then(e => console.log('You just double pressed the a key'));
The then
callback you provide whenipress will be fired as soon as all keys in the binding are pressed down at the same
time. Sometimes, however, you’ll want to listen for when the keys are released too. No sweat here!
whenipress('a', 'b', 'c')
.then(e => {
console.log('Keys are pressed!');
})
.whenReleased(e => {
console.log('Keys are released!');
});
By default, whenipress will ignore keybindings on form elements like inputs, textareas, and select boxes so that you
don’t have unexpected side effects in your application. To override this functionality and cause a keybinding to
fire even on these form elements, you may tag evenOnForms
on to the end of your binding registration.
whenipress('LeftShift', 'KeyA').then(e => alert("I work, even in inputs, textareas and selects!")).evenOnForms()
Sometimes, you may only want a keyboard event to fire if a node or children within that node are currently in focus.
For example, you may have a sidebar menu where, only when opened, you would like the escape key to close the menu for you.
Whenipress allows you to do this using the whileFocusIsWithin
method. This method accepts a query selector or an Element.
whenipress('Escape').whileFocusIsWithin('#slideout-menu').then(e => closeMenu())
// Or...
whenipress('Escape').whileFocusIsWithin(document.querySelector('#slideout-menu')).then(e => closeMenu())
Whenipress will make sure that the #slideout-menu
or one of its descendents has focus before executing your callback.
Whenipress was created to be extended. Whilst it offers tons of functionality out of the box, it can do so much more with a plugin.
What follows is a brief guide on how to get started creating your own plugins for whenipress.
Created a great plugin that you think would benefit the community? Create an issue for it and we’ll link you here!
To register a plugin in your application, whenipress provides a use
method.
import whenipress from 'whenipress/whenipress'
import plugin from 'awesomeplugin/plugin'
import anotherPlugin from 'thegreatplugin/plugin'
whenipress().use(plugin, anotherPlugin)
Whenipress plugins are essentially JSON objects. The properties on that JSON object will be called by whenipress during
different stages of the process, allowing you to hook in and perform any functionality you can think of. You do not
need to include every hook, only the ones you’re interested in using for your plugin.
What follows is a list of available hooks.
If you need to perform a setup step in your plugin, you should use the mounted
hook. It is called when your plugin
is first registered by the user. This receives the global whenipress
instance as a parameter.
You should use this in your plugin instead of calling whenipress
as the end user may have aliased whenipress
under
a different name.
const myPlugin = {
mounted: globalInstance => {
alert('Hello world!')
globalInstance.register('a', 'b', 'c').then(e => alert('You pressed a, b and c'))
}
}
Note that in a plugin, we can register new keyboard bindings using the register
method on the globalInstance.
If you want to be notified every time a new key combination is registered with whenipress, you can use the bindingRegistered
hook. It will receive the binding that was registered as the first parameter and the global whenipress
instance as the second
parameter. You should use this in your plugin instead of calling whenipress
as the end user may have aliased whenipress
under
a different name.
const myPlugin = {
bindingRegistered: (binding, globalInstance) => {
alert(`I'm now listening for any time you press ${binding.join(" + ")}.`)
}
}
Note that it is not guaranteed the user has initialised your plugin prior to creating their bindings. If you need to ensure
you have all bindings, you should iterate over registered bindings in your plugin’s mounted method.
If you wish to be notified of when a binding has been removed from whenipress, you can use the bindingStopped
hook.
It will receive the binding that was stopped as the first parameter and the global whenipress
instance as the second
parameter. You should use this in your plugin instead of calling whenipress
as the end user may have aliased whenipress
under
a different name.
const myPlugin = {
bindingStopped: (binding, globalInstance) => {
alert(`You are no longer listening for ${binding.join(" + ")}.`)
}
}
To be informed when all bindings in the application are stopped, you should use the allBindingsStopped
hook.
This receives the global whenipress
instance as a parameter.
You should use this in your plugin instead of calling whenipress
as the end user may have aliased whenipress
under
a different name.
const myPlugin = {
allBindingsStopped: globalInstance => {
alert(`Do not go gently into that good night...`)
}
}
It may be useful to perform an action just before a keyboard shortcut is handled. Say hello to the beforeBindingHandled
hook.
It will receive the binding that is to be handled as the first parameter and the global whenipress
instance as the second
parameter. You should use this in your plugin instead of calling whenipress
as the end user may have aliased whenipress
under
a different name.
const myPlugin = {
beforeBindingHandled: (binding, globalInstance) => {
alert(`You just pressed ${binding.join(" + ")}, but I got here first.`)
}
}
You can actually prevent the handler from ever firing by returning false
in you hook. This is useful if your plugin
adds conditional functionality.
const myPlugin = {
beforeBindingHandled: (binding, globalInstance) => {
if (userHasDisabledKeyboardShortcuts()) {
return false;
}
}
}
You may wish to know when a keyboard binding has been handled. You can use the afterBindingHandled
hook for this.
It will receive the binding that has been handled as the first parameter and the global whenipress
instance as the second
parameter. You should use this in your plugin instead of calling whenipress
as the end user may have aliased whenipress
under
a different name.
const myPlugin = {
afterBindingHandled: (binding, globalInstance) => {
alert(`You just pressed ${binding.join(" + ")}. It has been handled, but now I'm going to do something as well.`)
}
}
Whenipress provides a unified method of handling custom options to users of your plugin. To do so, register an options
field in your plugin JSON. You can include anything in here that you want, but be sure to let your users know in your
plugin’s documentation.
In our example below, we have decided to provide to options to the user, urlsToSkip
and skipAllUrls
. These are
completely unique to your plugin and you’re in charge of managing them.
const myPlugin = {
options: {
urlsToSkip: [],
skipAllUrls: false
}
}
Now, when your plugin is registered, these options can be overridden by the user.
import whenipress from 'whenipress/whenipress'
import myPlugin from 'awesomeplugin/myPlugin'
whenipress().use(whenipress().pluginWithOptions(myPlugin, { skipAllUrls: true }))
If you wish to stop all plugins running, you may use the flushPlugins
method.
whenipress().flushPlugins()
Thanks goes to these wonderful people (emoji key):
George Pickering 💻 |
This project follows the all-contributors specification. Contributions of any kind welcome!