Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic attributes ideas #240

Open
brecert opened this issue Nov 26, 2020 · 11 comments
Open

dynamic attributes ideas #240

brecert opened this issue Nov 26, 2020 · 11 comments

Comments

@brecert
Copy link

brecert commented Nov 26, 2020

Currently only the value of an attribute can have a dynamic value, having the ability to not just set the value but also the attribute name/type would be useful.

What I mean when talking about dynamic attributes is shown in the below examples.
As far as I know, other than the first, none of these are really possible at the moment.

  • a way to have the attribute value able to be modified (exists)
    let value = "value";
    html! { span title=(value) }
    // <span title="value">
  • a way to have the attribute name able to be modified
    let attribute_name = "title";
    html! { span (attribute_name)="value"
    // <span title="value">
  • a way to enable/disable adding said attribute with types that implement Try (partially exists as of v0.23.0 through toggles)
    html! { span (Some(attribute_name))?="value" } 
    // <span title="value">
    
    html! { span (None)?="value"  }
    // <span>
  • a way to destructure attributes, through an Iterator that iterates with a tuple that matches (K, V)
    // maybe with hashmap or someth
    let attributes = HashMap::new();
    attributes.insert("title", "value");
    attributes.insert("alt", "a value");
    html! { img (..)=(attributes)  }
    // <img title="value" alt="a value">
@lambda-fairy
Copy link
Owner

Thanks for the feedback!

I've been holding off on making the attribute name dynamic, because it complicates context-aware escaping (#181).

Do you have a concrete use case for these features? That'll help us shape what they'll look like.

@brecert
Copy link
Author

brecert commented Nov 29, 2020

I'm currently writing a markup language where certain properties are represented by html attributes.

Conditionally adding an attribute is useful for me as certain attributes behave differently between not existing, being a boolean, and having an empty string. For example, no href attribute, and an empty string href attribute behave different and are different in styling.

It's also beneficial to me to produce clean templates and html.

Destructuring is useful for me because the language and generation is meant to be extendable, so having a way to add attributes for the extended parts dynamically is important.

@zopieux
Copy link
Contributor

zopieux commented Jun 22, 2021

@lambda-fairy One concrete (and I believe very common?) use-case that today I don't see how to express in Maud is the dynamic presence of attributes, which is suggestion 3 from OP, ie. either A. span attr=(value) { } or B. span { }, where say value is Option<String>. Currently I need to either always have attr with an empty/non-empty value, which as you noted is different from not setting attr, which is what I want. Sure, I could have a verbose @if or @match and duplicate the entire element declaration, but that looks sub-optimal.

I naively tried to apply toggle syntax but obviously it failed to compile, as documented: span attr=(value.unwrap())[value.is_some()] { }

Am I missing a magic syntax trick here, or is this actually not possible without some refactoring/duplicating? Thanks for your help, and since this is my first comment here, thanks for Maud, it's amazing!

@Mathspy
Copy link

Mathspy commented Nov 19, 2021

By the way case 3 (the one I came to this issue for) has been addressed with v0.23.0 just 9 days ago, I feel super blessed by @lambda-fairy right now

Thank you for your amazing work ❤️

@lambda-fairy
Copy link
Owner

I'm honored, but please thank @zopieux instead – he did most of the work 😄

@Mathspy
Copy link

Mathspy commented Nov 20, 2021

I'm honored, but please thank @zopieux instead – he did most of the work 😄

Oh shoot sorry I should have actually clicked the PR number in the changelog, sorry!
Thank you BOTH very much <3

@IcyDefiance
Copy link

IcyDefiance commented Jul 24, 2022

Do you have a concrete use case for these features? That'll help us shape what they'll look like.

I have one for OP's example 4. I like to write my html in the form of components that render html, and I want to allow adding attributes to at least the root tag that the component renders. It would be great if I could write a material button component like this (stealing syntax from OP):

pub fn button(label: &str) -> Button {
    Button { label, ..Default::default() }
}

#[derive(Default)]
pub struct Button<'a> {
    label: &'a str,
    outlined: bool,
    raised: bool,
    attr: Attr,
}
impl<'a> Button<'a> {
    pub fn outlined(mut self) -> Self {
        self.outlined = true;
        self
    }

    pub fn raised(mut self) -> Self {
        self.raised = true;
        self
    }

    pub fn attr(mut self, attr: impl Into<Attr>) -> Self {
        self.attr = self.attr.merge(attr.into());
        self
    }
}
impl<'a> Render for Button<'a> {
    fn render(&self) -> Markup {
        let mut attr = self.attr.clone().add_class("mdc-button");
        if self.outlined {
            attr.add_class("mdc-button mdc-button--outlined");
        } else if self.raised {
            attr.add_class("mdc-button mdc-button--raised");
        }

        html! {
            button (..)=(attr) {
                span .mdc-button__ripple {}
                span .mdc-button__focus-ring {}
                span .mdc-button__label { (self.label) }
            }
        }
    }
}

Which would allow me to use it with Stimulus like so:

html! { (button("Add Item").attr(hashmap! { "type" => "button", "data-action" => "form-collection#addItem" })) }

Currently I would need to add specific support for "data-action" to the button component, which is unfortunate, because I don't think the button component should know or care whether I'm using Stimulus, or an onclick event, or something else entirely.

@Arteneko
Copy link

Arteneko commented Oct 7, 2022

Hi! Another use-case I see (and have) for the 4th option is similar to Icy's, which is for dynamic form building: I'd like to provide the ability to add custom attributes at will without having to re-make the macro or provide every attribute as value.

An example would be here.

fn input(id: &str, input_type: &str, value: String, required: bool, args: Option<Vec<(k, v)>>) -> Markup {
    html! {
        input #(id) name=(id) type=(input_type) value=(value) required[required] (..)=[args];
    }
}

This example would also benefit from the "optional" logic as no expansion would be done if args would eval to a None.

I'm sadly not skilled enough in macros and such to be able to try to make a POC for this...

@fdietze
Copy link

fdietze commented Apr 18, 2023

Hi! I just stumbled upon this one. I want to use this example from Alpine.js:

<input type="text" x-model.fill="foo">

https://alpinejs.dev/directives/model#fill

Is there a workaround, so I can use that option already right now?

Syntax-wise, I'm imagining something like this:

input type="text" "x-model.fill"="foo" {}

@fraschm1998
Copy link

Is there a workaround, so I can use that option already right now?

This is fixed in this PR: #393

@rben01
Copy link

rben01 commented Feb 7, 2024

I'd also be interested in evaluated tags, e.g.,

let tag = "p";
html! { (tag) style="whatever" }

This would be useful when reading tags from user input (e.g., building an HTML doc from JSON specifying the DOM).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants