From 96786b922b827fd4d0f2b17acc304557f555a949 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sat, 18 Dec 2021 14:32:34 -0300 Subject: [PATCH 1/7] Adds Turbo Frame, Turbo Stream, and Turbo Stream From Blade components --- .../views/components/turbo-frame.blade.php | 7 +++ .../components/turbo-stream-from.blade.php | 1 + .../views/components/turbo-stream.blade.php | 4 ++ resources/views/turbo-stream.blade.php | 6 +-- src/TurboServiceProvider.php | 7 +++ src/Views/Components/Frame.php | 45 ++++++++++++++++++ src/Views/Components/Stream.php | 47 +++++++++++++++++++ src/Views/Components/StreamFrom.php | 32 +++++++++++++ 8 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 resources/views/components/turbo-frame.blade.php create mode 100644 resources/views/components/turbo-stream-from.blade.php create mode 100644 resources/views/components/turbo-stream.blade.php create mode 100644 src/Views/Components/Frame.php create mode 100644 src/Views/Components/Stream.php create mode 100644 src/Views/Components/StreamFrom.php diff --git a/resources/views/components/turbo-frame.blade.php b/resources/views/components/turbo-frame.blade.php new file mode 100644 index 00000000..5c876d6e --- /dev/null +++ b/resources/views/components/turbo-frame.blade.php @@ -0,0 +1,7 @@ +{{ $slot }} diff --git a/resources/views/components/turbo-stream-from.blade.php b/resources/views/components/turbo-stream-from.blade.php new file mode 100644 index 00000000..2cf440fb --- /dev/null +++ b/resources/views/components/turbo-stream-from.blade.php @@ -0,0 +1 @@ + diff --git a/resources/views/components/turbo-stream.blade.php b/resources/views/components/turbo-stream.blade.php new file mode 100644 index 00000000..ea7d29b8 --- /dev/null +++ b/resources/views/components/turbo-stream.blade.php @@ -0,0 +1,4 @@ +@if ($action !== "remove")@endif diff --git a/resources/views/turbo-stream.blade.php b/resources/views/turbo-stream.blade.php index ee643720..10eb7452 100644 --- a/resources/views/turbo-stream.blade.php +++ b/resources/views/turbo-stream.blade.php @@ -1,7 +1,5 @@ - + @if ($partial ?? false) - @endif - + diff --git a/src/TurboServiceProvider.php b/src/TurboServiceProvider.php index 398b8805..60dcbfc0 100644 --- a/src/TurboServiceProvider.php +++ b/src/TurboServiceProvider.php @@ -21,6 +21,7 @@ use Tonysm\TurboLaravel\Http\TurboResponseFactory; use Tonysm\TurboLaravel\Testing\AssertableTurboStream; use Tonysm\TurboLaravel\Testing\ConvertTestResponseToTurboStreamCollection; +use Tonysm\TurboLaravel\Views\Components as TurboComponents; class TurboServiceProvider extends ServiceProvider { @@ -42,6 +43,12 @@ public function boot() $this->loadViewsFrom(__DIR__.'/../resources/views', 'turbo-laravel'); + $this->loadViewComponentsAs('turbo', [ + TurboComponents\StreamFrom::class, + TurboComponents\Stream::class, + TurboComponents\Frame::class, + ]); + $this->bindBladeMacros(); $this->bindRequestAndResponseMacros(); $this->bindTestResponseMacros(); diff --git a/src/Views/Components/Frame.php b/src/Views/Components/Frame.php new file mode 100644 index 00000000..35ea15f4 --- /dev/null +++ b/src/Views/Components/Frame.php @@ -0,0 +1,45 @@ + $this->domId(), + ]); + } + + private function domId(): string + { + if (is_string($this->id)) { + return $this->id; + } + + if ($this->id instanceof Model) { + return dom_id($this->id); + } + + return dom_id(...$this->id); + } +} diff --git a/src/Views/Components/Stream.php b/src/Views/Components/Stream.php new file mode 100644 index 00000000..2be0abaa --- /dev/null +++ b/src/Views/Components/Stream.php @@ -0,0 +1,47 @@ + $this->targetValue(), + ]); + } + + private function targetValue(): string + { + if (is_string($this->target)) { + return $this->target; + } + + if ($this->target instanceof Model) { + return dom_id($this->target); + } + + return dom_id(...$this->target); + } +} diff --git a/src/Views/Components/StreamFrom.php b/src/Views/Components/StreamFrom.php new file mode 100644 index 00000000..dbf58f5c --- /dev/null +++ b/src/Views/Components/StreamFrom.php @@ -0,0 +1,32 @@ + $this->source instanceof HasBroadcastChannel ? $this->source->broadcastChannel() : $this->source, + ]); + } +} From c99e6a6523159b4d2713766baa6738949ba5a73d Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sat, 18 Dec 2021 14:54:48 -0300 Subject: [PATCH 2/7] Apply any attributes to the custom HTML Element --- resources/views/components/turbo-stream-from.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/components/turbo-stream-from.blade.php b/resources/views/components/turbo-stream-from.blade.php index 2cf440fb..d6d420f8 100644 --- a/resources/views/components/turbo-stream-from.blade.php +++ b/resources/views/components/turbo-stream-from.blade.php @@ -1 +1 @@ - + From 79ccf6b2004b80cd4d6e0a790bf8a8bc24ff9b6b Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sat, 18 Dec 2021 14:58:22 -0300 Subject: [PATCH 3/7] Dont use constructor promotion --- src/Views/Components/Frame.php | 11 ++++++++++- src/Views/Components/Stream.php | 7 ++++++- src/Views/Components/StreamFrom.php | 7 ++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Views/Components/Frame.php b/src/Views/Components/Frame.php index 35ea15f4..b37c4cd8 100644 --- a/src/Views/Components/Frame.php +++ b/src/Views/Components/Frame.php @@ -9,13 +9,22 @@ class Frame extends Component { + public string|Model|array $id; + public string|null $src; + public string|null $target; + public string|null $loading; + /** * Create a new component instance. * * @return void */ - public function __construct(public string|Model|array $id, public ?string $target = null, public ?string $loading = null, public ?string $src = null) + public function __construct(string|Model|array $id, ?string $src = null, ?string $target = null, ?string $loading = null) { + $this->id = $id; + $this->src = $src; + $this->target = $target; + $this->loading = $loading; } /** diff --git a/src/Views/Components/Stream.php b/src/Views/Components/Stream.php index 2be0abaa..4c4f1d60 100644 --- a/src/Views/Components/Stream.php +++ b/src/Views/Components/Stream.php @@ -9,6 +9,9 @@ class Stream extends Component { + public string|Model|array $target; + public string $action; + /** * Create a new component instance. * @@ -16,8 +19,10 @@ class Stream extends Component * @param string $action One of the seven Turbo Stream actions: "append", "prepend", "before", "after", "replace", "update", or "remove". * @return void */ - public function __construct(public string|Model|array $target, public string $action) + public function __construct(string|Model|array $target, string $action) { + $this->target = $target; + $this->action = $action; } /** diff --git a/src/Views/Components/StreamFrom.php b/src/Views/Components/StreamFrom.php index dbf58f5c..f1123f5f 100644 --- a/src/Views/Components/StreamFrom.php +++ b/src/Views/Components/StreamFrom.php @@ -7,6 +7,9 @@ class StreamFrom extends Component { + public string|HasBroadcastChannel $source; + public string $type; + /** * Create a new component instance. * @@ -14,8 +17,10 @@ class StreamFrom extends Component * @param string $type The type of channel: "public", "private", or "presence". * @return void */ - public function __construct(public string|HasBroadcastChannel $source, public string $type = 'private') + public function __construct(string|HasBroadcastChannel $source, string $type = 'private') { + $this->source = $source; + $this->type = $type; } /** From e828c1fd1e070dc8721c96a7c6d954b5f281cc7a Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sat, 18 Dec 2021 15:04:57 -0300 Subject: [PATCH 4/7] Dont use 8.x type definitions --- src/Views/Components/Frame.php | 17 ++++++++++++++--- src/Views/Components/Stream.php | 6 ++++-- src/Views/Components/StreamFrom.php | 5 +++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Views/Components/Frame.php b/src/Views/Components/Frame.php index b37c4cd8..02157ff3 100644 --- a/src/Views/Components/Frame.php +++ b/src/Views/Components/Frame.php @@ -9,17 +9,28 @@ class Frame extends Component { - public string|Model|array $id; - public string|null $src; + /** @var string|Model|array */ + public $id; + + /** @var string|null */ + public $src; + + /** @var string|null */ public string|null $target; + + /** @var string|null */ public string|null $loading; /** * Create a new component instance. * + * @param string|Model|array $id + * @param string|null $src + * @param string|null $target + * @param string|null $loading * @return void */ - public function __construct(string|Model|array $id, ?string $src = null, ?string $target = null, ?string $loading = null) + public function __construct($id, $src = null, $target = null, $loading = null) { $this->id = $id; $this->src = $src; diff --git a/src/Views/Components/Stream.php b/src/Views/Components/Stream.php index 4c4f1d60..d9da1319 100644 --- a/src/Views/Components/Stream.php +++ b/src/Views/Components/Stream.php @@ -9,7 +9,9 @@ class Stream extends Component { - public string|Model|array $target; + /** @var string|Model|array */ + public $target; + public string $action; /** @@ -19,7 +21,7 @@ class Stream extends Component * @param string $action One of the seven Turbo Stream actions: "append", "prepend", "before", "after", "replace", "update", or "remove". * @return void */ - public function __construct(string|Model|array $target, string $action) + public function __construct($target, string $action) { $this->target = $target; $this->action = $action; diff --git a/src/Views/Components/StreamFrom.php b/src/Views/Components/StreamFrom.php index f1123f5f..972fcae8 100644 --- a/src/Views/Components/StreamFrom.php +++ b/src/Views/Components/StreamFrom.php @@ -7,7 +7,8 @@ class StreamFrom extends Component { - public string|HasBroadcastChannel $source; + /** @var string|HasBroadcastChannel */ + public $source; public string $type; /** @@ -17,7 +18,7 @@ class StreamFrom extends Component * @param string $type The type of channel: "public", "private", or "presence". * @return void */ - public function __construct(string|HasBroadcastChannel $source, string $type = 'private') + public function __construct($source, string $type = 'private') { $this->source = $source; $this->type = $type; From 9ad53ab11d9760d2871d0e7d6962f5528e88c63c Mon Sep 17 00:00:00 2001 From: tonysm Date: Sat, 18 Dec 2021 18:05:19 +0000 Subject: [PATCH 5/7] Fix styling --- src/Views/Components/Frame.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/Components/Frame.php b/src/Views/Components/Frame.php index 02157ff3..3f0b548a 100644 --- a/src/Views/Components/Frame.php +++ b/src/Views/Components/Frame.php @@ -13,7 +13,7 @@ class Frame extends Component public $id; /** @var string|null */ - public $src; + public $src; /** @var string|null */ public string|null $target; From 4ec42041c0cd4a61c680d9d0a1b69d235f167c4b Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Mon, 20 Dec 2021 22:18:55 -0300 Subject: [PATCH 6/7] Document the new blade components --- README.md | 83 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index fca110ef..2a7e143d 100644 --- a/README.md +++ b/README.md @@ -163,12 +163,12 @@ Here's how you can use Turbo Frames: Turbo Frames also allows you to lazy-load the frame's content. You may do so by adding a `src` attribute to the Turbo Frame tag. The conetnt of a lazy-loading Turbo Frame tag can be used to indicate "loading states", such as: ```blade - +

Loading...

``` -Turbo will automatically fire a GET AJAX request as soon as a lazy-loading Turbo Frame enters the DOM and replace its content with a matching Turbo Frame in the response. +Turbo will automatically dispatch a GET AJAX request as soon as a lazy-loading Turbo Frame enters the DOM and replace its content with a matching Turbo Frame in the response. You may also trigger a Turbo Frame with forms and links that are _outside_ of such frames by pointing to them like so: @@ -185,17 +185,17 @@ You could also "hide" this link and trigger a "click" event with JavaScript prog So far, all vanilla Hotwire and Turbo. -### Blade Directives and Helper Functions +### Blade Components, Directives, and Helper Functions Since Turbo rely a lot on DOM IDs, the package offers a helper to generate unique DOM IDs based on your models. You may use the `@domid` Blade Directive in your Blade views like so: ```blade - + - + ``` -This will generate a DOM ID string using your model's basename and its ID, such as `comment_123`. You may also give it a _content_ that will prefix your DOM ID, such as: +This will generate a DOM ID string using your model's basename and its ID, such as `comment_123`. You may also give it a prefix that will added to the DOM ID, such as: ```blade (99) @@ -203,7 +203,17 @@ This will generate a DOM ID string using your model's basename and its ID, such Which will generate a `comments_count_post_123` DOM ID. -The package also ships with a namespaced `dom_id()` helper function so you can use it outside of your own views: +You may also prefer using the `` Blade component that ships with the package. This way, you don't need to worry about using the `@domid()` helper for your Turbo Frame: + +```blade +(99) +``` + +To the `:id` prop, you may pass a string, which will be used as-is as the DOM ID, an Eloquent model instance, which will be passed to the `dom_id()` function that ships with the package (the same one as the `@domid()` Blade directive uses behind the scenes), or an array tuple where the first item is an instance of an Eloquent model and the second is the prefix of the DOM ID. + +Additionally, you may also pass along any prop that is supported by the Turbo Frame custom Element to the `` Blade component, like `target`, `src`, or `loading`. These are the listed attributes, but you any other attribute will also be forwarded to the `` tag that will be rendered using the `` component. For a full list of what's possible to do with Turbo Frames, see the [documentation](https://turbo.hotwired.dev/handbook/frames). + +The mentioned namespaced `dom_id()` helper function may also be used from anywhere in your application, like so: ```php use function Tonysm\TurboLaravel\dom_id; @@ -218,13 +228,13 @@ These helpers strip out the model's FQCN (see [config/turbo-laravel.php](config/ ### Turbo Streams -As mentioned earlier, out of everything Turbo provides, it's Turbo Streams that benefit the most from a back-end integration. +As mentioned earlier, out of everything Turbo provides, it's Turbo Streams that benefits the most from a back-end integration. -Turbo Drive will get your pages behaving like an SPA and Turbo Frames will allow you to have a finer grained control of chunks of your page instead of replace the entire page when a form is submitted or a link is clicked. +Turbo Drive will get your pages behaving like an SPA and Turbo Frames will allow you to have a finer grained control of chunks of your page instead of replacing the entire page when a form is submitted or a link is clicked. -However, sometimes you want to update _multiple_ parts of you page at the same time. For instance, after a form submission to create a comment, you may want to append the comment to the comment's list and also update the comment's count in the page. You may achieve that with Turbo Streams. +However, sometimes you want to update _multiple_ parts of your page at the same time. For instance, after a form submission to create a comment, you may want to append the comment to the comment's list and also update the comment's count in the page. You may achieve that with Turbo Streams. -Any non-GET form submission will get annotated by Turbo with a `Content-Type: text/vnd.turbo-stream.html` header (besides the other normal Content Types). This will indicate your back-end that you can return a Turbo Stream response for that form submission if you want to. +Form submissions will get annotated by Turbo with a `Content-Type: text/vnd.turbo-stream.html` header (besides the other normal Content Types). This will indicate to your back-end that you can return a Turbo Stream response for that form submission if you want to. Here's an example of a route handler detecting and returning a Turbo Stream response to a form submission: @@ -244,10 +254,12 @@ The `request()->wantsTurboStream()` macro added to the request will check if the Here's what the HTML response will look like: -```blade +```html ``` @@ -258,7 +270,7 @@ Most of these things were "guessed" based on the [naming conventions](#conventio return response()->turboStream($comment)->target('post_comments'); ``` -The model is optional, as it's only used to figure out the defaults based on the model state. You could manually create that same response like so: +Although it's handy to pass the model instance to the `turboStream()` response macro - which will be used to decide the default values of the Turbo Stream response based on the model's current state, sometimes you may want to build a Turbo Stream response manually, which can be achieved like so: ```php return response()->turboStream() @@ -289,7 +301,7 @@ response()->turboStream()->remove($comment); You can read more about Turbo Streams in the [Turbo Handbook](https://turbo.hotwired.dev/handbook/streams). -These shorthand methods return a pending object for the response which you can chain and override everything you want on it: +These shorthand methods return a pending object for the response which you can chain and override everything you want before it's rendered: ```php return response()->turboStream() @@ -317,14 +329,12 @@ return response()->turboStream([ ]); ``` -Although this is an option, it might feel like too much work for a controller. If that's the case, use [Custom Turbo Stream Views](#custom-turbo-stream-views). +Although this is a valid option, it might feel like too much work for a controller. If that's the case, use [Custom Turbo Stream Views](#custom-turbo-stream-views). ### Custom Turbo Stream Views -If you're not using the model partial [convention](#conventions) or if you have some more complex Turbo Stream constructs, you may use the `response()->turboStreamView()` version instead and specify your own Turbo Stream views. - -This is what it looks like: +If you're not using the model partial [convention](#conventions) or if you have some more complex Turbo Stream constructs to build, you may use the `response()->turboStreamView()` version instead and specify your own Blade view where Turbo Streams will be created. This is what that looks like: ```php return response()->turboStreamView('comments.turbo.created_stream', [ @@ -337,7 +347,7 @@ And here's an example of a more complex custom Turbo Stream view: ```blade @include('layouts.turbo.flash_stream') - + @@ -356,6 +366,16 @@ Remember, these are Blade views, so you have the full power of Blade at your han @endif ``` +Similar to the `` Blade component, there's also a `` Blade component that can simplify things quite a bit. It has the same convention of figureing out the DOM ID of the target when you're passing a model instance or an array as the `` component applied to the `target` attribute here. When using the component version, there's also no need to specify the template wrapper for the Turbo Stream tag, as that will be added by the component itself. So, the same example would look something like this: + +```blade +@include('layouts.turbo.flash_stream') + + + @include('comments._comment', ['comment' => $comment]) + +``` + I hope you can see how powerful this can be to reusing views. @@ -571,33 +591,20 @@ You may listen to a Turbo Stream broadcast message on your pages by adding the c ```blade ``` -By default, it expects a private channel, so the tag must be used in a page for already authenticated users. You can control the channel type in the tag with a `type` attribute. +You may prefer using the convenient `` Blade component, passing the model as the `source` prop to it, something like this: ```blade - + ``` -As this convention is not built into Laravel, you can use the model's `broadcastChannel()` method: +By default, it expects a private channel, so the it must be used in a page for already authenticated users. You may control the channel type in the tag with a `type` attribute. ```blade - -``` - -There is also a helper blade directive that you can use to generate the channel name for your models using the same convention if you want to: - -```blade - + ``` To register the Broadcast Auth Route you may use Laravel's built-in conventions as well: From 23e8a5ebd062d97ceedc81b66cd324fb7d5b005c Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Mon, 20 Dec 2021 22:29:18 -0300 Subject: [PATCH 7/7] Add a more realistic example for streams combo --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2a7e143d..a20f778d 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ The `request()->wantsTurboStream()` macro added to the request will check if the Here's what the HTML response will look like: ```html - +