DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Event-Handling-Basics

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

  1. Inline Closure Events
  2. NativeEventHandler
  3. Input Events
  4. Form Change Events
  5. Supported Event Names
  6. Accessing Event Data
  7. Utility Functions for Event Handling
  8. 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"
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    })
}
Enter fullscreen mode Exit fullscreen mode

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"
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's break down what's happening here:

  1. We render an <input> element with type: "text".
  2. The oninput event fires whenever the user types a character.
  3. Inside the closure, we call event.target() to get the DOM element that triggered the event.
  4. We attempt to cast it to HtmlInputElement using dyn_into.
  5. If successful, we extract the current value with input.value() and update the name_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());
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example:

  1. We render a checkbox input whose checked state is bound to agree_signal.
  2. The onchange event fires when the user toggles the checkbox.
  3. We extract the checked property from the HtmlInputElement and 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 on prefix 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();
    }
}
Enter fullscreen mode Exit fullscreen mode

This example demonstrates three common event data extraction patterns:

  1. MouseEvent data: Use dyn_ref::<MouseEvent>() to access mouse coordinates (client_x, client_y) and button information.
  2. KeyboardEvent data: Use dyn_ref::<KeyboardEvent>() to access the key that was pressed (key) and the physical key code (code).
  3. Input element data: Use event.target() combined with dyn_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());
    }))
}
Enter fullscreen mode Exit fullscreen mode

Usage:

let show_details: Signal<bool> = use_signal(|| false);

html! {
    primary_button { label: "Toggle" onclick: use_toggle(show_details) "Toggle" }
}
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }))
}
Enter fullscreen mode Exit fullscreen mode

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 {
            ""
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • use_toggle for a show/hide button
  • if/else conditional rendering for the counter section
  • NativeEventHandler for 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)