Skip to content

Commit

Permalink
feature #1946 [LiveComponent] Add 'live_action' twig function (pierre…
Browse files Browse the repository at this point in the history
…dup)

This PR was squashed before being merged into the 2.x branch.

Discussion
----------

[LiveComponent] Add 'live_action' twig function

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Issues        | N/A
| License       | MIT

Adds a new `live_action` twig function.

This helps with creating the proper attributes to call a live action. I can never remember the syntax and need to look up the docs every time to add a live action. This will make the process much easier.

Example Usage:
```diff
- <button data-action="live#action" data-live-action-param="save">Save</button>
+ <button {{ live_action('save') }}>Save</button>
```

Additional Parameters:
```diff
<button
-  data-action="live#action"
-  data-live-action-param="addItem"
-  data-live-id-param="{{ item.id }}"
-  data-live-item-name-param="CustomItem"
+  {{ live_action('addItem', {'id': item.id, 'itemName': 'CustomItem'}) }}
>Add Item</button>
```

Adding Modifiers:
```diff
<button
-  data-action="live#action:prevent"
-  data-live-action-param="debounce(300)|save"
+  {{ live_action('save', {}, {'debounce': 300, 'prevent': true}) }}
 >Save</button>
```

#### TODO:

- [x] Add CHANGELOG entry
- [x] Update docs

Commits
-------

b35807b [LiveComponent] Add 'live_action' twig function
  • Loading branch information
kbond committed Jul 31, 2024
2 parents 03a85b5 + b35807b commit f4dfe0e
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/LiveComponent/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# CHANGELOG

- Add `submitForm()` to `TestLiveComponent`.
- Add `live_action` Twig function

## 2.18.0

Expand Down
1 change: 1 addition & 0 deletions src/LiveComponent/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"require": {
"php": ">=8.1",
"symfony/property-access": "^5.4.5|^6.0|^7.0",
"symfony/stimulus-bundle": "^2.9",
"symfony/ux-twig-component": "^2.8",
"twig/twig": "^3.8.0"
},
Expand Down
17 changes: 17 additions & 0 deletions src/LiveComponent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,17 @@ You can also add several "modifiers" to the action:
The ``debounce(300)`` adds 300ms of "debouncing" before the action is executed.
In other words, if you click really fast 5 times, only one Ajax request will be made!

You can also use the ``live_action`` twig helper function to render the attributes:

.. code-block:: html+twig

<button {{ live_action('resetMax') }}>Reset Min/Max</button>

{# with modifiers #}

<button {{ live_action('save', {}, {'debounce': 300}) }}>Save</button>


Actions & Services
~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -1159,6 +1170,12 @@ You can also pass arguments to your action by adding each as a
>Add Item</button>
</form>

{# or #}

<form>
<button {{ live_action('addItem', {'id': item.id, 'itemName': 'CustomItem' })>Add Item</button>
</form>

In your component, to allow each argument to be passed, add
the ``#[LiveArg()]`` attribute::

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
new Reference('ux.twig_component.component_factory'),
new Reference('router'),
new Reference('ux.live_component.metadata_factory'),
new Reference('stimulus.helper'),
])
->addTag('twig.runtime')
;
Expand Down
1 change: 1 addition & 0 deletions src/LiveComponent/src/Twig/LiveComponentExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public function getFunctions(): array
{
return [
new TwigFunction('component_url', [LiveComponentRuntime::class, 'getComponentUrl']),
new TwigFunction('live_action', [LiveComponentRuntime::class, 'liveAction'], ['is_safe' => ['html_attr']]),
];
}
}
27 changes: 27 additions & 0 deletions src/LiveComponent/src/Twig/LiveComponentRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\UX\LiveComponent\LiveComponentHydrator;
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
use Symfony\UX\TwigComponent\ComponentFactory;

/**
Expand All @@ -28,6 +29,7 @@ public function __construct(
private ComponentFactory $factory,
private UrlGeneratorInterface $urlGenerator,
private LiveComponentMetadataFactory $metadataFactory,
private StimulusHelper $stimulusHelper,
) {
}

Expand All @@ -45,4 +47,29 @@ public function getComponentUrl(string $name, array $props = []): string

return $this->urlGenerator->generate($metadata->get('route'), $params, $metadata->get('url_reference_type'));
}

public function liveAction(string $actionName, array $parameters = [], array $modifiers = [], ?string $event = null): string
{
$attributes = $this->stimulusHelper->createStimulusAttributes();

$modifiers = array_map(static function (string $key, mixed $value) {
return $value ? \sprintf('%s(%s)', $key, $value) : $key;
}, array_keys($modifiers), array_values($modifiers));

$parts = explode(':', $actionName);

$parameters['action'] = $modifiers ? \sprintf('%s|%s', implode('|', $modifiers), $parts[0]) : $parts[0];

array_shift($parts);

$name = 'action';

if (\count($parts) > 0) {
$name .= ':'.implode(':', $parts);
}

$attributes->addAction('live', $name, $event, $parameters);

return (string) $attributes;
}
}
2 changes: 2 additions & 0 deletions src/LiveComponent/tests/Fixtures/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component1;
use Symfony\UX\LiveComponent\Tests\Fixtures\Serializer\Entity2Normalizer;
use Symfony\UX\LiveComponent\Tests\Fixtures\Serializer\MoneyNormalizer;
use Symfony\UX\StimulusBundle\StimulusBundle;
use Symfony\UX\TwigComponent\TwigComponentBundle;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
Expand Down Expand Up @@ -72,6 +73,7 @@ public function registerBundles(): iterable
yield new SecurityBundle();
yield new TwigComponentBundle();
yield new LiveComponentBundle();
yield new StimulusBundle();
yield new ZenstruckFoundryBundle();
}

Expand Down
44 changes: 44 additions & 0 deletions src/LiveComponent/tests/Unit/Twig/LiveComponentRuntimeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\LiveComponent\Tests\Unit;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\UX\LiveComponent\Twig\LiveComponentRuntime;

final class LiveComponentRuntimeTest extends KernelTestCase
{
public function testGetLiveAction(): void
{
$runtime = self::getContainer()->get('ux.live_component.twig.component_runtime');
\assert($runtime instanceof LiveComponentRuntime);

$props = $runtime->liveAction('action-name');
$this->assertSame('data-action="live#action" data-live-action-param="action-name"', $props);

$props = $runtime->liveAction('action-name', ['prop1' => 'val1', 'someProp' => 'val2']);
$this->assertSame('data-action="live#action" data-live-prop1-param="val1" data-live-some-prop-param="val2" data-live-action-param="action-name"', $props);

$props = $runtime->liveAction('action-name', ['prop1' => 'val1', 'prop2' => 'val2'], ['debounce' => 300]);
$this->assertSame('data-action="live#action" data-live-prop1-param="val1" data-live-prop2-param="val2" data-live-action-param="debounce(300)|action-name"', \html_entity_decode($props));
$this->assertSame('data-action="live#action" data-live-prop1-param="val1" data-live-prop2-param="val2" data-live-action-param="debounce&#x28;300&#x29;&#x7C;action-name"', $props);

$props = $runtime->liveAction('action-name:prevent', ['pro1' => 'val1', 'prop2' => 'val2'], ['debounce' => 300]);
$this->assertSame('data-action="live#action:prevent" data-live-pro1-param="val1" data-live-prop2-param="val2" data-live-action-param="debounce(300)|action-name"', \html_entity_decode($props));
$this->assertSame('data-action="live#action&#x3A;prevent" data-live-pro1-param="val1" data-live-prop2-param="val2" data-live-action-param="debounce&#x28;300&#x29;&#x7C;action-name"', $props);

$props = $runtime->liveAction('action-name:prevent', [], ['debounce' => 300]);
$this->assertSame('data-action="live#action:prevent" data-live-action-param="debounce(300)|action-name"', \html_entity_decode($props));

$props = $runtime->liveAction('action-name', [], [], 'keydown.esc');
$this->assertSame('data-action="keydown.esc->live#action" data-live-action-param="action-name"', \html_entity_decode($props));
}
}

0 comments on commit f4dfe0e

Please sign in to comment.