Skip to content

Commit

Permalink
docs: update click shortcut chapter in shortcut article (vaadin#2954)
Browse files Browse the repository at this point in the history
* docs: update click shortcut chapter in shortcut article

Adds a note to point out how click notifier's `addClickShortcut` method's default behaviour has changed since 24.3.

Related-to: vaadin/flow#5959

* Update articles/create-ui/shortcut.asciidoc

* First pass at editing.

* Second pass at editing.

---------

Co-authored-by: Russell JT Dyer <[email protected]>
Co-authored-by: Mikhail Shabarov <[email protected]>
  • Loading branch information
3 people authored Nov 13, 2023
1 parent 0ba44ca commit ee5a896
Show file tree
Hide file tree
Showing 2 changed files with 291 additions and 306 deletions.
291 changes: 291 additions & 0 deletions articles/create-ui/shortcut.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
---
title: Keyboard Shortcuts
description: Creating keyboard shortcuts to components for a better user experience.
order: 30
---


= Keyboard Shortcuts

Shortcuts allow you to assign keyboard shortcuts to your components to improve end-user experience. You can add available shortcuts, create your own custom shortcuts, and configure the reaction when a shortcut is triggered.

A shortcut key combination consists of one primary key, and zero to four key modifiers (i.e., kbd:[Alt], kbd:[Ctrl], kbd:[Meta], kbd:[Shift]).


== Adding Click Shortcuts

Click shortcuts define alternatives to the click action in components that implement the [interfacename]`ClickNotifier` interface. You can add a click shortcut using the [methodname]`addClickShortcut()` method.

The example below uses the [methodname]`addClickShortcut()` method to add a click shortcut to a `Button` component:

[source,java]
----
TextField userName = new TextField("User name");
PasswordField password = new PasswordField("Password");
Button login = new Button("Login");
login.addClickListener(event -> this.login());
login.addClickShortcut(Key.ENTER);
----

Instead of clicking the button, the user can use the kbd:[Enter] key to perform the action tied to the button click.

[NOTE]
Since Vaadin 24.3, click shortcut resets client-side focus on an active element (e.g., `<input>`, by default) before executing the shortcut action. For example, it might submit properly the input's value when it's focused and the shortcut is triggered. This behavior can be disabled by calling https://vaadin.com/api/platform/com/vaadin/flow/component/ShortcutRegistration.html[[methodname]`setResetFocusOnActiveElement(boolean)`] on the shortcut registration.


== Adding Focus Shortcuts

Focus shortcuts place the focus on a `Focusable` component, like an input field. You can add a focus shortcut using the [methodname]`addFocusShortcut()` method.

The example here is using the [methodname]`addFocusShortcut()` method to add a focus shortcut to a `TextTield`:

[source,java]
----
TextField textField = new TextField("Label");
textField.addFocusShortcut(Key.KEY_F, KeyModifier.ALT);
----

The user can focus on the `Label` text field using the kbd:[Alt+F] keyboard shortcut.


== Adding Custom Shortcuts

You can use the [methodname]`addShortcutListener()` method to add a shortcut that executes custom code when the shortcut is triggered.

Assume you have a custom method, [methodname]`public void openCustomerCreation()`, that opens an input form in which users can enter new customer information.

The following example is using the [methodname]`addShortcutListener()` method to add a custom shortcut that executes the [methodname]`openCustomerCreation()` method:

[source,java]
----
UI.getCurrent().addShortcutListener(
this::openCustomerCreation, Key.KEY_N,
KeyModifier.CONTROL, KeyModifier.ALT);
----

When the user presses kbd:[Ctrl+Alt+N], the form opens. The user can then input the new customer information.

You can configure a shortcut to run any code that complies with the [interfacename]`Command` functional interface. This interface has a single method called [methodname]`execute()`, which accepts zero arguments.

This example is using the [methodname]`addShortcutListener()` method to `show` a notification:

[source,java]
----
UI.getCurrent().addShortcutListener(
() -> Notification.show("Shortcut triggered"),
Key.SPACE);
----

[NOTE]
All methods that allow you to add shortcuts return an instance of [classname]`ShortcutRegistration`, which provides a fluent API that you can use to further configure your shortcuts.


== Configuring Active Scope

By default, shortcuts are registered to the global scope. This means that the shortcut is triggered when the user presses the correct keys, regardless of the location of their cursor, or which element is in focus on the screen.

You can configure the availability of a shortcut (e.g., only when the user focuses on an element) using the [methodname]`addShortcutListener()` method made available by the fluent [classname]`ShortcutRegistration` API.

The example here is using the [methodname]`addShortcutListener()` method to define the component to which the listener attaches:

[source,java]
----
public class Scope extends Div {
public Scope() {
TextField firstName = new TextField();
TextField lastName = new TextField();
add(firstName, lastName);
Command command = () -> {
firstName.setValue("");
lastName.setValue("");
firstName.focus();
};
// first parameter is the lifecycle owner
// of the shortcut and it is discussed later.
Shortcuts.addShortcutListener(this,
command, Key.ESCAPE)
// defines the component onto which
// the shortcuts listener is attached:
.listenOn(this);
}
}
----

The shortcut is tied to the parent component (`Scope`) of the input components. If the user types input into either `TextField` and then presses kbd:[Esc], both input fields are cleared and focus is returned to the first field. This is useful when the same action should be triggered by a shortcut configured on all fields contained inside the same scope, but not outside of the enveloping component.

The shortcut is created using the factory class [classname]`Shortcuts`, which offers the most generic method for creating shortcuts. See <<lifecycle-owners>> below for more.


== Removing Shortcuts

You can remove a registered shortcut using the [methodname]`Registration.remove()` method. A method that adds or registers a shortcut returns either a [classname]`Registration` or [classname]`ShortcutRegistration` object.

This example is using the [methodname]`Registration.remove()` method to remove a shortcut:

[source,java]
----
TextField textField = new TextField("Label");
ShortcutRegistration registration =
textField.addFocusShortcut(Key.KEY_F,
KeyModifier.ALT);
// something happens here
registration.remove(); // shortcut removed!
----


[[lifecycle-owners]]
== Shortcut Lifecycle Owners

Shortcuts have a lifecycle that's controlled by an associated `Component`, called the `lifecycleOwner` component. When the component acting as a `lifecycleOwner` is both _attached_ and _visible_, the shortcut is active. If these conditions aren't both met, the shortcut can't be triggered.

For focus and click shortcuts, the lifecycle owner is the component itself. It only makes sense for the click shortcut to be active when the button or input field is both in the layout and visible.

For shortcuts registered through `UI`, the lifecycle owner is the `UI`. This means that the shortcut only stops functioning when it's <<Removing Shortcuts,removed>>.

You can use the [methodname]`Shortcuts.addShortcutListener(...)` method to create a shortcut with a lifecycle bound to a specific component.

Binding a shortcut to the lifecycle of the `Paragraph` component using the [methodname]`Shortcuts.addShortcutListener(...)` method can be done like this:

[source,java]
----
Paragraph paragraph =
new Paragraph("When you see me, try Alt+G!");
Shortcuts.addShortcutListener(paragraph,
() -> Notification.show("Well done!"),
Key.KEY_G, KeyModifier.ALT);
add(paragraph);
----

The first parameter of the [methodname]`Shortcuts.addShortcutListener(Component, Command, Key, KeyModifier...)` method is the `lifecycleOwner` component. This code binds the kbd:[Alt+G] shortcut to the lifecycle of `paragraph` and is only active when the component is both attached and visible.

You can also use the [methodname]`bindLifecycleTo()` method to reconfigure the `lifecycleOwner` component of shortcuts.

Binding the lifecycle of a click shortcut to another component using the [methodname]`bindLifecycleTo()` method would look like this:

[source,java]
----
Grid<User> usersList = new Grid<>();
Button newUserButton = new Button("Add user", event -> {
// show new user form
});
newUserButton.addClickShortcut(Key.KEY_N, KeyModifier.CONTROL)
.bindLifecycleTo(usersList);
----

The keyboard shortcut for clicking the “Add user” button is active when the `usersList` component is visible on the page. Once the `usersList` component is detached or it becomes invisible, the shortcut is no longer active.


== Listening for Shortcut Events

The [methodname]`addShortcutListener()` method has an overload method that accepts a [classname]`ShortcutEventListener` instead of the <<Adding Custom Shortcuts,`Command`>> parameter. When the shortcut is detected, the event listener receives a [classname]`ShortcutEvent` that contains the `Key`, `KeyModifiers`, and both `listenOn` and `lifecycleOwner` components.

Registering a [classname]`ShortcutEventListener` and using it with the [methodname]`addShortcutListener()` overload method is done like so:

[source,java]
----
// handles multiple shortcuts
ShortcutEventListener listener = event -> {
if (event.matches(Key.KEY_G, KeyModifier.ALT)) {
// do something G-related
}
else if (event.matches(Key.KEY_J, KeyModifier.ALT)) {
// do something J-related
}
};
UI.getCurrent().addShortcutListener(listener,
Key.KEY_G, KeyModifier.ALT);
UI.getCurrent().addShortcutListener(listener,
Key.KEY_J, KeyModifier.ALT);
----

The `listener` handles events triggered by multiple shortcuts; both kbd:[Alt+G] and kbd:[Alt+J] invoke the listener. The [classname]`ShortcutEvent` provides the [methodname]`matches(Key, KeyModifier...)` method to determine which shortcut triggered the event.

For additional comparisons, you can use [methodname]`getSource()`, which returns the `listenOn` component. You can also use [methodname]`getLifecycleOwner()`, which returns the `lifecycleOwner` component.


== Shorthands for Shortcut Modifiers

[classname]`ShortcutRegistration` includes shorthands for assigning key modifiers to a shortcut.

The example here is using the [methodname]`withAlt()` and [methodname]`withShift()` key modifiers with the [methodname]`addFocusShortcut()` method:

[source,java]
----
Input input = new Input();
input.addFocusShortcut(Key.KEY_F).withAlt().withShift();
----

The focus shortcut here is triggered with kbd:[Alt+Shift+F].

[classname]`ShortcutRegistration` also has the [methodname]`withModifiers(KeyModifiers...modifiers)` method, which can be used to configure simultaneously all modifiers -- or to remove all modifiers. Calling [methodname]`withModifiers(...)` without parameters removes all modifiers from the shortcut.


== Shortcut Event Behavior on Client Side

[classname]`ShortcutRegistration` provides methods to define the behavior of events on the client side. With browser DOM events, you can control whether an event should propagate upwards in the DOM tree (i.e., component hierarchy), and whether it should allow default browser behavior.

By default, shortcuts consume the event. This means that events don't propagate upwards in the DOM tree (component hierarchy). Also, the default browser behavior is prevented. For example, the characters used in the shortcut aren't inserted into the input field, or clicking on a link prevents the browser from following the URL. See link:https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault[`Event.preventDefault()`] for more information.

As an exception, click shortcuts created with the [methodname]`ClickNotifier::addClickShortcut(Key, KeyModifier...)` method allows default browser behavior.

You can change the default behavior using the [methodname]`allowEventPropagation()` (fluent), [methodname]`allowBrowserDefault()` (fluent), [methodname]`setEventPropagationAllowed(boolean)`, and [methodname]`setBrowserDefaultAllowed(boolean)` methods.

The example that follows is using the [methodname]`allowBrowserDefault()` method to change the default behavior of a focus shortcut:

[source,java]
----
Input input = new Input();
input.addFocusShortcut(Key.KEY_F)
// the character 'f' is entered
// into the input, if it's focused
.allowBrowserDefault();
----

This next example is using the [methodname]`allowEventPropagation()` method to react to a shortcut event and change the styles of a form:

[source,java]
----
TextField name = new TextField("Name");
TextField address = new TextField("Address");
VerticalLayout form = new VerticalLayout(name, address);
add(form);
name.addFocusShortcut(Key.KEY_N, KeyModifier.CONTROL)
.listenOn(form)
// the shortcut event is propagated from the text field to the
// form and higher in the hierarchy
.allowEventPropagation();
// the listener attached to the view (this) can now catch the
// shortcut event and change the form styles
Shortcuts.addShortcutListener(this,
() -> form.setClassName("red-border"),
Key.KEY_N, KeyModifier.CONTROL)
.listenOn(this);
----

Once the "Name" field has focus and the shortcut is activated, the event is propagated higher in the component hierarchy and caught by the view component.


== Checking Shortcut States

[classname]`ShortcutRegistration` offers a variety of methods to check the internal state of a shortcut, as well as all configurable values that have corresponding getter methods.

Additionally, you can use the boolean [methodname]`isShortcutActive()` method to check whether the shortcut is enabled on the client side.


[discussion-id]`C949BD20-2809-4BD0-81FF-9A9A4E6F96E5`

++++
<style>
[class^=PageHeader-module-descriptionContainer] {display: none;}
</style>
++++
Loading

0 comments on commit ee5a896

Please sign in to comment.