Skip to content

Commit

Permalink
Merge pull request #151 from hotwired-laravel/tm/hotwire-native-migra…
Browse files Browse the repository at this point in the history
…tion

Migrate the Turbo Native helpers to Hotwire Native
  • Loading branch information
tonysm authored Dec 14, 2024
2 parents 42d2269 + c8daf9a commit f5040e0
Show file tree
Hide file tree
Showing 31 changed files with 475 additions and 304 deletions.
2 changes: 1 addition & 1 deletion config/turbo-laravel.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
|
*/
'features' => [
Features::turboNativeRoutes(),
Features::hotwireNativeRoutes(),
],

/*
Expand Down
2 changes: 1 addition & 1 deletion docs/csrf.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ Since Turbo.js intercepts form submissions and converts those to fetch requests

With that being said, you may still want to use the `@csrf` Blade directive if you want to support users with JavaScript disabled, since the forms will still work if they contain the CSRF token.

[Continue to Turbo Native...](/docs/{{version}}/turbo-native)
[Continue to Hotwire Native...](/docs/{{version}}/hotwire-native)
6 changes: 3 additions & 3 deletions docs/helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,11 @@ if (request()->wasFromTurboFrame(dom_id($post, 'create_comment'))) {
}
```

### The `request()->wasFromTurboNative()` macro
### The `request()->wasFromHotwireNative()` macro

The `request()->wasFromTurboNative()` macro added to the request class will check if the request came from a Turbo Native client and returns `true` or `false` accordingly.
The `request()->wasFromHotwireNative()` macro added to the request class will check if the request came from a Hotwire Native client and returns `true` or `false` accordingly.

Turbo Native clients are encouraged to override the `User-Agent` header in the WebViews to mention the words `Turbo Native` on them. This is what this macro uses to detect if it came from a Turbo Native client.
Hotwire Native clients are encouraged to override the `User-Agent` header in the WebViews to mention the words `Hotwire Native` on them. This is what this macro uses to detect if it came from a Hotwire Native client.

### The `response()->turboStream()` macro

Expand Down
134 changes: 134 additions & 0 deletions docs/hotwire-native.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Hotwire Native

[TOC]

## Introduction

Hotwire also has a [mobile side](https://native.hotwired.dev/) and Turbo Laravel provides some helpers to help integrating with that.

Turbo visits made by a Hotwire Native client should send a custom `User-Agent` header. Using that header, we can detect in the backend that a request is coming from a Hotwire Native client instead of a regular web browser.

This is useful if you want to customize the behavior a little bit different based on that information. For instance,
you may want to include some elements for mobile users, like a mobile-only CSS file include, for instance. To do that, you may use the `@hotwirenative` Blade directive in your Blade views:

```blade
@hotwirenative
<link rel="stylesheet" href="mobile.css">
@endhotwirenative
```

Alternatively, you may want to include some elements only if the client requesting it is _NOT_ a Hotwire Native client using the `@unlesshotwirenative` Blade helpers:

```blade
@unlesshotwirenative
<h1>Hello, Non-Hotwire Native Users!</h1>
@endunlesshotwirenative
```

You may also check if the request was made from a Hotwire Native visit using the request macro:

```php
if (request()->wasFromHotwireNative()) {
// ...
}
```

Or the Turbo Facade directly, like so:

```php
use HotwiredLaravel\TurboLaravel\Facades\Turbo;

if (Turbo::isHotwireNativeVisit()) {
// ...
}
```

## Interacting With Hotwire Native Navigation

Hotwire Native will hook into Turbo's visits so it displays them on mobile mimicking the mobile way of stacking screens instead of just replace elements on the same screen. This helps the native feel of our hybrid app.

However, sometimes we may need to customize the behavior of form request handler to avoid a weird screen jumping effect happening on the mobile client. Instead of regular redirects, we can send some signals by redirecting to specific routes that are detected by the Hotwire Native client.

For instance, if a form submission request came from a Hotwire Native client, the form was probably rendered on a native modal, which is not part of the screen stack, so we can just tell Turbo to `refresh` the current screen it has on stack instead. There are 3 signals we can send to the Hotwire Native client:

| Signal | Route| Description|
|---|---|---|
| `recede` | `/recede_historical_location` | Go back to previous screen |
| `resume` | `/resume_historical_location` | Stay on the current screen as is |
| `refresh`| `/refresh_historical_location` | Stay on the current screen but refresh |

Sending these signals is a matter of detecting if the request came from a Hotwire Native client and, if so, redirect the user to these signal URLs instead. The Hotwire Native client should detect the redirect was from one of these special routes and trigger the desired behavior.

You may use the `InteractsWithHotwireNativeNavigation` trait on your controllers to achieve this behavior and fallback to a regular redirect if the request wasn't from a Hotwire Native client:

```php
use HotwiredLaravel\TurboLaravel\Http\Controllers\Concerns\InteractsWithHotwireNativeNavigation;

class TraysController extends Controller
{
use InteractsWithHotwireNativeNavigation;

public function store()
{
// Tray creation...

return $this->recedeOrRedirectTo(route('trays.show', $tray));
}
}
```

In this example, when the request to create trays comes from a Hotwire Native client, we're going to redirect to the `/recede_historical_location` URL instead of the `trays.show` route. However, if the request was made from your web app, we're going to redirect the client to the `trays.show` route.

There are a couple of redirect helpers available:

```php
$this->recedeOrRedirectTo(string $url);
$this->resumeOrRedirectTo(string $url);
$this->refreshOrRedirectTo(string $url);
$this->recedeOrRedirectBack(string $fallbackUrl, array $options = []);
$this->resumeOrRedirectBack(string $fallbackUrl, array $options = []);
$this->refreshOrRedirectBack(string $fallbackUrl, array $options = []);
```

It's common to flash messages using the `->with()` method of the Redirect response in Laravel. However, since a Hotwire Native request will never actually redirect somewhere where the flash message will be rendered, the behavior of the `->with()` method was slightly modified too.

If you're setting flash messages like this after a form submission:

```php
use HotwiredLaravel\TurboLaravel\Http\Controllers\Concerns\InteractsWithHotwireNativeNavigation;

class TraysController extends Controller
{
use InteractsWithHotwireNativeNavigation;

public function store()
{
// Tray creation...

return $this->recedeOrRedirectTo(route('trays.show', $tray))
->with('status', __('Tray created.'));
}
}
```

If a request was sent from a Hotwire Native client, the flashed messages will be added to the query string instead of flashed into the session like they'd normally be. In this example, it would redirect like this:

```
/recede_historical_location?status=Tray%20created.
```

In the Hotwire Native client, you should be able to intercept these redirects, retrieve the flash messages from the query string and create native toasts, if you'd like to.

If the request wasn't from a Hotwire Native client, the message would be flashed into the session as normal, and the client would receive a redirect to the `trays.show` route in this case.

If you don't want these routes enabled, feel free to disable them by commenting out the feature on your `config/turbo-laravel.php` file (make sure the Turbo Laravel configs are published):

```php
return [
'features' => [
// Features::hotwireNativeRoutes(),
],
];
```

[Continue to Testing...](/docs/{{version}}/testing)
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
* [Broadcasting](/docs/{{version}}/broadcasting)
* [Validation Response Redirects](/docs/{{version}}/validation-response-redirects)
* [CSRF Protection](/docs/{{version}}/csrf)
* [Turbo Native](/docs/{{version}}/turbo-native)
* [Hotwire Native](/docs/{{version}}/hotwire-native)
* [Testing](/docs/{{version}}/testing)
* [Known Issues](/docs/{{version}}/known-issues)
4 changes: 2 additions & 2 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Turbo Laravel can be installed via [Composer](https://getcomposer.org/):

```bash
composer require hotwired-laravel/turbo-laravel:^2.0.0
composer require hotwired-laravel/turbo-laravel
```

After installing the package, you may run the `turbo:install` Artisan command. This command will add the Turbo.js dependency to your `package.json` file (when you're using Vite and NPM) or to your `routes/importmap.php` file (when it detects that you're using [Importmap Laravel](https://github.com/tonysm/importmap-laravel)). It also publishes some files to your `resources/js` folder, which imports Turbo for you:
Expand All @@ -12,6 +12,6 @@ After installing the package, you may run the `turbo:install` Artisan command. T
php artisan turbo:install
```

Note: Turbo used to work with Livewire, but somewhere around Livewire V3 the bridges stopped working. There's an open issue to investigate Livewire V3 compatibility. If you're into Livewire and would love to use Turbo in a Livewire app (maybe you want to augment your Livewire&Turbo app with Turbo Native or something like that), you're welcome to check out the issue and try to bring the compatibility back. If you wanted an application scaffolding like Laravel Breeze or Laravel Jetstream, checkout Turbo Breeze, our fork of Breeze that sets up a fresh Laravel app using Stimulus, Importmaps, TailwindCSS (via the CLI), and Turbo.
Note: Turbo used to work with Livewire, but somewhere around Livewire V3 the bridges stopped working. There's an open issue to investigate Livewire V3 compatibility. If you're into Livewire and would love to use Turbo in a Livewire app (maybe you want to augment your Livewire & Turbo app with Hotwire Native or something like that), you're welcome to check out the issue and try to bring the compatibility back. If you wanted an application scaffolding like Laravel Breeze or Laravel Jetstream, checkout Turbo Breeze, our fork of Breeze that sets up a fresh Laravel app using Stimulus, Importmaps, TailwindCSS (via the CLI), and Turbo.

[Continue to Overview...](/docs/{{version}}/overview)
16 changes: 8 additions & 8 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

Testing a Hotwired app is like testing a regular Laravel app. However, Turbo Laravel comes with a set of helpers that may be used to ease testing some aspects that are specific to Turbo:

1. **Turbo HTTP Request Helpers**. When you may want to mimic a Turbo visit, or a Turbo Native visit, or a request coming from a Turbo Frame.
1. **Turbo HTTP Request Helpers**. When you may want to mimic a Turbo visit, or a Hotwire Native visit, or a request coming from a Turbo Frame.
1. **Turbo Streams on HTTP Responses.** When you may want to test the Turbo Streams returned from HTTP requests.
1. **Turbo Stream Broadcasts.** When you're either using the broadcast methods on your models using the `Broadcasts` trait, or when you're using [Handmade Turbo Stream Broadcasts](https://turbo-laravel.com/docs/2.x/broadcasting#content-handmade-broadcasts).

Expand Down Expand Up @@ -69,9 +69,9 @@ class CreateCommentsTest extends TestCase
}
```

### Acting as Turbo Native
### Acting as Hotwire Native

Additionally, when you're building a Turbo Native mobile app, you may want to issue a request pretending to be sent from a Turbo Native client. That's done by setting the `User-Agent` header to something that mentions the word `Turbo Native`. The `InteractsWithTurbo` trait also has a `$this->turboNative()` method you may use that automatically sets the header correctly:
Additionally, when you're building a Hotwire Native mobile app, you may want to issue a request pretending to be sent from a Hotwire Native client. That's done by setting the `User-Agent` header to something that mentions the word `Hotwire Native`. The `InteractsWithTurbo` trait also has a `$this->hotwireNative()` method you may use that automatically sets the header correctly:

```php
use HotwiredLaravel\TurboLaravel\Testing\InteractsWithTurbo;
Expand All @@ -87,7 +87,7 @@ class CreateCommentsTest extends TestCase

$this->assertCount(0, $post->comments);

$this->turboNative()->post(route('posts.comments.store', $post), [
$this->hotwireNative()->post(route('posts.comments.store', $post), [
'content' => 'Hello World',
])->assertOk();

Expand All @@ -97,17 +97,17 @@ class CreateCommentsTest extends TestCase
}
```

When using this method, calls to `request()->wasFromTurboNative()` will return `true`. Additionally, the `@turbonative` and `@unlessturbonative` Blade directives will render as expected.
When using this method, calls to `request()->wasFromHotwireNative()` will return `true`. Additionally, the `@hotwirenative` and `@unlesshotwirenative` Blade directives will render as expected.

Additionally, a few macros were added to the `TestResponse` class to make it easier to assert based on the `recede`, `resume`, and `refresh` redirects using the specific assert methods:
A few other macros were added to the `TestResponse` class to make it easier to assert based on the `recede`, `resume`, and `refresh` redirects using the specific assert methods:

| Method | Descrition |
|---|---|
| `assertRedirectRecede(array $with = [])` | Asserts that a redirect was returned to the `/recede_historical_location` route. |
| `assertRedirectResume(array $with = [])` | Asserts that a redirect was returned to the `/resume_historical_location` route. |
| `assertRedirectRefresh(array $with = [])` | Asserts that a redirect was returned to the `/refresh_historical_location` route. |

The `$with` param will ensure that not only the route is correct, but also any flashed message will be included in the query string:
The `$with` argument will ensure that not only the route is correct, but also any flashed message will be included in the query string:

```php
use HotwiredLaravel\TurboLaravel\Testing\InteractsWithTurbo;
Expand All @@ -123,7 +123,7 @@ class CreateCommentsTest extends TestCase

$this->assertCount(0, $post->comments);

$this->turboNative()->post(route('posts.comments.store', $post), [
$this->hotwireNative()->post(route('posts.comments.store', $post), [
'content' => 'Hello World',
])->assertRedirectRecede(['status' => __('Comment created.')]);

Expand Down
134 changes: 0 additions & 134 deletions docs/turbo-native.md

This file was deleted.

Loading

0 comments on commit f5040e0

Please sign in to comment.