From b35807b26af7da9c32e9df51f52648b9169cbb6c Mon Sep 17 00:00:00 2001 From: Pierre du Plessis Date: Sat, 29 Jun 2024 19:23:05 +0200 Subject: [PATCH] [LiveComponent] Add 'live_action' twig function --- src/LiveComponent/CHANGELOG.md | 1 + src/LiveComponent/composer.json | 1 + src/LiveComponent/doc/index.rst | 17 +++++++ .../LiveComponentExtension.php | 1 + .../src/Twig/LiveComponentExtension.php | 1 + .../src/Twig/LiveComponentRuntime.php | 27 ++++++++++++ src/LiveComponent/tests/Fixtures/Kernel.php | 2 + .../Unit/Twig/LiveComponentRuntimeTest.php | 44 +++++++++++++++++++ 8 files changed, 94 insertions(+) create mode 100644 src/LiveComponent/tests/Unit/Twig/LiveComponentRuntimeTest.php diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md index c22e9de9f26..112a1f9ef90 100644 --- a/src/LiveComponent/CHANGELOG.md +++ b/src/LiveComponent/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG - Add `submitForm()` to `TestLiveComponent`. +- Add `live_action` Twig function ## 2.18.0 diff --git a/src/LiveComponent/composer.json b/src/LiveComponent/composer.json index f00a40c9db9..c9b2898c93f 100644 --- a/src/LiveComponent/composer.json +++ b/src/LiveComponent/composer.json @@ -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" }, diff --git a/src/LiveComponent/doc/index.rst b/src/LiveComponent/doc/index.rst index 4bfe2a1ecec..7023019e3b6 100644 --- a/src/LiveComponent/doc/index.rst +++ b/src/LiveComponent/doc/index.rst @@ -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 + + + + {# with modifiers #} + + + + Actions & Services ~~~~~~~~~~~~~~~~~~ @@ -1159,6 +1170,12 @@ You can also pass arguments to your action by adding each as a >Add Item + {# or #} + +
+ +
+ In your component, to allow each argument to be passed, add the ``#[LiveArg()]`` attribute:: diff --git a/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php b/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php index 86bb49c52a6..e79e6c5ecf9 100644 --- a/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php +++ b/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php @@ -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') ; diff --git a/src/LiveComponent/src/Twig/LiveComponentExtension.php b/src/LiveComponent/src/Twig/LiveComponentExtension.php index fa841ae22bf..c603087f304 100644 --- a/src/LiveComponent/src/Twig/LiveComponentExtension.php +++ b/src/LiveComponent/src/Twig/LiveComponentExtension.php @@ -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']]), ]; } } diff --git a/src/LiveComponent/src/Twig/LiveComponentRuntime.php b/src/LiveComponent/src/Twig/LiveComponentRuntime.php index daba8d3b7ef..56ef854f541 100644 --- a/src/LiveComponent/src/Twig/LiveComponentRuntime.php +++ b/src/LiveComponent/src/Twig/LiveComponentRuntime.php @@ -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; /** @@ -28,6 +29,7 @@ public function __construct( private ComponentFactory $factory, private UrlGeneratorInterface $urlGenerator, private LiveComponentMetadataFactory $metadataFactory, + private StimulusHelper $stimulusHelper, ) { } @@ -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; + } } diff --git a/src/LiveComponent/tests/Fixtures/Kernel.php b/src/LiveComponent/tests/Fixtures/Kernel.php index 4ec259598ae..b8b4df0bb95 100644 --- a/src/LiveComponent/tests/Fixtures/Kernel.php +++ b/src/LiveComponent/tests/Fixtures/Kernel.php @@ -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; @@ -72,6 +73,7 @@ public function registerBundles(): iterable yield new SecurityBundle(); yield new TwigComponentBundle(); yield new LiveComponentBundle(); + yield new StimulusBundle(); yield new ZenstruckFoundryBundle(); } diff --git a/src/LiveComponent/tests/Unit/Twig/LiveComponentRuntimeTest.php b/src/LiveComponent/tests/Unit/Twig/LiveComponentRuntimeTest.php new file mode 100644 index 00000000000..654ee92e785 --- /dev/null +++ b/src/LiveComponent/tests/Unit/Twig/LiveComponentRuntimeTest.php @@ -0,0 +1,44 @@ + + * + * 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(300)|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:prevent" data-live-pro1-param="val1" data-live-prop2-param="val2" data-live-action-param="debounce(300)|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)); + } +}