From 25ae4744cafedaa6521a835791c7ad9d0034e744 Mon Sep 17 00:00:00 2001 From: Eric Handtke Date: Thu, 26 Jun 2025 14:07:23 +0200 Subject: [PATCH 1/7] First version of event-dispatcher documentation. --- docs/docs/advanced/event-dispatcher.md | 77 ++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 docs/docs/advanced/event-dispatcher.md diff --git a/docs/docs/advanced/event-dispatcher.md b/docs/docs/advanced/event-dispatcher.md new file mode 100644 index 000000000..3d78e6284 --- /dev/null +++ b/docs/docs/advanced/event-dispatcher.md @@ -0,0 +1,77 @@ +--- +title: Event Dispatcher System +description: How the webforJ event dispatcher system works and how to use it for event-driven programming. +sidebar_position: 50 +--- + +# Event dispatcher system + +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. + +## Overview + +At the core of the system is the[`EventDispatcher`](https://webforj.com/javadoc/com/webforj/dispatcher/EventDispatcher.html) class, which manages event listeners and dispatches events. Components use the dispatcher to bridge browser/JavaScript events with Java event objects, ensuring seamless integration between client and server logic. + +## Key concepts + +- **EventDispatcher**: Manages registration and notification of event listeners. +- **EventListener**: Functional interface for handling events of a specific type. +- **ListenerRegistration**: Represents a handle to a registered listener, allowing removal. +- **EventObject**: The base class for all event payloads. + +## Registering an event listener + +You can register an event listener for a specific event type using the `addListener` method: + +```java +import com.webforj.dispatcher.EventDispatcher; +import com.webforj.dispatcher.EventListener; +import com.webforj.component.event.BlurEvent; + +EventDispatcher dispatcher = new EventDispatcher(); +dispatcher.addListener(BlurEvent.class, event -> { + // Handle blur event + System.out.println("Component blurred: " + event.getComponent()); +}); +``` + +## Removing an event listener + +When you add a listener, you receive a `ListenerRegistration` object. Use this to remove the listener when it's no longer needed: + +```java +ListenerRegistration registration = dispatcher.addListener(BlurEvent.class, listener); +// ... +registration.remove(); +``` + +## Dispatching events + +To notify all listeners of a specific event, use the `dispatchEvent` method: + +```java +dispatcher.dispatchEvent(new BlurEvent(component)); +``` + +## Component integration + +Most webforJ components have an internal event dispatcher. You typically don't need to create your own dispatcher; instead, use the component's API to register listeners: + +```java +passwordField.addEventListener(BlurEvent.class, event -> { + // Handle blur event for this field +}); +``` + +## Advanced usage + +- You can remove all listeners for a specific event type with `removeAllListeners(Class eventClass)`. +- The dispatcher supports concurrent access and is safe for use in multi-threaded environments. +- Custom events can be defined by extending `EventObject` and registering listeners for your custom event class. + +## 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, avoiding unnecessary client-server round-trips. +- Prefer using component APIs for event handling unless you have advanced requirements. + From 93ce3a5e1dc3cffe7939fad829b6be577e111234 Mon Sep 17 00:00:00 2001 From: Eric Handtke Date: Fri, 27 Jun 2025 10:51:39 +0200 Subject: [PATCH 2/7] Rewrote event dispatcher doc and added more detail about custom events and registration. --- docs/docs/advanced/event-dispatcher.md | 60 +++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/docs/docs/advanced/event-dispatcher.md b/docs/docs/advanced/event-dispatcher.md index 3d78e6284..3cc0788bc 100644 --- a/docs/docs/advanced/event-dispatcher.md +++ b/docs/docs/advanced/event-dispatcher.md @@ -4,8 +4,6 @@ description: How the webforJ event dispatcher system works and how to use it for sidebar_position: 50 --- -# Event dispatcher system - 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. ## Overview @@ -63,15 +61,65 @@ passwordField.addEventListener(BlurEvent.class, event -> { }); ``` -## Advanced usage +## Event types and custom events + +Events in webforJ are Java classes that extend `EventObject`. Many built-in events are provided (such as `BlurEvent`, `ClickEvent`, etc.), but you can also define your own custom events for specialized use cases. + +To define a custom event, extend `EventObject` (or `ComponentEvent` for UI components). For UI events, you can use annotations like `@EventName` and `@EventOptions` to configure event names and payloads. + +```java +@EventName("my-custom-event") +@EventOptions(data = { + @EventOptions.EventData(key = "value", exp = "event.detail.value"), + @EventOptions.EventData(key = "timestamp", exp = "event.timeStamp") +}) +public static class CustomEvent extends ComponentEvent { + public CustomEvent(MyComponent component, Map eventData) { + super(component, eventData); + } + public String getValue() { + return (String) getData().get("value"); + } + public Long getTimestamp() { + return ((Number) getData().get("timestamp")).longValue(); + } +} +``` + +You can then register listeners for your custom event just like for built-in events: + +```java +myComponent.addEventListener(CustomEvent.class, event -> { + System.out.println("Custom value: " + event.getValue()); +}); +``` + +## Advanced registration options -- You can remove all listeners for a specific event type with `removeAllListeners(Class eventClass)`. -- The dispatcher supports concurrent access and is safe for use in multi-threaded environments. -- Custom events can be defined by extending `EventObject` and registering listeners for your custom event class. +For advanced scenarios, you can register listeners with additional options using `ElementEventOptions`. These options allow you to: +- Add custom data to the event payload +- Debounce or throttle event firing +- Filter events based on conditions +- Execute JavaScript before the event is fired (for UI events) + +Example: + +```java +ElementEventOptions options = new ElementEventOptions(); +options.addData("value", "event.target.value"); +options.setDebounce(300); // Debounce by 300ms +options.setFilter("event.target.value.length > 2"); + +myComponent.addEventListener("input", event -> { + String value = (String) event.getData().get("value"); + System.out.println("Input value: " + value); +}, options); +``` ## 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, avoiding unnecessary client-server round-trips. - Prefer using component APIs for event handling unless you have advanced requirements. +- Use registration options (payload, debounce, filter) to optimize performance and event data handling. From ba04b6159164b5dd7cd003184933066b84dd527f Mon Sep 17 00:00:00 2001 From: Eric Handtke Date: Thu, 10 Jul 2025 15:07:45 +0200 Subject: [PATCH 3/7] Refactored according to feedback. --- docs/docs/advanced/event-dispatcher.md | 98 +++++-------------- .../EventDispatcherCustomEventView.java | 63 ++++++++++++ 2 files changed, 90 insertions(+), 71 deletions(-) create mode 100644 src/main/java/com/webforj/samples/views/advanced/EventDispatcherCustomEventView.java diff --git a/docs/docs/advanced/event-dispatcher.md b/docs/docs/advanced/event-dispatcher.md index 3cc0788bc..712ddeb50 100644 --- a/docs/docs/advanced/event-dispatcher.md +++ b/docs/docs/advanced/event-dispatcher.md @@ -6,92 +6,48 @@ sidebar_position: 50 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. -## Overview -At the core of the system is the[`EventDispatcher`](https://webforj.com/javadoc/com/webforj/dispatcher/EventDispatcher.html) class, which manages event listeners and dispatches events. Components use the dispatcher to bridge browser/JavaScript events with Java event objects, ensuring seamless integration between client and server logic. +## `EventDispatcher` -## Key concepts +[`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. -- **EventDispatcher**: Manages registration and notification of event listeners. -- **EventListener**: Functional interface for handling events of a specific type. -- **ListenerRegistration**: Represents a handle to a registered listener, allowing removal. -- **EventObject**: The base class for all event payloads. +### Available APIs -## Registering an event listener +- `addListener(Class eventClass, EventListener listener)`: Registers a listener for a specific event type. Returns a `ListenerRegistration` for later removal. +- `removeListener(Class eventClass, EventListener listener)`: Removes a specific listener for the given event type. +- `removeAllListeners(Class 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. -You can register an event listener for a specific event type using the `addListener` method: + + +### Creating and registering listeners ```java import com.webforj.dispatcher.EventDispatcher; import com.webforj.dispatcher.EventListener; -import com.webforj.component.event.BlurEvent; +import java.util.EventObject; +// Create the dispatcher EventDispatcher dispatcher = new EventDispatcher(); -dispatcher.addListener(BlurEvent.class, event -> { - // Handle blur event - System.out.println("Component blurred: " + event.getComponent()); -}); -``` - -## Removing an event listener - -When you add a listener, you receive a `ListenerRegistration` object. Use this to remove the listener when it's no longer needed: - -```java -ListenerRegistration registration = dispatcher.addListener(BlurEvent.class, listener); -// ... -registration.remove(); -``` - -## Dispatching events - -To notify all listeners of a specific event, use the `dispatchEvent` method: - -```java -dispatcher.dispatchEvent(new BlurEvent(component)); -``` -## Component integration - -Most webforJ components have an internal event dispatcher. You typically don't need to create your own dispatcher; instead, use the component's API to register listeners: - -```java -passwordField.addEventListener(BlurEvent.class, event -> { - // Handle blur event for this field +// Register a listener +ListenerRegistration reg = dispatcher.addListener(EventObject.class, event -> { + // handle event + System.out.println("Event received: " + event); }); -``` - -## Event types and custom events - -Events in webforJ are Java classes that extend `EventObject`. Many built-in events are provided (such as `BlurEvent`, `ClickEvent`, etc.), but you can also define your own custom events for specialized use cases. -To define a custom event, extend `EventObject` (or `ComponentEvent` for UI components). For UI events, you can use annotations like `@EventName` and `@EventOptions` to configure event names and payloads. +// Remove a listener +reg.remove(); +// or +dispatcher.removeListener(EventObject.class, reg.getListener()); -```java -@EventName("my-custom-event") -@EventOptions(data = { - @EventOptions.EventData(key = "value", exp = "event.detail.value"), - @EventOptions.EventData(key = "timestamp", exp = "event.timeStamp") -}) -public static class CustomEvent extends ComponentEvent { - public CustomEvent(MyComponent component, Map eventData) { - super(component, eventData); - } - public String getValue() { - return (String) getData().get("value"); - } - public Long getTimestamp() { - return ((Number) getData().get("timestamp")).longValue(); - } -} -``` - -You can then register listeners for your custom event just like for built-in events: - -```java -myComponent.addEventListener(CustomEvent.class, event -> { - System.out.println("Custom value: " + event.getValue()); -}); +// Dispatch an event +dispatcher.dispatchEvent(new EventObject(this)); ``` ## Advanced registration options diff --git a/src/main/java/com/webforj/samples/views/advanced/EventDispatcherCustomEventView.java b/src/main/java/com/webforj/samples/views/advanced/EventDispatcherCustomEventView.java new file mode 100644 index 000000000..1f5e8a696 --- /dev/null +++ b/src/main/java/com/webforj/samples/views/advanced/EventDispatcherCustomEventView.java @@ -0,0 +1,63 @@ +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 java.util.EventObject; + +@Route +@FrameTitle("Event Dispatcher") +public class EventDispatcherCustomEventView extends Composite
{ + 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; + } + } + + public EventDispatcherCustomEventView() { + Button button = new Button("Fire Custom Event"); + + Div statusText = new Div("waiting for custom event"); + statusText.setStyle("border", "2px solid #333"); + statusText.setStyle("padding", "8px 24px"); + statusText.setStyle("margin-top", "24px"); + statusText.setStyle("border-radius", "6px"); + statusText.setStyle("font-size", "1.1em"); + statusText.setStyle("background", "#fafbfc"); + + // Center the content using flexbox styles on the root Div + Div root = this.getBoundComponent(); + root.setStyle("display", "flex"); + root.setStyle("flex-direction", "column"); + root.setStyle("justify-content", "center"); + root.setStyle("align-items", "center"); + root.setStyle("height", "100vh"); + + root.add(button); + root.add(statusText); + + // Register a listener for the custom event + dispatcher.addListener(CustomMessageEvent.class, event -> { + statusText.setText("received custom event "); + button.setEnabled(false); + }); + + // Fire the custom event with a message when the button is clicked + button.onClick(e -> dispatcher.dispatchEvent(new CustomMessageEvent(this, "Hello from custom event!"))); + } +} From 2110052302b585c68583077272554f70a11d6082 Mon Sep 17 00:00:00 2001 From: Eric Handtke Date: Thu, 10 Jul 2025 15:22:40 +0200 Subject: [PATCH 4/7] Fixed linting issues. --- docs/docs/advanced/event-dispatcher.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/docs/advanced/event-dispatcher.md b/docs/docs/advanced/event-dispatcher.md index 712ddeb50..7b30791e6 100644 --- a/docs/docs/advanced/event-dispatcher.md +++ b/docs/docs/advanced/event-dispatcher.md @@ -77,5 +77,4 @@ myComponent.addEventListener("input", event -> { - Always remove listeners when they're no longer needed to avoid memory leaks. - Use the provided event payload methods to access event data efficiently, avoiding unnecessary client-server round-trips. - Prefer using component APIs for event handling unless you have advanced requirements. -- Use registration options (payload, debounce, filter) to optimize performance and event data handling. - +- Use registration options to control event data and handler behavior. From 6bbce20d2543158c446eb2e36e7f4eef28af1900 Mon Sep 17 00:00:00 2001 From: Eric Handtke Date: Thu, 4 Sep 2025 10:26:00 +0200 Subject: [PATCH 5/7] Reworked event dispatcher doc. --- docs/docs/advanced/event-dispatcher.md | 74 +++++++++++++++----------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/docs/docs/advanced/event-dispatcher.md b/docs/docs/advanced/event-dispatcher.md index 7b30791e6..58699ab87 100644 --- a/docs/docs/advanced/event-dispatcher.md +++ b/docs/docs/advanced/event-dispatcher.md @@ -4,14 +4,20 @@ description: How the webforJ event dispatcher system works and how to use it for sidebar_position: 50 --- -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. +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. This page covers the standalone `EventDispatcher` API for custom event management. (For element/component events, see their respective documentation.) -## `EventDispatcher` + + +## 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 APIs +**Available methods:** - `addListener(Class eventClass, EventListener listener)`: Registers a listener for a specific event type. Returns a `ListenerRegistration` for later removal. - `removeListener(Class eventClass, EventListener listener)`: Removes a specific listener for the given event type. @@ -19,17 +25,15 @@ The webforJ event dispatcher system provides a flexible and type-safe way to han - `removeAllListeners()`: Removes all listeners from the dispatcher. - `dispatchEvent(T event)`: Notifies all listeners of the given event. - +## 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. -### Creating and registering listeners ```java import com.webforj.dispatcher.EventDispatcher; import com.webforj.dispatcher.EventListener; +import com.webforj.dispatcher.ListenerRegistration; import java.util.EventObject; // Create the dispatcher @@ -37,8 +41,7 @@ EventDispatcher dispatcher = new EventDispatcher(); // Register a listener ListenerRegistration reg = dispatcher.addListener(EventObject.class, event -> { - // handle event - System.out.println("Event received: " + event); + console().log("Event received: " + event); }); // Remove a listener @@ -50,31 +53,42 @@ dispatcher.removeListener(EventObject.class, reg.getListener()); dispatcher.dispatchEvent(new EventObject(this)); ``` -## Advanced registration options -For advanced scenarios, you can register listeners with additional options using `ElementEventOptions`. These options allow you to: -- Add custom data to the event payload -- Debounce or throttle event firing -- Filter events based on conditions -- Execute JavaScript before the event is fired (for UI events) -Example: +## 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 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 -ElementEventOptions options = new ElementEventOptions(); -options.addData("value", "event.target.value"); -options.setDebounce(300); // Debounce by 300ms -options.setFilter("event.target.value.length > 2"); - -myComponent.addEventListener("input", event -> { - String value = (String) event.getData().get("value"); - System.out.println("Input value: " + value); -}, options); +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, avoiding unnecessary client-server round-trips. -- Prefer using component APIs for event handling unless you have advanced requirements. -- Use registration options to control event data and handler behavior. +- 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. From 8d8348b1a310222687b518bb0eecbae5dd286e67 Mon Sep 17 00:00:00 2001 From: Ben Brennan Date: Mon, 15 Sep 2025 12:03:16 -0600 Subject: [PATCH 6/7] Chore: Revision edits for PR #372 --- docs/docs/advanced/event-dispatcher.md | 9 +++- .../EventDispatcherCustomEventView.java | 52 +++++++++++-------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/docs/docs/advanced/event-dispatcher.md b/docs/docs/advanced/event-dispatcher.md index 58699ab87..619fefdaf 100644 --- a/docs/docs/advanced/event-dispatcher.md +++ b/docs/docs/advanced/event-dispatcher.md @@ -2,10 +2,17 @@ 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 --- + + -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. This page covers the standalone `EventDispatcher` API for custom event management. (For element/component events, see their respective documentation.) +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. +::: { +public class EventDispatcherCustomEventView extends Composite { private final EventDispatcher dispatcher = new EventDispatcher(); /** @@ -29,35 +33,41 @@ public String getMessage() { } } + Button button = new Button("Fire custom event"); + Div statusText = new Div("Waiting for custom event"); + FlexLayoutBuilder layoutBuilder = new FlexLayoutBuilder(button, statusText); + public EventDispatcherCustomEventView() { - Button button = new Button("Fire Custom Event"); - Div statusText = new Div("waiting for custom event"); + button.setWidth("fit-content"); + + statusText.setWidth("fit-content"); statusText.setStyle("border", "2px solid #333"); statusText.setStyle("padding", "8px 24px"); - statusText.setStyle("margin-top", "24px"); - statusText.setStyle("border-radius", "6px"); - statusText.setStyle("font-size", "1.1em"); - statusText.setStyle("background", "#fafbfc"); - - // Center the content using flexbox styles on the root Div - Div root = this.getBoundComponent(); - root.setStyle("display", "flex"); - root.setStyle("flex-direction", "column"); - root.setStyle("justify-content", "center"); - root.setStyle("align-items", "center"); - root.setStyle("height", "100vh"); - - root.add(button); - root.add(statusText); + 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)"); + + 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, event -> { - statusText.setText("received custom event "); + dispatcher.addListener(CustomMessageEvent.class, e -> { + statusText.setText("Received custom event"); button.setEnabled(false); }); // Fire the custom event with a message when the button is clicked - button.onClick(e -> dispatcher.dispatchEvent(new CustomMessageEvent(this, "Hello from custom event!"))); + button.onClick(e -> { + dispatcher.dispatchEvent(new CustomMessageEvent(this, "Hello from custom event!")); + }); } } From 2e57228f892e937e1b49c3015edb40b9cba3db17 Mon Sep 17 00:00:00 2001 From: Eric Handtke Date: Thu, 9 Oct 2025 13:56:22 +0200 Subject: [PATCH 7/7] Adjusted according to feedback. --- docs/docs/advanced/event-dispatcher.md | 65 +++++++++----- .../EventDispatcherCustomEventView.java | 90 ++++++++++--------- .../advanced/eventDispatcherCustomEvent.css | 7 ++ 3 files changed, 97 insertions(+), 65 deletions(-) create mode 100644 src/main/resources/static/css/advanced/eventDispatcherCustomEvent.css diff --git a/docs/docs/advanced/event-dispatcher.md b/docs/docs/advanced/event-dispatcher.md index 619fefdaf..f37df5c41 100644 --- a/docs/docs/advanced/event-dispatcher.md +++ b/docs/docs/advanced/event-dispatcher.md @@ -14,24 +14,6 @@ The webforJ event dispatcher system provides a flexible and type-safe way to han 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. ::: - - -## 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 eventClass, EventListener listener)`: Registers a listener for a specific event type. Returns a `ListenerRegistration` for later removal. -- `removeListener(Class eventClass, EventListener listener)`: Removes a specific listener for the given event type. -- `removeAllListeners(Class 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. - ## 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. @@ -94,8 +76,47 @@ Whenever needed, dispatch the event and the listener will be triggered. dispatcher.dispatchEvent(new MyCustomEvent("Hello from custom event!")); ``` -## Best practices + + +### Removing listeners and avoiding memory leaks + +When to remove listeners + +- Remove listeners when the consumer object is disposed or no longer reachable from the app UI (for example: closing a dialog, navigating away from a view, or when a long-lived service no longer needs callbacks). +- Remove listeners when the event handling is tied to a transient lifecycle (short-lived tasks, temporary subscriptions, or one-time flows). + +How to remove listeners + +Use the returned `ListenerRegistration` to unregister a listener explicitly: + +```java +ListenerRegistration reg = dispatcher.addListener(MyCustomEvent.class, ev -> handle(ev)); +// Later, when no longer needed +reg.remove(); +``` + +Or remove by passing the listener to the dispatcher: + +```java +dispatcher.removeListener(MyCustomEvent.class, myListener); +``` + +If you registered multiple listeners for the same class and want to remove all of them at once: + +```java +dispatcher.removeAllListeners(MyCustomEvent.class); +``` + +Listeners are objects or lambdas that often reference surrounding objects (for example, a view or its model). The EventDispatcher keeps those listener objects in internal collections; while stored, neither the listener nor the objects it references can be garbage-collected. In apps that create many short-lived views or components, forgotten listeners accumulate and keep those views alive, causing memory that can't be freed. + +- A dialog registers a listener that references the dialog's model. If the dialog is closed but the listener isn't removed, the dispatcher still references the listener, which in turn references the dialog and its model. The dialog can't be garbage-collected. +- Lambdas or inner classes implicitly capture `this` or local variables; the captured references become part of the listener object retained by the dispatcher. + +Removing the listener explicitly breaks the chain of strong references: the dispatcher no longer references the listener, so the listener and the objects it captured become eligible for garbage collection if there are no other live references to them. + +Prefer storing the returned `ListenerRegistration` where you can remove it during cleanup (for example, in a component's `dispose` or a view's `onDetach`), and avoid anonymous registrations that you can't later unhook. -- 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. diff --git a/src/main/java/com/webforj/samples/views/advanced/EventDispatcherCustomEventView.java b/src/main/java/com/webforj/samples/views/advanced/EventDispatcherCustomEventView.java index 1446b2388..b4810423b 100644 --- a/src/main/java/com/webforj/samples/views/advanced/EventDispatcherCustomEventView.java +++ b/src/main/java/com/webforj/samples/views/advanced/EventDispatcherCustomEventView.java @@ -4,6 +4,7 @@ import com.webforj.router.annotation.FrameTitle; import com.webforj.router.annotation.Route; import com.webforj.component.button.Button; +import com.webforj.annotation.StyleSheet; import com.webforj.component.Composite; import com.webforj.component.html.elements.Div; import com.webforj.component.layout.flexlayout.FlexJustifyContent; @@ -14,60 +15,63 @@ @Route @FrameTitle("Event Dispatcher") +@StyleSheet("ws://css/advanced/eventDispatcherCustomEvent.css") public class EventDispatcherCustomEventView extends Composite { - private final EventDispatcher dispatcher = new EventDispatcher(); + private final EventDispatcher dispatcher = new EventDispatcher(); - /** - * A custom event that carries a message string. - */ - public static class CustomMessageEvent extends EventObject { - private final String message; + /** + * 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 CustomMessageEvent(Object source, String message) { + super(source); + this.message = message; + } - public String getMessage() { - return 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); + Button button = new Button("Fire custom event"); + Button button2 = new Button("Reset"); + Div statusText = new Div("Waiting for custom event"); + FlexLayoutBuilder layoutBuilder = new FlexLayoutBuilder(button, button2, statusText); - public EventDispatcherCustomEventView() { + public EventDispatcherCustomEventView() { - button.setWidth("fit-content"); + button.setWidth("fit-content"); + button2.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)"); + statusText.addClassName("statusText"); - FlexLayout layout = layoutBuilder.vertical() - .justify().center() - .align().center() - .build() - .setHeight("100vh") - .setSpacing("var(--dwc-space-xl)"); + 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); + 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); - }); + // Register a listener for the custom event + dispatcher.addListener(CustomMessageEvent.class, e -> { + statusText.setText("Received custom event"); + button.setEnabled(false); + }); - // Fire the custom event with a message when the button is clicked - button.onClick(e -> { - dispatcher.dispatchEvent(new CustomMessageEvent(this, "Hello from custom event!")); - }); - } + // Fire the custom event with a message when the button is clicked + button.onClick(e -> { + dispatcher.dispatchEvent(new CustomMessageEvent(this, "Hello from custom event!")); + }); + + button2.onClick(e -> { + statusText.setText("Waiting for custom event"); + button.setEnabled(true); + }); + } } diff --git a/src/main/resources/static/css/advanced/eventDispatcherCustomEvent.css b/src/main/resources/static/css/advanced/eventDispatcherCustomEvent.css new file mode 100644 index 000000000..0750485d8 --- /dev/null +++ b/src/main/resources/static/css/advanced/eventDispatcherCustomEvent.css @@ -0,0 +1,7 @@ +.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); +}