summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Light <samlight1994@gmail.com>2025-03-30 14:20:57 +0100
committerSam Light <samlight1994@gmail.com>2025-03-30 14:20:57 +0100
commite744b8d67ef1d18050158dd523ba7d804c1c8528 (patch)
tree56c90a83912bd63bdd507e76e5ef00c82a904b50
parenta883170dd1c723bcd02916f9bc8a96ed85a61761 (diff)
Filters, sorting, escaping and more....
-rw-r--r--resources/views/table.blade.php10
-rw-r--r--resources/views/toolbar/select-filter.blade.php23
-rw-r--r--src/Columns/Column.php34
-rw-r--r--src/Concerns/Makable.php11
-rw-r--r--src/TableComponent.php65
-rw-r--r--src/Toolbar.php9
-rw-r--r--src/Toolbar/ColumnSelect.php4
-rw-r--r--src/Toolbar/Filter.php26
-rw-r--r--src/Toolbar/Item.php3
-rw-r--r--src/Toolbar/SelectFilter.php35
-rw-r--r--workbench/app/Livewire/ProductsTable.php36
11 files changed, 236 insertions, 20 deletions
diff --git a/resources/views/table.blade.php b/resources/views/table.blade.php
index bcefe57..448a290 100644
--- a/resources/views/table.blade.php
+++ b/resources/views/table.blade.php
@@ -9,7 +9,15 @@
<thead>
<tr>
@foreach($columns as $column)
- <th scope="col">{{ $column->getTitle() }}</th>
+ <th
+ scope="col"
+ @class(['sortable' => $column->isSortable()])
+ @if($column->isSortable())
+ wire:click="orderBy('{{ $column->name }}')"
+ @endif
+ >
+ {{ $column->getTitle() }}
+ </th>
@endforeach
</tr>
</thead>
diff --git a/resources/views/toolbar/select-filter.blade.php b/resources/views/toolbar/select-filter.blade.php
new file mode 100644
index 0000000..13cdbc1
--- /dev/null
+++ b/resources/views/toolbar/select-filter.blade.php
@@ -0,0 +1,23 @@
+<div class="filter d-flex align-items-center">
+ @if ($label)
+ <label for="{{ $id }}" class="mx-3">
+ {{ $label }}
+ </label>
+ @endif
+ <select
+ id="{{ $id }}"
+ class="form-control"
+ wire:model.live="filters.{{ $key }}"
+ >
+ @if (!empty($placeholder))
+ <option value="">
+ {{ $placeholder }}
+ </option>
+ @endif
+ @foreach ($options as $value => $option)
+ <option value="{{ $value }}">
+ {{ $option }}
+ </option>
+ @endforeach
+ </select>
+</div>
diff --git a/src/Columns/Column.php b/src/Columns/Column.php
index 895708f..a8dc2e6 100644
--- a/src/Columns/Column.php
+++ b/src/Columns/Column.php
@@ -5,6 +5,7 @@ namespace Lightscale\LaralightTables\Columns;
use Lightscale\LaralightTables\TableComponent;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\View\ComponentAttributeBag;
use Illuminate\Support\HtmlString;
@@ -14,6 +15,7 @@ class Column {
private TableComponent $table;
private bool $showInSelect;
+ private bool $shouldEscape = true;
private ?Closure $slotFn = null;
private ?Closure $sortFn = null;
@@ -54,12 +56,24 @@ class Column {
return $this;
}
- public function sortable(callable $fn) : static
+ public function sortable(?callable $fn): static
{
- $this->sortFn = Closure::fromCallable($fn);
+ $this->sortFn = $fn;
return $this;
}
+ public function isSortable(): bool
+ {
+ return $this->sortFn !== null;
+ }
+
+ public function applySort(Builder $query, string $dir): void
+ {
+ if ($this->sortFn !== null) {
+ ($this->sortFn)($query, $dir);
+ }
+ }
+
public function colClass(string $v) : static
{
$this->colClass = $v;
@@ -88,9 +102,23 @@ class Column {
return $this->showInSelect;
}
+ public function shouldEscape(bool $v): static
+ {
+ $this->shouldEscape = $v;
+ return $this;
+ }
+
+ public function escape(string $content): string
+ {
+ return $this->shouldEscape ? e($content) : $content;
+ }
+
protected function getContent(Model $row): string
{
- return $this->slotFn?->call($this, $row, $this) ?? $this->defaultSlot($row);
+ return $this->escape(
+ $this->slotFn?->call($this, $row, $this) ??
+ $this->defaultSlot($row)
+ );
}
public function view(Model $row): HtmlString
diff --git a/src/Concerns/Makable.php b/src/Concerns/Makable.php
new file mode 100644
index 0000000..7562970
--- /dev/null
+++ b/src/Concerns/Makable.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Lightscale\LaralightTables\Concerns;
+
+trait Makable
+{
+ public static function make(mixed ...$args): static
+ {
+ return new static(...$args);
+ }
+}
diff --git a/src/TableComponent.php b/src/TableComponent.php
index 0a55d5e..266cdc3 100644
--- a/src/TableComponent.php
+++ b/src/TableComponent.php
@@ -2,6 +2,8 @@
namespace Lightscale\LaralightTables;
+use Lightscale\LaralightTables\Columns\Column;
+
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
@@ -28,9 +30,19 @@ abstract class TableComponent extends Component
#[Url]
public int $pageSize;
+
public array $activeColumns = [];
- public function mount()
+ #[Url]
+ public array $filters = [];
+
+ #[Url]
+ public ?string $order = null;
+
+ #[Url]
+ public ?string $orderDirection = null;
+
+ public function mount(): void
{
$this->setDefaultActiveColumns();
$this->setDefaultPageSize();
@@ -63,6 +75,28 @@ abstract class TableComponent extends Component
}
}
+ public function updatedFilters(): void
+ {
+ $this->resetPage();
+ }
+
+ public function orderBy(string $column): void
+ {
+ if ($column === $this->order) {
+ if ($this->orderDirection === 'desc') {
+ $this->order = null;
+ $this->orderDirection = null;
+ }
+ else {
+ $this->orderDirection = 'desc';
+ }
+ }
+ else {
+ $this->order = $column;
+ $this->orderDirection = 'asc';
+ }
+ }
+
protected function toolbar(): ?Toolbar
{
return null;
@@ -91,6 +125,25 @@ abstract class TableComponent extends Component
protected function search(Builder $builder, string $search) : void {}
+ protected function getFilters(): Collection
+ {
+ return $this->getToolbar()?->getFilters() ?? collect();
+ }
+
+
+ protected function getOrderColumn(): ?Column
+ {
+ return ($name = $this->order) === null ? null : $this->getColumns()[$name] ?? null;
+ }
+
+ protected function applyOrder(Builder $query): void
+ {
+ $column = $this->getOrderColumn();
+ if ($column) {
+ $column->applySort($query, $this->orderDirection);
+ }
+ }
+
protected function buildQuery() : Builder
{
$query = $this->query();
@@ -99,16 +152,22 @@ abstract class TableComponent extends Component
$this->search($query, $this->search);
}
+ foreach ($this->getFilters() as $filter) {
+ $filter->applyFilter($query);
+ }
+
+ $this->applyOrder($query);
+
return $query;
}
private ?Collection $columnsCache = null;
- public function getColumns()
+ public function getColumns(): Collection
{
if($this->columnsCache === null) {
$this->columnsCache = collect($this->columns())->each(
fn($c) => $c->setTable($this)
- );
+ )->keyBy('name');
}
return $this->columnsCache;
diff --git a/src/Toolbar.php b/src/Toolbar.php
index 2db39f5..8539e0e 100644
--- a/src/Toolbar.php
+++ b/src/Toolbar.php
@@ -5,7 +5,7 @@ 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 Lightscale\LaralightTables\Toolbar\Filter as FilterItem;
use Illuminate\View\View;
use Illuminate\Support\Collection;
@@ -18,6 +18,7 @@ class Toolbar
protected Collection $startItems;
protected Collection $midItems;
protected Collection $endItems;
+ protected Collection $filterItems;
public function __construct(
private TableComponent $table
@@ -26,6 +27,7 @@ class Toolbar
$this->startItems = collect();
$this->midItems = collect();
$this->endItems = collect();
+ $this->filterItems = collect();
}
private function addItem(Collection $list, ToolbarItem $item): static
@@ -76,6 +78,11 @@ class Toolbar
return $this->pageSizeItem;
}
+ public function getFilters(): Collection
+ {
+ return $this->filterItems;
+ }
+
public function render(): View
{
return view('laralight-tables::toolbar', [
diff --git a/src/Toolbar/ColumnSelect.php b/src/Toolbar/ColumnSelect.php
index a70c49a..9380650 100644
--- a/src/Toolbar/ColumnSelect.php
+++ b/src/Toolbar/ColumnSelect.php
@@ -11,7 +11,9 @@ class ColumnSelect extends Item
public function render(): View
{
return view('laralight-tables::toolbar.column-select', [
- 'allColumns' => $this->getTable()->getColumns()
+ 'allColumns' => $this->getTable()
+ ->getColumns()
+ ->filter(fn($c) => $c->getShowInSelect())
]);
}
}
diff --git a/src/Toolbar/Filter.php b/src/Toolbar/Filter.php
index 19f29cc..51e0d9a 100644
--- a/src/Toolbar/Filter.php
+++ b/src/Toolbar/Filter.php
@@ -2,12 +2,30 @@
namespace Lightscale\LaralightTables\Toolbar;
+use Illuminate\Database\Eloquent\Builder;
+
use Closure;
abstract class Filter extends Item
{
protected ?Closure $filterCallback = null;
+ public function __construct(
+ protected string $key,
+ protected ?string $label = null,
+ ) {}
+
+ public function label(string $v): static
+ {
+ $this->label = $v;
+ return $this;
+ }
+
+ public function makeId(): string
+ {
+ return "filter_{$this->key}";
+ }
+
public function filter(callable $filterCB): static
{
$this->filterCallback = $filterCB;
@@ -16,7 +34,13 @@ abstract class Filter extends Item
public function applyFilter(Builder $query): void
{
- ($this->filterCallback)($query, $value);
+ if ($this->filterCallback !== null) {
+ $value = $this->getTable()->filters[$this->key] ?? null;
+
+ if (!empty($value)) {
+ ($this->filterCallback)($query, $value);
+ }
+ }
}
}
diff --git a/src/Toolbar/Item.php b/src/Toolbar/Item.php
index 168f877..e91698f 100644
--- a/src/Toolbar/Item.php
+++ b/src/Toolbar/Item.php
@@ -4,12 +4,15 @@ namespace Lightscale\LaralightTables\Toolbar;
use Lightscale\LaralightTables\TableComponent;
use Lightscale\LaralightTables\Toolbar;
+use Lightscale\LaralightTables\Concerns\Makable;
use Illuminate\View\View;
use Illuminate\Support\HtmlString;
abstract class Item
{
+ use Makable;
+
private Toolbar $toolbar;
public function setToolbar(Toolbar $toolbar): void
diff --git a/src/Toolbar/SelectFilter.php b/src/Toolbar/SelectFilter.php
new file mode 100644
index 0000000..0549885
--- /dev/null
+++ b/src/Toolbar/SelectFilter.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Lightscale\LaralightTables\Toolbar;
+
+use Illuminate\View\View;
+
+class SelectFilter extends Filter
+{
+ protected iterable $options;
+ protected ?string $placeholder = null;
+
+ public function placeholder(?string $v): static
+ {
+ $this->placeholder = $v;
+ return $this;
+ }
+
+ public function options(iterable $options): static
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ public function render(): View
+ {
+ return view('laralight-tables::toolbar.select-filter', [
+ 'id' => $this->makeId(),
+ 'key' => $this->key,
+ 'label' => $this->label,
+ 'options' => $this->options,
+ 'placeholder' => $this->placeholder,
+ ]);
+ }
+
+}
diff --git a/workbench/app/Livewire/ProductsTable.php b/workbench/app/Livewire/ProductsTable.php
index c5a0f61..9717b5d 100644
--- a/workbench/app/Livewire/ProductsTable.php
+++ b/workbench/app/Livewire/ProductsTable.php
@@ -3,12 +3,14 @@
namespace Workbench\App\Livewire;
use Workbench\App\Models\Product;
+use Workbench\App\Models\Category;
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 Lightscale\LaralightTables\Toolbar\SelectFilter;
use Illuminate\Database\Eloquent\Builder;
@@ -18,12 +20,21 @@ class ProductsTable extends Table
public function toolbar(): Toolbar
{
- return parent::toolbar()
- ->addStartItem(new Search())
- ->addEndItem(new PageSize())
- ->addEndItem(new ColumnSelect());
+ $categoryFilter = SelectFilter::make('category')
+ ->placeholder(__('Filter category'))
+ ->options(Category::pluck('name', 'id'))
+ ->filter(
+ fn(Builder $q, string $value) => $q->whereHas(
+ 'category',
+ fn(Builder $q) => $q->where('id', $value)
+ )
+ );
- return $toolbar;
+ return parent::toolbar()
+ ->addStartItem(Search::make())
+ ->addStartItem($categoryFilter)
+ ->addEndItem(PageSize::make())
+ ->addEndItem(ColumnSelect::make());
}
public function query(): Builder
@@ -40,12 +51,17 @@ class ProductsTable extends Table
public function columns(): array
{
return [
- Column::make('id', 'ID'),
- Column::make('name', 'Name'),
- Column::make('category.name', 'Category')
+ Column::make('id', 'ID')
+ ->sortable(fn(Builder $q, string $dir) => $q->orderBy('id', $dir)),
+ Column::make('name', 'Name')
+ ->sortable(fn(Builder $q, string $dir) => $q->orderBy('name', $dir)),
+ Column::make('category_name', 'Category')
->slot(fn($r) => $r->category->name),
- Column::make('price', 'Price'),
- Column::make('stock', 'Stock'),
+ Column::make('price', 'Price')
+ ->sortable(fn(Builder $q, string $dir) => $q->orderBy('price', $dir))
+ ->slot(fn($r, $c) => "£{$r->{$c->name}}"),
+ Column::make('stock', 'Stock')
+ ->sortable(fn(Builder $q, string $dir) => $q->orderBy('stock', $dir)),
];
}
}