Controllers
A controller handles the business logic for a given route. If you have a customers/new route that
needs custom logic such as an event handler, you'll want to create a customers/new controller to
hold the event handling functions as well as any potential tracked values.
Defining a Controller
$ auric generate controller customers/new
created src/controllers/customers/new_controller.rs
updated src/controllers/customers/mod.rs
updated src/controllers/mod.rs
==> make
Compiling auric-build v0.2.2 (/Users/davidr/workspaces/auric-spa/auric/crates/auric-build)
Compiling auric-derive v0.2.2 (/Users/davidr/workspaces/auric-spa/auric/crates/auric-derive)
Compiling auric-runtime v0.2.2 (/Users/davidr/workspaces/auric-spa/auric/crates/auric-runtime)
Compiling my-app v0.1.0 (/Users/davidr/workspaces/my-app)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.30s
Here's what the new controller looks like in its initial state:
use anyhow::Result;
use auric_runtime::{AuricController, Context, Controller, async_trait, controller};
#[derive(AuricController, Default)]
pub struct CustomersNewController {}
#[async_trait(?Send)]
impl Controller for CustomersNewController {
fn init(&mut self, _context: &dyn Context) -> Result<()> {
Ok(())
}
}
#[controller]
impl CustomersNewController {}
#[cfg(test)]
mod tests {
use super::CustomersNewController;
#[test]
fn can_construct() {
let _ = CustomersNewController::default();
}
}
Adding a tracked value
To add a tracked value, add it to your controller struct, and decorate it with the #[tracked] derive macro,
like this:
#[derive(AuricController, Default)]
pub struct CustomersNewController {
#[tracked]
counter: u16,
}
When you build your project, a corresponding Leptos signal will be generated into the view that corresponds with your controller:
// src/generated/views/customers_new.rs
let (counter, set_counter) = signal::<u16>(Default::default()); // tracked from controller
view! {
...
Handling Events
This example shows how to write an event handler to increment a tracked property, then display the latest value.
Let's start with the template:
<h2>Counter Example</h2>
Counter: {{this.counter}}
<button class="btn btn-default" {{on "click" this.increment}}>
Increment
</button>
Next, we need to add an event handler function to our controller:
#[controller]
impl CustomersNewController {
pub fn increment(&mut self) {
self.counter += 1;
}
}
That's it! Let's look at the Leptos view that was generated:
// This file is @generated by auric-build.
use auric_runtime::{*, components::*, routing::*};
use leptos::prelude::*;
use leptos_router::{components::*, hooks::use_params_map};
use std::sync::Arc;
#[component]
pub fn CustomersNewController() -> impl IntoView {
let config = use_context::<serde_json::Value>().expect("Config not found");
let store = use_context::<Arc<auric_runtime::data::Store>>().expect("Store not found");
let ctx = InitContext::new(config.clone(), store.clone());
let mut controller = crate::controllers::customers::new_controller::CustomersNewController::default();
controller.init(&ctx).expect("failed initializing controller");
let maybe_model = LocalResource::new(async move || {
let router = use_context::<Arc<Router>>().expect("Router not found");
let params = move || use_params_map().get();
router.set_params_for("customers.new", params().into());
router.model_for("customers.new").await
});
let model = Signal::derive(move || maybe_model.get().and_then(Result::ok));
let model_error = move || maybe_model.get().map(|result| result.map(|_| ()));
let (counter, set_counter) = signal::<u16>(Default::default()); // tracked from controller
view! {
<h2>"Counter Example"</h2>
"Counter: "
{move || counter.get()}
<button
class="btn btn-default"
on:click=move |_| {
controller.set_counter(&counter);
controller.increment();
set_counter.set(*controller.get_counter());
}
>
"Increment"
</button>
}
}
Controllers provide the means by which you can keep your templates lean and manageable, thanks to the power of HTMLbars.
With Auric, also notice that the path references within mustache expressions, such as
the {{this.counter}} tracked variable reference or the {{on "click" this.increment}} event handler function,
are statically bound to real Rust variables and functions at compile time.
Any mustache reference to a non-existent field, such as {{this.doesNotExist}}, is a compile error.
Auric leverages the Rust compiler to fully validate your templates.