predatorstudio / live-table
Interactive Livewire table component for Laravel — sortable, filterable, paginated, with drag-and-drop columns.
Requires
- php: ^8.2|^8.3|^8.4
- laravel/framework: ^11.0|^12.0|^13.0
- livewire/livewire: ^4.0
Requires (Dev)
- laravel/pint: ^1.28
- orchestra/testbench: ^9.0
- pestphp/pest: ^3.0
This package is not auto-updated.
Last update: 2026-05-08 19:47:08 UTC
README
Interaktywny komponent tabeli danych dla Laravela oparty na Livewire 4. Sortowanie, filtrowanie, paginacja, widoczność kolumn, drag & drop, edycja inline – bez własnego JavaScriptu.
Wymagania
| Zależność | Wersja |
|---|---|
| PHP | ^8.2 |
| Laravel | ^11.0 | ^12.0 |
| Livewire | ^4.0 |
| Bootstrap | 5.x |
| Alpine.js | 3.x |
Bootstrap 5 i Alpine.js muszą być załadowane przez aplikację hosta.
Instalacja
composer require predatorstudio/live-table
Pakiet rejestruje się automatycznie przez Laravel Package Discovery. Opcjonalnie opublikuj widoki, aby je dostosować:
php artisan vendor:publish --tag=live-table-views
Szybki start
1. Utwórz komponent
php artisan make:livewire UsersTable
2. Rozszerz BaseTable
<?php namespace App\Livewire; use App\Models\User; use Illuminate\Database\Eloquent\Builder; use PredatorStudio\LiveTable\BaseTable; use PredatorStudio\LiveTable\Column; use PredatorStudio\LiveTable\Filter; use PredatorStudio\LiveTable\Action; use PredatorStudio\LiveTable\BulkAction; class UsersTable extends BaseTable { protected function baseQuery(): Builder { return User::query(); } public function columns(): array { return [ Column::make('id', 'ID')->sortable(), Column::make('name', 'Imię i nazwisko')->sortable(), Column::make('email', 'E-mail')->sortable(), Column::date('created_at', 'Data rejestracji')->sortable(), ]; } protected function applySearch(Builder $query, string $search): Builder { return $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%"); }); } }
3. Umieść w widoku Blade
<livewire:users-table />
Kolumny (Column)
Typy kolumn
| Metoda fabryczna | Opis |
|---|---|
Column::make($key, $label) |
Tekst (domyślnie) |
Column::text($key, $label) |
Jawny tekst |
Column::number($key, $label, $decimals, $prefix, $suffix) |
Liczba z opcjonalnym formatowaniem |
Column::date($key, $label, $format) |
Data (domyślnie d.m.Y) |
Column::dateTime($key, $label, $format) |
Data i czas (domyślnie d.m.Y H:i) |
Column::time($key, $label, $format) |
Czas (domyślnie H:i) |
Column::money($key, $label, $format, $currency) |
Kwota pieniężna |
Column::link($key, $label, $urlResolver, $labelResolver) |
Klikalny link |
Column::badge($key, $label, $map) |
Kolorowy badge |
Column::checkbox($key, $label) |
Edytowalny checkbox (inline) |
Column::select($key, $label, $selectCell) |
Edytowalny select (inline) |
Column::custom($key, $label, $cell) |
Własna implementacja Cell |
Modyfikatory (fluent API)
Column::make('status', 'Status') ->sortable() // włącza sortowanie po kliknięciu nagłówka ->hidden() // domyślnie ukryta (można odkryć przez panel kolumn) ->format(fn($row, $value) => '<strong>' . e($value) . '</strong>');
Przykład – badge ze statusem
Column::badge('status', 'Status', [ 'active' => 'success', 'inactive' => 'secondary', 'banned' => 'danger', ])->sortable(),
Przykład – link
Column::link('name', 'Imię', fn($row) => route('users.show', $row->id)),
Filtry (Filter)
Zdefiniuj filtry w metodzie filters() i zastosuj je w applyFilters():
public function filters(): array { return [ Filter::text('name', 'Imię'), Filter::select('status', 'Status', [ 'active' => 'Aktywny', 'inactive' => 'Nieaktywny', ]), Filter::date('created_at', 'Data rejestracji'), ]; } protected function applyFilters(Builder $query): Builder { if (!empty($this->activeFilters['name'])) { $query->where('name', 'like', '%' . $this->activeFilters['name'] . '%'); } if (!empty($this->activeFilters['status'])) { $query->where('status', $this->activeFilters['status']); } if (!empty($this->activeFilters['created_at'])) { $query->whereDate('created_at', $this->activeFilters['created_at']); } return $query; }
Filtry są wyświetlane w modalnym oknie dialogowym.
Akcje nagłówkowe (Action)
Przyciski wyświetlane w prawym górnym rogu tabeli:
public function headerActions(): array { return [ Action::make('Dodaj użytkownika') ->href(route('users.create')) ->icon('<svg>...</svg>'), Action::make('Eksportuj') ->method('exportCsv'), ]; }
Akcje zbiorcze (BulkAction)
Akcje na zaznaczonych wierszach. Włącz selekcję przez $selectable = true:
protected bool $selectable = true; public function bulkActions(): array { return [ BulkAction::make('deleteSelected', 'Usuń zaznaczone') ->icon('<svg>...</svg>'), ]; } public function deleteSelected(): void { User::whereIn('id', $this->selected)->delete(); $this->selected = []; }
Zaznaczone ID wiersza są dostępne w $this->selected (tablica stringów).
Konfiguracja komponentu
Właściwości chronione, które można nadpisać w subklasie:
protected bool $selectable = false; // checkbox przy każdym wierszu protected string $primaryKey = 'id'; // klucz używany do selekcji protected bool $displaySearch = true; // pole wyszukiwania protected bool $displayColumnList = true; // przycisk zarządzania kolumnami
Edycja inline
Kolumny checkbox i select umożliwiają edycję bezpośrednio w tabeli.
Po zmianie wartości wywoływana jest metoda updateCell() w BaseTable, która przez interfejs EditableCell deleguje zapis do modelu.
Przykład SelectCell:
use PredatorStudio\LiveTable\Cells\SelectCell; Column::select('status', 'Status', new class extends SelectCell { protected array $options = [ 'active' => 'Aktywny', 'inactive' => 'Nieaktywny', ]; public function update(mixed $row, mixed $value): void { $row->update(['status' => $value]); } }),
Jak działa pipeline danych
baseQuery()
↓
applySearch() ← gdy pole wyszukiwania nie jest puste
↓
applyFilters() ← gdy są aktywne filtry
↓
orderBy() ← gdy wybrano sortowanie
↓
offset/limit ← paginacja
↓
render() → widok Blade
Każde zdarzenie Livewire (wpisanie w wyszukiwarce, kliknięcie sortowania, zmiana strony) uruchamia cały pipeline od nowa, gwarantując spójny stan.
Struktura plików pakietu
src/
├── BaseTable.php # Abstrakcyjny komponent Livewire
├── Column.php # Definicja kolumny, fluent API
├── Filter.php # Definicja filtra
├── Action.php # Akcja nagłówkowa
├── BulkAction.php # Akcja zbiorcza
├── LiveTableServiceProvider.php
├── Cells/
│ ├── Cell.php # Abstrakcja komórki (read-only)
│ ├── EditableCell.php # Abstrakcja komórki edytowalnej
│ ├── TextCell.php
│ ├── NumberCell.php
│ ├── DateCell.php
│ ├── DateTimeCell.php
│ ├── TimeCell.php
│ ├── MoneyCell.php
│ ├── LinkCell.php
│ ├── BadgeCell.php
│ ├── CheckboxCell.php
│ └── SelectCell.php
└── Enums/
├── DateFormat.php
├── DateTimeFormat.php
├── TimeFormat.php
└── MoneyFormat.php
resources/views/
├── bootstrap/base-table.blade.php # Szablon Bootstrap 5
└── tailwind/base-table.blade.php # Szablon Tailwind CSS
Bezpieczeństwo
Scopowanie zapytań do użytkownika
baseQuery() musi zawężać wyniki do zasobów bieżącego użytkownika, aby uniknąć nieautoryzowanego dostępu do cudzych danych:
protected function baseQuery(): Builder { return Order::where('user_id', auth()->id()); }
Hasła i wrażliwe pola
LiveTable automatycznie haszuje pola pasujące do wzorca password lub *_password przed zapisem do bazy (przez Hash::make()). Możesz wyłączyć to zachowanie przez protected bool $autoHashPasswords = false; i obsłużyć haszowanie ręcznie w beforeCreate() / beforeUpdate():
protected function beforeCreate(array &$data): void { if (isset($data['password'])) { $data['password'] = Hash::make($data['password']); } }
Autoryzacja akcji destruktywnych
Przesłoń authorizeAction() aby dodać kontrolę dostępu do operacji edycji i usuwania:
protected function authorizeAction(string $action, mixed $record = null): void { $this->authorize($action, $record ?? $this->model); }
Metoda jest wywoływana automatycznie w deleteRow(), updateRecord(), createRecord(), massDelete() i massEditUpdate().
Ograniczenie pól w formularzach
Użyj $creatableFields, aby określić które pola są dostępne w modalu tworzenia/edycji. Pola spoza listy (np. is_admin, stripe_id) nie będą eksponowane ani zapisywalne:
protected array $creatableFields = ['name', 'email', 'role'];
Limit zaznaczonych wierszy
Domyślnie użytkownik może zaznaczyć maksymalnie 10 000 wierszy ($maxSelected). Możesz zmienić limit w subklasie:
protected int $maxSelected = 500;
Testy
./vendor/bin/pest
Framework testowy: Pest PHP.
Testy jednostkowe w tests/Unit/, testy integracyjne w tests/Feature/.
Licencja
MIT © Predator Studio