Skip to content
Open
101 changes: 101 additions & 0 deletions docs/docs/advanced/event-dispatcher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: Event Dispatcher System
description: How the webforJ event dispatcher system works and how to use it for event-driven programming.
sidebar_position: 50
sidebar_class_name: new-content
---

<DocChip chip='since' label='23.06' />
<JavadocLink type="dispatcher" location="com/webforj/dispatcher/EventDispatcher" top='true'/>

The webforJ event dispatcher system provides a flexible and type-safe way to handle events in your app. It allows you to register listeners for specific event types and dispatch events to those listeners, enabling event-driven programming across components.

:::tip Element and component events
The `EventDispatcher` is for custom event management, see the [Events](/docs/building-ui/events) articles to learn how to handle standard element and component events.
:::

<ComponentDemo
path='/webforj/eventdispatchercustomevent'
javaE='https://raw.githubusercontent.com/webforj/webforj-documentation/refs/heads/main/src/main/java/com/webforj/samples/views/advanced/EventDispatcherCustomEventView.java'
height = '300px'
/>

## Event dispatcher APIs

[`EventDispatcher`](https://webforj.com/javadoc/com/webforj/dispatcher/EventDispatcher.html) is a minimalistic event manager for dispatching events to listeners. It's not tied to UI components or element events.

**Available methods:**

- `addListener(Class<T> eventClass, EventListener<T> listener)`: Registers a listener for a specific event type. Returns a `ListenerRegistration<T>` for later removal.
- `removeListener(Class<T> eventClass, EventListener<T> listener)`: Removes a specific listener for the given event type.
- `removeAllListeners(Class<T> eventClass)`: Removes all listeners for a given event type.
- `removeAllListeners()`: Removes all listeners from the dispatcher.
- `dispatchEvent(T event)`: Notifies all listeners of the given event.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this section, a list of methods is already available in the Java Docs.


## Creating and registering listeners

To respond to events in your app, you first need to register one or more listeners with the `EventDispatcher`. A listener is simply a function or lambda that will be called whenever an event of the specified type is dispatched. This mechanism allows you to decouple event producers from consumers. You can register listeners for standard Java events or for your own custom event classes.


```java
import com.webforj.dispatcher.EventDispatcher;
import com.webforj.dispatcher.EventListener;
import com.webforj.dispatcher.ListenerRegistration;
import java.util.EventObject;

// Create the dispatcher
EventDispatcher dispatcher = new EventDispatcher();

// Register a listener
ListenerRegistration<EventObject> reg = dispatcher.addListener(EventObject.class, event -> {
console().log("Event received: " + event);
});

// Remove a listener
reg.remove();
// or
dispatcher.removeListener(EventObject.class, reg.getListener());

// Dispatch an event
dispatcher.dispatchEvent(new EventObject(this));
```



## Creating and registering custom events

Create a class that extends `EventObject` (or another suitable base event class). Add any custom fields or methods you need:

```java
public static class MyCustomEvent extends EventObject {
private final String message;
public MyCustomEvent(String message) {
super(message); // The source can be any object, e.g., the dispatcher or sender
this.message = message;
}
public String getMessage() {
return message;
}
}
```

Then register a listener listening for your custom event.

```java
ListenerRegistration<MyCustomEvent> reg = dispatcher.addListener(MyCustomEvent.class, event -> {
// Handle the event (access custom fields)
console().log("Custom event received: " + event.getMessage());
});
```

Whenever needed, dispatch the event and the listener will be triggered.

```java
dispatcher.dispatchEvent(new MyCustomEvent("Hello from custom event!"));
```

## Best practices

- Always remove listeners when they're no longer needed to avoid memory leaks.
- Use the provided event payload methods to access event data efficiently.
- Use the EventDispatcher for custom, non-UI event flows. For UI/component events, see the relevant component documentation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first bullet point in the best practices could be expanded upon into it's own section, covering when to remove listeners, how to, and why. Please be specific when writing why removing listeners prevent memory leaks and avoid using a generic "optimizes performance" explanation.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.webforj.samples.views.advanced;

import com.webforj.dispatcher.EventDispatcher;
import com.webforj.router.annotation.FrameTitle;
import com.webforj.router.annotation.Route;
import com.webforj.component.button.Button;
import com.webforj.component.Composite;
import com.webforj.component.html.elements.Div;
import com.webforj.component.layout.flexlayout.FlexJustifyContent;
import com.webforj.component.layout.flexlayout.FlexLayout;
import com.webforj.component.layout.flexlayout.FlexLayoutBuilder;

import java.util.EventObject;

@Route
@FrameTitle("Event Dispatcher")
public class EventDispatcherCustomEventView extends Composite<FlexLayout> {
private final EventDispatcher dispatcher = new EventDispatcher();

/**
* A custom event that carries a message string.
*/
public static class CustomMessageEvent extends EventObject {
private final String message;

public CustomMessageEvent(Object source, String message) {
super(source);
this.message = message;
}

public String getMessage() {
return message;
}
}

Button button = new Button("Fire custom event");
Div statusText = new Div("Waiting for custom event");
FlexLayoutBuilder layoutBuilder = new FlexLayoutBuilder(button, statusText);

public EventDispatcherCustomEventView() {

button.setWidth("fit-content");

statusText.setWidth("fit-content");
statusText.setStyle("border", "2px solid #333");
statusText.setStyle("padding", "8px 24px");
statusText.setStyle("border-radius", "var(--dwc-border-radius-s)");
statusText.setStyle("font-size", "var(--dwc-font-size-l)");
statusText.setStyle("background", "var(--dwc-surface-3)");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I highly suggest replacing the multiple setStyle() calls with an InlineStyleSheet annotation and giving the statusText component a class name:

.statusText {
border: 2px solid #333;
border-radius: var(--dwc-border-radius-s);
padding: 8px 24px;
font-size: var(--dwc-font-size-l);
background: var(--dwc-surface-3);
}


FlexLayout layout = layoutBuilder.vertical()
.justify().center()
.align().center()
.build()
.setHeight("100vh")
.setSpacing("var(--dwc-space-xl)");

FlexLayout root = this.getBoundComponent();
root.setJustifyContent(FlexJustifyContent.CENTER)
.add(layout);

// Register a listener for the custom event
dispatcher.addListener(CustomMessageEvent.class, e -> {
statusText.setText("Received custom event");
button.setEnabled(false);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, the dispatcher event can only fire once. Add a way to be able to restart the sample, or create another button that can also fire the dispatcher event with different text.


// Fire the custom event with a message when the button is clicked
button.onClick(e -> {
dispatcher.dispatchEvent(new CustomMessageEvent(this, "Hello from custom event!"));
});
}
}