Support the ongoing development of Laravel.io →

Customizing Table Actions with Scoped Slots in Laravel

4 Nov, 2023 4 min read 171 views

Photo by Carl Raw on Unsplash

When building reusable Laravel blade components, it's common to define default rendering and behavior for things like actions or buttons. However, sometimes you may want the flexibility to override or customize these defaults. This is where scoped slots come in handy.

Adding Scoped Slot in Component

Let's extend the TBODY component from my last blog post, to have the ability for scoped slots. For the 'actions' column, it checks if a custom $actions scoped slot is provided, and renders that if so. Otherwise, it renders the default view/edit/delete actions.

For other columns, it displays the cell data from the record object. After looping through records, if there are no records it renders a single row with a "No records found" message.

The $slot variable allows the consuming component to inject custom content if needed.

@props(['columns', 'records'])  

<tbody {{ $attributes->merge(['id' => 'table-body']) }}>  
    @if(isset($records))  
        @forelse ($records as $record)  
            <x-table.tr id="row-{{ $record->id }}">  
                @foreach($columns as $column)  
                    <x-table.td>  
                        @if($column === 'actions')  
                            @if(isset($actions))  
                                {{ $actions($record) }}  
                            @else  
                                <x-table.actions.view :record="$record"/>  
                                <x-table.actions.edit :record="$record"/>  
                                <x-table.actions.delete :record="$record"/>  
                            @endif  
                        @else                            
                            {{ $record->{$column} }}  
                        @endif  
                    </x-table.td>  
                @endforeach  
            </x-table.tr>  
        @empty  
            <x-table.tr>  
                <x-table.td colspan="100%">No record found.</x-table.td>  
            </x-table.tr>  
        @endforelse  
    @endif    

    {{ $slot }}  
</tbody>

So in summary, this handles rendering the table body by looping through the records and columns. It also allows overriding the actions column via a scoped slot, and displays a default empty state.

Passing Data To Scoped Slot

The table components allow customizing the actions column using scoped slots. However, the scoped slot needs access to the current record data to render the actions.

At the time of writing this blog post, I could not find if there is any direct method to pass data into a scoped slot. But a useful package provides Blade macros to enable this.

The @scopedSlot directive can now accept a parameter to get the $record variable from the tbody component. Inside the scoped slot, the $record can be used to build custom actions. The default view action is still rendered for consistency.

So the full table is rendered as normal, showing the contact data. But only the actions column is overridden with a scoped slot. This allows custom actions while reusing the rest of the table components. The $record variable passes the necessary data into the scoped slot.

<div id="table-container" 
     hx-get="{{ route('contacts.index') }}" 
     hx-trigger="loadContacts from:body">  

    <x-table :columns="['name', 'email', 'phone', 'address', 'actions']">  

        <x-table.tbody :records="$contacts">  

          @scopedSlot('actions', ($record)) 
             <x-table.actions.view :record="$record"/>  
             <a href="{{ route('contacts.edit', $record->id)}}">
                 Custom Edit :)
             </a> 
          @endScopedSlot 

        </x-table.tbody>  

    </x-table>  

    <div id="pagination-links" class="p-3" 
        hx-boost="true" 
        hx-target="#table-container">  
        {{ $contacts->links() }}  
    </div>  

</div>

This achieves per-use customization of the actions, while retaining a consistent and reusable table structure. The scoped slot handles just the targeted customization needed.

Passing Data to Scoped Slots without a Package

You can also implement scoped slots to pass data without needing a third party package. It involves a small hack using PHP in the Blade template.

<div id="table-container" 
     hx-get="{{ route('contacts.index') }}" 
     hx-trigger="loadContacts from:body">  

    <x-table :columns="['name', 'email', 'phone', 'address', 'actions']">  

        <x-table.tbody :records="$contacts">  

              <?php  $__env->slot('actions', function($record) use ($__env) { ?>  
                    <x-table.actions.view :record="$record"/>  
                    <a href="{{ route('contacts.edit', $record->id)}}">
                         Custom Edit :)
                    </a> 
              <?php }); ?> 

        </x-table.tbody>  

    </x-table>  

    <div id="pagination-links" class="p-3" 
       ...
    </div>  

</div>

While hacky, this allows passing data to a scoped slot without adding a package. The @env directive handles injecting the slot content.

In summary:

  • Define a PHP function using @env->slot()
  • Accept the $record parameter
  • Render custom actions inside the function
  • Access $record data as needed

This article was first published at muhammadshafeeq.com. Be sure to check out the original post there as well for additional details and context.

You can find the complete source code on GitHub. Make sure to follow me on Twitter to get notified when I publish more content.

Last updated 2 weeks ago.

driesvints liked this article

1
Like this article? Let the author know and give them a clap!
mshaf (Muhammad Shafeeq) Writing at muhammadshafeeq.com

Other articles you might like

November 18th 2024

Laravel Custom Query Builders Over Scopes

Hello 👋 Alright, let's talk about Query Scopes. They're awesome, they make queries much easier to r...

Read article
November 19th 2024

Access Laravel before and after running Pest tests

How to access the Laravel ecosystem by simulating the beforeAll and afterAll methods in a Pest test....

Read article
November 11th 2024

🍣 Sushi — Your Eloquent model driver for other data sources

In Laravel projects, we usually store data in databases, create tables, and run migrations. But not...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2024 Laravel.io - All rights reserved.