diff --git a/Cargo.lock b/Cargo.lock index a1b0f5b..8c1e707 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1360,6 +1360,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "slhx" version = "0.1.0" dependencies = [ + "hemplate", "slhx-core", "slhx-derive", ] diff --git a/examples/kanban/src/main.rs b/examples/kanban/src/main.rs index 4a2afdd..06e7205 100644 --- a/examples/kanban/src/main.rs +++ b/examples/kanban/src/main.rs @@ -4,7 +4,7 @@ use axum::routing::get; use axum::Router; use futures_util::{stream, StreamExt}; use hemplate::Hemplate; -use slhx::{Handle, IntoEffect, SafeHtml}; +use slhx::{Handle, IntoEffect, RenderSlotExt, SafeHtml}; use slhx_axum::{runtime_js, sse, DispatchRejection, EffectResponse, HandlerRegistry, InteractionForm, PageRequest}; use slhx_kanban_example::ui; use std::collections::BTreeMap; @@ -131,13 +131,13 @@ async fn interact( // req: push/001 req: push/003 req: examples/001 async fn events(Query(params): Query>) -> impl IntoResponse { if params.contains_key("once") { - let effect = ui::board::slots::presence.html(render_presence(1)); + let effect = ui::board::slots::presence.render_view(&Presence { count: 1 }); return sse(stream::iter([Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT))]).boxed()); } let batches = stream::unfold(1_u64, |count| async move { tokio::time::sleep(Duration::from_secs(4)).await; - let effect = ui::board::slots::presence.html(render_presence(count)); + let effect = ui::board::slots::presence.render_view(&Presence { count }); Some((Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT)), count + 1)) }) .boxed(); @@ -199,7 +199,7 @@ fn registry(state: Arc) -> HandlerRegistry { fn board_effects(board: &BoardState, notice: &'static str) -> impl IntoEffect { ( - ui::board::slots::board.html(render_board(board)), + ui::board::slots::board.render_view(&board_view(board)), ui::board::slots::notice.text(notice), ui::board::forms::create_card.clear("title"), ) @@ -269,9 +269,9 @@ fn render_options() -> SafeHtml { }) } -fn render_board(board: &BoardState) -> SafeHtml { +fn board_view(board: &BoardState) -> BoardColumns { // req: html_safety/002 req: view/001 - render_html(&BoardColumns { + BoardColumns { columns: COLUMNS .iter() .enumerate() @@ -285,7 +285,12 @@ fn render_board(board: &BoardState) -> SafeHtml { .collect(), }) .collect(), - }) + } +} + +fn render_board(board: &BoardState) -> SafeHtml { + // req: html_safety/002 req: view/001 + render_html(&board_view(board)) } fn render_card(card: &Card) -> BoardCard { @@ -300,11 +305,6 @@ fn render_card(card: &Card) -> BoardCard { } } -fn render_presence(count: u64) -> SafeHtml { - // req: html_safety/002 req: view/001 - render_html(&Presence { count }) -} - fn render_html(template: &impl Hemplate) -> SafeHtml { SafeHtml::trusted(render_template(template)) } @@ -368,7 +368,7 @@ mod tests { // req: html_safety/002 req: view/001 req: test/005 #[test] fn presence_payload_is_rendered_by_a_hemplate_view() { - let html = render_presence(7); + let html = render_html(&Presence { count: 7 }); let document = Html::parse_fragment(html.as_str()); assert_eq!(document.select(&selector("span.presence")).count(), 2); assert_eq!( diff --git a/slhx/Cargo.toml b/slhx/Cargo.toml index 97d561c..5d77718 100644 --- a/slhx/Cargo.toml +++ b/slhx/Cargo.toml @@ -7,5 +7,6 @@ edition.workspace = true path = "src/lib.rs" [dependencies] +hemplate = { path = "../../hemplate/hemplate" } slhx-core = { path = "../slhx-core" } slhx-derive = { path = "../slhx-derive" } diff --git a/slhx/src/lib.rs b/slhx/src/lib.rs index 6df598f..71dffa9 100644 --- a/slhx/src/lib.rs +++ b/slhx/src/lib.rs @@ -6,11 +6,52 @@ pub use slhx_core::*; pub use slhx_derive::{app, component, form, handler, surface}; +pub fn render_html(view: &impl hemplate::Hemplate) -> SafeHtml { + let mut html = String::new(); + view.render_into(&mut html) + .expect("hemplate view renders into slhx effect payload"); + SafeHtml::trusted(html) +} + +pub trait RenderSlotExt { + fn render_view(self, view: &impl hemplate::Hemplate) -> Effect; +} + +impl RenderSlotExt for Slot { + fn render_view(self, view: &impl hemplate::Hemplate) -> Effect { + self.html(render_html(view)) + } +} + +pub trait RenderKeyedSlotExt { + fn append_view(self, key: K, view: &impl hemplate::Hemplate) -> Effect; + fn prepend_view(self, key: K, view: &impl hemplate::Hemplate) -> Effect; + fn replace_view(self, key: K, view: &impl hemplate::Hemplate) -> Effect; +} + +impl RenderKeyedSlotExt for KeyedSlot +where + K: ToString, +{ + fn append_view(self, key: K, view: &impl hemplate::Hemplate) -> Effect { + self.append_html(key, render_html(view)) + } + + fn prepend_view(self, key: K, view: &impl hemplate::Hemplate) -> Effect { + self.prepend_html(key, render_html(view)) + } + + fn replace_view(self, key: K, view: &impl hemplate::Hemplate) -> Effect { + self.replace_html(key, render_html(view)) + } +} + pub mod prelude { pub use slhx_core::{ navigate, push, redirect, replace, Atom, BuildFingerprint, ComponentRef, CssClass, CssClasses, Effect, EventName, Form, FormModel, FormValue, Handle, IntoEffect, KeyedSlot, Slot, }; + pub use crate::{render_html, RenderKeyedSlotExt, RenderSlotExt}; pub use slhx_derive::{app, component, form, handler, surface}; }