Skip to content

Commit 23a9740

Browse files
committed
Custom action support
1 parent 01839ab commit 23a9740

File tree

3 files changed

+79
-4
lines changed

3 files changed

+79
-4
lines changed

src/Turbo/src/Helper/TurboStream.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,15 @@ public static function remove(string $target): string
6161
*/
6262
public static function before(string $target, string $html): string
6363
{
64-
return self::wrap('before', $target, $html);
64+
return self::custom('before', $target, $html);
6565
}
6666

6767
/**
6868
* Inserts after the element(s) designated by the target CSS selector.
6969
*/
7070
public static function after(string $target, string $html): string
7171
{
72-
return self::wrap('after', $target, $html);
72+
return self::custom('after', $target, $html);
7373
}
7474

7575
/**
@@ -86,12 +86,45 @@ public static function refresh(?string $requestId = null): string
8686
return \sprintf('<turbo-stream action="refresh" request-id="%s"></turbo-stream>', htmlspecialchars($requestId));
8787
}
8888

89+
/**
90+
* Custom action and attributes.
91+
* When passing attributes use null value to for boolean attributes (e.g. disabled).
92+
*
93+
* @param array<string, string|int|float|null> $attr
94+
*/
95+
public static function custom(string $action, string $target, string $html, array $attr = []): string
96+
{
97+
if (\array_key_exists('action', $attr) || \array_key_exists('targets', $attr)) {
98+
throw new \InvalidArgumentException('The "action" and "targets" attributes are reserved and cannot be used.');
99+
}
100+
101+
$attrString = '';
102+
foreach ($attr as $key => $value) {
103+
$key = htmlspecialchars($key);
104+
if (null === $value) {
105+
$attrString .= \sprintf(' %s', $key);
106+
} elseif (\is_int($value) || \is_float($value)) {
107+
$attrString .= \sprintf(' %s="%s"', $key, $value);
108+
} else {
109+
$attrString .= \sprintf(' %s="%s"', $key, htmlspecialchars($value));
110+
}
111+
}
112+
113+
return self::wrap(htmlspecialchars($action), $target, $html, $attrString);
114+
}
115+
89116
private static function wrap(string $action, string $target, string $html, string $attr = ''): string
90117
{
91-
return \sprintf(<<<EOHTML
118+
return \sprintf(
119+
<<<EOHTML
92120
<turbo-stream action="%s" targets="%s"%s>
93121
<template>%s</template>
94122
</turbo-stream>
95-
EOHTML, $action, htmlspecialchars($target), $attr, $html);
123+
EOHTML,
124+
$action,
125+
htmlspecialchars($target),
126+
$attr,
127+
$html
128+
);
96129
}
97130
}

src/Turbo/src/TurboStreamResponse.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,16 @@ public function refresh(?string $requestId = null): static
104104

105105
return $this;
106106
}
107+
108+
/**
109+
* @param array<string, string|int|float|null> $attr
110+
*
111+
* @return $this
112+
*/
113+
public function action(string $action, string $target, string $html, array $attr = []): static
114+
{
115+
$this->setContent($this->getContent().TurboStream::custom($action, $target, $html, $attr));
116+
117+
return $this;
118+
}
107119
}

src/Turbo/tests/Helper/TurboStreamTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\UX\Turbo\Tests\Helper;
1313

14+
use PHPUnit\Framework\Attributes\DataProvider;
15+
use PHPUnit\Framework\Attributes\TestWith;
1416
use PHPUnit\Framework\TestCase;
1517
use Symfony\UX\Turbo\Helper\TurboStream;
1618

@@ -76,4 +78,32 @@ public function testRefreshWithId(): void
7678
TurboStream::refresh('a"b')
7779
);
7880
}
81+
82+
public function testCustom(): void
83+
{
84+
$this->assertSame(<<<EOHTML
85+
<turbo-stream action="customAction" targets="some[&quot;selector&quot;]" someAttr="someValue" boolAttr intAttr="0" floatAttr="3.14">
86+
<template><div>content</div></template>
87+
</turbo-stream>
88+
EOHTML,
89+
TurboStream::custom('customAction', 'some["selector"]', '<div>content</div>', ['someAttr' => 'someValue', 'boolAttr' => null, 'intAttr' => 0, 'floatAttr' => 3.14])
90+
);
91+
}
92+
93+
/**
94+
* @dataProvider customThrowsExceptionDataProvider
95+
*
96+
* @param array<string, string|int|null> $attr
97+
*/
98+
public function testCustomThrowsException(string $action, string $target, string $html, array $attr): void
99+
{
100+
$this->expectException(\InvalidArgumentException::class);
101+
TurboStream::custom($action, $target, $html, $attr);
102+
}
103+
104+
public static function customThrowsExceptionDataProvider(): \Generator
105+
{
106+
yield ['customAction', 'some["selector"]', '<div>content</div>', ['action' => 'someAction']];
107+
yield ['customAction', 'some["selector"]', '<div>content</div>', ['targets' => 'someTargets']];
108+
}
79109
}

0 commit comments

Comments
 (0)