Oorian uses a JDK-style event model that will feel immediately familiar if you have experience with Swing, JavaFX, or SWT. Elements fire events, and listeners handle them. There are no custom annotations, no magic strings, and no reflection tricks — just standard Java interfaces and method calls.
The event system supports six categories of events, each scoped to a different level of the application:
| Event Type | Scope | Use Case |
|---|---|---|
ClientEvent |
Element | User interactions from the browser (clicks, keystrokes, form submits) |
ServerEvent |
Element | Server-side component communication with event bubbling |
ExtServerEvent |
Element | Server-side events with custom handler methods |
PageEvent |
Page | Communication between components on the same page |
SessionEvent |
Session | Communication across pages within a user's session |
ApplicationEvent |
Application | Broadcasting to all active sessions (system-wide notifications) |
The Event Architecture
All events extend from a common Event<T> base class that uses generics to bind each event to its specific listener type. Every event type has a corresponding listener interface with an onEvent() method. This is the same observer pattern used throughout the Java ecosystem.
The general pattern is always the same:
- Implement the listener interface for the events you want to handle
- Register your listener with
registerListener() - Handle events in the
onEvent()callback
Client Events
Client events originate in the browser and are dispatched to listeners on the server. When a user clicks a button, types in a field, or submits a form, Oorian sends the event to the server and routes it to the registered listener on the appropriate element.
Every client event carries a source reference (the element the listener was registered on) and a target reference (the element the user actually interacted with). Use getSource() to route events when multiple elements share a listener.
| Category | Events | Listener |
|---|---|---|
| Mouse (click only) | MouseClickedEvent |
MouseClickListener |
| Mouse (all) | MouseClickedEvent, MouseDblClickedEvent, MouseDownEvent, MouseUpEvent, MouseMoveEvent, MouseOverEvent, MouseOutEvent |
MouseListener |
| Keyboard | KeyDownEvent, KeyUpEvent |
KeyListener |
| Input | InputChangeEvent, InputCompleteEvent |
InputListener |
| Form | FormEvent |
FormListener |
| Focus | FocusInEvent, FocusOutEvent |
FocusListener |
| Scroll | ScrollEvent, ScrollEndEvent |
ScrollListener |
| Touch | TapEvent, TapHoldEvent, SwipeEvent, SwipeLeftEvent, SwipeRightEvent |
TouchListener |
| Drag & Drop | DragStartEvent, DropEvent, DragEnterEvent, DragLeaveEvent, etc. |
DragDropListener |
| File | ClientFileSelectEvent, ClientFileUploadEvent |
(see File Uploads) |
| Custom | UserEvent |
UserEventListener |
All client events and listeners are in the com.oorian.messaging.events.client package.
Here is a complete example. The page implements MouseClickListener and registers itself on two buttons. When either button is clicked, the onEvent() method is called and getSource() identifies which button was clicked:
@Page("/counter")
public class CounterPage extends HtmlPage implements MouseClickListener
{
private int count = 0;
private Span countLabel;
private Button incrementBtn;
private Button resetBtn;
@Override
protected void createBody(Body body)
{
countLabel = new Span();
countLabel.setText("0");
body.addElement(countLabel);
incrementBtn = new Button("Increment");
incrementBtn.registerListener(this, MouseClickedEvent.class);
body.addElement(incrementBtn);
resetBtn = new Button("Reset");
resetBtn.registerListener(this, MouseClickedEvent.class);
body.addElement(resetBtn);
}
@Override
public void onEvent(MouseClickedEvent event)
{
if (event.getSource() == incrementBtn)
{
count++;
}
else if (event.getSource() == resetBtn)
{
count = 0;
}
countLabel.setText(String.valueOf(count));
sendUpdate();
}
}
MouseClickListener vs. MouseListener
If you only need click events, implement MouseClickListener — it has a single onEvent(MouseClickedEvent) method. If you need the full range of mouse interactions (double-click, mouse down/up, move, over/out), implement MouseListener instead, which defines handler methods for all seven mouse event types.
Server Events
Server events are created and dispatched entirely on the server. They are scoped to the element hierarchy and support event bubbling — when dispatched on an element, they propagate up through parent elements, allowing ancestors to observe events from their descendants.
This makes server events ideal for communication between components. A child component can dispatch an event, and a parent container can listen for it without the child knowing who is listening.
To create a custom server event, extend ServerEvent and parameterize it with your listener interface:
public class ItemSelectedEvent extends ServerEvent<ItemSelectedListener>
{
private final String itemId;
public ItemSelectedEvent(String itemId)
{
this.itemId = itemId;
}
public String getItemId()
{
return itemId;
}
}
public interface ItemSelectedListener extends ServerEventListener<ItemSelectedEvent>
{
// onEvent(ItemSelectedEvent) is inherited from ServerEventListener
}
// In a child component â dispatch when something is selected
dispatchEvent(new ItemSelectedEvent("product-42"));
// In a parent component â register to listen for the event
itemList.registerListener(this, ItemSelectedEvent.class);
// Handle the event
@Override
public void onEvent(ItemSelectedEvent event)
{
String id = event.getItemId();
// update detail panel, etc.
}
Because server events bubble by default, you can register the listener on a parent element and it will receive events dispatched by any descendant. To create a non-bubbling server event, pass false to the superclass constructor:
public ItemSelectedEvent(String itemId)
{
super(false); // this event will not bubble
this.itemId = itemId;
}
Extended Server Events (ExtServerEvent)
Extended server events work like server events — they are element-scoped and support bubbling — but with one key difference: the ExtServerEventListener interface is empty. Instead of inheriting a generic onEvent() method, each listener subinterface defines its own specifically-named handler methods.
This is useful when a component fires multiple related events and you want distinct handler method names instead of overloaded onEvent() methods:
public class TabOpenedEvent extends ExtServerEvent<TabEventListener>
{
private final int tabIndex;
public TabOpenedEvent(int tabIndex) { this.tabIndex = tabIndex; }
public int getTabIndex() { return tabIndex; }
@Override
public void dispatchTo(TabEventListener listener)
{
listener.onTabOpened(this);
}
}
public class TabClosedEvent extends ExtServerEvent<TabEventListener>
{
private final int tabIndex;
public TabClosedEvent(int tabIndex) { this.tabIndex = tabIndex; }
public int getTabIndex() { return tabIndex; }
@Override
public void dispatchTo(TabEventListener listener)
{
listener.onTabClosed(this);
}
}
public interface TabEventListener extends ExtServerEventListener<ExtServerEvent>
{
void onTabOpened(TabOpenedEvent event);
void onTabClosed(TabClosedEvent event);
}
// Register for both events through the shared listener
tabPanel.registerListener(this, TabOpenedEvent.class, TabClosedEvent.class);
// Each event dispatches to its own handler method
@Override
public void onTabOpened(TabOpenedEvent event)
{
loadTabContent(event.getTabIndex());
}
@Override
public void onTabClosed(TabClosedEvent event)
{
cleanupTab(event.getTabIndex());
}
ServerEvent vs. ExtServerEvent
Use ServerEvent when you have a single event type with one handler. Use ExtServerEvent when a component fires multiple related events and you want descriptive handler method names like onTabOpened() and onTabClosed() instead of multiple onEvent() overloads. Both support event bubbling through the element hierarchy.
Page Events
Page events are scoped to a single HtmlPage instance. Unlike server events, they are not tied to any element and do not bubble. Any component on the page can dispatch a page event, and any component that has registered a listener on the page will receive it.
This makes page events ideal for decoupled communication between sibling components that don't share a parent-child relationship in the element tree. A sidebar component can notify a content panel that the user selected a new category, without either component holding a direct reference to the other.
public class CategoryChangedEvent extends PageEvent
{
private final String category;
public CategoryChangedEvent(String category)
{
this.category = category;
}
public String getCategory()
{
return category;
}
}
Register and dispatch page events through the page or through any element (which delegates to the page automatically):
// Dispatch a page event from any element
dispatchEvent(new CategoryChangedEvent("electronics"));
// Register for page events (can register on the page or on any element)
registerListener(this, CategoryChangedEvent.class);
@Override
public void onEvent(CategoryChangedEvent event)
{
loadProducts(event.getCategory());
}
Page events also support a stop() mechanism. A listener can call event.stop() to signal that the event has been fully handled. Subsequent listeners can check event.isStopped() to decide whether to act.
Session Events
Session events are scoped to the current user's OorianSession. They are delivered to all listeners registered on that session, regardless of which page the listener belongs to. This makes session events the right choice when you need to communicate across pages within a single user's session.
A common use case is notifying all open pages when a user's preferences change, or when background processing completes:
public class ThemeChangedEvent extends SessionEvent<ThemeChangedListener>
{
private final String theme;
public ThemeChangedEvent(String theme)
{
this.theme = theme;
}
public String getTheme()
{
return theme;
}
}
public interface ThemeChangedListener extends SessionEventListener<ThemeChangedEvent>
{
// onEvent(ThemeChangedEvent) is inherited from SessionEventListener
}
// Any page can register to receive session events
OorianSession.get().registerListener(this, ThemeChangedEvent.class);
// Dispatch to all listeners in this session
new ThemeChangedEvent("dark").dispatch();
// Handle the event
@Override
public void onEvent(ThemeChangedEvent event)
{
applyTheme(event.getTheme());
sendUpdate();
}
Session events provide a convenience dispatch() method that automatically dispatches the event to the current session. You can also dispatch explicitly with OorianSession.get().dispatchEvent(event).
Application Events
Application events are the broadest scope in the event system. When dispatched, they are delivered to every active session in the application. This makes them ideal for system-wide broadcasts such as maintenance notifications, configuration changes, or real-time alerts that should reach all connected users.
public class MaintenanceAlert extends ApplicationEvent<MaintenanceAlertListener>
{
private final String message;
private final int minutesUntilShutdown;
public MaintenanceAlert(String message, int minutesUntilShutdown)
{
this.message = message;
this.minutesUntilShutdown = minutesUntilShutdown;
}
public String getMessage() { return message; }
public int getMinutesUntilShutdown() { return minutesUntilShutdown; }
}
public interface MaintenanceAlertListener extends ApplicationEventListener<MaintenanceAlert>
{
// onEvent(MaintenanceAlert) is inherited from ApplicationEventListener
}
// Each page registers to receive application events via its session
OorianSession.get().registerListener(this, MaintenanceAlert.class);
// Broadcast to every active session in the application
new MaintenanceAlert("Server restarting for updates", 5).dispatch();
// Every registered listener across all sessions receives the event
@Override
public void onEvent(MaintenanceAlert event)
{
showBanner(event.getMessage());
sendUpdate();
}
Application Events Reach All Users
Application events are dispatched to every active session. Use them only for truly global notifications. For user-specific communication, use session events instead.
Choosing the Right Event Type
| Scenario | Event Type |
|---|---|
| Responding to a button click | ClientEvent (MouseClickedEvent) |
| Child component notifying its parent | ServerEvent (bubbles up) |
| Component with multiple related events | ExtServerEvent (custom handler names) |
| Sidebar notifying content panel on the same page | PageEvent |
| Updating all open tabs when user preferences change | SessionEvent |
| Announcing server maintenance to all connected users | ApplicationEvent |
The sendUpdate() Pattern
After modifying elements in an event handler, call sendUpdate() to push the changes to the browser. This is Oorian's core interaction pattern:
- An event is dispatched to your listener
- Your handler modifies element properties (text, visibility, styles, etc.)
- You call
sendUpdate()to push all pending changes to the browser
Oorian automatically detects which elements have changed and sends only the minimal updates needed. If you change the text of one label, only that label's update is sent — not the entire page.
When to Call sendUpdate()
Call sendUpdate() once at the end of your event handler, after all modifications are complete. Oorian batches all changes into a single response, so calling it multiple times is unnecessary. For server-side events (ServerEvent, PageEvent, etc.) that modify the UI, remember to call sendUpdate() just as you would for client events.
