Using Events with Components

Your component class can provide an event handling API using the event bus provided by the Component base class. The event bus supports event classes extending ComponentEvent, and the listeners are of the type ComponentEventListener<EventType>.

Defining an Event

Events used with the event bus should extend ComponentEvent. The base type is parameterized with the type of the component firing the event, so that getSource() can automatically return the right component type.

The second constructor parameter is used to let the user know whether the event has been triggered by a DOM event in the browser or through the component’s server-side API.

public class ChangeEvent extends ComponentEvent<TextField> {
    public ChangeEvent(TextField source, boolean fromClient) {
        super(source, fromClient);
    }
}

Event listeners are of the generic type ComponentEventListener<EventType>, so you don’t need to create a separate interface for your event type.

You don’t need to implement a separate method for removing an event listener. Instead, the method for adding a listener returns a handle that can be used for removing the listener.

@Tag("input")
public class TextField extends Component {
    public Registration addChangeListener(
            ComponentEventListener<ChangeEvent> listener) {
        return addListener(ChangeEvent.class, listener);
    }

    // Other component methods omitted
}

The user can then add and remove listeners like this.

TextField textField = new TextField();
Registration registration = textField
        .addChangeListener(e -> System.out.println("Event fired"));

// In some other part of the code
registration.remove();

Firing Events From the Server

You can fire an event on the server by creating the event instance and passing it to the fireEvent method. The event should be created with false as the second constructor parameter to indicate that the event does not come from the client.

@Tag("input")
public class TextField extends Component {

    public void setValue(String value) {
        getElement().setAttribute("value", value);
        fireEvent(new ChangeEvent(this, false));
    }

    // Other component methods omitted
}

Firing Events From the Client

You can connect a component event to a DOM event that will be fired by the element in the browser. To do this, you only need to tell the framework the name of the DOM event to listen to by adding a @DomEvent annotation to your event class. The framework will automatically add a DOM listener to the element when a component event listener is added.

@DomEvent("change")
public class ChangeEvent extends ComponentEvent<TextField> {
    public ChangeEvent(TextField source, boolean fromClient) {
        super(source, fromClient);
    }
}

Adding Event Data

An event can also include additional information about what has happened, e.g. which mouse button was used for a click event. If you use @DomEvent, any additional constructor parameters should have an @EventData annotation that tells the framework what data to send from the browser.

@DomEvent("click")
public class ClickEvent extends ComponentEvent<NativeButton> {
    private final int button;

    public ClickEvent(NativeButton source, boolean fromClient,
            @EventData("event.button") int button) {
        super(source, fromClient);
        this.button = button;
    }

    public int getButton() {
        return button;
    }
}

The @EventData definition is run as JavaScript in the browser with the DOM event available as event and the element to which the listener was added available as element. See the javadocs for DomListenerRegistration.addEventData for more information about how event data is collected and sent to the server.

Tip
See https://developer.mozilla.org/en-US/docs/Web/API/Event for an overview of the standard DOM events and their properties.

Filtering Events

It is sometimes not appropriate to send all DOM events to the server, but instead determine whether to send an event based on something related to the event. To achieve this, you can add define a filter in the @DomEvent annotation.

@DomEvent(value = "keypress", filter = "event.key == 'Enter'")
public class EnterPressEvent extends ComponentEvent<TextField> {
    public EnterPressEvent(TextField source, boolean fromClient) {
        super(source, fromClient);
    }
}

The filter definition is run as JavaScript in the browser with the DOM event available as event and the element to which the listener was added available as element. See the docs for DomListenerRegistration.setFilter for more information about how the filter is used.

Limiting Event Frequency

Some kinds of events are fired very frequently while the user is doing something, like text input events while the user is typing. The rate of sending events to the server can be configured using different debounce settings in the @DomEvent annotation. Debouncing always requires a timeout in milliseconds and one or several phases of a burst for which an event should be sent to the server.

  • Listening for the LEADING phase means that an event will be sent to the server at the beginning of a burst, but subsequent events will be dropped until one timeout period has passed without any new events. This may be useful for things like button clicks if you want to prevent accidental double submissions.

  • With the INTERMEDIATE phase, there will be periodical events to the server while a burst is ongoing. Another event will not be sent to the server until the timeout period has passed since the last event was sent. This may be useful for tings like text input if you want to react continuously while the user is typing.

  • The TRAILING phase is triggered at the end of a burst, when the timeout period has passed without any further events. This may be useful for things like text input if you want to react only when the user stops typing.

In this example, an input event will be sent to the server when the user has been typing, but there has been half a second since the last time some input was made.

@DomEvent(value = "input",
          debounce = @DebounceSettings(
              timeout = 250,
              phases = DebouncePhase.TRAILING))
public class InputEvent extends ComponentEvent<TextField> {
    private String value;

    public InputEvent(TextField source, boolean fromClient,
            @EventData("element.value") String value) {
        super(source, fromClient);
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

You can configure your event to be active for several phases at once, e.g. to both receive an event for the LEADING phase immediately when a burst starts but also INTERMEDIATE events while the burst goes on.

@DomEvent(value = "input",
          debounce = @DebounceSettings(
              timeout = 500,
              phases = {DebouncePhase.LEADING,
                        DebouncePhase.INTERMEDIATE }))
public class ContiniousInputEvent extends ComponentEvent<TextField> {
    private String value;

    public ContiniousInputEvent(TextField source, boolean fromClient,
            @EventData("element.value") String value) {
        super(source, fromClient);
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

See the docs for DomListenerRegistration.debounce for more information about how the filter is used.

Note
If both filter and debounce are configured at once, only events that pass the filter are considered when determining whether a burst has ended.