-
-
Notifications
You must be signed in to change notification settings - Fork 277
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
Spread attributes #237
Spread attributes #237
Conversation
I'm not as familiar with the code as you are. But you might want to make sure this doesn't clash with It looks like the parser specifically looks for |
This is not an issue, the parser is divided into smaller parsers, and each smaller parser has a higher priority than the other. So here we have the So there isn't really any conflict. Moreover, it's important to understand that the However, for safety, in case the order changes one day, I will modify my unit tests. ^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome PR!
parser/v2/elementparser.go
Outdated
} | ||
|
||
// Expression. | ||
if attr.Expression, ok, err = exp.Parse(pi); err != nil || !ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential use case for a parser.Until
, will prevent you from having to trim as below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may not work in this case actually. But worth thinking about!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential use case for a
parser.Until
, will prevent you from having to trim as below.
This can't work here because the expression parser grab the three dots.
The only thing I can do is try to change the "expression parser" but the issue is if I remove the ellipses from the value.
I can't after in the spreadAttributesParser
check if In my expression I got my ellipses since I need to look into the past.
I think for now the simplest thing is as it its now. (Note, I try things before saying that)
It looks like due to type Attribute struct {
Key, Value string
}
type Attributes []Attribute The usage wouldn't be too different if it was moved to a slice:
|
Yes, I was going to say that I had forgotten that Go randomly randomizes the maps and therefore that it breaks the tests. So in HTML it's not a problem but it's annoying. I personally think it would be better to don't change the signature and just make the runtime insert attribute by alphabetical order. |
I had already written this for a personal project: sortedAttributes := make([]string, 0, len(attributes))
for k := range attributes {
sortedAttributes = append(sortedAttributes, k)
}
sort.Strings(sortedAttributes)
for _, k := range sortedAttributes {
v := attributes[k]
} I don't think this will have such a negative impact on performance and at least we are sure of the order. |
Also now I think using slice could make some runtime mistake if someone create a slice with only the key and not a value or strange case when someone create an attribute with a key, a value and something else. component := BasicTemplate(templ.Attributes{
{"id"},
{"hx-get", "/page", "third"},
}) Map prevent those case. |
@gungun974 That would not be the case: |
Yep, don't think performance would be too much of an issue either, these maps should be relatively small. |
Oh you make me learn something. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all looks good, will need @a-h to take a look and decide if this is the API we want to go with.
My only reservation is that the ...
syntax in Go is for slices/arrays rather than maps, but maybe that is okay.
I can understand But to argue with this syntax. Templ already use
If you (@joerdav) or @a-h want to change the way the parser represent that new attribute type or the way the generator generate. I'm willing of course to improve this PR ^^ |
Sorry for not merging this. Still not sure I've thought through all of the consequences, and want to get the design right. I was thinking about how you would send things like optional attributes down the chain. There are also boolean attributes that don't have a key/value pair, e.g. I thought the design might need to be more than a package spread_attributes
type Attribute struct {
Name string
Value string
OmitValue bool // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
Visible func(ctx context.Context) bool
}
templ AppButton(attrs templ.Attributes) {
<button { attrs... } />
}
type AttrOpt func(*templ.Attributes)
func ConditionalAttr(condition bool, name, value string) AttrOpt {
return func(a *templ.Attributes) {
a[name] = value
a[name].Visible = func(ctx context.Context) bool { return condition }
}
}
func Attr(name, value string) AttrOpt {
return func(a *templ.Attributes) {
a[name] = value
}
}
func BooleanAttr(name string) AttrOpt {
return func(a *templ.Attributes) {
a[name].OmitValue = true
}
}
func Attributes(opts ...AttrOpt) templ.Attributes {
a := make(templ.Attributes)
for _, opt := range opts {
opt(a)
}
return a
}
templ Consume() {
@AppButton(Attributes(BooleanAttr("noshade"), ConditionalAttr(isLoggedIn, "class", "authenticated"), Attr("data-id": "my-custom-id"))) {
Click
}
} Or am I overthinking this one? |
So it's complicated for me to say if you "overthink" since I'm biased about my syntax but looking with the spec of HTML boolean attributes you have shown.
It's written : That mean in HTML you can write a So for the example of Otherwise I think this Syntax can be Interesting. I think with some formatting it can be regular to read templ Consume() {
@AppButton(
Attributes(
BooleanAttr("noshade"),
ConditionalAttr(isLoggedIn, "class", "authenticated"),
Attr("data-id": "my-custom-id"),
)
) {
Click
}
} My only issue is that make the user need to learn a lot of custom structure just to do at the end a spread of attributes 👀 When you said to someone it's just a The only thing this I see in this Syntax that is better from my current PR is This can be very annoying since you have to create a helper function returning the write value. But I think this feature can be implemented if Templ has a way to define go code directly in the template. You could just define early the map, do the if append and then use it. I think you (@a-h) mention early in an other Issue you planed to add this native Anyway that my take on this problem and I could be very wrong but I think it's the simpler and more go user friendly we can do for now. |
Sorry for the intrusion, but what about just having
I don't know the templ code base well enough to say what approach would be faster, but this declarative one looks more intuitive to me. |
Oh that's a great Idea I love it ! ❤️ That would mean the developer can pass everything and in the runtime we only easily ignore the attribute if the value is not valid (for example if it's was a float). Also the fact we reused
Of course the loop will be slower since we need to cast if the The thing that hype me the most it's the kind of little change you suggest @cmar0027 that's so clean and so perfect that we couldn't see at first gland with our eyes so deep in complex solution ! |
Hey, quickly chiming in: I would double down on the point that @gungun974 made. We can easily write attribute values on e.g. Also, @cmar0027's syntax is great imho! With this, there is no need to learn all the new attribute structs. |
Hum CI/CD failed, I guess I will need to rebase this branch and verify everything still work. For now 75ea299 add what we have discuss yesterday. I didn't have yet update the documentation since I'm too bad to write english and explain thing. I will fix later this day CI/CD but I hope the new code I wrote will be at your standard @a-h |
75ea299
to
343eeb8
Compare
Well I don't know what happen but there is something wrong with the CI/CD for me ??? I rebase with main and CI/CD still failed. So since I use NixOS I try to run the flake
But using And finally updating the And the worst is those strange behavior I can reproduce on the I don't know what happen to me but I don't think it's my fault :( |
Don't worry, I'll check out what's going on with the linter. |
@a-h @gungun974 Any updates on this one? |
343eeb8
to
79d1c99
Compare
OK, I've reworked this PR and I think the end result is a bit simpler. |
You have no idea how happy I am right now! Thank you so much for your efforts @a-h, @gungun974 & everyone who contributed by either reviewing or shaping the feature otherwise! |
Am I misunderstanding something or you can't pass a single attribute and use it like: { attr }? A new slog package has Attr struct, templ should have had a similar one, instead of being map[string]any: type Attr struct {
Key string
Value Value // more or less type any
} |
Hey, I'm no maintainer or even contributor (yet), but I want to quickly ask how the current implementation prevents you from achieving your goal here? I am not even sure if having As for the syntax decisions: the feature is consistent with existing templ functionality, e.g. what @a-h calls child spread expressions I would argue that consistency inside the library itself is more important than parity with the underlying language. |
Introduction
This Pull Request was created to address the primary issue of #229.
The Templ component system prevents passing attributes directly to an element within the component.
This can be quite problematic as it complicates the use of libraries like HTMX, alpinejs, or hyperscript due to an almost infinite number of attributes.
This Pull Request adds a "spread" attribute behavior to Templ similar to what can be found in React.
The "spread attributes" syntax
"Spread attributes" are a new type of attribute that allows adding directly to the generated code a
map[string]string
as a list of an element's attributes.For this, the Templ generator detects the "spread attributes" syntax and adds to the code a
for range
of this map and escapes the name and the value of the attribute at runtime.An alias
templ.Attributes
is available in the runtime to enhance visibility and avoid writingmap[string]string
.Here's an example of code written with this syntax.
This syntax was inspired by the proposal from @joerdav.
Todo
This Pull Request is not yet complete; here are the missing points:
[ ] (Not sure) Overwrite conflicting attributes (This could have a higher runtime impact as this would break constant attributes with the current operation)(It's not my responsibility in the end)