With this system of components, module-wide key combinations can be specified in one place - at the top of a module and applied through a <CommandList>
instance. Further within the component tree, <HasCommand>
is used to apply handlers to the key combinations to suit the requirements and fulfill workflows for the module. These special components function as wrappers for core hotkey components, allowing for additional capabilities in discoverability to be built into the system.
This is a top-level component that stores default shortcut configurations. This configuration is re-usable and can be used to set up interfaces for customizeable shortcuts.
prop | type | description | required |
---|---|---|---|
commands |
array | Array of top-level command objects - commonly with only names, shortcuts, and labels. Shortcut keys that might be configurable or re-used throughout your application. | ✔ |
children |
node, array of nodes | Rendered children | ✔ |
Placed within the component tree to expose commands in a declarative way within the module.
prop | type | description | required |
---|---|---|---|
commands |
array | Array of command objects - commonly names and handlers - Shortcut keys that should be active within this component's children. | ✔ |
children |
node, array of nodes | Rendered children | ✔ |
scope |
DOMNode | Element that shortcut keys should delegate to. Defaults to the outer DOMNode of rendered children | |
isWithinScope |
function | Function called at handle-time to see whether or not the handler should be executed. This, in combination with the scope prop can be used to expand the functionality of the component outside of its own children. |
property | type | description |
---|---|---|
name |
string | Static identifier for the command within module code. |
shortcut |
string | Expresses the desired shortcut. ex mod+s . |
handler |
function | Callback that executes when the shortcut is pressed. |
The shortcut
property of command objects can account for the platform-specific modifiers via the mod
substring. This will use the command
key on MacOS and the ctrl
key on other OS's.
Default commands can be kept in an exterior file for tidiness and re-use of the configuration. This example configures four top-level commands and their corresponding shortcuts. These command names and their corresponding keys will be passed down for use within the body of the module. If you specify any handler keys here, the shortcuts will be active anywhere in your module after step 2.
// keyboardCommands.js
import React from 'react';
const commands = [
{
name: 'save',
shortcut: 'mod+s',
},
{
name: 'new',
shortcut: 'alt+n',
},
{
name: 'search',
shortcut: 'mod+alt+h',
},
{
name: 'edit',
shortcut: 'mod+alt+e',
}
];
export default commands;
A centralized set of FOLIO-wide shortcut defaults is available. Simply import... it contains basic key mapping for commands named save, new, edit, search, expandAllSections, collapseAllSections, and duplicateRecord"
import { defaultKeyboardShortcuts as keyboardCommands } from '@folio/stripes/components';
Next, in the module's index (or other top-level code) we use the command configuration by setting up an instance of <CommandList>
.
// index.js
<CommandList
commands={keyboardCommands}
>
{/* top level module things... routes, etc... */}
</CommandList>
Next, the main UI of the app - the "search view"...
// searchView.js - view containing the search listing with the ability to create a new record.
// static commands defined as instance property.
constructor(props){
super(props);
...
this.keyCommands = [
{
name: 'search',
handler: this.focusSearch
},
{
name: 'new',
handler: this.createNew
}
];
...
this.container = React.createRef();
}
<HasCommand commands={this.keyCommands}>
<div ref={this.container}>
{/* Search/listing/view components */}
{/* Conditional render for a detail display... */}
{ detailView && (
<Details containerRef={this.container}/>
)
}
</div>
</HasCommand>
Workflows within your module may call for different shortcuts to be active at different times. The declaritive nature of <HasCommand>
makes this possible. That conditional render in the previous step - say we want an 'edit' shortcut key to start working there, but we want to allow for keyboard focus to be anywhere within the module. (In other words, as long as a detail view is open, we can use keyboard shortcuts to shift to an edit mode.) Here's how.
// Details.js
this.detailKeyCommands = [
{
name: 'edit',
handler: this.goToEdit,
}
];
// this function is called at runtime to test whether the call should execute. This example tests the location of focus within the document, but other criteria could be used as well. By default, HasCommand will use its child contents to gauge whether or not a command should execute - but in this case, we want to expand that scope so that the command may be executed any time the detail view is visible.
checkScope() {
const { containerRef } = this.props;
return containerRef.current.contains(document.activeElement);
}
<HasCommand
commands={this.detailKeyCommands}
isWithinScope={this.checkScope}
scope={document.body}
>
{/* Detail contents */}
</HasCommand>
Care must be taken if a known browser shortcut is supplied to <CommandList>
, e.g. mod+s
is used by browsers to save the actual html page locally but is a comfortable convention for users when saving a form. In most cases, the shortcut should be changed to something else. To avoid the browser's behavior, you must call e.preventDefault()
within the shortcut's handler.
handleKeyboardSave(e) {
e.preventDefault();
executeSave();
}