Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Adds template fragments to Blade #44774

Merged
merged 2 commits into from
Nov 2, 2022
Merged

[9.x] Adds template fragments to Blade #44774

merged 2 commits into from
Nov 2, 2022

Conversation

brunoalod
Copy link
Contributor

Hello everyone! This PR adds fragments to Blade, suggested in #44468.

Working with frontend frameworks that use the html over-the-wire concept such as Turbo, Unpoly, Htmx or Pjax, sometimes when handling a request we have to return just a fragment of a view to replace a portion of the DOM.

Adding this feature will allow developers to return said fragment from a controller without having to create a component and split the view in several files.

As mentioned in the original discussion there are several implementations of this feature in other backend template engines, here's a detailed list.

Although this can be extracted to a package I think it would be a nice addition to the framework.

Usage

// UserController.php

public function updateStatus() 
{
    // Business logic

    // This will only return the content inside the fragment named actions.
    return view('users.profile', $data)->fragment('actions');
}
<!-- resources/views/users/profile.blade.php -->

<!-- Example using HTMX.js -->

<div>
    First Name {{ $firstName }}
    Last Name: {{ $lastName }}

    @fragment('actions')
        <div hx-target="this">
            @if($enabled)
                <button hx-patch="/mark-as-disabled">Mark as Disabled</button>
            @else
                <button hx-patch="/mark-as-enabled">Mark as Enabled</button>
            @endif
        </div>
    @endfragment
</div>

@ryangjchandler
Copy link
Contributor

Whilst this concept is interesting and most likely useful for some, I imagine that 90% of use cases would hit performance problems since the entire view needs to be rendered with all of the correct data fully before the fragment can be retrieved / returned from the controller. At that point the logical solution would be abstracting out the view into a partial and using it in both the initially rendered template and separately to render and return via a controller, right?

@brunoalod
Copy link
Contributor Author

brunoalod commented Oct 31, 2022

Whilst this concept is interesting and most likely useful for some, I imagine that 90% of use cases would hit performance problems since the entire view needs to be rendered with all of the correct data fully before the fragment can be retrieved / returned from the controller. At that point the logical solution would be abstracting out the view into a partial and using it in both the initially rendered template and separately to render and return via a controller, right?

Currently the only alternative is to send the entire view to the front anyway (also consuming more bandwidth), then the front library would interpret what portion of the DOM to replace, of course only if it has this feature, some libraries expect a specific element as the root of the tree.

The other alternative is to split the file into components (which defeats the purpose of this feature), and then return them via return Blade::renderComponent(new MyComponent($data));.

To me it definitely feels like there should be a way to return just a fragment of a view. I think the question is whether this should be packaged or core.

@tonysm
Copy link
Contributor

tonysm commented Oct 31, 2022

First of all, this is really cool. There's an issue in the Turbo Laravel repo to investigate something like this. We do have a possible solution of just not rendering the layout when it's a Turbo Frame request, but I do like the idea of having something like this in code.

To @ryangjchandler's point, do you think we could find a way to only render the fragment itself, but not the whole view? That would be dope because it could potentially avoid evaluating other parts of the view that are not necessary (let's say other parts are making database calls, for instance).

Although that would be wonderful, I still think this is useful because it may reduce the amount of data sent over the wire and it reduces the surface of the response, which is really cool especially while in development mode since we can view the source of the response more clearly (without the surroundings).

For Turbo Laravel, I think the main middleware could implement something like this:

<?php

namespace Tonysm\TurboLaravel\Http\Middleware;

use Illuminate\Contracts\View\View;

class TurboMiddleware
{
  public function handle($request, $next)
  {
    $response = $next($request);

    // Do other things the middleware does...

    if ($frame = $request->header('Turbo-Frame') && $response instanceof View) {
      return $view->segment($frame);
    }

    return $response;
  }
}

This assumes each Turbo Frame declares its own fragment.

I dig it.

@brunoalod
Copy link
Contributor Author

brunoalod commented Oct 31, 2022

Hey @tonysm. Nice package! We've been using it for a while now. 💯

Personally I don't think a Fragment should be rendered without a context, because then it'd just fall under the component-ish category, which is not. I think of a Fragment as a way to extract a portion of a view.

Your example reminds me of the middleware Spatie created for Pjax, where basically the render of the view was parsed, and then an element of the DOM extracted. I view the result of that process as a Fragment. Actually, I was porting that middleware to Turbo (frames) when I realized this implementation of Fragments might be better.

Regardless of the implementation I do believe Fragments are an imporant tool for us who use html over-the-wire.

@subaruba
Copy link

we need this for unpoly 👍👍

@taylorotwell taylorotwell merged commit a2faef8 into laravel:9.x Nov 2, 2022
@taylorotwell
Copy link
Member

Thanks

@mauricius
Copy link

This is funny! I've just released a helper package for HTMX that handles Blade fragments as well https://github.com/mauricius/laravel-htmx. The implementation relies on a regex expression so it's likely gonna break in some weird cases, but it allows to extract a portion of a view without rendering the entire file.

@hdogan
Copy link

hdogan commented Nov 7, 2022

IMHO, fragments should work without passing whole variables used by the view. It should be sufficient to just pass the variables to be used by the fragment.

So, laravel-htmx package's solution is better.

@decadence
Copy link
Contributor

What's problem with just moving fragment to separate blade file and include it in main blade file and in response?

public function updateStatus() 
{
    return view('users.profile.actions', $data);
}

@mauricius
Copy link

@decadence it's perfectly fine to extract fragments in separate files, but sometimes it's more useful to have them at the level of the parent template. This article explains the issue better than I can do.

salehhashemi1992 added a commit to salehhashemi1992/docs that referenced this pull request Feb 4, 2023
taylorotwell added a commit to laravel/docs that referenced this pull request Feb 5, 2023
* [9.x] Add Fragment Helpers

Docs for PR: laravel/framework#44774

* formatting

---------

Co-authored-by: Taylor Otwell <taylor@laravel.com>
salehhashemi1992 added a commit to salehhashemi1992/docs that referenced this pull request Feb 8, 2023
* [9.x] Add Fragment Helpers

Docs for PR: laravel/framework#44774

* formatting

---------

Co-authored-by: Taylor Otwell <taylor@laravel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants