Event Handling Basics in euv
Project Code:https://github.com/euv-dev/euv
euv is a Rust + WASM frontend UI framework that enables developers to build interactive web applications using the power of reactive signals and the html! macro. One of the most critical aspects of any UI framework is how it handles user interactions. In this article, we will take a deep dive into euv's event handling system — from inline closures to native event handlers, from input events to form changes, and from the comprehensive list of supported event names to utility functions that simplify common patterns.
Table of Contents
- Inline Closure Events
- NativeEventHandler
- Input Events
- Form Change Events
- Supported Event Names
- Accessing Event Data
- Utility Functions for Event Handling
- Putting It All Together
Inline Closure Events
The most straightforward way to handle events in euv is through inline closures. You define the event handler directly within the html! macro using the move |event: Event| { ... } syntax.
html! {
button {
onclick: move |event: Event| { }
"Click me"
}
}
This pattern is ideal for simple, self-contained event handlers that don't need to be reused across multiple components. The move keyword ensures that any captured variables (like signals) are moved into the closure, which is essential for the Rust ownership model.
Inline closures work with any event type — not just onclick. You can use them for keyboard events, focus events, mouse events, and more. The closure receives an Event object that you can inspect to extract relevant data.
NativeEventHandler
For more complex scenarios where you need reusable event handlers or want to define handlers outside the html! macro, euv provides the NativeEventHandler type. This allows you to create named, parameterized event handler functions.
pub fn counter_on_increment(counter: Signal<i32>) -> NativeEventHandler {
NativeEventHandler::create("click", move |_event: Event| {
let current: i32 = counter.get();
counter.set(current + 1);
})
}
In this example, counter_on_increment is a function that takes a Signal<i32> and returns a NativeEventHandler. The handler is created with NativeEventHandler::create, which takes the event name ("click") and a closure that increments the counter signal.
The key advantage of NativeEventHandler is reusability. You can define a handler once and use it across multiple components. It also keeps your html! macro clean by moving complex logic into dedicated functions.
To use a NativeEventHandler in your template, you would reference it like any other event handler:
html! {
button {
onclick: counter_on_increment(counter_signal)
"Increment"
}
}
Input Events
Handling user input is one of the most common tasks in web development. euv provides the oninput event for capturing text input in real time.
html! {
input {
r#type: "text"
oninput: move |event: Event| {
if let Some(target) = event.target()
&& let Ok(input) = target.clone().dyn_into::<HtmlInputElement>()
{
name_signal.set(input.value());
}
}
}
}
Let's break down what's happening here:
- We render an
<input>element withtype: "text". - The
oninputevent fires whenever the user types a character. - Inside the closure, we call
event.target()to get the DOM element that triggered the event. - We attempt to cast it to
HtmlInputElementusingdyn_into. - If successful, we extract the current value with
input.value()and update thename_signal.
The r#type syntax is used because type is a reserved keyword in Rust. The r# prefix allows us to use it as a regular identifier.
This pattern is essential for building controlled input components where the signal is the single source of truth for the input's value.
Form Change Events
For checkbox inputs and other form elements that use the change event, euv provides the onchange handler. This event fires when the value of a form element changes and the element loses focus (for text inputs) or immediately (for checkboxes).
html! {
input {
r#type: "checkbox"
checked: agree_signal
onchange: move |event: Event| {
if let Some(target) = event.target()
&& let Ok(input) = target.clone().dyn_into::<HtmlInputElement>()
{
agree_signal.set(input.checked());
}
}
}
}
In this example:
- We render a checkbox input whose
checkedstate is bound toagree_signal. - The
onchangeevent fires when the user toggles the checkbox. - We extract the
checkedproperty from theHtmlInputElementand update the signal.
This two-way binding pattern — where the signal drives the initial state and user interactions update the signal — is the foundation of form handling in euv.
Supported Event Names
euv supports a comprehensive range of browser events, organized into the following categories:
Mouse Events
-
onclick— Single click -
ondblclick— Double click -
onmousedown— Mouse button pressed -
onmouseup— Mouse button released -
onmousemove— Mouse moved -
onmouseenter— Mouse enters element -
onmouseleave— Mouse leaves element -
onmouseover— Mouse hovers over element -
onmouseout— Mouse moves away from element -
oncontextmenu— Right-click context menu
Input Events
-
oninput— Input value changes
Keyboard Events
-
onkeydown— Key pressed down -
onkeyup— Key released -
onkeypress— Key pressed (character input)
Focus Events
-
onfocus— Element gains focus -
onblur— Element loses focus -
onfocusin— Focus enters element (bubbles) -
onfocusout— Focus leaves element (bubbles)
Form Events
-
onsubmit— Form submitted -
onchange— Form element value changes
Drag and Drop Events
-
ondrag— Dragging -
ondragstart— Drag started -
ondragend— Drag ended -
ondragover— Dragging over element -
ondragenter— Drag enters element -
ondragleave— Drag leaves element -
ondrop— Element dropped
Touch Events
-
ontouchstart— Touch started -
ontouchend— Touch ended -
ontouchmove— Touch moved -
ontouchcancel— Touch cancelled
Scroll Events
-
onwheel— Mouse wheel scrolled
Clipboard Events
-
oncopy— Content copied -
oncut— Content cut -
onpaste— Content pasted
Media Events
-
onplay— Media started playing -
onpause— Media paused -
onended— Media finished playing -
onloadeddata— Media data loaded -
oncanplay— Media can start playing -
onvolumechange— Volume changed -
ontimeupdate— Playback time updated
Routing Events
-
onhashchange— URL hash changed -
onpopstate— Browser history state changed
Window Events
-
onresize— Window resized -
onscroll— Window scrolled -
onload— Content loaded -
onunload— Content unloaded -
onbeforeunload— Before content unloaded -
onerror— Error occurred -
ononline— Browser went online -
onoffline— Browser went offline -
onvisibilitychange— Page visibility changed
Animation Events
-
onanimationstart— Animation started -
onanimationend— Animation ended -
onanimationiteration— Animation iteration completed
Transition Events
-
ontransitionstart— Transition started -
ontransitionend— Transition ended -
ontransitionrun— Transition running
Custom Events
- Any event name with the
onprefix followed by a custom event name is supported, making euv fully compatible with custom DOM events.
This extensive event coverage means you can handle virtually any user interaction or browser event directly within euv's html! macro.
Accessing Event Data
When an event fires, the closure receives an Event object. Depending on the event type, you can cast this to a more specific event type to access detailed data.
move |event: Event| {
if let Some(mouse_event) = event.dyn_ref::<MouseEvent>() {
let x: i32 = mouse_event.client_x();
let y: i32 = mouse_event.client_y();
let button: i16 = mouse_event.button();
}
if let Some(keyboard_event) = event.dyn_ref::<KeyboardEvent>() {
let key: String = keyboard_event.key();
let code: String = keyboard_event.code();
}
if let Some(target) = event.target()
&& let Ok(input) = target.clone().dyn_into::<HtmlInputElement>()
{
let value: String = input.value();
let checked: bool = input.checked();
}
}
This example demonstrates three common event data extraction patterns:
-
MouseEvent data: Use
dyn_ref::<MouseEvent>()to access mouse coordinates (client_x,client_y) and button information. -
KeyboardEvent data: Use
dyn_ref::<KeyboardEvent>()to access the key that was pressed (key) and the physical key code (code). -
Input element data: Use
event.target()combined withdyn_into::<HtmlInputElement>()to access the current value and checked state of form elements.
The dyn_ref method returns an Option<&T>, so you should always use if let Some(...) to handle the case where the event is not of the expected type. This pattern of type-checking and casting is idiomatic in Rust and ensures type safety at runtime.
Utility Functions for Event Handling
euv provides several utility functions that simplify common event handling patterns.
use_toggle
The use_toggle function creates a click handler that toggles a boolean signal:
pub fn use_toggle(signal: Signal<bool>) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_event: Event| {
signal.set(!signal.get());
}))
}
Usage:
let show_details: Signal<bool> = use_signal(|| false);
html! {
primary_button { label: "Toggle" onclick: use_toggle(show_details) "Toggle" }
}
This is perfect for any show/hide toggle, dropdown menu, or collapsible section.
on_input_value
The on_input_value function creates an input handler that automatically extracts the value from text inputs, textareas, and select elements:
pub fn on_input_value(signal: Signal<String>) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |event: Event| {
let value: Option<String> = event.target().and_then(|target| {
if let Ok(input) = target.clone().dyn_into::<HtmlInputElement>() {
return Some(input.value());
}
if let Ok(textarea) = target.clone().dyn_into::<HtmlTextAreaElement>() {
return Some(textarea.value());
}
if let Ok(select) = target.clone().dyn_into::<HtmlSelectElement>() {
return Some(select.value());
}
None
});
if let Some(value) = value {
signal.set(value);
}
}))
}
This utility is incredibly useful because it handles three different input element types (HtmlInputElement, HtmlTextAreaElement, HtmlSelectElement) in a single function, making it a versatile tool for form handling.
Putting It All Together
Let's combine everything we've learned into a practical example — a simple counter with increment and toggle functionality:
let counter: Signal<i32> = use_signal(|| 0);
let show_counter: Signal<bool> = use_signal(|| true);
pub fn counter_on_increment(counter: Signal<i32>) -> NativeEventHandler {
NativeEventHandler::create("click", move |_event: Event| {
let current: i32 = counter.get();
counter.set(current + 1);
})
}
html! {
div {
button {
onclick: use_toggle(show_counter)
"Toggle Counter"
}
if { show_counter.get() } {
div {
span { counter }
button {
onclick: counter_on_increment(counter)
"Increment"
}
}
} else {
""
}
}
}
This example demonstrates:
-
use_togglefor a show/hide button -
if/elseconditional rendering for the counter section -
NativeEventHandlerfor the increment button - Reactive signals driving the UI state
Conclusion
euv's event handling system is both powerful and ergonomic. Inline closures provide a quick way to handle simple interactions, NativeEventHandler enables reusable and testable event logic, and the comprehensive event name support ensures you can respond to any browser event. Combined with utility functions like use_toggle and on_input_value, you have everything you need to build interactive, responsive web applications.
In the next article, we will explore how to access browser APIs directly from euv, including the Window, Document, Location, and Storage interfaces.
Project Code:https://github.com/euv-dev/euv
Top comments (0)