feat: add ability to set `node_ref` and pass additional attributes to `<Form/>` and friends (#853)

This commit is contained in:
Greg Johnston 2023-04-14 14:25:52 -04:00 committed by GitHub
parent 5072539917
commit 93da88eac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 121 additions and 80 deletions

View File

@ -42,6 +42,6 @@ impl<'a> IntoIterator for &'a AdditionalAttributes {
type IntoIter = AdditionalAttributesIter<'a>;
fn into_iter(self) -> Self::IntoIter {
todo!()
AdditionalAttributesIter(self.0.iter())
}
}

View File

@ -141,6 +141,8 @@
//! # }
//! ```
mod additional_attributes;
pub use additional_attributes::*;
pub use leptos_config::{self, get_configuration, LeptosOptions};
#[cfg(not(all(
target_arch = "wasm32",
@ -180,7 +182,9 @@ pub use for_loop::*;
pub use show::*;
mod suspense;
pub use suspense::*;
mod text_prop;
mod transition;
pub use text_prop::TextProp;
#[cfg(debug_assertions)]
#[doc(hidden)]
pub use tracing;

43
leptos/src/text_prop.rs Normal file
View File

@ -0,0 +1,43 @@
use std::{fmt::Debug, rc::Rc};
/// Describes a value that is either a static or a reactive string, i.e.,
/// a [String], a [&str], or a reactive `Fn() -> String`.
#[derive(Clone)]
pub struct TextProp(Rc<dyn Fn() -> String>);
impl TextProp {
/// Accesses the current value of the property.
#[inline(always)]
pub fn get(&self) -> String {
(self.0)()
}
}
impl Debug for TextProp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TextProp").finish()
}
}
impl From<String> for TextProp {
fn from(s: String) -> Self {
TextProp(Rc::new(move || s.clone()))
}
}
impl From<&str> for TextProp {
fn from(s: &str) -> Self {
let s = s.to_string();
TextProp(Rc::new(move || s.clone()))
}
}
impl<F> From<F> for TextProp
where
F: Fn() -> String + 'static,
{
#[inline(always)]
fn from(s: F) -> Self {
TextProp(Rc::new(s))
}
}

View File

@ -1,4 +1,3 @@
use crate::{additional_attributes::AdditionalAttributes, TextProp};
use cfg_if::cfg_if;
use leptos::*;
use std::{cell::RefCell, rc::Rc};
@ -20,8 +19,7 @@ impl BodyContext {
.map(|val| format!("class=\"{}\"", val.get()));
let attributes = self.attributes.borrow().as_ref().map(|val| {
val.with(|val| {
val.0
.iter()
val.into_iter()
.map(|(n, v)| format!("{}=\"{}\"", n, v.get()))
.collect::<Vec<_>>()
.join(" ")
@ -102,8 +100,10 @@ pub fn Body(
if let Some(attributes) = attributes {
let attributes = attributes.get();
for (attr_name, attr_value) in attributes.0.into_iter() {
for (attr_name, attr_value) in attributes.into_iter() {
let el = el.clone();
let attr_name = attr_name.to_owned();
let attr_value = attr_value.to_owned();
create_render_effect(cx, move |_|{
let value = attr_value.get();
_ = el.set_attribute(&attr_name, &value);

View File

@ -1,4 +1,3 @@
use crate::{additional_attributes::AdditionalAttributes, TextProp};
use cfg_if::cfg_if;
use leptos::*;
use std::{cell::RefCell, rc::Rc};
@ -32,8 +31,7 @@ impl HtmlContext {
.map(|val| format!("class=\"{}\"", val.get()));
let attributes = self.attributes.borrow().as_ref().map(|val| {
val.with(|val| {
val.0
.iter()
val.into_iter()
.map(|(n, v)| format!("{}=\"{}\"", n, v.get()))
.collect::<Vec<_>>()
.join(" ")
@ -131,8 +129,10 @@ pub fn Html(
if let Some(attributes) = attributes {
let attributes = attributes.get();
for (attr_name, attr_value) in attributes.0.into_iter() {
for (attr_name, attr_value) in attributes.into_iter() {
let el = el.clone();
let attr_name = attr_name.to_owned();
let attr_value = attr_value.to_owned();
create_render_effect(cx, move |_|{
let value = attr_value.get();
_ = el.set_attribute(&attr_name, &value);

View File

@ -59,7 +59,6 @@ use std::{
#[cfg(any(feature = "csr", feature = "hydrate"))]
use wasm_bindgen::{JsCast, UnwrapThrowExt};
mod additional_attributes;
mod body;
mod html;
mod link;
@ -68,7 +67,6 @@ mod script;
mod style;
mod stylesheet;
mod title;
pub use additional_attributes::*;
pub use body::*;
pub use html::*;
pub use link::*;
@ -309,47 +307,6 @@ pub fn generate_head_metadata_separated(cx: Scope) -> (String, String) {
(head, format!("<body{body_meta}>"))
}
/// Describes a value that is either a static or a reactive string, i.e.,
/// a [String], a [&str], or a reactive `Fn() -> String`.
#[derive(Clone)]
pub struct TextProp(Rc<dyn Fn() -> String>);
impl TextProp {
#[inline(always)]
fn get(&self) -> String {
(self.0)()
}
}
impl Debug for TextProp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TextProp").finish()
}
}
impl From<String> for TextProp {
fn from(s: String) -> Self {
TextProp(Rc::new(move || s.clone()))
}
}
impl From<&str> for TextProp {
fn from(s: &str) -> Self {
let s = s.to_string();
TextProp(Rc::new(move || s.clone()))
}
}
impl<F> From<F> for TextProp
where
F: Fn() -> String + 'static,
{
#[inline(always)]
fn from(s: F) -> Self {
TextProp(Rc::new(s))
}
}
#[cfg(debug_assertions)]
pub(crate) fn feature_warning() {
if !cfg!(any(feature = "csr", feature = "hydrate", feature = "ssr")) {

View File

@ -17,7 +17,7 @@ pub struct TitleContext {
impl TitleContext {
/// Converts the title into a string that can be used as the text content of a `<title>` tag.
pub fn as_string(&self) -> Option<String> {
let title = self.text.borrow().as_ref().map(|f| (f.0)());
let title = self.text.borrow().as_ref().map(|f| f.get());
title.map(|title| {
if let Some(formatter) = &*self.formatter.borrow() {
(formatter.0)(title)

View File

@ -1,5 +1,5 @@
use crate::{use_navigate, use_resolved_path, ToHref, Url};
use leptos::*;
use leptos::{html::form, *};
use std::{error::Error, rc::Rc};
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use wasm_bindgen_futures::JsFuture;
@ -42,6 +42,12 @@ pub fn Form<A>(
/// to a form submission.
#[prop(optional)]
on_response: Option<OnResponse>,
/// A [`NodeRef`] in which the `<form>` element should be stored.
#[prop(optional)]
node_ref: Option<NodeRef<html::Form>>,
/// Arbitrary attributes to add to the `<form>`
#[prop(optional, into)]
attributes: Option<MaybeSignal<AdditionalAttributes>>,
/// Component children; should include the HTML of the form elements.
children: Children,
) -> impl IntoView
@ -59,6 +65,8 @@ where
on_response: Option<OnResponse>,
class: Option<Attribute>,
children: Children,
node_ref: Option<NodeRef<html::Form>>,
attributes: Option<MaybeSignal<AdditionalAttributes>>,
) -> HtmlElement<html::Form> {
let action_version = version;
let on_submit = move |ev: web_sys::SubmitEvent| {
@ -152,17 +160,25 @@ where
let method = method.unwrap_or("get");
view! { cx,
<form
method=method
action=move || action.get()
enctype=enctype
on:submit=on_submit
class=class
>
{children(cx)}
</form>
let mut form = form(cx)
.attr("method", method)
.attr("action", move || action.get())
.attr("enctype", enctype)
.on(ev::submit, on_submit)
.attr("class", class)
.child(children(cx));
if let Some(node_ref) = node_ref {
form = form.node_ref(node_ref)
};
if let Some(attributes) = attributes {
let attributes = attributes.get();
for (attr_name, attr_value) in attributes.into_iter() {
let attr_name = attr_name.to_owned();
let attr_value = attr_value.to_owned();
form = form.attr(attr_name, move || attr_value.get());
}
}
form
}
let action = use_resolved_path(cx, move || action.to_href()());
@ -178,6 +194,8 @@ where
on_response,
class,
children,
node_ref,
attributes,
)
}
@ -197,6 +215,12 @@ pub fn ActionForm<I, O>(
/// A signal that will be set if the form submission ends in an error.
#[prop(optional)]
error: Option<RwSignal<Option<Box<dyn Error>>>>,
/// A [`NodeRef`] in which the `<form>` element should be stored.
#[prop(optional)]
node_ref: Option<NodeRef<html::Form>>,
/// Arbitrary attributes to add to the `<form>`
#[prop(optional, into)]
attributes: Option<MaybeSignal<AdditionalAttributes>>,
/// Component children; should include the HTML of the form elements.
children: Children,
) -> impl IntoView
@ -286,19 +310,18 @@ where
});
});
let class = class.map(|bx| bx.into_attribute_boxed(cx));
let props = FormProps::builder()
let mut props = FormProps::builder()
.action(action_url)
.version(version)
.on_form_data(on_form_data)
.on_response(on_response)
.method("post")
.class(class)
.children(children);
let props = if let Some(error) = error {
props.error(error).build()
} else {
props.build()
};
.children(children)
.build();
props.error = error;
props.node_ref = node_ref;
props.attributes = attributes;
Form(cx, props)
}
@ -318,6 +341,12 @@ pub fn MultiActionForm<I, O>(
/// A signal that will be set if the form submission ends in an error.
#[prop(optional)]
error: Option<RwSignal<Option<Box<dyn Error>>>>,
/// A [`NodeRef`] in which the `<form>` element should be stored.
#[prop(optional)]
node_ref: Option<NodeRef<html::Form>>,
/// Arbitrary attributes to add to the `<form>`
#[prop(optional, into)]
attributes: Option<MaybeSignal<AdditionalAttributes>>,
/// Component children; should include the HTML of the form elements.
children: Children,
) -> impl IntoView
@ -360,16 +389,24 @@ where
};
let class = class.map(|bx| bx.into_attribute_boxed(cx));
view! { cx,
<form
method="POST"
action=action
class=class
on:submit=on_submit
>
{children(cx)}
</form>
let mut form = form(cx)
.attr("method", "POST")
.attr("action", action)
.on(ev::submit, on_submit)
.attr("class", class)
.child(children(cx));
if let Some(node_ref) = node_ref {
form = form.node_ref(node_ref)
};
if let Some(attributes) = attributes {
let attributes = attributes.get();
for (attr_name, attr_value) in attributes.into_iter() {
let attr_name = attr_name.to_owned();
let attr_value = attr_value.to_owned();
form = form.attr(attr_name, move || attr_value.get());
}
}
form
}
fn extract_form_attributes(