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

[Twig] Add (alternate) attribute rendering system #1442

Merged
merged 1 commit into from Feb 8, 2024

Conversation

kbond
Copy link
Member

@kbond kbond commented Feb 1, 2024

Q A
Bug fix? no
New feature? yes
Issues n/a
License MIT

This is an alternate to #1404 as suggested by Ryan here.

This PR allows the following:

<div
    class="{{ attributes.render('class') }} appended-default"
    style="prepended-default {{ attributes.render('style') }}"
    data-foo="{{ attributes.render('data-foo')|default('replaced-default') }}"
    {{ attributes }}
>

When calling attributes.render('name') (or magically via attributes.name), the attribute is marked as rendered. Later when calling just {{ attributes }}, the attributes marked as already rendered are excluded. Whether or not an attribute is considered rendered is only evaluated when converting ComponentAttributes to a string.

TODO:

  • Docs
  • Add test ensuring works in real twig component

@carsonbot carsonbot added the Status: Needs Review Needs to be reviewed label Feb 1, 2024
@smnandre
Copy link
Collaborator

smnandre commented Feb 1, 2024

{# This is doing the same thing too, right ? #}
data-foo="{{ attributes.get('data-foo') ?? 'replaced-default' }}"
{# Could we do this ? #}
data-foo="{{ attributes.get('data-foo', 'replaced-default') }}"
{# Or even better... ? #}
data-foo="{{ attributes.dataFoo ??  'replaced-default' }}"

@@ -31,7 +34,10 @@ public function __construct(private array $attributes)
public function __toString(): string
{
return array_reduce(
array_keys($this->attributes),
array_filter(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array_filter + \ ARRAY_FILTER_USE_KEY ?

{
$value = $this->attributes[$attribute] ?? '';

if (!\is_string($value)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Stringable ?

@weaverryan
Copy link
Member

Those are interesting suggestions @smnandre ...

@kbond
Copy link
Member Author

kbond commented Feb 2, 2024

What about killing ComponentAttributeValue so only this would be possible:

<div
    class="{{ attributes.class }} appended-default"
    style="prepended-default {{ attributes.style }}"
    data-foo="{{ attributes.get('data-foo')|default('replaced-default') }}"
    {{ attributes }}
>

BTW, the only real reason I added that class was to make using a filter (like tailwind_merge) on your attributes would be simpler:

<div
    class="{{ attributes.class.append('appended-default')|tailwind_merge }}"
    {{ attributes }}
>

{# vs #}

<div
    class="{{ (attributes.class ~ ' appended-default')|tailwind_merge }} appended-default"
    {{ attributes }}
>

I should note that if we choose not to include this class, adding it in the future would be a bc break I think.

@smnandre
Copy link
Collaborator

smnandre commented Feb 3, 2024

I like the fact this class exists, and i like the idea of "consuming" some attributes.

I think we can improve the DX a bit more with utilities

And the fact this could be forwared to a filter is a really valid point, pushing more the need to a short simple call chain.

Lastely, i would really hate we suggest people to use any " ~ " :)

But i would like to avoid this syntax, as it can be hard to dynamise (values and modes can depend on other things)

<div class="{{ attributes.class.append('alert') }}" >

--

When we say "default preprended" in fact the "default" notion has no meaning, right ? It's something prepended in every case. Same for append.

Then again, i see not a lot of real life usage for "append" (there probably are, but prefxing CSS or data value feels a lot more common need than adding something)

That gives us two main calls to consider: default value, and prepend one.

I still think default value can follow standard twig usages, but i'm eager to add a getter with default too.

<div class="{{ attributes.class|default('alert') }}" >
<div class="{{ attributes.class ?? 'alert' }}" >
<div class="{{ attributes.get('class', 'alert') }}" >

There we have only one problem, by doing so we forbid any usage of 'get' as attribute. Not a big problem, but something to keep in mind

We can add a second method, like prepend() or prependValue or getPrepend('class', 'alert') ...

But then, why not use the 'get' method ? If we agree the prepend is the default behaviour, why not ....

{# Prepend #} 
<div class="{{ attributes.get('class', 'alert', true) }}" >
<div class="{{ attributes.get('class', 'alert', 'prepend') }}" >

{# Append #} 
<div class="{{ attributes.get('class', 'alert', false) }}" >
<div class="{{ attributes.get('class', 'alert', 'append') }}" >

Lastly, if we want something even shorter / sweeter

So my suggestion is :

{# jQuery mood (not sure i like that ^^, but really front-end-like #}

<div class="{{ attributes.class('alert', true) }}" >

@weaverryan
Copy link
Member

Taking all of this into account, how about this:

A) I think we more or less agree on this syntax. I did tweak how the last looks - I don't think this is a code change, but using ?? instead of |default looks better.

<div
    class="{{ attributes.class }} appended-default"
    style="prepended-default {{ attributes.style }}"
    data-foo="{{ attributes.get('data-foo') ?? 'replaced-default' }}"
    {{ attributes }}
>

The get() method could also have a default value 2nd arg - attributes.get('data-foo', 'default-value').

B) Then, for the question of prepending vs replacing vs default values. I like Simon's idea of something like attributes.get('class', 'alert', 'append') - it feels pure to have everything on .get(). But this means that you need to switch from attributes.class to attributes.get('class') when you need this behavior. And so, I still like Kevin's initial attributes.class.prepend('foo') style.

Given this @kbond - are there any tweaks this PR needs to match before review?

@kbond
Copy link
Member Author

kbond commented Feb 7, 2024

I don't think this is a code change, but using ?? instead of |default looks better.

This actually won't be possible, {{ attributes.class }} and {{ attributes.get('class') }} always returns an object (even if the attribute does not exist). I don't believe ?: would work either. I'd prefer to keep this behaviour as it keeps the syntax simpler. This is assuming we keep ComponentAttributeValue (which I think we want to do?).

@weaverryan
Copy link
Member

This actually won't be possible, {{ attributes.class }} and {{ attributes.get('class') }} always returns an object (even if the attribute does not exist)

Ah, ok. I see it now. This feels like a reason to NOT have ComponentAttributeValue then: fetching an attribute is a simple string fetch, nothing fancy. Iirc, the main problem removing this caused was the |tailwind_merge situation. Could that be handled with this slightly-different style?

<div
    class="{{ attributes.prepend('class', 'prepended-stuff') }}"

    or="this-idea"
    class="{{ attributes.get('class', 'prepended-stuff', true) }}"
>

@kbond
Copy link
Member Author

kbond commented Feb 7, 2024

I think so. What about, for this PR, we keep it simple and just have __get(): ?string and get(): ?string (with the rendering flag system).

@weaverryan
Copy link
Member

That sounds reasonable - then we can add args to get() later or add these prepend style methods also later. That'll let us start playing with things

@kbond kbond force-pushed the feat/twig-attribute-behaviour-2 branch from b73e1b9 to 0d027d5 Compare February 7, 2024 21:38
@kbond kbond changed the title [Twig] Add attribute merging behaviour (2) [Twig] Add (alternate) attribute rendering system Feb 7, 2024
@carsonbot carsonbot added Status: Reviewed Has been reviewed by a maintainer and removed Status: Needs Review Needs to be reviewed labels Feb 8, 2024
@weaverryan
Copy link
Member

Thanks Kevin!

@weaverryan weaverryan merged commit f170c59 into symfony:2.x Feb 8, 2024
31 of 34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Reviewed Has been reviewed by a maintainer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants