Skip to content

Commit

Permalink
Change _ref attribute to use NodeRef type
Browse files Browse the repository at this point in the history
  • Loading branch information
gbj committed Nov 25, 2022
1 parent 5399f54 commit 994debe
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 32 deletions.
2 changes: 1 addition & 1 deletion benchmarks/src/todomvc/leptos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<WriteSignal<Todos>>(cx).unwrap();
let input: Element;
let input = NodeRef::new(cx);

let save = move |value: &str| {
let value = value.trim();
Expand Down
2 changes: 1 addition & 1 deletion docs/book/project/ch03_building_ui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 2 additions & 3 deletions examples/todomvc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ pub fn Todo(cx: Scope, todo: Todo) -> Element {
let set_todos = use_context::<WriteSignal<Todos>>(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();
Expand Down Expand Up @@ -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::<HtmlInputElement>() {
if let Some(input) = input.get().dyn_ref::<HtmlInputElement>() {
input.focus();
}
}>
Expand Down
3 changes: 1 addition & 2 deletions leptos_dom/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
2 changes: 2 additions & 0 deletions leptos_dom/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod class;
mod event_delegation;
mod logging;
mod mount;
mod node_ref;
mod operations;
mod property;

Expand Down Expand Up @@ -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::*;

Expand Down
75 changes: 75 additions & 0 deletions leptos_dom/src/node_ref.rs
Original file line number Diff line number Diff line change
@@ -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::<web_sys::HtmlInputElement>();
/// log!("value is {:?}", node.value())
/// };
///
/// view! {
/// cx,
/// <div>
/// // `node_ref` loads the input
/// <input _ref=input_ref type="text"/>
/// // the button consumes it
/// <button on:click=on_click>"Click me"</button>
/// </div>
/// }
/// ```
#[derive(Copy, Clone, PartialEq)]
pub struct NodeRef(RwSignal<Option<web_sys::Element>>);

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<web_sys::Element> {
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<Element>;

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()
}
}
}
}
4 changes: 2 additions & 2 deletions leptos_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, <input type="text" _ref=my_input/> }
/// // `my_input` now contains an `Element` that we can use anywhere
/// # ;
Expand Down
25 changes: 2 additions & 23 deletions leptos_macro/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<web_sys::Element>();
#ident.load(#el_id.unchecked_ref::<web_sys::Element>());
}
} else {
panic!("'ref' needs to be passed a variable name")
Expand Down

0 comments on commit 994debe

Please sign in to comment.