rappasoft / laravel-livewire-tables

A dynamic table component for Laravel Livewire
https://rappasoft.com/docs/laravel-livewire-tables/v2/introduction
MIT License
1.74k stars 329 forks source link

Nested Children #121

Closed inmanturbo closed 3 years ago

inmanturbo commented 3 years ago

Is there a way nest children rows, perhaps collapsible? Or perhaps inject another livewire component at the row level?

i.e.

inmanturbo commented 3 years ago

Never mind. I see this is very easy to do if you just publish the views.

invaders-xx commented 3 years ago

@inmanturbo , how did you handle that please ?

inmanturbo commented 3 years ago

@invaders-xx

Sure, I created an abstract base class for the datatables with a property of withChildren that defaults to false:

namespace App\Http\Livewire;

use Rappasoft\LaravelLivewireTables\TableComponent;

abstract class BaseTable extends TableComponent
{
    /**
     * @var bool
     */
    public $withChildren = false;
}

Then extend that class for other tables. Then added the following attributes to the tr element in resources/views/vendor/laravel-livewire-tables/bootstrap-4/includes/data.blade.php :

        @if($this->withChildren && $model->children()->exists())
            data-toggle="collapse"
            aria-expanded="false"
            data-target=".{{ $this->setTableRowId($model) }}-children"
            aria-controls="{{ $this->setTableRowId($model) }}-children"
        @endif

And nested a loop through the child rows after the originl <tr> closes:

    @if($this->withChildren && $model->children()->exists())
            @foreach($model->children as $child)
            <tr
            class="collapse {{ $this->setTableRowId($model) }}-children"
            >
                @foreach($columns as $column)
                    @if ($column->isVisible())
                        <td style="border-left: 40px solid transparent !important;"
                            class="{{ $this->setTableDataClass($column->getAttribute(), data_get($child, $column->getAttribute())) }}"
                            id="{{ $this->setTableDataId($column->getAttribute(), data_get($child, $column->getAttribute())) }}"
                            @foreach ($this->setTableDataAttributes($column->getAttribute(), data_get($child, $column->getAttribute())) as $key => $value)
                            {{ $key }}="{{ $value }}"
                            @endforeach
                        >
                            @if ($column->isFormatted())
                                @if ($column->isRaw())
                                    {!! $column->formatted($child, $column) !!}
                                @else
                                    {{ $column->formatted($child, $column) }}
                                @endif
                            @else
                                @if ($column->isRaw())
                                    {!! data_get($child, $column->getAttribute()) !!}
                                @else
                                    {{ data_get($child, $column->getAttribute()) }}
                                @endif
                            @endif
                        </td>
                    @endif
                @endforeach
            </tr>
            @endforeach
    @endif

setTableRowId($model) already exists in the original TableComponent so you don't need to add it to your base class, however it just return null ATM so you will need to override it decide how you way to give the parent <tr>'s all unique ids.

Here is an example which will work if you only ever have a single variation of the same table in a singe page once:

    public function setTableRowId($model): ?string
    {
        return 'modelRow-' . $model->id;
    }

Note that most css methods for striping won't work with this because all of the rows aren't always visible, but then you don't have to make them collapsible.

Oh and I also added the following column to these types of tables:

Column::make(__('Children'))
    ->format(function (ModelName $model) {
        return view('includes.has-children', ['model' => $model, 'withChildren' => $this->withChildren]);
  }),

and in the has-children template did something like this:

@if($withChildren)
    @if($model->children()->exists())
    <div
        class="btn btn-sm btn-light shadow-sm"
        onclick="this.firstElementChild.classList.toggle('fa-folder');this.firstElementChild.classList.toggle('fa-folder-open')"
        ><i class="fa fa-folder"></i></div>
     @else
        <div class="btn-sm"><i class="fa fa-minus-circle"></i></div>
    @endif
@endif

note this is just a quick example outline and not the most elegant. For instance the above toggle will get out of sync if someone clicks somewhere else in the row instead of on the folder icon