From 1f81ce361cb454b1655d6e2a7ac031bc1e3b2ede Mon Sep 17 00:00:00 2001
From: Sam Light <samlight1994@gmail.com>
Date: Thu, 27 Mar 2025 10:53:34 +0000
Subject: Seperating toolbar into its own class and views

---
 resources/views/table.blade.php                    | 39 +---------
 resources/views/toolbar.blade.php                  | 17 +++++
 resources/views/toolbar/column-select.blade.php    | 16 ++++
 resources/views/toolbar/page-size.blade.php        |  5 ++
 resources/views/toolbar/search.blade.php           |  3 +
 src/Columns/Column.php                             | 19 ++---
 src/TableComponent.php                             | 78 ++++++++++++-------
 src/Toolbar.php                                    | 88 ++++++++++++++++++++++
 src/Toolbar/ColumnSelect.php                       | 17 +++++
 src/Toolbar/Filter.php                             |  8 ++
 src/Toolbar/Item.php                               | 32 ++++++++
 src/Toolbar/PageSize.php                           | 19 +++++
 src/Toolbar/Search.php                             | 25 ++++++
 workbench/app/Livewire/CategoriesTable.php         |  3 +-
 workbench/app/Livewire/OrdersTable.php             |  3 +-
 workbench/app/Livewire/ProductsTable.php           | 17 ++++-
 workbench/app/Livewire/Table.php                   | 16 ++++
 .../views/components/layouts/app.blade.php         |  1 +
 18 files changed, 324 insertions(+), 82 deletions(-)
 create mode 100644 resources/views/toolbar.blade.php
 create mode 100644 resources/views/toolbar/column-select.blade.php
 create mode 100644 resources/views/toolbar/page-size.blade.php
 create mode 100644 resources/views/toolbar/search.blade.php
 create mode 100644 src/Toolbar.php
 create mode 100644 src/Toolbar/ColumnSelect.php
 create mode 100644 src/Toolbar/Filter.php
 create mode 100644 src/Toolbar/Item.php
 create mode 100644 src/Toolbar/PageSize.php
 create mode 100644 src/Toolbar/Search.php
 create mode 100644 workbench/app/Livewire/Table.php

diff --git a/resources/views/table.blade.php b/resources/views/table.blade.php
index 783255e..bcefe57 100644
--- a/resources/views/table.blade.php
+++ b/resources/views/table.blade.php
@@ -1,42 +1,5 @@
 <div>
-    @if($searchable || $showColumnSelect || $showPageSizeSelect)
-        <div class="table-controls pb-2 d-flex justify-content-between align-items-center">
-            <div>
-                @if($searchable)
-                    <input class="form-control border-secondary" type="search"
-                           wire:model.live.debounce.{{ $searchDebounce}}="search"
-                           placeholder="{{ __('Search') }}..." />
-                @endif
-            </div>
-            <div class="d-flex gap-3">
-                @if($showColumnSelect)
-                    <div class="dropdown">
-                        <button type="button" class="btn btn-outline-secondary border-secondary"
-                                data-bs-toggle="dropdown" aria-expanded="false"
-                                data-bs-auto-close="outside">
-                            {{ __('Columns') }}
-                        </button>
-                        <div class="dropdown-menu p-4">
-                            @foreach($allColumns->filter(fn($c) => $c->getShowInSelect()) as $column)
-                            <label class="d-block">
-                                <input type="checkbox" wire:model.live="activeColumns"
-                                       value="{{ $column->name }}" />
-                                {{ $column->getTitle() }}
-                            </label>
-                            @endforeach
-                        </div>
-                    </div>
-                @endif
-                @if($showPageSizeSelect)
-                    <select wire:model.live="pageSize" class="form-select border-secondary">
-                        @foreach($pageSizes as $size)
-                            <option value="{{ $size }}">{{ $size }}</option>
-                        @endforeach
-                    </select>
-                @endif
-            </div>
-        </div>
-    @endif
+    {{ $toolbar?->render() }}
     <table class="table">
         <colgroup>
             @foreach($columns as $column)
diff --git a/resources/views/toolbar.blade.php b/resources/views/toolbar.blade.php
new file mode 100644
index 0000000..fee00b4
--- /dev/null
+++ b/resources/views/toolbar.blade.php
@@ -0,0 +1,17 @@
+<div class="table-controls pb-2 d-flex justify-content-between align-items-center">
+    <div class="d-flex gap-3">
+        @foreach ($startItems as $item)
+            {{ $item->render() }}
+        @endforeach
+    </div>
+    <div class="d-flex gap-3">
+        @foreach ($midItems as $item)
+            {{ $item->render() }}
+        @endforeach
+    </div>
+    <div class="d-flex gap-3">
+        @foreach ($endItems as $item)
+            {{ $item->render() }}
+        @endforeach
+    </div>
+</div>
diff --git a/resources/views/toolbar/column-select.blade.php b/resources/views/toolbar/column-select.blade.php
new file mode 100644
index 0000000..6fa7474
--- /dev/null
+++ b/resources/views/toolbar/column-select.blade.php
@@ -0,0 +1,16 @@
+<div class="dropdown">
+    <button type="button" class="btn btn-outline-secondary border-secondary"
+            data-bs-toggle="dropdown" aria-expanded="false"
+            data-bs-auto-close="outside">
+        {{ __('Columns') }}
+    </button>
+    <div class="dropdown-menu p-4">
+        @foreach($allColumns->filter(fn($c) => $c->getShowInSelect()) as $column)
+            <label class="d-block">
+                <input type="checkbox" wire:model.live="activeColumns"
+                       value="{{ $column->name }}" />
+                {{ $column->getTitle() }}
+            </label>
+        @endforeach
+    </div>
+</div>
diff --git a/resources/views/toolbar/page-size.blade.php b/resources/views/toolbar/page-size.blade.php
new file mode 100644
index 0000000..cc35ff4
--- /dev/null
+++ b/resources/views/toolbar/page-size.blade.php
@@ -0,0 +1,5 @@
+<select wire:model.live="pageSize" class="form-select border-secondary">
+    @foreach($pageSizes as $size)
+        <option value="{{ $size }}">{{ $size }}</option>
+    @endforeach
+</select>
diff --git a/resources/views/toolbar/search.blade.php b/resources/views/toolbar/search.blade.php
new file mode 100644
index 0000000..04fd000
--- /dev/null
+++ b/resources/views/toolbar/search.blade.php
@@ -0,0 +1,3 @@
+<input class="form-control border-secondary" type="search"
+       wire:model.live.debounce.{{ $debounce }}="search"
+       placeholder="{{ __('Search') }}..." />
diff --git a/src/Columns/Column.php b/src/Columns/Column.php
index 94e386f..895708f 100644
--- a/src/Columns/Column.php
+++ b/src/Columns/Column.php
@@ -28,24 +28,24 @@ class Column {
         $this->showInSelect = $this->title !== null;
     }
 
-    public static function make(string $name, ?string $title = null) : static
+    public static function make(string $name, ?string $title = null): static
     {
         return new static($name, $title);
     }
 
-    public function setTable(TableComponent $table) : void
+    public function setTable(TableComponent $table): void
     {
         $this->table = $table;
     }
 
-    public function getTitle()
+    public function getTitle(): string
     {
         return $this->title;
     }
 
-    private function defaultSlot(Model $row)
+    private function defaultSlot(Model $row): string
     {
-        return $row->{$this->name};
+        return (string) $row->{$this->name};
     }
 
     public function slot(callable $fn) : static
@@ -77,22 +77,23 @@ class Column {
         return $this;
     }
 
-    public function showInSelect($show = true)
+    public function showInSelect($show = true): static
     {
         $this->showInSelect = $show;
+        return $this;
     }
 
-    public function getShowInSelect()
+    public function getShowInSelect(): bool
     {
         return $this->showInSelect;
     }
 
-    protected function getContent(Model $row)
+    protected function getContent(Model $row): string
     {
         return $this->slotFn?->call($this, $row, $this) ?? $this->defaultSlot($row);
     }
 
-    public function view(Model $row)
+    public function view(Model $row): HtmlString
     {
         $attributes = $this->tdAttributesFn?->call($this, $row) ?? [];
         $attributes = (new ComponentAttributeBag($attributes))->toHtml();
diff --git a/src/TableComponent.php b/src/TableComponent.php
index b8fae6a..0a55d5e 100644
--- a/src/TableComponent.php
+++ b/src/TableComponent.php
@@ -9,6 +9,7 @@ use Livewire\Attributes\Url;
 use Illuminate\Pagination\Paginator;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Support\Str;
+use Illuminate\Support\Collection;
 
 use Exception;
 
@@ -18,35 +19,65 @@ abstract class TableComponent extends Component
 
     // Config
     protected $paginationTheme = 'bootstrap';
-    protected bool $searchable = true;
-    protected int $searchMinLength = 2;
-    protected int $searchDebounce = 250;
-    protected bool $showPageSizeSelect = true;
-    protected bool $showColumnSelect = true;
-
-    protected array $pageSizes = [10, 25, 50];
-
     protected $model = null;
+    protected int $defaultPageSize = 10;
 
     // Properties
+    #[Url]
     public string $search = '';
 
     #[Url]
-    public int $pageSize = 0;
+    public int $pageSize;
     public array $activeColumns = [];
 
-    public function __construct()
+    public function mount()
     {
-        $this->pageSize = $this->pageSizes[0] ?? 0;
+        $this->setDefaultActiveColumns();
+        $this->setDefaultPageSize();
     }
 
-    public function mount()
+    protected function setDefaultPageSize(): void
+    {
+        if (!isset($this->pageSize)) {
+            $this->pageSize = $this->defaultPageSize;
+        }
+    }
+
+    protected function setDefaultActiveColumns(): void
     {
         foreach($this->getColumns() as $column) {
             $this->activeColumns[] = $column->name;
         }
     }
 
+    protected function isSearching(): bool
+    {
+        $search = $this->getToolbar()->getSearch();
+        return $search !== null && Str::length($this->search) >= $search->getMinLength();
+    }
+
+    public function updatedSearch(): void
+    {
+        if ($this->isSearching()) {
+            $this->resetPage();
+        }
+    }
+
+    protected function toolbar(): ?Toolbar
+    {
+        return null;
+    }
+
+    private ?Toolbar $toolbarCache;
+
+    protected function getToolbar(): ?Toolbar
+    {
+        if (!isset($this->_toolbar)) {
+            $this->toolbar = $this->toolbar();
+        }
+        return $this->toolbar;
+    }
+
     protected function query() : Builder
     {
         if($this->model === null) {
@@ -60,25 +91,19 @@ abstract class TableComponent extends Component
 
     protected function search(Builder $builder, string $search) : void {}
 
-    protected function filters() : array
-    {
-        return [];
-    }
-
     protected function buildQuery() : Builder
     {
         $query = $this->query();
 
-        if($this->searchable && (Str::length($this->search) >= $this->searchMinLength)) {
+        if ($this->isSearching()) {
             $this->search($query, $this->search);
         }
 
         return $query;
     }
 
-    private $columnsCache = null;
-
-    protected function getColumns()
+    private ?Collection $columnsCache = null;
+    public function getColumns()
     {
         if($this->columnsCache === null) {
             $this->columnsCache = collect($this->columns())->each(
@@ -94,17 +119,12 @@ abstract class TableComponent extends Component
         $data = $this->buildQuery()->paginate($this->pageSize);
         $allColumns = $this->getColumns();
         $columns = $allColumns->filter(fn($c) => in_array($c->name,$this->activeColumns));
+        $toolbar = $this->toolbar();
 
         Paginator::defaultView('laralight-tables::pagination');
 
-        return view('laralight-tables::table', [
-            'searchable' => $this->searchable,
-            'searchDebounce' => $this->searchDebounce,
-            'showPageSizeSelect' => $this->showPageSizeSelect,
-            'showColumnSelect' => $this->showColumnSelect,
-            'pageSizes' => $this->pageSizes,
-        ] + compact(
-            'data', 'allColumns', 'columns'
+        return view('laralight-tables::table', compact(
+            'data', 'allColumns', 'columns', 'toolbar',
         ));
     }
 }
diff --git a/src/Toolbar.php b/src/Toolbar.php
new file mode 100644
index 0000000..2db39f5
--- /dev/null
+++ b/src/Toolbar.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Lightscale\LaralightTables;
+
+use Lightscale\LaralightTables\Toolbar\Item as ToolbarItem;
+use Lightscale\LaralightTables\Toolbar\Search as SearchItem;
+use Lightscale\LaralightTables\Toolbar\PageSize as PageSizeItem;
+use Lightscale\LaralightTables\Toolbar\Filter as FitlerItem;
+
+use Illuminate\View\View;
+use Illuminate\Support\Collection;
+
+class Toolbar
+{
+    protected ?SearchItem $searchItem = null;
+    protected ?PageSizeItem $pageSizeItem = null;
+
+    protected Collection $startItems;
+    protected Collection $midItems;
+    protected Collection $endItems;
+
+    public function __construct(
+        private TableComponent $table
+    )
+    {
+        $this->startItems = collect();
+        $this->midItems = collect();
+        $this->endItems = collect();
+    }
+
+    private function addItem(Collection $list, ToolbarItem $item): static
+    {
+        $item->setToolbar($this);
+
+        if ($item instanceof FilterItem) {
+            $this->filterItems->push($item);
+        }
+        else if ($item instanceof PageSizeItem) {
+            $this->pageSizeItem = $item;
+        }
+        else if ($item instanceof SearchItem) {
+            $this->searchItem = $item;
+        }
+
+        $list->push($item);
+        return $this;
+    }
+
+    public function addStartItem(ToolbarItem $item): static
+    {
+        return $this->addItem($this->startItems, $item);
+    }
+
+    public function addMidItem(ToolbarItem $item): static
+    {
+        return $this->addItem($this->midItems, $item);
+    }
+
+    public function addEndItem(ToolbarItem $item): static
+    {
+        return $this->addItem($this->endItems, $item);
+    }
+
+    public function getTable(): TableComponent
+    {
+        return $this->table;
+    }
+
+    public function getSearch(): ?SearchItem
+    {
+        return $this->searchItem;
+    }
+
+    public function getPageSize(): ?PageSizeItem
+    {
+        return $this->pageSizeItem;
+    }
+
+    public function render(): View
+    {
+        return view('laralight-tables::toolbar', [
+            'startItems' => $this->startItems,
+            'midItems' => $this->midItems,
+            'endItems' => $this->endItems,
+        ]);
+    }
+
+}
diff --git a/src/Toolbar/ColumnSelect.php b/src/Toolbar/ColumnSelect.php
new file mode 100644
index 0000000..a70c49a
--- /dev/null
+++ b/src/Toolbar/ColumnSelect.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Lightscale\LaralightTables\Toolbar;
+
+use Illuminate\View\View;
+
+class ColumnSelect extends Item
+{
+    public function __construct() {}
+
+    public function render(): View
+    {
+        return view('laralight-tables::toolbar.column-select', [
+            'allColumns' => $this->getTable()->getColumns()
+        ]);
+    }
+}
diff --git a/src/Toolbar/Filter.php b/src/Toolbar/Filter.php
new file mode 100644
index 0000000..ebda2c6
--- /dev/null
+++ b/src/Toolbar/Filter.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Lightscale\LaralightTables\Toolbar;
+
+abstract class Filter extends Item
+{
+
+}
diff --git a/src/Toolbar/Item.php b/src/Toolbar/Item.php
new file mode 100644
index 0000000..168f877
--- /dev/null
+++ b/src/Toolbar/Item.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Lightscale\LaralightTables\Toolbar;
+
+use Lightscale\LaralightTables\TableComponent;
+use Lightscale\LaralightTables\Toolbar;
+
+use Illuminate\View\View;
+use Illuminate\Support\HtmlString;
+
+abstract class Item
+{
+    private Toolbar $toolbar;
+
+    public function setToolbar(Toolbar $toolbar): void
+    {
+        $this->toolbar = $toolbar;
+    }
+
+    public function getToolbar(): Toolbar
+    {
+        return $this->toolbar;
+    }
+
+    public function getTable(): TableComponent
+    {
+        return $this->getToolbar()->getTable();
+    }
+
+    abstract public function render(): View|HtmlString|string|null;
+
+}
diff --git a/src/Toolbar/PageSize.php b/src/Toolbar/PageSize.php
new file mode 100644
index 0000000..9c2821a
--- /dev/null
+++ b/src/Toolbar/PageSize.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Lightscale\LaralightTables\Toolbar;
+
+use Illuminate\View\View;
+
+class PageSize extends Item
+{
+    public function __construct(
+        private array $pageSizes = [10, 25, 50],
+    ) {}
+
+    public function render(): View
+    {
+        return view('laralight-tables::toolbar.page-size', [
+            'pageSizes' => $this->pageSizes,
+        ]);
+    }
+}
diff --git a/src/Toolbar/Search.php b/src/Toolbar/Search.php
new file mode 100644
index 0000000..0b0dc37
--- /dev/null
+++ b/src/Toolbar/Search.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Lightscale\LaralightTables\Toolbar;
+
+use Illuminate\View\View;
+
+class Search extends Item
+{
+    public function __construct(
+        protected int $debounce = 250,
+        protected int $minLength = 2,
+    ) {}
+
+    public function getMinLength(): int
+    {
+        return $this->minLength;
+    }
+
+    public function render(): View
+    {
+        return view('laralight-tables::toolbar.search', [
+            'debounce' => $this->debounce,
+        ]);
+    }
+}
diff --git a/workbench/app/Livewire/CategoriesTable.php b/workbench/app/Livewire/CategoriesTable.php
index 92287b9..14cd5a8 100644
--- a/workbench/app/Livewire/CategoriesTable.php
+++ b/workbench/app/Livewire/CategoriesTable.php
@@ -4,10 +4,9 @@ namespace Workbench\App\Livewire;
 
 use Workbench\App\Models\Product;
 
-use Lightscale\LaralightTables\TableComponent;
 use Lightscale\LaralightTables\Columns\Column;
 
-class CategoriesTable extends TableComponent
+class CategoriesTable extends Table
 {
     protected $model = Product::class;
 
diff --git a/workbench/app/Livewire/OrdersTable.php b/workbench/app/Livewire/OrdersTable.php
index 97ef195..bb909a3 100644
--- a/workbench/app/Livewire/OrdersTable.php
+++ b/workbench/app/Livewire/OrdersTable.php
@@ -4,10 +4,9 @@ namespace Workbench\App\Livewire;
 
 use Workbench\App\Models\Product;
 
-use Lightscale\LaralightTables\TableComponent;
 use Lightscale\LaralightTables\Columns\Column;
 
-class OrdersTable extends TableComponent
+class OrdersTable extends Table
 {
     protected $model = Product::class;
 
diff --git a/workbench/app/Livewire/ProductsTable.php b/workbench/app/Livewire/ProductsTable.php
index 3b327d9..e3fefc3 100644
--- a/workbench/app/Livewire/ProductsTable.php
+++ b/workbench/app/Livewire/ProductsTable.php
@@ -4,15 +4,28 @@ namespace Workbench\App\Livewire;
 
 use Workbench\App\Models\Product;
 
-use Lightscale\LaralightTables\TableComponent;
 use Lightscale\LaralightTables\Columns\Column;
+use Lightscale\LaralightTables\Toolbar;
+use Lightscale\LaralightTables\Toolbar\Search;
+use Lightscale\LaralightTables\Toolbar\PageSize;
+use Lightscale\LaralightTables\Toolbar\ColumnSelect;
 
 use Illuminate\Database\Eloquent\Builder;
 
-class ProductsTable extends TableComponent
+class ProductsTable extends Table
 {
     protected $model = Product::class;
 
+    public function toolbar(): Toolbar
+    {
+        return parent::toolbar()
+            ->addStartItem(new Search())
+            ->addEndItem(new PageSize())
+            ->addEndItem(new ColumnSelect());
+
+        return $toolbar;
+    }
+
     protected function search(Builder $q, string $s): void
     {
         $q->search($s);
diff --git a/workbench/app/Livewire/Table.php b/workbench/app/Livewire/Table.php
new file mode 100644
index 0000000..5bffaed
--- /dev/null
+++ b/workbench/app/Livewire/Table.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Workbench\App\Livewire;
+
+use Lightscale\LaralightTables\TableComponent;
+use Lightscale\LaralightTables\Toolbar;
+
+abstract class Table extends TableComponent
+{
+
+    public function toolbar(): Toolbar
+    {
+        return new Toolbar($this);
+    }
+
+}
diff --git a/workbench/resources/views/components/layouts/app.blade.php b/workbench/resources/views/components/layouts/app.blade.php
index 68016cd..599f5c4 100644
--- a/workbench/resources/views/components/layouts/app.blade.php
+++ b/workbench/resources/views/components/layouts/app.blade.php
@@ -38,6 +38,7 @@
                 </div>
             </div>
         </header>
+
         <div class="container">
             {{ $slot }}
         </div>
-- 
cgit v1.2.3