DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Routing-and-Navigation

Routing and Navigation in euv

Project Code:https://github.com/euv-dev/euv

Routing and navigation are fundamental aspects of any single-page application (SPA). They determine how users move between different views and how the application's UI reflects the current state. In euv, routing is handled through a combination of browser APIs, reactive signals, and the framework's component system.

This article provides a comprehensive guide to implementing routing and navigation in euv applications, from basic navigation patterns to advanced techniques like nested routes and programmatic navigation.

Understanding the Virtual DOM and Navigation

Before diving into routing, it's important to understand how euv's virtual DOM works. The virtual DOM in euv consists of several VirtualNode variants:

  • Element: Represents an HTML element with attributes and children.
  • Text: Represents a text node.
  • Fragment: Represents a group of nodes without a wrapper element.
  • Dynamic: Represents a node whose content can change reactively.
  • Empty: Represents an empty or removed node.

When implementing routing, you're essentially swapping out parts of the virtual DOM based on the current route. The html! macro makes this straightforward with its support for conditional rendering:

html! {
    div {
        if current_route.get() == "home" {
            home_page()
        } else if current_route.get() == "about" {
            about_page()
        } else {
            not_found_page()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Basic Navigation with Signals

The simplest approach to routing in euv uses a signal to track the current route. When the user navigates, the signal is updated, and the UI reactively renders the appropriate view:

let current_route: Signal<String> = use_signal(|| "home".to_string());

html! {
    nav {
        button {
            onclick: move |_event: Event| {
                current_route.set("home".to_string());
            }
            "Home"
        }
        button {
            onclick: move |_event: Event| {
                current_route.set("about".to_string());
            }
            "About"
        }
    }
    div {
        if current_route.get() == "home" {
            h1 { "Home Page" }
        } else if current_route.get() == "about" {
            h1 { "About Page" }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This pattern uses the html! macro's if/else support to conditionally render different views based on the current route. When the current_route signal changes, the virtual DOM is updated automatically.

Using the Browser's History API

For a more robust navigation experience, you should integrate with the browser's History API. This ensures that the browser's back and forward buttons work correctly, and that the URL reflects the current view.

In euv, you can access browser APIs through the framework's browser integration layer. Here's how you might set up basic History API integration:

let current_route: Signal<String> = use_signal(|| "home".to_string());

// Listen for popstate events (back/forward button)
// Update current_route when the URL changes

button {
    onclick: move |_event: Event| {
        current_route.set("about".to_string());
        // Push new state to browser history
    }
    "Go to About"
}
Enter fullscreen mode Exit fullscreen mode

The key principle is to keep the signal in sync with the browser's URL. When the user clicks a navigation button, you update both the signal and the URL. When the user uses the back/forward button, you update the signal based on the URL change.

Component-Based Routing

As your application grows, you'll want to organize your routes into components. The #[component] macro makes it easy to create route components:

#[derive(Clone, Default)]
struct MyCardProps {
    title: &'static str,
    onclick: Option<Rc<dyn Fn(Event)>>,
    disabled: Signal<bool>
}

#[component]
pub fn my_card(node: VirtualNode<MyCardProps>) -> VirtualNode {
    let MyCardProps { title, .. } = node.try_get_props().unwrap_or_default();
    let children: VirtualNode = node.try_get_child_node();
    html! {
        div {
            h3 { title }
            children
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

You can create specialized route components by passing route information through props. For example, a navigation link component might accept a route prop and handle navigation internally:

html! {
    my_card {
        title: "Navigation Card"
        onclick: Rc::new(move |event: Event| {
            current_route.set("dashboard".to_string());
        })
        button { "Go to Dashboard" }
    }
}
Enter fullscreen mode Exit fullscreen mode

The class! Macro for Active Route Styling

When building navigation menus, you typically want to highlight the currently active route. The class! macro in euv makes it easy to apply conditional CSS classes:

html! {
    nav {
        button {
            class: if current_route.get() == "home" { "active" } else { "" }
            onclick: move |_event: Event| {
                current_route.set("home".to_string());
            }
            "Home"
        }
        button {
            class: if current_route.get() == "about" { "active" } else { "" }
            onclick: move |_event: Event| {
                current_route.set("about".to_string());
            }
            "About"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This applies the "active" CSS class only to the button whose route matches the current route, providing visual feedback to the user about their current location.

Programmatic Navigation

Sometimes you need to navigate programmatically — for example, after a successful form submission or when an API call completes. In euv, this is as simple as updating the route signal:

let current_route: Signal<String> = use_signal(|| "home".to_string());

button {
    onclick: move |_event: Event| {
        // Perform some action (e.g., form submission)
        let mut validation_errors: Vec<String> = Vec::new();
        if username.get().trim().is_empty() {
            validation_errors.push("Username is required".to_string());
        }
        if validation_errors.is_empty() {
            errors.set("".to_string());
            submitted.set(format!("Submitted: {}", username.get()));
            // Navigate to success page
            current_route.set("success".to_string());
        } else {
            errors.set(validation_errors.join("; "));
        }
    }
    "Submit"
}
Enter fullscreen mode Exit fullscreen mode

This pattern is useful for multi-step workflows, where you want to guide the user through a sequence of views.

Route Guards and Conditional Navigation

In many applications, you want to prevent users from accessing certain routes unless certain conditions are met (e.g., they're authenticated). In euv, you can implement route guards using the watch! macro:

watch!(current_route, |route: String| {
    if route == "admin" && !is_authenticated.get() {
        current_route.set("login".to_string());
    }
});
Enter fullscreen mode Exit fullscreen mode

This watcher checks every route change and redirects to the login page if the user tries to access the admin route without being authenticated.

Nested Routes

For complex applications with nested layouts, you can use a nested routing pattern. The parent component manages the top-level route, and child components manage their own sub-routes:

let current_route: Signal<String> = use_signal(|| "home".to_string());

html! {
    div {
        // Global navigation
        nav {
            button {
                onclick: move |_event: Event| {
                    current_route.set("home".to_string());
                }
                "Home"
            }
        }
        // Route-specific content
        div {
            if current_route.get() == "home" {
                h1 { "Home Page" }
            } else if current_route.get() == "dashboard" {
                // Nested routing within the dashboard
                dashboard_component { current_route: current_route }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

URL-Based Routing

For a production-quality routing solution, you'll want to derive the current route from the browser's URL rather than maintaining it solely in a signal. This ensures that:

  1. Users can bookmark specific pages.
  2. The browser's URL always reflects the current view.
  3. Sharing links works correctly.

The approach involves reading the current URL on application startup, setting up listeners for URL changes, and updating the signal accordingly. The signal then drives the UI rendering through the virtual DOM.

Summary

Routing and navigation in euv are built on the framework's reactive signal system and virtual DOM. Key patterns include:

  • Signal-based routing: Use a signal to track the current route and conditionally render views.
  • History API integration: Sync the signal with the browser's URL for proper back/forward support.
  • Component-based routing: Organize routes into reusable components with the #[component] macro.
  • Active route styling: Use the class! macro to highlight the current route in navigation menus.
  • Programmatic navigation: Update the route signal to navigate in response to application events.
  • Route guards: Use the watch! macro to prevent unauthorized access to routes.
  • Nested routes: Manage sub-routes within child components for complex layouts.

By combining these patterns, you can build euv applications with robust, user-friendly navigation that provides a smooth single-page application experience.


Project Code:https://github.com/euv-dev/euv

Top comments (0)