From cde262c2a86e7e5d9b3808f4a08ab71aa46561d2 Mon Sep 17 00:00:00 2001 From: Jonathan Cornaz Date: Mon, 16 Dec 2024 20:29:52 +0100 Subject: [PATCH] `attr::none` fix #12 --- CHANGELOG.md | 5 +++++ src/attr.rs | 17 +++++++++++++++++ src/lib.rs | 25 +++++++++++++++++++++---- tests/render_spec.rs | 2 ++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78cfb15..015b174 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added + +* `attr::none()` for conditional rendering of an attribute +* implement `Default` for `Element` and `Attribute` + ## [1.4.0] - 2024-12-13 diff --git a/src/attr.rs b/src/attr.rs index bb3e9cf..c02ea5c 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -13,6 +13,23 @@ impl>> From<(&'static str, T)> for Attribute { } } +/// Do not render any attribute. Useful for conditional rendering. +/// +/// # Example +/// +/// ``` +/// use fun_html::{attr, elt}; +/// +/// let name: Option<&str> = None; +/// let element = elt::div([ +/// // Note, `unwrap_or_default()` would have the same effect here +/// name.map(attr::name).unwrap_or(attr::none()), +/// ], []); +/// ``` +pub fn none() -> Attribute { + Attribute(crate::AttributeInner::None) +} + /// `id` attribute pub fn id(id: impl Into>) -> Attribute { Attribute::new("id", id) diff --git a/src/lib.rs b/src/lib.rs index f69cc15..aa1ae03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,7 @@ pub struct Document(Element); /// /// assert_eq!(element.to_string(), "
"); /// ``` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Element(ElementInner); #[derive(Debug, Clone)] @@ -116,6 +116,12 @@ enum ElementInner { None, } +impl Default for ElementInner { + fn default() -> Self { + Self::None + } +} + /// An attribute /// /// It can be created via [`Self::new`], [`Self::new_flag`] @@ -132,7 +138,7 @@ enum ElementInner { /// r#"id="foo""#, /// ) /// ``` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Attribute(AttributeInner); #[derive(Debug, Clone)] @@ -140,6 +146,13 @@ enum AttributeInner { KeyValue(Cow<'static, str>, Cow<'static, str>), KeyValueInt(Cow<'static, str>, i32), Flag(Cow<'static, str>), + None, +} + +impl Default for AttributeInner { + fn default() -> Self { + Self::None + } } impl Default for Document { @@ -237,9 +250,12 @@ impl Display for Element { fn write_attributes( f: &mut alloc::fmt::Formatter<'_>, - attributes: &Vec, + attributes: &[Attribute], ) -> Result<(), alloc::fmt::Error> { - for attribute in attributes { + for attribute in attributes + .iter() + .filter(|a| !matches!(&a.0, AttributeInner::None)) + { write!(f, " {attribute}")?; } Ok(()) @@ -260,6 +276,7 @@ impl Display for Attribute { write!(f, "{key}=\"{value}\"") } AttributeInner::Flag(key) => write!(f, "{key}"), + AttributeInner::None => Ok(()), } } } diff --git a/tests/render_spec.rs b/tests/render_spec.rs index 53acb4a..cd2afb7 100644 --- a/tests/render_spec.rs +++ b/tests/render_spec.rs @@ -29,6 +29,7 @@ fn should_render_html_document() { } #[rstest] +#[case(attr::none(), "")] #[case(("foo", "bar").into(), "foo=\"bar\"")] #[case(("x-on:keyup.enter", "doSomething").into(), "x-on:keyup.enter=\"doSomething\"")] #[case(("@keyup.enter", "doSomething").into(), "@keyup.enter=\"doSomething\"")] @@ -98,6 +99,7 @@ fn should_render_attribute(#[case] attr: Attribute, #[case] expected: &str) { #[rstest] #[case(elt::none(), "")] #[case([elt::div([], []), elt::div([], [])].into(), "
")] +#[case(elt::div([attr::none(), attr::none()], []), "
")] #[case(elt::div([], elt::div([], [])), "
")] #[case(elt::text("hello"), "hello")] #[case(elt::text("hello".to_string()), "hello")]