From 994debea3fda3ddfd9f042d17fc148a14c55bbed Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 25 Nov 2022 15:38:46 -0500 Subject: [PATCH] Change `_ref` attribute to use `NodeRef` type --- benchmarks/src/todomvc/leptos.rs | 2 +- .../book/project/ch03_building_ui/src/main.rs | 2 +- examples/todomvc/src/lib.rs | 5 +- leptos_dom/Cargo.toml | 3 +- leptos_dom/src/lib.rs | 2 + leptos_dom/src/node_ref.rs | 75 +++++++++++++++++++ leptos_macro/src/lib.rs | 4 +- leptos_macro/src/view.rs | 25 +------ 8 files changed, 86 insertions(+), 32 deletions(-) create mode 100644 leptos_dom/src/node_ref.rs diff --git a/benchmarks/src/todomvc/leptos.rs b/benchmarks/src/todomvc/leptos.rs index 725205e4b..661c51b08 100644 --- a/benchmarks/src/todomvc/leptos.rs +++ b/benchmarks/src/todomvc/leptos.rs @@ -222,7 +222,7 @@ pub fn TodoMVC(cx: Scope, todos: Todos) -> Element { pub fn Todo(cx: Scope, todo: Todo) -> Element { let (editing, set_editing) = create_signal(cx, false); let set_todos = use_context::>(cx).unwrap(); - let input: Element; + let input = NodeRef::new(cx); let save = move |value: &str| { let value = value.trim(); diff --git a/docs/book/project/ch03_building_ui/src/main.rs b/docs/book/project/ch03_building_ui/src/main.rs index e066d0b2c..6d82ec315 100644 --- a/docs/book/project/ch03_building_ui/src/main.rs +++ b/docs/book/project/ch03_building_ui/src/main.rs @@ -4,7 +4,7 @@ fn main() { mount_to_body(|cx| { let name = "gbj"; let userid = 0; - let _input_element: Element; + let _input_element = NodeRef::new(cx); view! { cx, diff --git a/examples/todomvc/src/lib.rs b/examples/todomvc/src/lib.rs index c0b567efe..abbd55bf4 100644 --- a/examples/todomvc/src/lib.rs +++ b/examples/todomvc/src/lib.rs @@ -262,7 +262,7 @@ pub fn Todo(cx: Scope, todo: Todo) -> Element { let set_todos = use_context::>(cx).unwrap(); // this will be filled by _ref=input below - let input: Element; + let input = NodeRef::new(cx); let save = move |value: &str| { let value = value.trim(); @@ -295,8 +295,7 @@ pub fn Todo(cx: Scope, todo: Todo) -> Element { set_editing(true); // guard against the fact that in SSR mode, that ref is actually to a String - #[cfg(any(feature = "csr", feature = "hydrate"))] - if let Some(input) = input.dyn_ref::() { + if let Some(input) = input.get().dyn_ref::() { input.focus(); } }> diff --git a/leptos_dom/Cargo.toml b/leptos_dom/Cargo.toml index 24dee8571..1d59fd08d 100644 --- a/leptos_dom/Cargo.toml +++ b/leptos_dom/Cargo.toml @@ -66,14 +66,13 @@ features = [ "AnimationEvent", "PointerEvent", "TouchEvent", - "TransitionEvent" + "TransitionEvent", ] [dev-dependencies] leptos = { path = "../leptos", default-features = false, version = "0.0" } leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0" } - [features] csr = ["leptos_reactive/csr", "leptos_macro/csr", "leptos/csr"] hydrate = ["leptos_reactive/hydrate", "leptos_macro/hydrate", "leptos/hydrate"] diff --git a/leptos_dom/src/lib.rs b/leptos_dom/src/lib.rs index 26120dc36..86fc9b2bb 100644 --- a/leptos_dom/src/lib.rs +++ b/leptos_dom/src/lib.rs @@ -22,6 +22,7 @@ mod class; mod event_delegation; mod logging; mod mount; +mod node_ref; mod operations; mod property; @@ -80,6 +81,7 @@ pub use child::*; pub use class::*; pub use logging::*; pub use mount::*; +pub use node_ref::*; pub use operations::*; pub use property::*; diff --git a/leptos_dom/src/node_ref.rs b/leptos_dom/src/node_ref.rs new file mode 100644 index 000000000..23c3a4a5c --- /dev/null +++ b/leptos_dom/src/node_ref.rs @@ -0,0 +1,75 @@ +use leptos_reactive::{ + create_rw_signal, RwSignal, Scope, UntrackedGettableSignal, UntrackedSettableSignal, +}; + +/// Contains a shared reference to a DOM node creating while using the [view](leptos::view) +/// macro to create your UI. +/// +/// ``` +/// # use leptos::*; +/// #[component] +/// pub fn MyComponent(cx: Scope) -> Element { +/// let input_ref = NodeRef::new(cx); +/// +/// let on_click = move |_| { +/// let node = input_ref +/// .get() +/// .expect("input_ref should be loaded by now") +/// .unchecked_into::(); +/// log!("value is {:?}", node.value()) +/// }; +/// +/// view! { +/// cx, +///
+/// // `node_ref` loads the input +/// +/// // the button consumes it +/// +///
+/// } +/// ``` +#[derive(Copy, Clone, PartialEq)] +pub struct NodeRef(RwSignal>); + +impl NodeRef { + /// Creates an empty reference. + pub fn new(cx: Scope) -> Self { + Self(create_rw_signal(cx, None)) + } + + /// Gets the element that is currently stored in the reference. + pub fn get(&self) -> Option { + self.0.get_untracked() + } + + #[doc(hidden)] + /// Loads an element into the reference/ + pub fn load(&self, node: &web_sys::Element) { + self.0.set_untracked(Some(node.clone())) + } +} + +cfg_if::cfg_if! { + if #[cfg(not(feature = "stable"))] { + impl FnOnce<()> for NodeRef { + type Output = Option; + + extern "rust-call" fn call_once(self, _args: ()) -> Self::Output { + self.get() + } + } + + impl FnMut<()> for NodeRef { + extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output { + self.get() + } + } + + impl Fn<()> for RwSignal { + extern "rust-call" fn call(&self, _args: ()) -> Self::Output { + self.get() + } + } + } +} diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index 7d0cdd9e4..8855fe34e 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -179,13 +179,13 @@ mod server; /// # }); /// ``` /// -/// 8. You can use the `_ref` attribute to store a reference to its DOM element in a variable to use later. +/// 8. You can use the `_ref` attribute to store a reference to its DOM element in a [NodeRef](leptos::NodeRef) to use later. /// ```rust /// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast; /// # run_scope(create_runtime(), |cx| { /// # if !cfg!(any(feature = "csr", feature = "hydrate")) { /// let (value, set_value) = create_signal(cx, 0); -/// let my_input: Element; +/// let my_input = NodeRef::new(cx); /// view! { cx, } /// // `my_input` now contains an `Element` that we can use anywhere /// # ; diff --git a/leptos_macro/src/view.rs b/leptos_macro/src/view.rs index 3de95801a..516c4da5b 100644 --- a/leptos_macro/src/view.rs +++ b/leptos_macro/src/view.rs @@ -537,34 +537,13 @@ fn attr_to_tokens( // refs if name == "ref" { - let ident = match &node.value { - Some(expr) => { - if let Some(ident) = expr_to_ident(expr) { - quote_spanned! { span => #ident } - } else { - quote_spanned! { span => compile_error!("'ref' needs to be passed a variable name") } - } - } - None => { - quote_spanned! { span => compile_error!("'ref' needs to be passed a variable name") } - } - }; - - if mode == Mode::Ssr { - // fake the initialization; should only be used in effects or event handlers, which will never run on the server - // but if we don't initialize it, the compiler will complain - navigations.push(quote_spanned! { - span => #ident = String::new(); - }); - } else { + if mode != Mode::Ssr { expressions.push(match &node.value { Some(expr) => { if let Some(ident) = expr_to_ident(expr) { quote_spanned! { span => - // we can't pass by reference because the _el won't live long enough (it's dropped when template returns) - // so we clone here; this will be unnecessary if it's the last attribute, but very necessary otherwise - #ident = #el_id.clone().unchecked_into::(); + #ident.load(#el_id.unchecked_ref::()); } } else { panic!("'ref' needs to be passed a variable name")