Skip to content

[Twig][Live] add component attribute system/helper #220

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

Merged
merged 1 commit into from
Jan 31, 2022

Conversation

kbond
Copy link
Member

@kbond kbond commented Jan 14, 2022

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

A common pattern with other component library's (ie blade components/vue) is to pass html attributes to your component that are set on the component's root node. This proposal adds a way to do this with twig components. Enable on your component by adding the HasAttributesTrait. Attributes are any data passed to component() that cannot be mounted on the component itself. This extra data is added to a ComponentAttributes object that lives as a public property on your component (available as attributes in your component's template).

This should all work out-of-the box with live components.

Todo:

Usage

To use, add the HasAttributesTrait to your component:

#[AsTwigComponent('my_component')]
class MyComponent
{
    use HasAttributesTrait;
}

Then, in your component's template:

{# templates/components/my_component.html.twig #}

<div{{ attributes }}>
  My Component!
</div>

When rendering the component, you can pass an array of html attributes to add:

{{ component('my_component', { class: 'foo', style: 'color:red' }) }}

{# renders as: #}
<div class="foo" style="color:red">
  My Component!
</div>

Defaults & Merging

In your component template, you can set defaults that are merged with
passed attributes. The passed attributes override the default with
the exception of class. For class, the defaults are prepended:

{# templates/components/my_component.html.twig #}

<button{{ attributes.defaults({ class: 'bar', type: 'button' }) }}>
  My Component!
</button>

{# render component #}
{{ component('my_component', { style: 'color:red' }) }}
{{ component('my_component', { class: 'foo', type: 'submit' }) }}

{# renders as: #}
<div class="bar" style="color:red">
  My Component!
</div>
<div class="bar foo" type="submit">
  My Component!
</div>

Only

{# templates/components/my_component.html.twig #}

<div{{ attributes.only('class')) }}>
  My Component!
</div>

{# render component #}
{{ component('my_component', { class: 'foo', style: 'color:red' }) }}

{# renders as: #}
<div class="foo">
  My Component!
</div>

Without

{# templates/components/my_component.html.twig #}

<div{{ attributes.without('class')) }}>
  My Component!
</div>

{# render component #}
{{ component('my_component', { class: 'foo', style: 'color:red' }) }}

{# renders as: #}
<div style="color:red">
  My Component!
</div>

Live Components

We removed the init_live_component() twig function (BC BREAK) and replaced with the attributes variable which is always available (even if your component doesn't use HasAttributesTrait).

To upgrade, replace all calls to init_live_component() with attributes:

- <div {{ init_live_component() }}>
+ <div {{ attributes }}>

@kbond
Copy link
Member Author

kbond commented Jan 14, 2022

629d834 demonstrates a bit of a hack to remove the requirement for {{ attributes|raw }}. Maybe there's a better way to do this?

BTW, I'm totally fine to just live with |raw.

@seb-jean
Copy link
Contributor

I love this feature @kbond

@kbond
Copy link
Member Author

kbond commented Jan 14, 2022

To make this feature live, I was thinking to mark the property as a LiveProp (with a hydrator) so it just works when making an existing component live.

@weaverryan
Copy link
Member

I like this - and the idea is proven by other libraries.

629d834 demonstrates a bit of a hack to remove the requirement for {{ attributes|raw }}. Maybe there's a better way to do this?

See https://twig.symfony.com/doc/3.x/api.html#escaper-extension - look for addSafeClass. I think that's the correct way to handle this.

To make this feature live, I was thinking to mark the property as a LiveProp (with a hydrator) so it just works when making an existing component live.

I can't think of why this would be a problem. So 👍

This proposal adds a way to do this (albeit a bit verbose) with twig components

2 ideas for this, both which are beyond the scope of this PR:

A) Allow using "…args" for the “props”

{{ component('my_component', attributes: { class: 'foo', style: 'color:red' }, otherProp: 'bar' ) }}

It removes an extra {} basically.

  1. Allowing using an <component attributes="" style for the components.

@weaverryan
Copy link
Member

Also, it would be great if, for live components, we could also do:

<div{{ attributes }}>

Instead of:

<div {{ init_live_component(this) }}>

@kbond
Copy link
Member Author

kbond commented Jan 14, 2022

See https://twig.symfony.com/doc/3.x/api.html#escaper-extension - look for addSafeClass. I think that's the correct way to handle this.

This is what I used. The hack I was referring to is the weird way of adding it in the extension. I don't think there's a great way for a third party bundle to add these.

@kbond kbond changed the title [WIP][LiveComponent] add component attribute system/helper [WIP][TwigComponent] add component attribute system/helper Jan 19, 2022
@kbond kbond force-pushed the feature/twig-components-attributes branch 3 times, most recently from 13a6727 to 947cac7 Compare January 21, 2022 17:10
@kbond kbond force-pushed the feature/twig-components-attributes branch 2 times, most recently from 11b6989 to 3123f35 Compare January 21, 2022 22:47
@kbond kbond force-pushed the feature/twig-components-attributes branch from 5f5fd76 to ae725d4 Compare January 21, 2022 23:35
@kbond
Copy link
Member Author

kbond commented Jan 21, 2022

The LiveComponent failing test will be fixed once #241 is merged/rebased. The cs fixer will pass once #240 is merged/rebased.

@kbond kbond force-pushed the feature/twig-components-attributes branch from a8fa0ab to dc76d03 Compare January 22, 2022 23:20
weaverryan added a commit that referenced this pull request Jan 25, 2022
This PR was squashed before being merged into the 2.x branch.

Discussion
----------

[LiveComponent] add ability to have array props

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Tickets       | n/a
| License       | MIT

Discovered a bug when working on #220. You currently can't have `LiveProp`'s that are "arrays". This is because the [_expose_ system](https://symfony.com/bundles/ux-live-component/current/index.html#modifying-embedded-properties-with-the-exposed-option) uses arrays and we weren't strict enough in determining when this system was being used.

Commits
-------

f71e718 [LiveComponent] add ability to have array props
@kbond kbond force-pushed the feature/twig-components-attributes branch from 09a404e to af957de Compare January 25, 2022 14:34
@kbond kbond force-pushed the feature/twig-components-attributes branch from af957de to f921ece Compare January 25, 2022 15:15
Copy link
Member

@weaverryan weaverryan left a comment

Choose a reason for hiding this comment

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

I really like this version!

@kbond kbond force-pushed the feature/twig-components-attributes branch 2 times, most recently from 7ebc0f2 to b192afc Compare January 27, 2022 21:31
@kbond kbond force-pushed the feature/twig-components-attributes branch 3 times, most recently from a9edf88 to 3c800d8 Compare January 28, 2022 18:27
- adds `HasAttributesTrait` for components which makes
  `attributes` variable available in component templates
- adds `PostMount` component hook to intercept extra props
- adds `PreRenderEvent` to intercept/manipulate twig
  template/variables before rendering
- [BC BREAK] Remove `init_live_component()` twig function,
  use `{{ attributes }}` instead.
@kbond kbond force-pushed the feature/twig-components-attributes branch from 3c800d8 to d638782 Compare January 28, 2022 19:01
@weaverryan
Copy link
Member

Thank you Kevin!

@weaverryan weaverryan merged commit db75d11 into symfony:2.x Jan 31, 2022
@kbond kbond changed the title [WIP][TwigComponent] add component attribute system/helper [Twig][Live] add component attribute system/helper Jan 31, 2022
@kbond kbond mentioned this pull request Jan 31, 2022
1 task
@seb-jean
Copy link
Contributor

seb-jean commented Feb 2, 2022

Hi,

I wanted to use a null value but I got the following error:

An exception has been thrown during the rendering of a template ("Unable to use "value" (null) as an attribute. Attributes must be scalar. If you meant to mount this value on your component, make sure this is a writable property.").

{{ component('input', {type: 'text', name: 'query', value: form.address.vars.data }) }}

form.address.vars.data can be null at times.

I saw that it was this error that is affected: https://github.com/symfony/ux-twig-component/blob/2.x/src/HasAttributesTrait.php#L49

@kbond
Copy link
Member Author

kbond commented Feb 2, 2022

Ah, good catch, so you'd expect the html to be:

<input type="text" name="query" value="" />

@seb-jean
Copy link
Contributor

seb-jean commented Feb 2, 2022

@kbond Exactly

@seb-jean
Copy link
Contributor

seb-jean commented Feb 2, 2022

Something else to improve (maybe):
Sometimes, we don't need to provide a value like for the autofocus property.
How could we do to have just autofocus instead of autofocus="" ?
https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/autofocus

@kbond
Copy link
Member Author

kbond commented Feb 2, 2022

Another good catch, @weaverryan and I were discussing and we think null or empty string should mean render the attribute name only:

{{ component('input', {type: 'text', name: 'query', value: null, autofocus: null }) }}

<input type="text" name="query" value autofocus />

WDYT?

@seb-jean
Copy link
Contributor

seb-jean commented Feb 2, 2022

Yes, null is better I think.
I don't see any other solutions anyway.

@kbond
Copy link
Member Author

kbond commented Feb 2, 2022

You're thinking?

{{ component('input', {type: 'text', name: 'query', value: '', autofocus: null }) }}

<input type="text" name="query" value="" autofocus />

There are some limitations to doing this when working with live components. But we are discussing some solutions.

@seb-jean
Copy link
Contributor

seb-jean commented Feb 2, 2022

Yeah

weaverryan added a commit that referenced this pull request Feb 3, 2022
This PR was merged into the 2.x branch.

Discussion
----------

[Live] refactor AddLiveAttributesSubscriber

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| Tickets       | n/a
| License       | MIT

Cleanup from #220, this subscriber should not depend on the twig runtime.

Commits
-------

2d429b0 [Live] refactor AddLiveAttributesSubscriber
weaverryan added a commit that referenced this pull request Feb 4, 2022
…autofocus) (kbond)

This PR was merged into the 2.x branch.

Discussion
----------

[Twig][Live] null attribute values render without value (ie autofocus)

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Tickets       | n/a
| License       | MIT

Ref: #220 (comment)

A null attribute value renders without the _value_:

```twig
{# templates/components/my_component.html.twig #}
<input{{ attributes}}/>

{# render component #}
{{ component('my_component', { type: 'text', value: '', autofocus: null }) }}

{# renders as: #}
<input type="text" value="" autofocus/>
```

**TODO:**
- [x] `null` vs `''` (#220 (comment))
- [x] attributes without a value like `autofocus` (#220 (comment))
- [x] LiveComponent dehydration/hydration issues (`null` values are lost during re-render) - solved in #264

Commits
-------

c1e484b [Twig][Live] null attribute values render without value (ie autofocus)
symfony-splitter pushed a commit to symfony/ux-live-component that referenced this pull request Feb 4, 2022
…autofocus) (kbond)

This PR was merged into the 2.x branch.

Discussion
----------

[Twig][Live] null attribute values render without value (ie autofocus)

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Tickets       | n/a
| License       | MIT

Ref: symfony/ux#220 (comment)

A null attribute value renders without the _value_:

```twig
{# templates/components/my_component.html.twig #}
<input{{ attributes}}/>

{# render component #}
{{ component('my_component', { type: 'text', value: '', autofocus: null }) }}

{# renders as: #}
<input type="text" value="" autofocus/>
```

**TODO:**
- [x] `null` vs `''` (symfony/ux#220 (comment))
- [x] attributes without a value like `autofocus` (symfony/ux#220 (comment))
- [x] LiveComponent dehydration/hydration issues (`null` values are lost during re-render) - solved in #264

Commits
-------

c1e484b [Twig][Live] null attribute values render without value (ie autofocus)
weaverryan added a commit that referenced this pull request Feb 4, 2022
This PR was merged into the 2.x branch.

Discussion
----------

[Twig][Live] Native attributes

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Tickets       | n/a
| License       | MIT

This removes the need for the `HasAttributesTrait` that was introduced in #220. Attributes are now "a thing all component have".

`ComponentFactory::create()` returns a new `MountedComponent` object that holds the "mounted" component, attributes and component metadata. `LiveComponentHydrator::dehydrate()` now accepts an instance of `MountedComponent` and `LiveComponentHydrator::hydrate()` now returns an instance of `MountedComponent`.

If the component is live, any attributes are dehydrated/hydrated as "readonly" data (they can't be modified by the frontend).

**TODO**
- [x] Update docs referring to `HasAttributesTrait`

Commits
-------

37acff2 [Twig][Live] make attributes a "native" feature
marcuswebapp added a commit to marcuswebapp/ux that referenced this pull request Aug 1, 2023
…autofocus) (kbond)

This PR was merged into the 2.x branch.

Discussion
----------

[Twig][Live] null attribute values render without value (ie autofocus)

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Tickets       | n/a
| License       | MIT

Ref: symfony/ux#220 (comment)

A null attribute value renders without the _value_:

```twig
{# templates/components/my_component.html.twig #}
<input{{ attributes}}/>

{# render component #}
{{ component('my_component', { type: 'text', value: '', autofocus: null }) }}

{# renders as: #}
<input type="text" value="" autofocus/>
```

**TODO:**
- [x] `null` vs `''` (symfony/ux#220 (comment))
- [x] attributes without a value like `autofocus` (symfony/ux#220 (comment))
- [x] LiveComponent dehydration/hydration issues (`null` values are lost during re-render) - solved in #264

Commits
-------

c1e484b [Twig][Live] null attribute values render without value (ie autofocus)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants