lambda-fairy / maud

:pencil: Compile-time HTML templates for Rust
https://maud.lambda.xyz
Apache License 2.0
2.13k stars 142 forks source link

Add attributes to partials #440

Open Xenira opened 2 months ago

Xenira commented 2 months ago

I know there have already been some issues regarding dynamic attributes, but I want to make this one for my specific case. I am using maud together with htmx and want to create reusable components.

Given this simple example component I want to add different hx-* attributes to the button depending on where I use it.

fn my_fancy_btn(icon_name: &str, title: &str) -> Markup {
    html! {
        button.some.button.classes title=(title) {
            (icon(icon_name))
        }
    }
}

Possible solutions would be an Attribute function parameter similar to the Markup type (In this case a list of attributes would also need to be possible)

fn my_fancy_btn(icon_name: &str, title: &str, some_attr: Attribute) -> Markup {
    html! {
        button.some.button.classes title=(title) (attribute) {
            (icon(icon_name))
        }
    }
}

And/Or an option to add attributes to the root element of the Markup type:

html! {
  (my_fancy_btn("icon", "title")) hx-get="some-url"
  (my_fancy_btn("icon", "delete")) hx-delete="some-url"
}

Syntax for this one may need some tweeking

puetzp commented 1 month ago

Have you considered using a match expression to achieve this? Currently there are two ways to use a match to create a reusable component like this. Let's say Attribute is an enum:

Option 1 (mauds own internal @match)

fn my_fancy_btn(icon_name: &str, title: &str, some_attr: Attribute) -> Markup {
    html! {
        @match some_attr {
            Attribute::HxGet(url) => {
                button.some.button.classes title=(title) hx-get=(url) {
                    (icon(icon_name))
                }
            },
            Attribute::HxDelete(url) => {
                button.some.button.classes title=(title) hx-delete=(url) {
                    (icon(icon_name))
                }
            },
            _ => todo!()
        }
    }
}

Option 2 (rusts pattern matching)

fn my_fancy_btn(icon_name: &str, title: &str, some_attr: Attribute) -> Markup {
    match some_attr {
        Attribute::HxGet(url) => html! {
            button.some.button.classes title=(title) hx-get=(url) {
                (icon(icon_name))
            }
        },
        Attribute::HxDelete(url) => html !{
            button.some.button.classes title=(title) hx-delete=(url) {
                (icon(icon_name))
            }
        },
        _ => todo!()
    }
}

Of course both possibilities are more verbose than the solution you are interested in. I just wanted to offer a workaround for your specific issue in case you were not aware how to resolve it.

Xenira commented 1 month ago

Thanks for the reply. In cases where it just one attribute I did exactly this, but with more attributes it is not rly maintainable.

I also used

hx-get=[get_uri] hx-put=[put_uri] hx-switch=[switch] hx-target=[target] [...]

With a series of optionals in other cases where nested matches would have been way too much.

Still think that the ability to pass attributes directly would be great and would be willing to try implementing that.