Skip to content

Morph Streams #149

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 7 commits into from
Sep 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ If you're rendering a Turbo Stream inside a your Blade files, you may use the `<

```blade
<x-turbo::stream :target="$post" action="update">
@include('posts._post', ['post' => $post])
@include('posts.partials.post', ['post' => $post])
<x-turbo::stream>
```

Expand Down
2 changes: 1 addition & 1 deletion docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ class CreatesCommentsTest extends TestCase
TurboStream::assertBroadcasted(function (PendingBroadcast $broadcast) use ($todo) {
return $broadcast->target === 'comments'
&& $broadcast->action === 'append'
&& $broadcast->partialView === 'comments._comment'
&& $broadcast->partialView === 'comments.partials.comment'
&& $broadcast->partialData['comment']->is($todo->comments->first())
&& count($broadcast->channels) === 1
&& $broadcast->channels[0]->name === sprintf('private-%s', $todo->broadcastChannel());
Expand Down
48 changes: 41 additions & 7 deletions docs/turbo-streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Although it's handy to pass a model instance to the `turbo_stream()` function -
turbo_stream()
->target('comments')
->action('append')
->view('comments._comment', ['comment' => $comment]);
->view('comments.partials.comment', ['comment' => $comment]);
```

There are also shorthand methods which may be used as well:
Expand Down Expand Up @@ -122,7 +122,7 @@ For both the `before` and `after` methods you need additional calls to specify t
```php
turbo_stream()
->before($comment)
->view('comments._flash_message', [
->view('comments.partials.flash_message', [
'message' => __('Comment created!'),
]);
```
Expand All @@ -143,6 +143,40 @@ It will build a `remove` Turbo Stream if the model was just deleted (or if it wa
return turbo_stream($comment, 'append');
```

## Turbo Stream & Morph

Both the `update` and `replace` Turbo Stream actions can specify a `[method="morph"]`, so the action will use DOM morphing instead of the default renderer.

```php
turbo_stream()->replace(dom_id($post, 'comments'), view('comments.partials.comment', [
'comment' => $comment,
]))->morph();
```

This would generate the following Turbo Stream HTML:

```html
<turbo-stream action="replace" target="comments_post_123" method="morph">
<template>...</template>
</turbo-stream>
```

And here's the `update` action version:

```php
turbo_stream()->update(dom_id($post, 'comments'), view('comments.partials.comment', [
'comment' => $comment,
]))->morph();
```

This would generate the following Turbo Stream HTML:

```html
<turbo-stream action="update" target="comments_post_123" method="morph">
<template>...</template>
</turbo-stream>
```

## Target Multiple Elements

Turbo Stream elements can either have a `target` with a DOM ID or a `targets` attribute with a CSS selector to [match multiple elements](https://turbo.hotwired.dev/reference/streams#targeting-multiple-elements). You may use the `xAll` shorthand methods to set the `targets` attribute instead of `target`:
Expand All @@ -165,7 +199,7 @@ When creating Turbo Streams using the builders, you may also specify the CSS cla
turbo_stream()
->targets('.comment')
->action('append')
->view('comments._comment', ['comment' => $comment]);
->view('comments.partials.comment', ['comment' => $comment]);
```

## Turbo Stream Macros
Expand Down Expand Up @@ -247,7 +281,7 @@ return turbo_stream([
->append($comment)
->target(dom_id($comment->post, 'comments')),
turbo_stream()
->update(dom_id($comment->post, 'comments_count'), view('posts._comments_count', [
->update(dom_id($comment->post, 'comments_count'), view('posts.partials.comments_count', [
'post' => $comment->post,
])),
]);
Expand All @@ -274,7 +308,7 @@ Here's an example of a more complex custom Turbo Stream view:

<turbo-stream target="@domid($comment->post, 'comments')" action="append">
<template>
@include('comments._comment', ['comment' => $comment])
@include('comments.partials.comment', ['comment' => $comment])
</template>
</turbo-stream>
```
Expand All @@ -285,7 +319,7 @@ Remember, these are Blade views, so you have the full power of Blade at your han
@if (session()->has('status'))
<turbo-stream target="notice" action="append">
<template>
@include('layouts._flash')
@include('layouts.partials.flash')
</template>
</turbo-stream>
@endif
Expand All @@ -297,7 +331,7 @@ Similar to the `<x-turbo::frame>` Blade component, there's also a `<x-turbo::str
@include('layouts.turbo.flash_stream')

<x-turbo::stream :target="[$comment->post, 'comments']" action="append">
@include('comments._comment', ['comment' => $comment])
@include('comments.partials.comment', ['comment' => $comment])
</x-turbo::stream>
```

Expand Down
4 changes: 1 addition & 3 deletions src/Broadcasting/Limiter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

class Limiter
{
public function __construct(protected array $keys = [], protected int $delay = 500)
{
}
public function __construct(protected array $keys = [], protected int $delay = 500) {}

public function clear(): void
{
Expand Down
16 changes: 16 additions & 0 deletions src/Broadcasting/PendingBroadcast.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@ public function attributes(array $attributes)
return $this;
}

public function morph(): self
{
return $this->method('morph');
}

public function method(?string $method = null): self
{
if ($method) {
return $this->attributes(array_merge($this->attributes, [
'method' => $method,
]));
}

return $this->attributes(Arr::except($this->attributes, 'method'));
}

public function rendering(Rendering $rendering)
{
$this->partialView = $rendering->partial;
Expand Down
2 changes: 1 addition & 1 deletion src/Broadcasting/Rendering.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static function forContent(View|HtmlString|string $content)

public static function empty(): self
{
return new self();
return new self;
}

public static function forModel(Model $model): self
Expand Down
2 changes: 1 addition & 1 deletion src/Commands/TurboInstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,6 @@ private function existingLayoutFiles()

private function phpBinary()
{
return (new PhpExecutableFinder())->find(false) ?: 'php';
return (new PhpExecutableFinder)->find(false) ?: 'php';
}
}
32 changes: 24 additions & 8 deletions src/Http/PendingTurboStreamResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Traits\Macroable;

Expand All @@ -21,6 +22,8 @@ class PendingTurboStreamResponse implements Htmlable, Renderable, Responsable
{
use Macroable;

private array $defaultActions = ['append', 'prepend', 'update', 'replace', 'before', 'after', 'remove', 'refresh'];

private string $useAction;

private ?string $useTarget = null;
Expand All @@ -37,7 +40,7 @@ class PendingTurboStreamResponse implements Htmlable, Renderable, Responsable

public static function forModel(Model $model, ?string $action = null): self
{
$builder = new self();
$builder = new self;

// We're treating soft-deleted models as they were deleted. In other words, we
// will render the remove Turbo Stream. If you need to treat a soft-deleted
Expand Down Expand Up @@ -108,6 +111,22 @@ public function attributes(array $attributes): self
return $this;
}

public function morph(): self
{
return $this->method('morph');
}

public function method(?string $method = null): self
{
if ($method) {
return $this->attributes(array_merge($this->useCustomAttributes, [
'method' => $method,
]));
}

return $this->attributes(Arr::except($this->useCustomAttributes, 'method'));
}

public function append(Model|string $target, $content = null): self
{
return $this->buildAction(
Expand Down Expand Up @@ -267,8 +286,7 @@ private function buildActionAll(string $action, Model|string $targets, $content

public function broadcastTo($channel, ?callable $callback = null)
{
$callback = $callback ?? function () {
};
$callback = $callback ?? function () {};

return tap($this, function () use ($channel, $callback) {
$callback($this->asPendingBroadcast($channel));
Expand All @@ -277,8 +295,7 @@ public function broadcastTo($channel, ?callable $callback = null)

public function broadcastToPrivateChannel($channel, ?callable $callback = null)
{
$callback = $callback ?? function () {
};
$callback = $callback ?? function () {};

return $this->broadcastTo(null, function (PendingBroadcast $broadcast) use ($channel, $callback) {
$broadcast->toPrivateChannel($channel);
Expand All @@ -288,8 +305,7 @@ public function broadcastToPrivateChannel($channel, ?callable $callback = null)

public function broadcastToPresenceChannel($channel, ?callable $callback = null)
{
$callback = $callback ?? function () {
};
$callback = $callback ?? function () {};

return $this->broadcastTo(null, function (PendingBroadcast $broadcast) use ($channel, $callback) {
$callback($broadcast->toPresenceChannel($channel));
Expand Down Expand Up @@ -327,7 +343,7 @@ private function contentAsRendering()
*/
public function toResponse($request)
{
if (! in_array($this->useAction, ['remove', 'refresh']) && ! $this->partialView && $this->inlineContent === null) {
if (! in_array($this->useAction, ['remove', 'refresh']) && in_array($this->useAction, $this->defaultActions) && ! $this->partialView && $this->inlineContent === null) {
throw TurboStreamResponseFailedException::missingPartial();
}

Expand Down
2 changes: 1 addition & 1 deletion src/Models/Broadcasts.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ trait Broadcasts

public static function bootBroadcasts()
{
static::observe(new ModelObserver());
static::observe(new ModelObserver);
}

public static function withoutTurboStreamBroadcasts(callable $callback)
Expand Down
2 changes: 1 addition & 1 deletion src/Models/Naming/Name.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static function forModel(object $model)

public static function build(string $className)
{
$name = new static();
$name = new static;

$name->className = $className;
$name->classNameWithoutRootNamespace = static::removeRootNamespaces($className);
Expand Down
2 changes: 1 addition & 1 deletion src/Testing/ConvertTestResponseToTurboStreamCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ConvertTestResponseToTurboStreamCollection
public function __invoke(TestResponse $response): Collection
{
libxml_use_internal_errors(true);
$document = tap(new DOMDocument())->loadHTML($response->content());
$document = tap(new DOMDocument)->loadHTML($response->content());
$elements = $document->getElementsByTagName('turbo-stream');

$streams = collect();
Expand Down
2 changes: 1 addition & 1 deletion src/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function turbo_stream($model = null, ?string $action = null): MultiplePendingTur
}

if ($model === null) {
return new PendingTurboStreamResponse();
return new PendingTurboStreamResponse;
}

return PendingTurboStreamResponse::forModel($model, $action);
Expand Down
2 changes: 1 addition & 1 deletion tests/Broadcasting/LimiterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public function debounces()
{
$this->freezeTime();

$debouncer = new Limiter();
$debouncer = new Limiter;

$this->assertFalse($debouncer->shouldLimit('my-key'));
$this->assertTrue($debouncer->shouldLimit('my-key'));
Expand Down
54 changes: 54 additions & 0 deletions tests/FunctionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ public function namespaced_turbo_stream_fn()
HTML),
trim(turbo_stream($this->article)),
);

$expected = trim(view('articles._article', [
'article' => $this->article,
])->render());

$this->assertEquals(
trim(<<<HTML
<turbo-stream target="article_{$this->article->id}" action="replace" method="morph">
<template>{$expected}</template>
</turbo-stream>
HTML),
trim(turbo_stream($this->article->fresh())->morph()),
);

// Unsets method
$expected = trim(view('articles._article', [
'article' => $this->article,
])->render());

$this->assertEquals(
trim(<<<HTML
<turbo-stream target="article_{$this->article->id}" action="replace">
<template>{$expected}</template>
</turbo-stream>
HTML),
trim(turbo_stream($this->article->fresh())->morph()->method()),
);
}

/** @test */
Expand Down Expand Up @@ -105,6 +132,33 @@ public function global_turbo_stream_fn()
HTML),
trim(\turbo_stream($this->article)),
);

$expected = trim(view('articles._article', [
'article' => $this->article,
])->render());

$this->assertEquals(
trim(<<<HTML
<turbo-stream target="article_{$this->article->id}" action="replace" method="morph">
<template>{$expected}</template>
</turbo-stream>
HTML),
trim(\turbo_stream($this->article->fresh())->morph()),
);

// Unsets method
$expected = trim(view('articles._article', [
'article' => $this->article,
])->render());

$this->assertEquals(
trim(<<<HTML
<turbo-stream target="article_{$this->article->id}" action="replace">
<template>{$expected}</template>
</turbo-stream>
HTML),
trim(\turbo_stream($this->article->fresh())->morph()->method()),
);
}

/** @test */
Expand Down
4 changes: 2 additions & 2 deletions tests/Http/ResponseMacrosTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public function append_shorthand_passing_string()
$response = response()
->turboStream()
->append('some_dom_id', 'Hello World')
->toResponse(new Request());
->toResponse(new Request);

$expected = <<<'HTML'
<turbo-stream target="some_dom_id" action="append">
Expand Down Expand Up @@ -944,7 +944,7 @@ public function builds_multiple_turbo_stream_responses()
response()->turboStream()->append($article)->target('append-target-id'),
response()->turboStream()->prepend($article)->target('prepend-target-id'),
response()->turboStream()->remove($article)->target('remove-target-id'),
])->toResponse(new Request());
])->toResponse(new Request);

$expected = collect([
view('turbo-laravel::turbo-stream', [
Expand Down
Loading
Loading