This package gives you a set of conventions to make the most out of Hotwire in Laravel

Overview

Logo Turbo Laravel

Total Downloads Latest Stable Version License

Introduction

This package gives you a set of conventions to make the most out of Hotwire in Laravel (inspired by the turbo-rails gem).

There is a companion application that shows how to use the package and its conventions.

Installation

Turbo Laravel may be installed via composer:

composer require tonysm/turbo-laravel

After installing, you may execute the turbo:install Artisan command, which will add a couple JS dependencies to your package.json file, publish some JS scripts to your resources/js folder that configures Turbo.js for you:

php artisan turbo:install

Next, you may install your JS dependencies and compile the assets so the changes take effect:

npm install
npm run dev

If you are using Jetstream with Livewire, you may add the --jet flag to the turbo:install Artisan command, which will add a couple more JS dependencies to make sure Alpine.js works nicely with Turbo.js. This will also changes a couple lines to the layout files that ships with Jetstream, which will make sure Livewire works nicely as well:

php artisan turbo:install --jet

Then, you can run install your NPM dependencies and compile your assets normally.

These are the dependencies needed so Jetstream with Livewire works with Turbo.js:

  • Livewire Turbo Plugin needed so Livewire works nicely. This one will be added to your Jetstream layouts as script tags fetching from a CDN (both app.blade.php and guest.blade.php)

You may also optionally install Stimulus.js passing --stimulus flag to the turbo:install Artisan command:

php artisan turbo:install --stimulus

Here's the full list of flags:

php artisan turbo:install --jet --stimulus

Turbo HTTP Middleware

The package ships with a middleware which applies some conventions on your redirects, specially around how failed validations are handled automatically by Laravel. Read more about this in the Conventions section of the documentation.

You may add the middleware to the "web" route group on your HTTP Kernel:

\Tonysm\TurboLaravel\Http\Middleware\TurboMiddleware::class,

Like so:

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    protected $middlewareGroups = [
        'web' => [
            // ...
            \Tonysm\TurboLaravel\Http\Middleware\TurboMiddleware::class,
        ],
    ];
}

Keep reading the documentation to have a full picture on how you can make the most out of the technique.

Documentation

It's highly recommended reading the Turbo Handbook. Out of everything Turbo provides, it's Turbo Streams that benefits the most from a tight integration with Laravel. We can generate Turbo Streams from your models and either return them from HTTP responses or broadcast your model changes over WebSockets using Laravel Echo.

Conventions

First of all, none of these conventions are mandatory. Feel free to pick the ones you like and also add your own. With that out of the way, here's a list of some conventions that I find helpful:

  • You may want to use resource routes for most things (posts.index, posts.store, etc)
  • You may want to split your views into smaller chunks or partials (small portions of HTML for specific fragments), such as comments/_comment.blade.php that displays a comment resource, or comments/_form.blade.php for the form to either create/update comments. This will allow you to reuse these partials in Turbo Streams
  • Your model's partial (such as the comments/_comment.blade.php for a Comment model, for example) may only rely on having a $comment instance passed to it. When broadcasting your model changes and generating the Turbo Streams in background, the package will pass the model instance using the model's basename in camelCase to that partial - although you can fully control this behavior
  • You may use the model's Fully Qualified Class Name, or FQCN for short, on your Broadcasting Channel authorization routes with a wildcard, such as App.Models.Comment.{comment} for a Comment model living in App\\Models\\ - the wildcard's name doesn't matter. This is now the default broadcasting channel in Laravel (see here).

In the Overview section below you will see how to override most of the default behaviors, if you want to.

Overview

Once the assets are compiled, you will have Turbo-specific custom HTML tags that you may annotate your views with (Turbo Frames and Turbo Streams). This is vanilla Hotwire. Again, it's recommended to read the Turbo Handbook. Once you understand how these few pieces work together, the challenge will be in decomposing your UI to work as you want them to.

Notes on Turbo Drive and Turbo Frames

To keep it short, Turbo Drive will turn links and form submissions into AJAX requests and will replace the page with the response. That's useful when you want to navigate to another page entirely.

If you want some elements to persist across these navigations, you may annotate these elements with a DOM ID and add the data-turbo-permanent custom attribute to them. As long as the response also contains an element with the same ID and data-turbo-permanent, Turbo will not touch it.

Sometimes you don't want the entire page to change, but instead just a portion of the page. That's what Turbo Frames are all about. Links and Form submissions that are trapped inside a Turbo Frame tag (or that point to one!) will instruct Turbo Drive to NOT replace the entire body of the document, but instead to look for a matching Turbo Frame in the response using its DOM ID and replace that specific portion of the page.

Here's how you can use Turbo Frames:

Hello, World!

I'm a trigger. My response must have a matching Turbo Frame tag (same ID) ">
<turbo-frame id="my_frame">
    <h1>Hello, World!h1>
    <a href="/somewhere">
        I'm a trigger. My response must have a matching Turbo Frame tag (same ID)
    a>
turbo-frame>

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:

Loading...

">
<turbo-frame id="my_frame" src="{{ route('my.page') }}">
    <p>Loading...p>
turbo-frame>

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.

You may also trigger a Turbo Frame with forms and links that are outside of such frames by pointing to them like so:

I'm a link
">
<div>
    <a href="/somewhere" data-turbo-frame="my_frame">I'm a linka>

    <turbo-frame id="my_frame">turbo-frame>
div>

You could also "hide" this link and trigger a "click" event with JavaScript programmatically to trigger the Turbo Frame to reload, for example.

So far, all vanilla Hotwire and Turbo.

Blade 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:

">
<turbo-frame id="@domid($comment)">
    
turbo-frame>

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:

(99) ">
<turbo-frame id="@domid($post, 'comments_count')">(99)turbo-frame>

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:

use function Tonysm\TurboLaravel\dom_id;

dom_id($comment);

When a new instance of a model is passed to any of these DOM ID helpers, since it doesn't have an ID, it will prefix the resource anme with a create_ prefix. This way, new instances of an App\\Models\\Comment model will generate a create_comment DOM ID.

These helpers strip out the model's FQCN (see config/turbo-laravel.php if you use an unconventional location for your models).

Turbo Streams

As mentioned earlier, out of everything Turbo provides, it's Turbo Streams that benefit 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.

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.

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.

Here's an example of a route handler detecting and returning a Turbo Stream response to a form submission:

Route::post('posts/{post}/comments', function (Post $post) {
    $comment = $post->comments()->create(/** params */);

    if (request()->wantsTurboStream()) {
        return response()->turboStream()->append($comment);
    }

    return back();
});

The request()->wantsTurboStream() macro added to the request will check if the request accepts Turbo Stream and return true or false accordingly.

Here's what the HTML response will look like:

">
<turbo-stream action="append" target="comments">
    <template>
        @include('comments._comment', ['comment' => $comment])
    template>
turbo-stream>

Most of these things were "guessed" based on the naming conventions we talked about earlier. But you can override most things, like so:

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:

return response()->turboStream()
    ->target('comments')
    ->action('append')
    ->view('comments._comment', ['comment' => $comment]);

There are 7 actions in Turbo Streams. They are:

  • append & prepend: to add the elements in the target element at the top or at the bottom of its contents, respectively
  • before & after: to add the elements next to the target element before or after, respectively
  • replace: will replace the existing element entirely with the contents of the template tag in the Turbo Stream
  • update: will keep the target and only replace the contents of it with the contents of the template tag in the Turbo Stream
  • remove: will remove the element. This one doesn't need a tag. It accepts either an instance of a Model or the DOM ID of the element to be removed as a string.

Which means you will find shorthand methods for them all, like:

response()->turboStream()->append($comment);
response()->turboStream()->prepend($comment);
response()->turboStream()->before($comment, 'target_dom_id');
response()->turboStream()->after($comment, 'target_dom_id');
response()->turboStream()->replace($comment);
response()->turboStream()->update($comment);
response()->turboStream()->remove($comment);

You can read more about Turbo Streams in the Turbo Handbook.

These shorthand methods return a pending object for the response which you can chain and override everything you want on it:

return response()->turboStream()
    ->append($comment)
    ->view('comments._comment_card', ['comment' => $comment]);

As mentioned earlier, passing a model to the response()->turboStream() macro will pre-fill the pending response object with some defaults based on the model's state.

It will build a remove Turbo Stream if the model was deleted (or if it is trashed - in case it's a Soft Deleted model), an append if the model was recently created (which you can override the action as the second parameter of the macro), a replace if the model was just updated (you can also override the action as the second parameter.) Here's how overriding would look like:

return response()->turboStream($comment, 'append');

Custom Turbo Stream Views

If you're not using the model partial convention 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:

return response()->turboStreamView('comments.turbo.created_stream', [
    'comment' => $comment,
]);

And here's an example of a more complex custom Turbo Stream view:

">
@include('layouts.turbo.flash_stream')

<turbo-stream target="comments" action="append">
    <template>
        @include('comments._comment', ['comment' => $comment])
    template>
turbo-stream>

Remember, these are Blade views, so you have the full power of Blade at your hands. In this example, we're including a shared Turbo Stream partial which could append any flash messages we may have. That layouts.turbo.flash_stream could look like this:

@endif ">
@if (session()->has('status'))
<turbo-stream target="notice" action="append">
    <template>
        @include('layouts._flash')
    template>
turbo-stream>
@endif

I hope you can see how powerful this can be to reusing views.

Broadcasting Turbo Streams Over WebSockets With Laravel Echo

So far, we have used Turbo Streams over HTTP to handle the case of updating multiple parts of the page for a single user after a form submission. In addition to that, you may want to broadcast model changes over WebSockets to all users that are viewing the same page. Although nice, you don't have to use WebSockets if you don't have the need for it. You may still benefit from Turbo Streams over HTTP.

Those same Turbo Stream responses we are returning to a user after a form submission, we can also send those to other users connected to a Laravel Echo channel and have their pages update reflecting the model change made by other users.

You may still feed the user making the changes with Turbo Streams over HTTP and broadcast the changes to other users over WebSockets. This way, the user making the change will have an instant feedback compared to having to wait for a background worker to pick up the job and send it to them over WebSockets.

First, setup the Laravel Broadcasting component for your app. One of the first steps is to configure your environment variables to something that looks like this:

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=us2
PUSHER_APP_HOST=websockets.test
PUSHER_APP_PORT=6001

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
MIX_PUSHER_APP_HOST="localhost"
MIX_PUSHER_APP_PORT="${PUSHER_APP_PORT}"
MIX_PUSHER_APP_USE_SSL=false

Notice that some of these environment variables are used by your front-end assets during compilation. That's why you see some duplicates that are just prefixed with MIX_.

These settings assume you're using the Laravel WebSockets package. Check out the Echo configuration at resources/js/bootstrap.js to see which environment variables are needed during build time. You may also use Pusher or Ably instead of the Laravel WebSockets package, if you don't want to host it yourself.

Broadcasting Model Changes

With Laravel Echo properly configured, you may now broadcast model changes using WebSockets. First thing you need to do is use the Broadcasts trait in your model:

use Tonysm\TurboLaravel\Models\Broadcasts;

class Comment extends Model
{
    use Broadcasts;
}

This trait will add some methods to your model that you can use to trigger broadcasts. Here's how you can broadcast appending a new comment to all users visiting the post page:

Route::post('posts/{post}/comments', function (Post $post) {
    $comment = $post->comments()->create(/** params */);

    $comment->broadcastAppend()->later();

    if (request()->wantsTurboStream()) {
        return response()->turboStream($comment);
    }

    return back();
});

Here are the methods now available to your model:

$comment->broadcastAppend();
$comment->broadcastPrepend();
$comment->broadcastBefore('target_dom_id');
$comment->broadcastAfter('target_dom_id');
$comment->broadcastReplace();
$comment->broadcastUpdate();
$comment->broadcastRemove();

These methods will assume you want to broadcast the Turbo Streams to your model's channel. However, you will also find alternative methods where you can specify either a model or the broadcasting channels you want to send the broadcasts to:

$comment->broadcastAppendTo($post);
$comment->broadcastPrependTo($post);
$comment->broadcastBeforeTo($post, 'target_dom_id');
$comment->broadcastAfterTo($post, 'target_dom_id');
$comment->broadcastReplaceTo($post);
$comment->broadcastUpdateTo($post);
$comment->broadcastRemoveTo($post);

These broadcastXTo() methods accept either a model, a channel instance or an array containing both of these. When it receives a model, it will guess the channel name using the broadcasting channel convention (see #conventions).

All of these broadcasting methods return an instance of a PendingBroadcast class that will only dispatch the broadcasting job when that pending object is being garbage collected. Which means that you can control a lot of the properties of the broadcast by chaining on that instance before it goes out of scope, like so:

$comment->broadcastAppend()
    ->to($post)
    ->view('comments/_custom_view_partial', [
        'comment' => $comment,
        'post' => $post,
    ])
    ->toOthers() // Do not send to the current user.
    ->later(); // Dispatch a background job to send.

You may want to hook those methods in the model events of your model to trigger Turbo Stream broadcasts whenever your models are changed in any context, such as:

class Comment extends Model
{
    use Broadcasts;

    protected static function booted()
    {
        static::created(function (Comment $comment) {
            $comment->broadcastPrependTo($comment->post)
                ->toOthers()
                ->later();
        });

        static::updated(function (Comment $comment) {
            $comment->broadcastReplaceTo($comment->post)
                ->toOthers()
                ->later();
        });

        static::deleted(function (Comment $comment) {
            $comment->broadcastRemoveTo($comment->post)
                ->toOthers()
                ->later();
        });
    }
}

In case you want to broadcast all these changes automatically, instead of specifying them all, you may want to add a $broadcasts property to your model, which will instruct the Broadcasts trait to trigger the Turbo Stream broadcasts for the created, updated and deleted model events, like so:

class Comment extends Model
{
    use Broadcasts;

    protected $broadcasts = true;
}

This will achieve almost the same thing as the example where we registered the model events manually, with a couple nuanced differences. First, by default, it will broadcast an append Turbo Stream to newly created models. You may want to use prepend instead. You can do so by using an array with a insertsBy key and prepend action as value instead of a boolean, like so:

class Comment extends Model
{
    use Broadcasts;

    protected $broadcasts = [
        'insertsBy' => 'prepend',
    ];
}

This will also automatically hook into the model events, but instead of broadcasting new instances as append it will use prepend.

Secondly, it will send all changes to this model's broadacsting channel. In our case, we want to direct the broadcasts to the post linked to this model instead. We can achieve that by adding a $broadcastsTo property to the model, like so:

class Comment extends Model
{
    use Broadcasts;

    protected $broadcasts = [
        'insertsBy' => 'prepend',
    ];

    protected $broadcastsTo = 'post';

    public function post()
    {
        return $this->belongsTo(Post::class);
    }
}

That property can either be a string that contains to the name of a relationship of this model or an array of relationships.

Alternatively, you may prefer to have more control over where these broadcasts are being sent to by implementing a broadcastsTo method in your model instead of using the property. This way, you can return a single model, a broadcasting channel instance or an array containing either of them, like so:

use Illuminate\Broadcasting\Channel;

class Comment extends Model
{
    use Broadcasts;

    protected $broadcasts = [
        'insertsBy' => 'prepend',
    ];

    public function post()
    {
        return $this->belongsTo(Post::class);
    }

    public function broadcastsTo()
    {
        return [
            $this,
            $this->post,
            new Channel('full-control'),
        ];
    }
}

Listening to Turbo Stream Broadcasts

You may listen to a Turbo Stream broadcast message on your pages by adding the custom HTML tag that is published to your application's assets (see here). You need to pass the channel you want to listen to broadcasts on using the channel attribute of this element, like so.

id }}" /> ">
<turbo-echo-stream-source
    channel="App.Models.Comments.{{ $comment->id }}"
/>

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.

id }}" type="presence" /> ">
<turbo-echo-stream-source
    channel="App.Models.Comments.{{ $comment->id }}"
    type="presence"
/>

As this convention is not built into Laravel, you can use the model's broadcastChannel() method:

broadcastChannel() }}" /> ">
<turbo-echo-stream-source
    channel="{{ $comment->broadcastChannel() }}"
/>

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:

">
<turbo-echo-stream-source
    channel="@channel($comment)"
/>

To register the Broadcast Auth Route you may use Laravel's built-in conventions as well:

// file: routes/channels.php

use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Broadcast;

Broadcast::channel(Post::class, function (User $user, Post $post) {
    return $user->belongsToTeam($post->team);
});

You may want to read the Laravel Broadcasting documentation.

Broadcasting Turbo Streams to Other Users Only

As mentioned erlier, you may want to feed the current user with Turbo Streams using HTTP requests and only send the broadcasts to other users. There are a couple ways you can achieve that.

First, you can chain on the broadcasting methods, like so:

$comment->broadcastAppendTo($post)
    ->toOthers();

Second, you can use the Turbo Facade like so:

use Tonysm\TurboLaravel\Facades\Turbo;

Turbo::broadcastToOthers(function () {
    // ...
});

This way, any broadcast that happens inside the scope of the Closure will only be sent to other users.

Third, you may use that same method but without the Closure inside a ServiceProvider, for instance, to instruct the package to only send turbo stream broadcasts to other users globally:



namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Tonysm\TurboLaravel\Facades\Turbo;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Turbo::broadcastToOthers();
    }
}

Validation Response Redirects

By default, Laravel will redirect failed validation exceptions "back" to the page the triggered the request. This is a bit problematic when it comes to Turbo Frames, since a form might be included in a page that don't render the form initially, and after a failed validation exception from a form submission we would want to re-render the form with the invalid messages.

In other words, a Turbo Frame inherits the context of the page where it was inserted in, and a form might not be part of that page itself. We can't redirect "back" to display the form again with the error messages, because the form might not be re-rendered there by default. Instead, we have two options:

  1. Render a Blade view with the form as a non-200 HTTP Status Code, then Turbo will look for a matching Turbo Frame inside the response and replace only that portion or page, but it won't update the URL as it would for other Turbo Visits; or
  2. Redirect the request to a page that renders the form directly instead of "back". There you can render the validation messages and all that. Turbo will follow the redirect (303 Status Code) and fetch the Turbo Frame with the form and invalid messages and update the existing one.

When using the \Tonysm\TurboLaravel\Http\Middleware\TurboMiddleware middleware that ships with the package on your HTTP Kernel's "web" route group, it will override Laravel's default handling for failed validation exceptions.

For any route name ending in .store, it will redirect to a .create route for the same resource with all the route params from the previous request. In the same way, for any .update routes, it will redirect to a .edit route of the same resource.

Examples:

  • posts.comments.store will redirect to posts.comments.create with the {post} route param.
  • comments.store will redirect to comments.create with no route params.
  • comments.update will redirect to comments.edit with the {comment} param.

If a guessed route name doesn't exist, the middleware will not change the redirect response. You may override this behavior by catching the ValidationException yourself and re-throwing it overriding the redirect with the redirectTo method. If the exception has that, the middleware will respect it.

public function store()
{
  try {
     request()->validate(['name' => 'required']);
  } catch (\Illuminate\Validation\ValidationException $exception) {
    throw $exception->redirectTo(url('/somewhere'));
  }
}

You may also catch the ValidationException and return a non-200 response, if you want to.

Turbo Native

Hotwire also has a mobile side, and the package provides some goodies on this front too.

Turbo Visits made by a Turbo Native client will send a custom User-Agent header. So we added another Blade helper you may use to toggle fragments or assets (such as mobile specific stylesheets) on and off depending on whether your page is being rendered for a Native app or a Web app:

@turbonative
    <h1>Hello, Turbo Native Users!h1>
@endturbonative

Alternatively, you can check if it's not a Turbo Native visit using the @unlessturbonative Blade helpers:

@unlessturbonative
    <h1>Hello, Non-Turbo Native Users!h1>
@endunlessturbonative

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

if (request()->wasFromTurboNative()) {
    // ...
}

Or the Turbo Facade directly, like so:

use Tonysm\TurboLaravel\Facades\Turbo;

if (Turbo::isTurboNativeVisit()) {
    // ...
}

Testing Helpers

There are two aspects of your application using Turbo Laravel that are specific this approach itself:

  1. Turbo Stream HTTP responses. As you return Turbo Stream responses from your route handlers/controllers to be applied by Turbo itself; and
  2. Turbo Stream broadcasts. Which is the side-effect of certain model changes or whenever you call $model->broadcastAppend() on your models, for instance.

We're going to cover both of these scenarios here.

Making Turbo & Turbo Native HTTP requests

To enhance your testing capabilities here, Turbo Laravel adds a couple of macros to the TestResponse that Laravel uses under the hood. The goal is that testing Turbo Stream responses is as convenient as testing regular HTTP responses.

To mimic Turbo requests, which means sending a request setting the correct Content-Type in the Accept: HTTP header, you need to use the InteractsWithTurbo trait to your testcase. Now you can mimic a Turbo HTTP request by using the $this->turbo() method before you make the HTTP call itself. You can also mimic Turbo Native specific requests by using the $this->turboNative() also before you make the HTTP call. The first method will add the correct Turbo Stream content type to the Accept: header, and the second method will add Turbo Native User-Agent: value.

These methods are handy when you are conditionally returning Turbo Stream responses based on the request()->wantsTurboStream() helper, for instance. Or when using the @turbonative or @unlessturbonative Blade directives.

Testing Turbo Stream HTTP Responses

You can test if you got a Turbo Stream response by using the assertTurboStream. Similarly, you can assert that your response is not a Turbo Stream response by using the assertNotTurboStream() macro:

use Tonysm\TurboLaravel\Testing\InteractsWithTurbo;

class CreateTodosTest extends TestCase
{
    use InteractsWithTurbo;

    /** @test */
    public function creating_todo_from_turbo_request_returns_turbo_stream_response()
    {
        $response = $this->turbo()->post(route('todos.store'), [
            'content' => 'Test the app',
        ]);

        $response->assertTurboStream();
    }

    /** @test */
    public function creating_todo_from_regular_request_does_not_return_turbo_stream_response()
    {
        // Notice we're not chaining the `$this->turbo()` method here.
        $response = $this->post(route('todos.store'), [
            'content' => 'Test the app',
        ]);

        $response->assertNotTurboStream();
    }
}

The controller for such response would be something like this:

class TodosController
{
    public function store()
    {
        $todo = auth()->user()->todos()->create(request()->validate([
            'content' => ['required'],
        ]));

        if (request()->wantsTurboStream()) {
            return response()->turboStream($todo);
        }

        return redirect()->route('todos.index');
    }
}

Fluent Turbo Stream Testing

You can get specific on your Turbo Stream responses by passing a callback to the assertTurboStream(fn) method. This can be used to test that you have a specific Turbo Stream tag being returned, or that you're returning exactly 2 Turbo Stream tags, for instance:

/** @test */
public function create_todos()
{
    $this->get(route('todos.store'))
        ->assertTurboStream(fn (AssertableTurboStream $turboStreams) => (
            $turboStreams->has(2)
            && $turboStreams->hasTurboStream(fn ($turboStream) => (
                $turboStream->where('target', 'flash_messages')
                            ->where('action', 'prepend')
                            ->see('Todo was successfully created!')
            ))
            && $turboStreams->hasTurboStream(fn ($turboStream) => (
                $turboStream->where('target', 'todos')
                            ->where('action', 'append')
                            ->see('Test the app')
            ))
        ));
}

Testing Turbo Stream Broadcasts

Every broadcast will be dispatched using the Tonysm\TurboLaravel\Jobs\BroadcastAction job (either to a worker or process synchronously). You may also use that to test your broadcasts like so:

use App\Models\Todo;
use Tonysm\TurboLaravel\Jobs\BroadcastAction;

class CreatesCommentsTest extends TestCase
{
    /** @test */
    public function creates_comments()
    {
        Bus::fake(BroadcastAction::class);

        $todo = Todo::factory()->create();

        $this->turbo()->post(route('todos.comments.store', $todo), [
            'content' => 'Hey, this is really nice!',
        ])->assertTurboStream();

        Bus::assertDispatched(function (BroadcastAction $job) use ($todo) {
            return count($job->channels) === 1
                && $job->channels[0]->name === sprintf('private-%s', $todo->broadcastChannel())
                && $job->target === 'comments'
                && $job->action === 'append'
                && $job->partial === 'comments._comment'
                && $job->partialData['comment']->is(
                    $todo->comments->first()
                );
        });
    }
}

Note: make sure your turbo-laravel.queue config key is set to false, otherwise actions may not be dispatched during test because the model observer only fires them after the transaction is commited, which never happens in tests since they run inside a transaction.

Closing Notes

Try the package out. Use your Browser's DevTools to inspect the responses. You will be able to spot every single Turbo Frame and Turbo Stream happening.

"The proof of the pudding is in the eating."

Make something awesome!

Testing the Package

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Drop me an email at [email protected] if you want to report security vulnerabilities.

License

The MIT License (MIT). Please see License File for more information.

Credits

Comments
  • Lazy-load url clashes with Laravel back() and previous()

    Lazy-load url clashes with Laravel back() and previous()

    Think I've found a bug-ish? Say you have a lazy-loading section:

    <turbo-frame id="my_frame" :src="route('my.page')">
        <p>Loading...</p>
    </turbo-frame>
    

    After page loads, content is then fetched GET then replace/update my_frame. Fine. I have a back button defined with

    <a href="{{ url()->previous() }}">Back</a>
    

    When I click the back link, it goes to route('my.page')'s url. The behaviour is correct, in some sense. How to avoid this? By not using url()->previous()?

    EDIT: This seems to relate to this.

    opened by SylarRuby 13
  • Dropdown not work when install fresh laravel jetstream with turbo-laravel

    Dropdown not work when install fresh laravel jetstream with turbo-laravel

    • I followed install guide.
    • Create new account in register page
    • After login success, dropdown menu not work
    • If i refresh page, everything work again

    If i put @livewireScripts and livewire-turbolinks.js to guest layout everthing work as expected.

    opened by vanthao03596 12
  • Change the default invalid form handling to follow redirects internally

    Change the default invalid form handling to follow redirects internally

    Changed

    • Instead of redirecting to the form route when a ValidationException is thrown, we're following the redirects internally returning the form response body with a 422 status code, which is better handled by Turbo Native.

    Issue #29

    This borrows from the Inertia dialog implementation.

    opened by tonysm 11
  • [SUGGESTION] Don't render data-turbo-permanent elements + solution

    [SUGGESTION] Don't render data-turbo-permanent elements + solution

    So I've been using Turbo and I love it. But I've been struggling with some basic things. For example I have a live chat that appears on every page and it has to load all the messages and their user relation (2 querys). I've added a "data-turbo-permanent" attribute (and an ID) to the live chat element and it works, but the server still has to render those elements.

    My solution was to add a header to every Turbo request by searching for every "data-turbo-permanent" attribute on the page

    // app.js
    document.addEventListener('turbo:before-fetch-request', (e) => {
        let permanentElements = []
        document.querySelectorAll('[data-turbo-permanent]').forEach(function(el) {
            permanentElements.push(el.id)
        })
        e.detail.fetchOptions.headers['Turbo-Permanent'] = JSON.stringify(permanentElements)
    });
    

    Then in my blade templates I check if this header exists and return nothing if it does

    // app.blade.php
    <div id="live-chat" data-turbo-permanent>
        // Check if header exists && has the "live-chat" id
        @if(!(request()->hasHeader('Turbo-Permanent') && in_array('live-chat', json_decode(request()->header('Turbo-Permanent')))))
            // My life chat html & logic
        @endif
    </div>
    

    If I visit the page the first time or visit the page with Turbo and the element appears the first time, it gets rendered. And if I visit another page with Turbo and the element still exists, it doesn't get rendered and the response has an empty <div> so Turbo knows not to delete the element, like this:

    // response
    ...
    <body>
        ...
        <div id="live-chat" data-turbo-permanent></div>
        ...
    </body>
    ...
    

    I bet this could be programmed in a much better way without having to check the request in the blade template and also the render method also needs the request checking logic which I didn't include here.



    I'd also have a solution for persisting scroll positions with a custom "data-turbo-scroll" attribute

    // app.js
    Turbo['scrollPositionsToSave'] = []
    document.addEventListener('turbo:before-visit', function () {
        let elements = document.querySelectorAll('[data-turbo-scroll]')
        elements.forEach(function(el) {
            if (el.scrollTop) {
                Turbo['scrollPositionsToSave'].push({ element: el,  scrollPosition : el.scrollTop })
            }
        })
    }, false)
    document.addEventListener('turbo:load', function () {
        Turbo['scrollPositionsToSave'].forEach(function(el) { el.element.scrollTo(0, el.scrollPosition) })
        Turbo['scrollPositionsToSave'] = [];
    }, false)
    
    opened by WalterWoshid 7
  • Errors When Installing v1.4.0

    Errors When Installing v1.4.0

    Your requirements could not be resolved to an installable set of packages.

    Problem 1 - Root composer.json requires tonysm/turbo-laravel ^1.4 -> satisfiable by tonysm/turbo-laravel[1.4.0]. - tonysm/turbo-laravel 1.4.0 requires illuminate/support ^9.0 -> found illuminate/support[v9.0.0-beta.1, ..., 9.x-dev] but these were not loaded, likely because it conflicts with another require.

    opened by stevebrainng 6
  • "return redirect" fully reloads the page

    I have been developing a Laravel 9 application with Livewire and using Turbo Laravel package for give it a SPA feeling. It works fine with regular links, but there is a register page in my application and after registration is successful, I have given a redirect to the same page. but after I clicked the register button the page does a full reload. How can I fix that? Is this a issue with the package. I have tried different redirect notations but the issue remains.

    $user = new User;
            $user->name = $this->name;
            $user->username = $this->username;
            $user->gender = $this->gender;
            $user->user_image_path = $userImageName;
            $user->email = $this->email;
            $user->password = Hash::make($this->password);
            $save = $user->save();
    
            if ($save) {
                return redirect('/regsiter');
            } else {
                return $this->error = 'Something went wrong! Please try again.';
            }
    
    opened by pasindujr 5
  • Release preparation

    Release preparation

    Just so I don't forget.

    Release notes:

    • New return types to the macros: here and here
    • New targets behavior (needs documentation, don't forget the new *All() methods) - here and here
    • Stimulus options were removed (see here)
    • New turbo_stream() and turbo_stream_view() helper functions (needs documentation) - see here

    Note: The targets change needs to be tested properly. We need to create some queued Turbo Stream broadcasts with the previous package version, let them sleep in the queue as we update the package, then try to process them with the new package version. I think it should be fine, but not sure. If this works fine, we can tag a minor version. Otherwise, we will need a major version bump.

    opened by tonysm 5
  • Select contents of input after clicking on edit

    Select contents of input after clicking on edit

    Hi!

    Not sure where to ask this, appreciate if you can help.

    Supposed I have a button to edit, is it possible (to run some javascript) to select all text in the input box via TurboStream after clicking to edit? Or is Stimulus the only way? Thank you!

    opened by bilogic 5
  • Impossible to import Bootstrap

    Impossible to import Bootstrap

    After following the installation steps I get a lot of errors saying bootstrap is not defined

    I am using Laravel 9, Livewire 2

    Installation process:

    1. composer require tonysm/turbo-laravel
    2. php artisan turbo:install --jet
    3. npm install
    4. npm run dev

    The errors I am getting are like this:

    Uncaught ReferenceError: bootstrap is not defined at Proxy.__self.result (eval at safeAsyncFunction (app.js:5094:14), <anonymous>:7:21) at runIfTypeOfFunction (app.js:5126:24) at app.js:5114:9 at tryCatch (app.js:5039:12) at evaluate (app.js:5057:32) at app.js:6461:35 at Function.<anonymous> (app.js:5764:55) at flushHandlers (app.js:5169:46) at stopDeferring (app.js:5174:5) at deferHandlingDirectives (app.js:5177:3) __self.result @ VM1579:7 runIfTypeOfFunction @ app.js:5126 (anonymous) @ app.js:5114 tryCatch @ app.js:5039 evaluate @ app.js:5057 (anonymous) @ app.js:6461 (anonymous) @ app.js:5764 flushHandlers @ app.js:5169 stopDeferring @ app.js:5174 deferHandlingDirectives @ app.js:5177 initTree @ app.js:5376 (anonymous) @ app.js:5336 start @ app.js:5335 ./resources/js/libs/alpine.js @ app.js:7024 __webpack_require__ @ app.js:7859 ./resources/js/app.js @ app.js:6846 __webpack_require__ @ app.js:7859 (anonymous) @ app.js:8012 __webpack_require__.O @ app.js:7893 (anonymous) @ app.js:8014 (anonymous) @ app.js:8016 setTimeout (async) handleError @ app.js:5049 tryCatch @ app.js:5041 evaluate @ app.js:5057 (anonymous) @ app.js:6461 (anonymous) @ app.js:5764 flushHandlers @ app.js:5169 stopDeferring @ app.js:5174 deferHandlingDirectives @ app.js:5177 initTree @ app.js:5376 (anonymous) @ app.js:5336 start @ app.js:5335 ./resources/js/libs/alpine.js @ app.js:7024 __webpack_require__ @ app.js:7859 ./resources/js/app.js @ app.js:6846 __webpack_require__ @ app.js:7859 (anonymous) @ app.js:8012 __webpack_require__.O @ app.js:7893 (anonymous) @ app.js:8014 (anonymous) @ app.js:8016

    Inside app.js I have tried with the following options

    1. window.bootstrap = require('bootstrap/dist/js/bootstrap.bundle.min.js'); This was working before installing TurboLaravel
    2. const bootstrap = require('bootstrap/dist/js/bootstrap.bundle.min.js'); Not working
    3. import bootstrap from 'bootstrap' Not working
    4. import './bootstrap'; Not working
    5. const bootstrap = require('bootstrap') Not working
    6. window.bootstrap = require("bootstrap") Not working

    How is the correct way to import bootstrap for TurboLaravel?

    opened by Jecs9 5
  • Add ability for other resources to be turbo-streamable

    Add ability for other resources to be turbo-streamable

    Purpose

    This PR adds the ability for any PHP object to be turbo-streamable by implementing the TurboStreamable interface.

    Description

    I'm the developer of a PHP package called LdapRecord that heavily integrates into Laravel and want to be able to use turbo-laravel with LdapRecord models. This isn't possible currently, since Eloquent models are type-hinted into this package.

    This PR opens the door for any PHP object to be "turbo-streamable" by implementing a getDomId() method.

    In my case, this is how I would utilize this with LdapRecord models:

    use LdapRecord\Models\Model;
    use Tonysm\TurboLaravel\Views\TurboStreamable;
    
    class User extends Model implements TurboStreamble
    {
        public function getDomId()
        {
            return $this->getObjectGuid();
        }
    }
    

    Notes

    The namespace and location of TurboStreamable might need to be changed -- I wasn't sure where you might want this placed or named.

    Let me know if you have any comments/concerns/questions. Thanks for your hard work on this package!

    opened by stevebauman 5
  • Uncaught TypeError: Livewire.on is not a function

    Uncaught TypeError: Livewire.on is not a function

    I am receiving following error in console.

    Uncaught TypeError: Livewire.on is not a function

    I have installed turbo-laravel package and also using livewire-turbolinks bridge.

    opened by dhruva81 4
  • Broadcasting Without Models

    Broadcasting Without Models

    Added

    • Adds a new TurboStream facade that can be used to broadcast turbo streams without depending on a modal
    • We can now fake broadcasts using the TurboStream::fake() helper method and assert broadcasts with TurboStream::assertBroadcasted(fn), TurboStream::assertBroadcastedTimes(fn, int) and TurboStream::assertNothingWasBroadcasted()

    Changed

    • The Broadcasts trait now uses the Turbo Stream Factory to create broadcasts

    closes #96

    Missing:

    • [ ] Docs
    • [ ] Test if the previous version's serialized at-rest broadcasts still work after the upgrade
    opened by tonysm 0
  • [RFC] Decouple Broadcasting from Models

    [RFC] Decouple Broadcasting from Models

    I want to change the broadcasting feature to allow broadcasting independent of Eloquent models.

    Right now, broadcasting is only available from a model that uses the Broadcasts trait:

    class Comment extends Model
    {
      use Broadcasts;
    }
    
    // Then:
    
    $comment->broadcastAppendTo($comment->post)
      ->toOthers()
      ->later();
    

    We could decouple broadcasting Turbo Streams from models and change the modal broadcasting system to use this new way.

    An example API would be:

    // Using the Facade:
    
    Turbo::broadcastAppendTo(
      channel: "general",
      target: "notifications",
      content: view('layouts.notification', ['message' => __('Hey users!')]),
    )->toOthers();
    

    This assumes your application has a public broadcasting channel called general. Since this would return a pending broadcast builder class, we could also chain the channel like so:

    Turbo::broadcastAppendTo(/* ... */)
      ->channel('general', type: 'public')
      ->toOthers();
    

    In the builder we could have some helper for private/presence channels:

    Turbo::broadcastAppendTo(/* ... */)
      ->privateChannel('general')
      ->toOthers()
    
    Turbo::broadcastAppendTo(/* ... */)
      ->presenceChannel('general')
      ->toOthers()
    

    Changing The Broadcasting

    With this new abstraction in place, we could change the Broadcasts trait to use this new system. I think only this method would have to change, which is where we return the PendingBroadcast class. This would use the Turbo Facade. We could even return the same response type, making this a non-breaking change.

    Testing

    Our current way of testing broadcasting is by faking the BroadcastAction job, as described in the docs.

    Since the new broadcast system would use the facade, we could improve testing! Introducing a Broadcasting manager, we could then offer the ability to run Turbo::fakeBroadcasting() or something. Then we could allow asserting on what was broadcasted instead of asserting the Broadcast job was dispatched.

    Think something like this:

    use App\Models\Todo;
    use Tonysm\TurboLaravel\Facades\Turbo;
     
    class CreatesCommentsTest extends TestCase
    {
        /** @test */
        public function creates_comments()
        {
            Turbo::fakeBroadcasts();
     
            $todo = Todo::factory()->create();
     
            $this->turbo()->post(route('todos.comments.store', $todo), [
                'content' => 'Hey, this is really nice!',
            ])->assertTurboStream();
     
            Turbo::assertBroadcasted(function ($broadcast) use ($todo) {
                return count($broadcast->channels) === 1
                    && $broadcast->channels[0]->name === sprintf('private-%s', $todo->broadcastChannel())
                    && $broadcast->target === 'comments'
                    && $broadcast->action === 'append'
                    && $broadcast->view === 'comments._comment'
                    && $broadcast->see('Hey, this is really nice!')
            });
        }
    }
    
    opened by tonysm 0
  • Investigate ways of only rendering the layout's content (slot) on Turbo Frame requests

    Investigate ways of only rendering the layout's content (slot) on Turbo Frame requests

    Requests done inside a Turbo Frame have a special Header, such as Turbo-Frame: post_311. By detecting if this header exists, we can programmatically avoid rendering the layout and only render its contents.

    One "simple" way of doing this would be to parse the response content and remove only the Turbo Frame it wants from it and return, which would result in less data being sent through the pipes.

    However, I think the best way of doing this would be to find a way to not render the layout at all, so we don't spend computational power in there. Also, if users have things like View composer or are making use of lazy-loading relationships and things like that, those wouldn't even execute, to begin with.

    enhancement 
    opened by tonysm 2
  • Singular Noun Names

    Singular Noun Names

    Hi, I was wondering if there was an easy 'configuration' based way to be able to use singular nouns rather than the conventional plural.

    So instead of comments.create and comments.store package would look for comment.create and comment.store

    Thank you, @tonysm so much for this package.

    opened by chimalsky 3
Releases(1.8.1)
  • 1.8.1(Dec 28, 2022)

    Changelog

    • FIXED: Ensure the published turbo-echo-stream-source custom HTML element is defined only once (https://github.com/tonysm/turbo-laravel/commit/d3f5ad84012d758c803c2361192da70fbc42a367)

    Note: Those that already have the custom element published may change the file manually.

    Source code(tar.gz)
    Source code(zip)
  • 1.8.0(Dec 10, 2022)

  • 1.7.0(Dec 9, 2022)

  • 1.6.0(Sep 18, 2022)

    Changelog

    • NEW: The response macro now has return types which should help out autocompletion on some IDEs or when using the Laravel IDE Helper - by @nielsbauman (PRs https://github.com/tonysm/turbo-laravel/pull/84 and https://github.com/tonysm/turbo-laravel/pull/85)
    • NEW: We now support targeting multiple elements on Turbo Streams by @timvdalen. See documentation (https://github.com/tonysm/turbo-laravel/pull/86)
    • NEW: On top of the multiple targets work mentioned above, we now have new appendAll(), prependAll(), updateAll(), replaceAll(), removeAll(), beforeAll, and afterAll methods that take a CSS class selector and the content (which can be a string, a View instance, or an instance of the HtmlSafe class) see the documentation - by @tonysm (https://github.com/tonysm/turbo-laravel/pull/87)
    • NEW: You may create turbo streams responses or tags now using the new turbo_stream() and turbo_stream_view() helper functions. These ship as namespaced functions under the Tonysm\TurboLaravel namespace, but also as globally available functions (easier to use them in contexts like Blade views, for instance). These work the same as the Response::turboStream() and Response::turboStreamView() macros (in fact, the macros now use them), they are just shorter. Suggested by @bilogic implemented by me @tonysm. See the documentation (https://github.com/tonysm/turbo-laravel/pull/92)
    • REMOVED: The --stimulus option was removed from the turbo:install command. It now only has the --jet for Jetstream installs and --alpine for Breeze installs. I'm working on a new package called Stimulus Laravel for the folks using Stimulus out there.

    You may also have noticed that I am moving the documentation content to markdown files inside the docs/ folder. That's because I'm working on a new website where the docs will be hosted (and some more stuff I'm working on). So I'll keep the docs in the readme and inside the docs/ folder for now, but once it's ready, we'll only have them inside the docs/ folder.

    Internal Breaking Changes

    Some method signatures were changed to accommodate the new multiple targets behavior. We could have introduced the new targets attribute at the end method signature as nullable, but I've decided to change the order of the parameters, so the target and targets are closer to each other. Technically, this is a breaking change if you're extending BroadcastAction job class or manually creating these jobs. I didn't want to tag a major version because of this as I'm not aware of folks extending the internals of the package. If you do have an issue with that, please stick to the 1.5.x version and let me know.

    This doesn't affect applications using the package normally, just those more advanced scenarios where folks are extending the internals here.

    The signature changes were made to these classes:

    Source code(tar.gz)
    Source code(zip)
  • 1.5.1(Jul 3, 2022)

  • 1.5.0(Jul 3, 2022)

    Changelog

    • CHANGED: The turbo:install command was broken since the new frontend change in Laravel. This version adapts the command to the new setup and keeps it working on old Laravel installs that use Mix. It works with Importmap Laravel as well, of course.
    Source code(tar.gz)
    Source code(zip)
  • 1.4.1(Jun 20, 2022)

    Changelog

    • FIXED: Support to Laravel 8 was dropped without a major version bump, so I brought it back (https://github.com/tonysm/turbo-laravel/pull/77)
    Source code(tar.gz)
    Source code(zip)
  • 1.4.0(Jun 19, 2022)

  • 1.3.0(Jun 17, 2022)

    Changelog

    • NEW: The shorthand methods for building Turbo Streams all now accept either a model instance or a string as the first parameter to be used to derive the target. Optionally, you may pass a content variable to be rendered inside the Turbo Stream, which can be a string, an View instance, or an instance of a carefully handcrafted HtmlString (https://github.com/tonysm/turbo-laravel/pull/74 and docs).
    • NEW: Documented the issue with Laravel's url()->previous() and Turbo Frames (docs)
    • BC: The channel name generated for a newly created model using the auto-broadcast feature was previously that model's broadcasting channel based on Laravel's conventions. However, no user will ever be listening on that channel (since the model was just created), so we're now defaulting to the basename of the model's class in plural, so if you have a model named App\Models\Comment, broadcasts for the newly created models will be sent to a private comments channel by default. You may specify the stream option in the $broadcasts property. The existing $broadcastTo property or broadcastTo() methods will have precedence over this (https://github.com/tonysm/turbo-laravel/pull/74).
    • BC: The before and after shorthand stream builders were previously accepting a model instance and a target string. Now, the first parameter can be either a model instance of a string which will be used to derive the target of the Turbo Stream. Just like the other shorthand methods, you may also pass a string, a view, or an HtmlString instance as the second parameter for the content of the Turbo Stream. Without the content, you must specify the view/partial to render inside the Turbo Stream, otherwise an exception will be thrown. I consider this change a fix (https://github.com/tonysm/turbo-laravel/pull/74)

    Note: I consider both BC changes fixes, so I'm not tagging a major release. If you're relying on the previous behavior, take a closer look at the documentation and make the changes when you upgrade!

    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Jun 15, 2022)

    Changelog

    • NEW: new optional --alpine flag to the turbo:install command for when you want to install Alpine in a non-Jetstream context (maybe you're more into Breeze)
    • CHANGED: the turbo:install command now detects when you're using Importmap Laravel and installs the dependencies there accordingly instead of always assuming NPM
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(Feb 9, 2022)

  • 1.0.0(Dec 22, 2021)

    The first 1.0 release is here!! πŸŽ‰πŸŽ‰πŸŽ‰

    Changelog

    • CHANGED: The Laravel Echo setup was commented out by default as users will likely not have the backend WS part of it set up when doing a fresh install of the package. Having it not commented out was only going to generate errors in the DevTools. So instead, it's now mentioned in the documentation to uncomment the Laravel Echo when setting up that part. Thanks to Espen Kristensen for the idea on Twitter.
    • CHANGED: The route guessing code was simplified quite a bit. Thanks to @sleicester. https://github.com/tonysm/turbo-laravel/pull/42
    • NEW: There are now three new Blade components: <x-turbo-frame>, <x-turbo-stream>, and <x-turbo-stream-from> which can simplify using the custom HTML Elements a bit and also potentially improve the autocompletion part for IDEs since some can detect Blade components defined in packages. By @tonysm https://github.com/tonysm/turbo-laravel/pull/41
    Source code(tar.gz)
    Source code(zip)
  • 0.18.0(Nov 11, 2021)

    Changelog

    • FIXED: the previous way to opt-out of auto-wiring the Turbo middleware should was previously using a Facade and now it's using a config key
    Source code(tar.gz)
    Source code(zip)
  • 0.17.0(Nov 11, 2021)

    Changelog

    • CHANGED: When handling invalid form submissions we were previously flashing the errors and redirecting to the form page (based on the resource route naming convention). This had to change because Turbo expects only successful responses to redirect, while invalid responses should return a 422 status code with the form containing the invalid messages. https://github.com/tonysm/turbo-laravel/pull/36 Thanks to @bakerkretzmar @taylorotwell @tobyzerner for the help there.
    • CHANGED: The TurboMiddleware is now automatically prepended to the web route group. If you were already using it, either move it to the top of the group stack or remove it entirely from your HTTP Kernel.
    Source code(tar.gz)
    Source code(zip)
  • 0.16.0(Oct 20, 2021)

    Changelog

    • NEW: We can now build and combine multiple Turbo Stream responses in a single Response. This should be an alternative to using a custom Turbo Stream view. https://github.com/tonysm/turbo-laravel/pull/39
    Source code(tar.gz)
    Source code(zip)
  • 0.15.1(Oct 1, 2021)

  • 0.15.0(Sep 24, 2021)

    Changelog

    • CHANGED: Changed the default JS scaffolding (for new applications using php artisan turbo:install)
    • CHANGED: Bumps Turbo to final 7.0.0 version and Stimulus to the final 3.0.0 version
    • FIX: Bumped the Livewire bridge to install the latest 0.1.4 version, which fixes the issue with entangle
    Source code(tar.gz)
    Source code(zip)
  • 0.14.0(Sep 15, 2021)

  • 0.13.0(Sep 15, 2021)

    Changelog

    • CHANGED: updated the JS scaffolding to add a resources/js/libs folder with a file for each lib. New installs should be more organized this way (I think)
    • CHANGED: updated @hotwired/turbo to the latest RC3
    • CHANGED: updated Stimulus imports and installs. The package moved from stimulus to @hotwired/stimulus.
    • CHANGED: tweaked the install command so it generates a more organized initial setup.
    Source code(tar.gz)
    Source code(zip)
  • 0.12.0(Jul 23, 2021)

    Changelog

    • NEW: there is a new request macro request()->wasFromTurboNative(), which can be used in content negotiation (https://github.com/tonysm/turbo-laravel/pull/30)
    Source code(tar.gz)
    Source code(zip)
  • 0.11.2(Jul 23, 2021)

    Changelog

    • CHANGED: Replaces the usage of simplexml_load_string with DOMDocument and DOMElement in the Turbo Stream testing helpers (https://github.com/tonysm/turbo-laravel/pull/28)
    Source code(tar.gz)
    Source code(zip)
  • 0.11.1(Jul 23, 2021)

  • 0.11.0(Jul 19, 2021)

  • 0.10.0(Jun 23, 2021)

    Changelog

    • CHANGED: You can now pass any entity to the turbo stream helpers, not just Eloquent models. All the entity needs is to implement a getKey method that returns the identifier of that entity. [#26, 05c35c]
    Source code(tar.gz)
    Source code(zip)
  • 0.9.0(Jun 17, 2021)

    Changelog

    • BC: The broadcastDetaultTargets method on the Broadcasts trait was renamed to brodcastDefaultStreamables
    • NEW: Adds a before and after method to the response builder. These were introduced in the v7.0.0-beta.6
    • NEW: Adds the broadcastBefore, broadcastAfter, broadcastBeforeTo, and beforeAfterTo to the Broadcasts trait. These were introduced in the v7.0.0-beta.6
    • CHANGED: The installer now requires Alpine.js 3 with the adapter snippet included
    • CHANGED: The installer now requires Turbo v7.0.0-beta.7
    Source code(tar.gz)
    Source code(zip)
  • 0.8.0(Jun 16, 2021)

    Changed

    • BC: Updated the minimum required Laravel version to ^8.45 so we can use the new broadcast goodies
    • Use (and recommend in docs) the new broadcast conventions in Laravel
    • Use the broadcast_sync instead of the broadcast_now function (it got deprecated)
    Source code(tar.gz)
    Source code(zip)
  • 0.7.1(Jun 16, 2021)

  • 0.7.0(May 31, 2021)

    Changed

    • Tweaked the JS scaffolding (https://github.com/tonysm/turbo-laravel/commit/6fafd48ea53c08568f28768d3bd4be6bec3ad9db)

    Added

    • Added a @unlessturbonative custom blade IF macro which works as the opposite of the existing @turbonative macro (https://github.com/tonysm/turbo-laravel/commit/1d333314cff2ba182770a08df86a60169249a6f6)
    Source code(tar.gz)
    Source code(zip)
  • 0.6.0(May 14, 2021)

  • 0.5.1(Apr 20, 2021)

Owner
Tony Messias
Tony Messias
Tighten linter for Laravel conventions.

Install (Requires PHP 7.3+) composer global require tightenco/tlint Upgrade composer global update tightenco/tlint What Is It? This is an opinionate

Tighten 425 Dec 25, 2022
A light weight laravel package that facilitates dealing with arabic concepts using a set of classes and methods to make laravel speaks arabic

A light weight laravel package that facilitates dealing with arabic concepts using a set of classes and methods to make laravel speaks arabic! concepts like , Hijri Dates & Arabic strings and so on ..

Adnane Kadri 49 Jun 22, 2022
Package for Laravel that gives artisan commands to setup and edit environment files.

Setup and work with .env files in Laravel from the command line NOTE: This doesn't work with Laravel 5 since .env files were changed. This is for Lara

Matt Brunt 6 Dec 17, 2022
This package gives Eloquent models the ability to manage their friendships.

Laravel 5 Friendships This package gives Eloquent models the ability to manage their friendships. You can easily design a Facebook like Friend System.

Alex Kyriakidis 690 Nov 27, 2022
This Laravel 8 package makes it possible for you to set your website in "Under Construction" mode.

Laravel Under Construction This Laravel package makes it possible to set your website in "Under Construction" mode. Only users with the correct 4 digi

Lars Janssen 571 Dec 18, 2022
πŸ––Repository Pattern in Laravel. The package allows to filter by request out-of-the-box, as well as to integrate customized criteria and any kind of filters.

Repository Repository Pattern in Laravel. The package allows to filter by request out-of-the-box, as well as to integrate customized criteria and any

Awes.io 160 Dec 26, 2022
A base API controller for Laravel that gives sorting, filtering, eager loading and pagination for your resources

Bruno Introduction A Laravel base controller class and a trait that will enable to add filtering, sorting, eager loading and pagination to your resour

Esben Petersen 165 Sep 16, 2022
Worlds (soon to be) most advanced Anime site! Featuring Administration features and everything you need for users and yourself. The successor of aniZero.

/**********************************************************************\ | _____ H33Tx & xHENAI __ 31.01.2022| |

HENAI.eu 40 Jan 3, 2023
This package provides you with a simplistic `php artisan make:user` command

Laracademy Generators Laracademy make:user Command - provides you with a simplistic artisan command to generate users from the console. Author(s): Lar

Laracademy 18 Jan 19, 2019
Relational Metrics - lararvel package help you to make your metrics easier

Relational Metrics This package will help you to make your metrics easier, You could get metrics about your Models, Models depending on their relation

Syrian Open Source 25 Oct 12, 2022
Laravel-OvalFi helps you Set up, test, and manage your OvalFi integration directly in your Laravel App.

OvalFi Laravel Package Laravel-OvalFi helps you Set up, test, and manage your OvalFi integration directly in your Laravel App. Installation You can in

Paul Adams 2 Sep 8, 2022
A full-stack framework for Laravel that takes the pain out of building dynamic UIs.

A full-stack framework for Laravel that takes the pain out of building dynamic UIs.

Livewire 17.7k Jan 1, 2023
πŸ§™β€β™€οΈ Arcanist takes the pain out of building multi-step form wizards in Laravel.

Installation Arcanist requires PHP 8 and Laravel 8. composer require laravel-arcanist/arcanist Documentation You can find the full documentation here

Arcanist 378 Jan 3, 2023
A nice GUI for Laravel Artisan, ready out of the box, configurable and handy for non-CLI experienced developers.

Artisan UI A nice GUI for Laravel Artisan, ready out of the box, configurable and handy for non-CLI experienced developers. Supported commands must be

Pablo Leone 1 Dec 3, 2021
Gallium is a TALL stack starter kit offering a robust set of options enabling you to get up and running in a snap.

Very short description of the package This is where your description should go. Try and limit it to a paragraph or two, and maybe throw in a mention o

null 1 Nov 20, 2021
πŸš€ Zero-downtime deployment out-of-the-box

?? Laravel Deployer Looking for the old Laravel Deployer? Click here. Laravel Deployer is no longer the package it used to be. Since that package was

Loris Leiva 1.6k Dec 31, 2022
Library that offers Input Filtering based on Annotations for use with Objects. Check out 2.dev for 2.0 pre-release.

DMS Filter Component This library provides a service that can be used to filter object values based on annotations Install Use composer to add DMS\Fil

Rafael Dohms 89 Nov 28, 2022
Laravel package for giving admin-created accounts to users via 'set-password' email.

Invytr When making a website where users are created instead of registering themselves, you are faced with the challenge of safely giving users the ac

GlaivePro 64 Jul 17, 2022
Useful blade components and functionality for most Laravel projects.

laravel-base Note: Package is still in early stages of development, so functionality is subject to change. LaravelBase is a package I've created to pr

Randall Wilk 3 Jan 16, 2022