feat: add ability to set `node_ref` and pass additional attributes to `<Form/>` and friends (#853)
This commit is contained in:
parent
5072539917
commit
93da88eac0
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue