From 185a3e03c9326a7fcc52b3301afa6a2a30f56503 Mon Sep 17 00:00:00 2001 From: Mark Salmon Date: Sat, 27 Jun 2020 22:40:14 +0100 Subject: [PATCH 1/8] wip --- README.md | 8 + composer.json | 3 +- config/config.php | 8 - config/livewire-datatables.php | 8 + resources/views/livewire/datatable.blade.php | 43 +++--- src/Field.php | 32 ++-- src/Fieldset.php | 31 ++-- src/Http/Livewire/Datatable.php | 11 ++ src/LivewireDatatablesServiceProvider.php | 8 +- src/Traits/LivewireDatatable.php | 145 ++++++++++++------- 10 files changed, 190 insertions(+), 107 deletions(-) delete mode 100644 config/config.php create mode 100644 config/livewire-datatables.php create mode 100644 src/Http/Livewire/Datatable.php diff --git a/README.md b/README.md index ee2502cc..a01fa541 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ Advanced datatable with sorting, filtering, searching ... +## Requirements +- [Laravel 7.x](https://laravel.com/docs/7.x) +- [Livewire 1.x](https://laravel-livewire.com/) + + ## Installation You can install the package via composer: @@ -17,6 +22,9 @@ composer require mediconesystems/livewire-datatables ## Basic Usage +- Create a new Livewire component for your table. ``` php artisan livewire:make UsersTable``` +- You can delete the blade view ```resources/view/livewire/users-table.blade.php``` + ``` php 'H:i', + 'default_date_format' => 'd/m/Y' + +]; diff --git a/resources/views/livewire/datatable.blade.php b/resources/views/livewire/datatable.blade.php index 29f0b9ca..f3f11f84 100644 --- a/resources/views/livewire/datatable.blade.php +++ b/resources/views/livewire/datatable.blade.php @@ -1,4 +1,4 @@ -
+
@foreach($fields as $index => $field) @endforeach
-
- @foreach ($this->columns as $key) + @foreach($this->visibleFields as $index => $field)
-
@foreach($this->results as $result)
- @foreach($this->columns as $key) + @foreach($this->visibleFields as $field)
- @if($result->$key === 'check-circle') + @if($result->{$field['name']} === 'check-circle') - @elseif($result->$key === 'x-circle') + @elseif($result->{$field['name']} === 'x-circle') @else - {!! $result->$key !!} + {!! $result->{$field['name']} !!} @endif
@endforeach @@ -74,57 +73,60 @@
+ {{-- @dump($activeSelectFilters, $this->selectFilters) --}} + @if($this->activeFilters)
-
-
+
@if(isset($dates['field']) && $dates['field'] !== '' && ((isset($dates['start']) && $dates['start'] !== '') || (isset($dates['end']) && $dates['end'] !== ''))) - @endif @if(isset($times['field']) && $times['field'] !== '') - @endif @foreach($activeSelectFilters as $index => $activeSelectFilter) @foreach($activeSelectFilter as $key => $value) - @endforeach @endforeach @foreach($activeBooleanFilters as $index => $activeBooleanFilter) - @endforeach @foreach($activeTextFilters as $index => $activeTextFilter) - @endforeach
+ @endif @if(count($this->dateFilters))
- @@ -155,7 +157,7 @@
- @@ -177,7 +179,7 @@
- @@ -237,7 +239,6 @@
@endif -
diff --git a/src/Field.php b/src/Field.php index 4cd69067..4eff389d 100644 --- a/src/Field.php +++ b/src/Field.php @@ -2,13 +2,14 @@ namespace Mediconesystems\LivewireDatatables; -use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; class Field { public $name; public $column; + public $sort; + public $defaultSort; public $callback; public $selectFilter; public $booleanFilter; @@ -16,6 +17,7 @@ class Field public $dateFilter; public $timeFilter; public $hidden; + public $params = []; public static function fromColumn($column) @@ -51,6 +53,18 @@ public function name($name) return $this; } + public function sortBy($column) + { + $this->sort = $column; + return $this; + } + + public function defaultSort($direction = 'desc') + { + $this->defaultSort = $direction; + return $this; + } + public function withSelectFilter($selectFilter) { $this->selectFilter = $selectFilter; @@ -100,35 +114,35 @@ public function formatBoolean() return $this; } - public function linkTo($model) + public function linkTo($model, $pad) { $this->callback = 'makeLink'; - $this->params = $model; + $this->params = func_get_args(); return $this; } - public function formatDate($format = 'd/m/Y') + public function formatDate($format = null) { $this->callback = 'formatDate'; - $this->params = $format; + $this->params = func_get_args(); return $this; } - public function formatTime($format = 'H:i') + public function formatTime($format = null) { $this->callback = 'formatTime'; - $this->params = $format; + $this->params = func_get_args(); return $this; } public function round($precision = 0) { $this->callback = 'round'; - $this->params = $precision; + $this->params = func_get_args(); return $this; } - public function callback($callback, $params = null) + public function callback($callback, $params = []) { $this->callback = $callback; $this->params = $params; diff --git a/src/Fieldset.php b/src/Fieldset.php index ef441427..6e44dce6 100644 --- a/src/Fieldset.php +++ b/src/Fieldset.php @@ -9,19 +9,21 @@ class Fieldset public $table; public $fields; + public function __construct($table, $fields) + { + $this->table = $table; + $this->fields = $fields; + } + public static function fromModel($model) { - $instance = $model::first(); - $fieldset = new static; - $fieldset->table = $instance->getTable(); + $instance = $model::firstOrFail(); - $fieldset->fields = collect($instance->getAttributes())->keys()->reject(function ($name) use ($instance) { + return new static($instance->getTable(), collect($instance->getAttributes())->keys()->reject(function ($name) use ($instance) { return in_array($name, $instance->getHidden()); - })->map(function ($attribute) use ($fieldset) { - return Field::fromColumn($fieldset->table . '.' . $attribute); - }); - - return $fieldset; + })->map(function ($attribute) use ($instance) { + return Field::fromColumn($instance->getTable() . '.' . $attribute); + })); } public function except($fields) @@ -41,7 +43,7 @@ public function formatDates($columns, $format = 'd/m/Y') foreach ($columns as $column) { if ($field = $this->fields->firstWhere('column', $column)) { $field->callback = 'formatDate'; - $field->params = $format; + $field->params = [$format]; } } return $this; @@ -57,6 +59,15 @@ public function dateFilters($columns) return $this; } + public function uppercase($values) + { + foreach ($values as $column) { + $field = $this->fields->firstWhere('column', $column); + $field->name = strtoupper($field->name); + } + return $this; + } + public function rename($values) { foreach ($values as $column => $newName) { diff --git a/src/Http/Livewire/Datatable.php b/src/Http/Livewire/Datatable.php new file mode 100644 index 00000000..af26939e --- /dev/null +++ b/src/Http/Livewire/Datatable.php @@ -0,0 +1,11 @@ +loadViewsFrom(__DIR__ . '/../resources/views', 'datatables'); $this->loadViewsFrom(__DIR__ . '/../resources/views/icons', 'icons'); @@ -26,7 +30,7 @@ public function boot() ], 'config'); $this->publishes([ - __DIR__ . '/../resources/views' => resource_path('views/vendor/livewire-datatables'), + __DIR__ . '/../resources/views/livewire' => resource_path('views/livewire'), __DIR__ . '/../resources/views/icons' => resource_path('views/vendor/livewire-datatables/icons'), ], 'views'); } @@ -34,6 +38,6 @@ public function boot() public function register() { - $this->mergeConfigFrom(__DIR__ . '/../config/config.php', 'livewire-datatables'); + $this->mergeConfigFrom(__DIR__ . '/../config/livewire-datatables.php', 'livewire-datatables'); } } diff --git a/src/Traits/LivewireDatatable.php b/src/Traits/LivewireDatatable.php index 8e1d0a82..36a3cb8f 100644 --- a/src/Traits/LivewireDatatable.php +++ b/src/Traits/LivewireDatatable.php @@ -14,6 +14,7 @@ trait LivewireDatatable { use WithPagination; + public $model; public $fields; public $sort; public $direction; @@ -25,12 +26,18 @@ trait LivewireDatatable public $times; public $perPage = 10; - public function mount() + public function mount($model = null) { + $this->model = $model; $this->fields = $this->fields()->map->toArray()->toArray(); $this->initialiseSort(); } + public function model() + { + return $this->model; + } + public function builder() { return $this->model()::query(); @@ -41,17 +48,43 @@ public function fields() return Fieldset::fromModel($this->model())->fields(); } - private function initialiseSort() + public function fieldsetFromModel() + { + return Fieldset::fromModel($this->model()); + } + + public function initialiseSort() { - $this->sort = $this->getColumns()->first(); + $this->sort = $this->defaultSort() ? $this->defaultSort()['key'] : $this->visibleFields->keys()->first(); + $this->direction = $this->defaultSort()['direction'] === 'asc'; + // dd($this->sort, $this->direction); + } + + public function defaultSort() + { + $fieldIndex = $this->fields()->search(function ($field) { + return is_string($field->defaultSort); + }); + + return $fieldIndex ? [ + 'key' => $fieldIndex, + 'direction' => $this->fields()[$fieldIndex]->defaultSort + ] : null; } - public function sort($field) + public function getSortString() { - if ($this->sort === $field) { + return $this->fields()[$this->sort]->sort + ? $this->fields()[$this->sort]->sort + : $this->fields()[$this->sort]->name; + } + + public function sort($index) + { + if ($this->sort === (int) $index) { $this->direction = !$this->direction; } else { - $this->sort = $field; + $this->sort = (int) $index; } } @@ -59,7 +92,7 @@ public function toggle($index) { $this->fields[$index]['hidden'] = !$this->fields[$index]['hidden']; - if ($this->sort === $this->fields[$index]['name']) { + if ($this->sort === $index) { $this->initialiseSort(); } } @@ -90,14 +123,6 @@ public function removeSelectFilter($column, $key = null) } } - public function clearDateFilter() - { - $this->dates = [ - 'start' => null, - 'end' => null, - ]; - } - public function lastMonth() { $this->dates['start'] = now()->subMonth()->startOfMonth()->format('Y-m-d'); @@ -134,13 +159,21 @@ public function yearToToday() $this->dates['end'] = now()->format('Y-m-d'); } + public function clearDateFilter() + { + $this->dates = null; + } + public function clearTimeFilter() { - $this->times = [ - 'field' => '', - 'start' => '', - 'end' => '', - ]; + $this->times = null; + } + + public function clearFilters() + { + $this->activeSelectFilters = []; + $this->activeBooleanFilters = []; + $this->activeTextFilters = []; } public function clearAllFilters() @@ -162,19 +195,14 @@ public function removeTextFilter($column) unset($this->activeTextFilters[$column]); } - public function visibleFields() + public function getVisibleFieldsProperty() { return collect($this->fields)->reject->hidden; } - public function getColumns() - { - return $this->visibleFields()->map->name; - } - public function getSelectStatements() { - return $this->visibleFields()->map(function ($field) { + return $this->visibleFields->map(function ($field) { return $field['column'] ? $field['column'] . ' AS ' . $field['name'] : null; })->filter(); } @@ -279,17 +307,22 @@ public function addTimeRangeFilter($builder) }); } - public function getResults() + public function scopeFields() + { + return $this->visibleFields->filter(function ($field, $key) { + return isset($field['scope']); + }); + } + + public function buildDatabaseQuery() { return $this->builder() - ->when(true, function ($query) { - $this->visibleFields()->filter(function ($field, $key) { - return isset($field['scope']); - })->each(function ($field) use ($query) { + ->select($this->getSelectStatements()->filter()->toArray()) + ->when(count($this->scopeFields()), function ($query) { + $this->scopeFields()->each(function ($field) use ($query) { $query->{$field['scope']}($field['name']); }); }) - ->addSelect($this->getSelectStatements()->filter()->toArray()) ->when(count($this->activeSelectFilters) > 0, function ($query) { return $this->addSelectFilters($query); }) @@ -305,27 +338,23 @@ public function getResults() ->when(isset($this->times['field']) && $this->times['field'] !== '', function ($query) { return $this->addTimeRangeFilter($query); }) - // ->when(isset($this->queryString), function ($query) { - // return $this->parseQueryIntoBuilder($query, $this->queryString, 'and'); - // }) ->when(isset($this->sort), function ($query) { - return $query->orderBy($this->sort, $this->direction ? 'asc' : 'desc'); + return $query->orderBy($this->getSortString(), $this->direction ? 'asc' : 'desc'); }); } - public function mapCallbacks() + public function mapCallbacks($paginatedCollection) { - $results = $this->getResults()->paginate($this->perPage); - // dd($results->getCollection()); - $results->getCollection()->map(function ($row, $i) { - + $paginatedCollection->getCollection()->map(function ($row, $i) { foreach ($row->getAttributes() as $name => $value) { - $row->$name = $this->getFieldCallback($name)['callback'] ? $this->{$this->getFieldCallback($name)['callback']}($value, $this->getFieldCallback($name)['params'] ?? null) : $value; + $row->$name = $this->getFieldCallback($name)['callback'] + ? $this->{$this->getFieldCallback($name)['callback']}($value, ...$this->getFieldCallback($name)['params'] ?? null) + : $value; } return $row; }); - return $results; + return $paginatedCollection; } public function getFieldCallback($fieldName) @@ -334,14 +363,14 @@ public function getFieldCallback($fieldName) ? Arr::only(collect($this->fields)->firstWhere('name', $fieldName), ['callback', 'params']) : null; } - public function formatTime($time) + public function formatTime($time, $format = null) { - return $time ? Carbon::parse($time)->format('H:i') : null; + return $time ? Carbon::parse($time)->format($format ?? config('livewire-datatables.default_time_format')) : null; } - public function formatDate($date, $format) + public function formatDate($date, $format = null) { - return $date ? Carbon::parse($date)->format($format) : null; + return $date ? Carbon::parse($date)->format($format ?? config('livewire-datatables.default_date_format')) : null; } public function round($value, $precision = 0) @@ -356,9 +385,9 @@ public function boolean($value) : 'x-circle'; } - public function makeLink($value, $model) + public function makeLink($value, $model, $pad = null) { - return '' . str_pad($value, 6, '0', STR_PAD_LEFT) . ''; + return '' . ($pad ? str_pad($value, $pad, '0', STR_PAD_LEFT) : $value) . ''; } public function truncate($value) @@ -369,12 +398,7 @@ public function truncate($value) public function getResultsProperty() { - return $this->mapCallbacks(); - } - - public function getColumnsProperty() - { - return $this->getColumns(); + return $this->mapCallbacks($this->buildDatabaseQuery()->paginate($this->perPage)); } public function getSelectFiltersProperty() @@ -402,6 +426,15 @@ public function getTimeFiltersProperty() return collect($this->fields)->filter->timeFilter; } + public function getActiveFiltersProperty() + { + return isset($this->dates['field']) + || isset($this->times['field']) + || count($this->activeSelectFilters) + || count($this->activeBooleanFilters) + || count($this->activeTextFilters); + } + public function render() { return view('datatables::livewire.datatable'); From 6076b20608d4b8667ac18a60cdef12e04e56ca50 Mon Sep 17 00:00:00 2001 From: Mark Salmon Date: Sun, 28 Jun 2020 01:21:16 +0100 Subject: [PATCH 2/8] convert to class --- resources/views/livewire/datatable.blade.php | 8 +++----- .../Livewire}/LivewireDatatable.php | 17 +++++++++++------ src/LivewireDatatablesServiceProvider.php | 5 +++-- 3 files changed, 17 insertions(+), 13 deletions(-) rename src/{Traits => Http/Livewire}/LivewireDatatable.php (95%) diff --git a/resources/views/livewire/datatable.blade.php b/resources/views/livewire/datatable.blade.php index f3f11f84..f9a2f0f0 100644 --- a/resources/views/livewire/datatable.blade.php +++ b/resources/views/livewire/datatable.blade.php @@ -73,7 +73,6 @@
- {{-- @dump($activeSelectFilters, $this->selectFilters) --}} @if($this->activeFilters)
@@ -85,14 +84,13 @@
- @if(isset($dates['field']) && $dates['field'] !== '' && ((isset($dates['start']) && $dates['start'] !== '') || - (isset($dates['end']) && $dates['end'] !== ''))) + @if(isset($dates['field']) && ((isset($dates['start']) || isset($dates['end'])))) @endif - @if(isset($times['field']) && $times['field'] !== '') + @if(isset($times['field']) && ((isset($times['start']) || isset($times['end']))))
@endforeach diff --git a/src/Traits/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php similarity index 95% rename from src/Traits/LivewireDatatable.php rename to src/Http/Livewire/LivewireDatatable.php index 36a3cb8f..0f199037 100644 --- a/src/Traits/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -1,16 +1,17 @@ when(count($this->activeTextFilters) > 0, function ($query) { return $this->addTextFilters($query); }) - ->when(isset($this->dates['field']) && ((isset($this->dates['start']) && $this->dates['start'] !== '') || (isset($this->dates['end']) && $this->dates['end'] !== '')), function ($query) { + ->when(isset($this->dates['field']) && (isset($this->dates['start']) || (isset($this->dates['end']))), function ($query) { return $this->addDateRangeFilter($query); }) - ->when(isset($this->times['field']) && $this->times['field'] !== '', function ($query) { + ->when(isset($this->times['field']) && (isset($this->times['start']) || (isset($this->times['end']))), function ($query) { return $this->addTimeRangeFilter($query); }) ->when(isset($this->sort), function ($query) { @@ -418,12 +419,16 @@ public function getTextFiltersProperty() public function getDateFiltersProperty() { - return collect($this->fields)->filter->dateFilter; + return tap(collect($this->fields)->filter->dateFilter, function ($fields) { + $this->dates['field'] = $fields->keys()->first(); + }); } public function getTimeFiltersProperty() { - return collect($this->fields)->filter->timeFilter; + return tap(collect($this->fields)->filter->timeFilter, function ($fields) { + $this->times['field'] = $fields->keys()->first(); + }); } public function getActiveFiltersProperty() diff --git a/src/LivewireDatatablesServiceProvider.php b/src/LivewireDatatablesServiceProvider.php index 3506289a..bdb7dba2 100644 --- a/src/LivewireDatatablesServiceProvider.php +++ b/src/LivewireDatatablesServiceProvider.php @@ -5,13 +5,14 @@ use Livewire\Livewire; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; -use Mediconesystems\LivewireDatatables\Http\Livewire\Datatable; +use Mediconesystems\LivewireDatatables\Http\Livewire\LivewireDatatable; + class LivewireDatatablesServiceProvider extends ServiceProvider { public function boot() { - Livewire::component('datatable', Datatable::class); + Livewire::component('livewire-datatable', LivewireDatatable::class); $this->loadViewsFrom(__DIR__ . '/../resources/views', 'datatables'); $this->loadViewsFrom(__DIR__ . '/../resources/views/icons', 'icons'); From 5e43e77702429a5e0db1335f60ac04082bad6fae Mon Sep 17 00:00:00 2001 From: Mark Salmon Date: Sun, 28 Jun 2020 22:28:53 +0100 Subject: [PATCH 3/8] wip --- src/Http/Livewire/LivewireDatatable.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Http/Livewire/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php index 0f199037..22e7502f 100644 --- a/src/Http/Livewire/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -76,8 +76,8 @@ public function defaultSort() public function getSortString() { return $this->fields()[$this->sort]->sort - ? $this->fields()[$this->sort]->sort - : $this->fields()[$this->sort]->name; + ?? $this->fields()[$this->sort]->column + ?? $this->fields()[$this->sort]->raw; } public function sort($index) @@ -208,6 +208,11 @@ public function getSelectStatements() })->filter(); } + public function getRawStatements() + { + return $this->visibleFields->map->raw->filter(); + } + public function getFieldColumn($index) { return $this->fields[$index]['column']; @@ -318,7 +323,12 @@ public function scopeFields() public function buildDatabaseQuery() { return $this->builder() - ->select($this->getSelectStatements()->filter()->toArray()) + ->select($this->getSelectStatements()->toArray()) + ->when(count($this->getRawStatements()), function ($query) { + $this->getRawStatements()->each(function ($statement) use ($query) { + $query->selectRaw($statement); + }); + }) ->when(count($this->scopeFields()), function ($query) { $this->scopeFields()->each(function ($field) use ($query) { $query->{$field['scope']}($field['name']); From 374b505b3f52e9f7cada9e0010d118b3509b4c9e Mon Sep 17 00:00:00 2001 From: Mark Salmon Date: Sun, 28 Jun 2020 22:29:02 +0100 Subject: [PATCH 4/8] wip --- .vscode/settings.json | 3 + README.md | 69 +++++-- resources/views/link.blade.php | 1 + resources/views/livewire/datatable.blade.php | 58 ++++-- resources/views/tooltip.blade.php | 4 + src/Field.php | 12 ++ src/Fieldset.php | 30 ++- src/Http/Livewire/LivewireDatatable.php | 195 ++++++++----------- src/LivewireDatatablesServiceProvider.php | 7 +- src/Traits/HandlesProperties.php | 109 +++++++++++ src/Traits/WithCallbacks.php | 44 +++++ src/Traits/WithPresetDateFilters.php | 43 ++++ src/Traits/WithPresetTimeFilters.php | 31 +++ 13 files changed, 442 insertions(+), 164 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 resources/views/link.blade.php create mode 100644 resources/views/tooltip.blade.php create mode 100644 src/Traits/HandlesProperties.php create mode 100644 src/Traits/WithCallbacks.php create mode 100644 src/Traits/WithPresetDateFilters.php create mode 100644 src/Traits/WithPresetTimeFilters.php diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..652edf60 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "workbench.colorTheme": "Monokai Dimmed" +} \ No newline at end of file diff --git a/README.md b/README.md index a01fa541..33e4b60b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,12 @@ [![Quality Score](https://img.shields.io/scrutinizer/g/mediconesystems/livewire-datatables.svg?style=flat-square)](https://scrutinizer-ci.com/g/mediconesystems/livewire-datatables) [![Total Downloads](https://img.shields.io/packagist/dt/mediconesystems/livewire-datatables.svg?style=flat-square)](https://packagist.org/packages/mediconesystems/livewire-datatables) -Advanced datatable with sorting, filtering, searching ... +### Features +- Use a model or query builder to supply data +- Mutate and format fields using preset or cutom callbacks +- Sort data using field or computed field +- Filter using booleans, times, dates, selects or free text +- Show / hide columns ## Requirements - [Laravel 7.x](https://laravel.com/docs/7.x) @@ -22,28 +27,56 @@ composer require mediconesystems/livewire-datatables ## Basic Usage -- Create a new Livewire component for your table. ``` php artisan livewire:make UsersTable``` -- You can delete the blade view ```resources/view/livewire/users-table.blade.php``` +- Use the ```livewire-datatables``` component in your blade view, and pass in a model: +```html +... -``` php - -namespace App\Http\Livewire; +... +``` + +## Modifying Fields +- There are many ways to modify the table by passing additional properties into the component: +```html + +``` +| Property | Accepts | Result | +|----|----|----| +|**hide-show**|*Boolean* default: *false*|Panel of buttons to show/hide columns in table is removed if this is ```true```| +|**except**|*Array* of column definitions|columns are excluded from table| +|**uppercase**|*Array* of column definitions|field names are capitalised. Useful for ID fields or abbreviations| +|**truncate**|*Array* of column definitions|field values are truncated, the whole text can be seen in tooltip on hover| +|**formatDates**|*Array* of column definitions|field values are formatted as per the default date format| +|**dateFilters**|*Array* of column definitions|Date filters are made available on the table for these fields| +|**rename**|*Associative Array* of column definitions and desired name|Applies custom field names| + + + + + + + + + + +- To get more control over the table, create a new livewire component that extends ```Mediconesystems\LivewireDatatables\LivewireDatatable``` + + (if you use the ```livewire:make``` artisan command you can delete the blade view file) + +- The new compnent must have a -use App\User; -use Livewire\Component; -use Mediconesystems\LivewireDatatables\Traits\LivewireDatatable; -class UsersTable extends Component -{ - use LivewireDatatable; - public function model() - { - return User::class; - } -} -``` ### Testing diff --git a/resources/views/link.blade.php b/resources/views/link.blade.php new file mode 100644 index 00000000..1a64f9c9 --- /dev/null +++ b/resources/views/link.blade.php @@ -0,0 +1 @@ +{{ $slot }} \ No newline at end of file diff --git a/resources/views/livewire/datatable.blade.php b/resources/views/livewire/datatable.blade.php index f9a2f0f0..5147afc3 100644 --- a/resources/views/livewire/datatable.blade.php +++ b/resources/views/livewire/datatable.blade.php @@ -1,14 +1,17 @@
-
+ @if($this->showHide) +
@foreach($fields as $index => $field) @endforeach
+ @endif
-
+
+ @if($this->header)
@foreach($this->visibleFields as $index => $field)
@@ -27,6 +30,7 @@
@endforeach
+ @endif @foreach($this->results as $result)
@foreach($this->visibleFields as $field) @@ -44,6 +48,7 @@ @endforeach
+ @if($this->paginationControls)
@@ -58,11 +63,11 @@
- {{ $this->results->links('datatables::tailwind-simple-pagination') }} + {{ $this->results->links('livewire-datatables::tailwind-simple-pagination') }}
@@ -72,6 +77,7 @@
+ @endif
@if($this->activeFilters) @@ -129,24 +135,25 @@
-
- +
+
+ - - -
-
- - - - - - + + +
+
+ @foreach(get_class_methods(Mediconesystems\LivewireDatatables\Traits\WithPresetDateFilters::class) as $preset) + + @endforeach +
@endif @@ -160,6 +167,7 @@
+
+
+ @foreach(get_class_methods(Mediconesystems\LivewireDatatables\Traits\WithPresetTimeFilters::class) as $preset) + + @endforeach +
+
@endif diff --git a/resources/views/tooltip.blade.php b/resources/views/tooltip.blade.php new file mode 100644 index 00000000..bfe24276 --- /dev/null +++ b/resources/views/tooltip.blade.php @@ -0,0 +1,4 @@ + + {{ Str::limit($slot, 16) }} + + \ No newline at end of file diff --git a/src/Field.php b/src/Field.php index 4eff389d..ab0fc21b 100644 --- a/src/Field.php +++ b/src/Field.php @@ -3,11 +3,13 @@ namespace Mediconesystems\LivewireDatatables; use Illuminate\Support\Str; +use Illuminate\Support\Facades\DB; class Field { public $name; public $column; + public $raw; public $sort; public $defaultSort; public $callback; @@ -29,6 +31,16 @@ public static function fromColumn($column) return $field; } + public static function fromRaw($raw) + { + $field = new static; + $field->raw = $raw; + $field->name = (string) Str::of($raw)->afterLast(' AS ')->replace('`', ''); + $field->sort = DB::raw((string) Str::of($raw)->beforeLast(' AS ')); + + return $field; + } + public static function fromScope($scope, $alias) { $field = new static; diff --git a/src/Fieldset.php b/src/Fieldset.php index 6e44dce6..8ca8959b 100644 --- a/src/Fieldset.php +++ b/src/Fieldset.php @@ -30,11 +30,23 @@ public function except($fields) { $fields = is_array($fields) ? $fields : explode(', ', $fields); - foreach ($fields as $field) { - $this->fields = $this->fields->reject(function ($f) use ($field) { - return $f->column === $field; - }); + $this->fields = $this->fields->reject(function ($f) use ($fields) { + return in_array($f->column, $fields); + }); + + return $this; + } + + public function hidden($fields) + { + $fields = is_array($fields) ? $fields : explode(', ', $fields); + + foreach ($fields as $field) { + if ($field = $this->fields->firstWhere('column', $field)) { + $field->hidden = true; + } } + return $this; } @@ -76,6 +88,16 @@ public function rename($values) return $this; } + public function truncate($values) + { + foreach ($values as $column) { + $field = $this->fields->firstWhere('column', $column); + $field->callback = 'truncate'; + $field->params = func_get_args(); + }; + return $this; + } + public function fields() { return collect($this->fields); diff --git a/src/Http/Livewire/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php index 22e7502f..18fe7a17 100644 --- a/src/Http/Livewire/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -2,18 +2,20 @@ namespace Mediconesystems\LivewireDatatables\Http\Livewire; +use ReflectionMethod; +use Livewire\Component; use Illuminate\Support\Arr; -use Illuminate\Support\Str; use Livewire\WithPagination; -use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; -use Livewire\Component; -use Mediconesystems\LivewireDatatables\Field; use Mediconesystems\LivewireDatatables\Fieldset; +use Mediconesystems\LivewireDatatables\Traits\WithCallbacks; +use Mediconesystems\LivewireDatatables\Traits\HandlesProperties; +use Mediconesystems\LivewireDatatables\Traits\WithPresetDateFilters; +use Mediconesystems\LivewireDatatables\Traits\WithPresetTimeFilters; class LivewireDatatable extends Component { - use WithPagination; + use WithPagination, WithCallbacks, WithPresetDateFilters, WithPresetTimeFilters, HandlesProperties; public $model; public $fields; @@ -22,15 +24,24 @@ class LivewireDatatable extends Component public $activeSelectFilters = []; public $activeBooleanFilters = []; public $activeTextFilters = []; + public $showHide = true; + public $header = true; + public $paginationControls = true; public $dates; public $times; public $perPage = 10; - public function mount($model = null) + public function mount($model = null, $except = [], $hidden = [], $uppercase = [], $truncate = [], $formatDates = [], $showHide = null, $header = null, $paginationControls = null, $dateFilters = [], $rename = []) { - $this->model = $model; + $this->model = $this->model ?? $model; $this->fields = $this->fields()->map->toArray()->toArray(); + $this->processProperties(func_get_args()); + $this->addUppercases($uppercase); + $this->addTruncates($truncate); + $this->addFormatDates($formatDates); + $this->addDateFilters($dateFilters); + $this->addRenames($rename); $this->initialiseSort(); } @@ -49,7 +60,7 @@ public function fields() return Fieldset::fromModel($this->model())->fields(); } - public function fieldsetFromModel() + public function fieldset() { return Fieldset::fromModel($this->model()); } @@ -58,7 +69,6 @@ public function initialiseSort() { $this->sort = $this->defaultSort() ? $this->defaultSort()['key'] : $this->visibleFields->keys()->first(); $this->direction = $this->defaultSort()['direction'] === 'asc'; - // dd($this->sort, $this->direction); } public function defaultSort() @@ -87,6 +97,7 @@ public function sort($index) } else { $this->sort = (int) $index; } + $this->page = 1; } public function toggle($index) @@ -124,42 +135,6 @@ public function removeSelectFilter($column, $key = null) } } - public function lastMonth() - { - $this->dates['start'] = now()->subMonth()->startOfMonth()->format('Y-m-d'); - $this->dates['end'] = now()->subMonth()->endOfMonth()->format('Y-m-d'); - } - - public function lastQuarter() - { - $this->dates['start'] = now()->subQuarter()->startOfQuarter()->format('Y-m-d'); - $this->dates['end'] = now()->subQuarter()->endOfQuarter()->format('Y-m-d'); - } - - public function lastYear() - { - $this->dates['start'] = now()->subYear()->startOfYear()->format('Y-m-d'); - $this->dates['end'] = now()->subYear()->endOfYear()->format('Y-m-d'); - } - - public function monthToToday() - { - $this->dates['start'] = now()->subMonth()->addDay()->format('Y-m-d'); - $this->dates['end'] = now()->format('Y-m-d'); - } - - public function quarterToToday() - { - $this->dates['start'] = now()->subQuarter()->addDay()->format('Y-m-d'); - $this->dates['end'] = now()->format('Y-m-d'); - } - - public function yearToToday() - { - $this->dates['start'] = now()->subYear()->addDay()->format('Y-m-d'); - $this->dates['end'] = now()->format('Y-m-d'); - } - public function clearDateFilter() { $this->dates = null; @@ -320,91 +295,25 @@ public function scopeFields() }); } - public function buildDatabaseQuery() - { - return $this->builder() - ->select($this->getSelectStatements()->toArray()) - ->when(count($this->getRawStatements()), function ($query) { - $this->getRawStatements()->each(function ($statement) use ($query) { - $query->selectRaw($statement); - }); - }) - ->when(count($this->scopeFields()), function ($query) { - $this->scopeFields()->each(function ($field) use ($query) { - $query->{$field['scope']}($field['name']); - }); - }) - ->when(count($this->activeSelectFilters) > 0, function ($query) { - return $this->addSelectFilters($query); - }) - ->when(count($this->activeBooleanFilters) > 0, function ($query) { - return $this->addBooleanFilters($query); - }) - ->when(count($this->activeTextFilters) > 0, function ($query) { - return $this->addTextFilters($query); - }) - ->when(isset($this->dates['field']) && (isset($this->dates['start']) || (isset($this->dates['end']))), function ($query) { - return $this->addDateRangeFilter($query); - }) - ->when(isset($this->times['field']) && (isset($this->times['start']) || (isset($this->times['end']))), function ($query) { - return $this->addTimeRangeFilter($query); - }) - ->when(isset($this->sort), function ($query) { - return $query->orderBy($this->getSortString(), $this->direction ? 'asc' : 'desc'); - }); - } - - public function mapCallbacks($paginatedCollection) - { - $paginatedCollection->getCollection()->map(function ($row, $i) { - foreach ($row->getAttributes() as $name => $value) { - $row->$name = $this->getFieldCallback($name)['callback'] - ? $this->{$this->getFieldCallback($name)['callback']}($value, ...$this->getFieldCallback($name)['params'] ?? null) - : $value; - } - return $row; - }); - - return $paginatedCollection; - } - public function getFieldCallback($fieldName) { return collect($this->fields)->firstWhere('name', $fieldName) ? Arr::only(collect($this->fields)->firstWhere('name', $fieldName), ['callback', 'params']) : null; } - public function formatTime($time, $format = null) - { - return $time ? Carbon::parse($time)->format($format ?? config('livewire-datatables.default_time_format')) : null; - } - - public function formatDate($date, $format = null) - { - return $date ? Carbon::parse($date)->format($format ?? config('livewire-datatables.default_date_format')) : null; - } - - public function round($value, $precision = 0) + public function getHeaderProperty() { - return $value ? round($value, $precision) : null; + return method_exists(static::class, 'header'); // ? $this->header() : $this->header; } - public function boolean($value) + public function getShowHideProperty() { - return $value - ? 'check-circle' - : 'x-circle'; + return $this->showHide() ?? $this->showHide; } - public function makeLink($value, $model, $pad = null) + public function getPaginationControlsProperty() { - return '' . ($pad ? str_pad($value, $pad, '0', STR_PAD_LEFT) : $value) . ''; - } - - public function truncate($value) - { - return ' - ' . Str::limit($value, 16) . ''; + return $this->paginationControls() ?? $this->paginationControls; } public function getResultsProperty() @@ -450,8 +359,58 @@ public function getActiveFiltersProperty() || count($this->activeTextFilters); } + public function buildDatabaseQuery() + { + return $this->builder() + ->select('*') + ->addSelect($this->getSelectStatements()->toArray()) + ->when(count($this->getRawStatements()), function ($query) { + $this->getRawStatements()->each(function ($statement) use ($query) { + $query->selectRaw($statement); + }); + }) + ->when(count($this->scopeFields()), function ($query) { + $this->scopeFields()->each(function ($field) use ($query) { + $query->{$field['scope']}($field['name']); + }); + }) + ->when(count($this->activeSelectFilters) > 0, function ($query) { + return $this->addSelectFilters($query); + }) + ->when(count($this->activeBooleanFilters) > 0, function ($query) { + return $this->addBooleanFilters($query); + }) + ->when(count($this->activeTextFilters) > 0, function ($query) { + return $this->addTextFilters($query); + }) + ->when(isset($this->dates['field']) && (isset($this->dates['start']) || (isset($this->dates['end']))), function ($query) { + return $this->addDateRangeFilter($query); + }) + ->when(isset($this->times['field']) && (isset($this->times['start']) || (isset($this->times['end']))), function ($query) { + return $this->addTimeRangeFilter($query); + }) + ->when(isset($this->sort), function ($query) { + return $query->orderBy($this->getSortString(), $this->direction ? 'asc' : 'desc'); + }); + } + + public function mapCallbacks($paginatedCollection) + { + $paginatedCollection->getCollection()->map(function ($row, $i) { + foreach ($row->getAttributes() as $name => $value) { + $row->$name = $this->getFieldCallback($name)['callback'] + ? $this->{$this->getFieldCallback($name)['callback']}($value, $row, ...$this->getFieldCallback($name)['params'] ?? null) + : $value; + } + return $row; + }); + + return $paginatedCollection; + } + public function render() { - return view('datatables::livewire.datatable'); + // dd($this->dateFilters); + return view('livewire-datatables::livewire.datatable'); } } diff --git a/src/LivewireDatatablesServiceProvider.php b/src/LivewireDatatablesServiceProvider.php index bdb7dba2..e567e7a6 100644 --- a/src/LivewireDatatablesServiceProvider.php +++ b/src/LivewireDatatablesServiceProvider.php @@ -14,7 +14,7 @@ public function boot() { Livewire::component('livewire-datatable', LivewireDatatable::class); - $this->loadViewsFrom(__DIR__ . '/../resources/views', 'datatables'); + $this->loadViewsFrom(__DIR__ . '/../resources/views', 'livewire-datatables'); $this->loadViewsFrom(__DIR__ . '/../resources/views/icons', 'icons'); Blade::component('icons::arrow-left', 'icons.arrow-left'); @@ -27,11 +27,12 @@ public function boot() if ($this->app->runningInConsole()) { $this->publishes([ - __DIR__ . '/../config/config.php' => config_path('livewire-datatables.php'), + __DIR__ . '/../config/livewire-datatables.php' => config_path('livewire-datatables.php'), ], 'config'); $this->publishes([ - __DIR__ . '/../resources/views/livewire' => resource_path('views/livewire'), + __DIR__ . '/../resources/views' => resource_path('views/vendor/livewire-datatables'), + __DIR__ . '/../resources/views/livewire' => resource_path('views/vendor/livewire-datatables/livewire'), __DIR__ . '/../resources/views/icons' => resource_path('views/vendor/livewire-datatables/icons'), ], 'views'); } diff --git a/src/Traits/HandlesProperties.php b/src/Traits/HandlesProperties.php new file mode 100644 index 00000000..a2c393d1 --- /dev/null +++ b/src/Traits/HandlesProperties.php @@ -0,0 +1,109 @@ +getParameters())->mapWithKeys(function ($p) use ($properties) { + return [$p->name => $properties[$p->getPosition()]]; + })->each(function ($value, $name) { + if (in_array($name, ['showHide', 'header', 'paginationControls']) && $value) { + $this->{$name} = $value; + } else if (in_array($name, ['except']) && $value) { + $this->fields = collect($this->fields)->reject(function ($field) use ($value) { + return in_array($field['column'], $value); + })->toArray(); + } else if (in_array($name, ['hidden']) && $value) { + $this->fields = collect($this->fields)->map(function ($field) use ($value) { + $field['hidden'] = in_array($field['column'], $value); + return $field; + })->toArray(); + } + }); + } + + public function addExcepts($fields) + { + if (!count($fields)) { + return; + } + + $this->fields = collect($this->fields)->reject(function ($field) use ($fields) { + return in_array($field['column'], $fields); + })->toArray(); + } + + public function addUppercases($fields) + { + if (!count($fields)) { + return; + } + $this->fields = collect($this->fields)->map(function ($field) use ($fields) { + if (in_array($field['column'], $fields)) { + $field['name'] = strtoupper($field['name']); + } + return $field; + })->toArray(); + } + + public function addTruncates($fields) + { + if (!count($fields)) { + return; + } + + $this->fields = collect($this->fields)->map(function ($field) use ($fields) { + if (in_array($field['column'], $fields)) { + $field['callback'] = 'truncate'; + } + return $field; + })->toArray(); + } + + public function addFormatDates($fields) + { + if (!count($fields)) { + return; + } + + $this->fields = collect($this->fields)->map(function ($field) use ($fields) { + if (in_array($field['column'], $fields)) { + $field['callback'] = 'formatDate'; + } + return $field; + })->toArray(); + } + + public function addDateFilters($fields) + { + if (!count($fields)) { + return; + } + + $this->fields = collect($this->fields)->map(function ($field) use ($fields) { + if (in_array($field['column'], $fields)) { + $field['dateFilter'] = true; + } + return $field; + })->toArray(); + } + + public function addRenames($fields) + { + if (!count($fields)) { + return; + } + + $this->fields = collect($this->fields)->map(function ($field) use ($fields) { + if (in_array($field['column'], collect($fields)->keys()->toArray())) { + $field['name'] = $fields[$field['column']]; + } + return $field; + })->toArray(); + } +} diff --git a/src/Traits/WithCallbacks.php b/src/Traits/WithCallbacks.php new file mode 100644 index 00000000..b2b4f8a5 --- /dev/null +++ b/src/Traits/WithCallbacks.php @@ -0,0 +1,44 @@ +format($format ?? config('livewire-datatables.default_time_format')) : null; + } + + public function formatDate($date, $row, $format = null) + { + return $date ? Carbon::parse($date)->format($format ?? config('livewire-datatables.default_date_format')) : null; + } + + public function round($value, $row, $precision = 0) + { + return $value ? round($value, $precision) : null; + } + + public function boolean($value) + { + return $value + ? 'check-circle' + : 'x-circle'; + } + + public function makeLink($value, $row, $model, $pad = null) + { + return view('livewire-datatables::link', [ + 'href' => "/$model/$value", + 'slot' => $pad ? str_pad($value, $pad, '0', STR_PAD_LEFT) : $value + ]); + } + + public function truncate($value) + { + return view('livewire-datatables::tooltip', ['slot' => $value]); + } +} diff --git a/src/Traits/WithPresetDateFilters.php b/src/Traits/WithPresetDateFilters.php new file mode 100644 index 00000000..de0fd566 --- /dev/null +++ b/src/Traits/WithPresetDateFilters.php @@ -0,0 +1,43 @@ +dates['start'] = now()->subMonth()->startOfMonth()->format('Y-m-d'); + $this->dates['end'] = now()->subMonth()->endOfMonth()->format('Y-m-d'); + } + + public function lastQuarter() + { + $this->dates['start'] = now()->subQuarter()->startOfQuarter()->format('Y-m-d'); + $this->dates['end'] = now()->subQuarter()->endOfQuarter()->format('Y-m-d'); + } + + public function lastYear() + { + $this->dates['start'] = now()->subYear()->startOfYear()->format('Y-m-d'); + $this->dates['end'] = now()->subYear()->endOfYear()->format('Y-m-d'); + } + + public function monthToToday() + { + $this->dates['start'] = now()->subMonth()->addDay()->format('Y-m-d'); + $this->dates['end'] = now()->format('Y-m-d'); + } + + public function quarterToToday() + { + $this->dates['start'] = now()->subQuarter()->addDay()->format('Y-m-d'); + $this->dates['end'] = now()->format('Y-m-d'); + } + + public function yearToToday() + { + $this->dates['start'] = now()->subYear()->addDay()->format('Y-m-d'); + $this->dates['end'] = now()->format('Y-m-d'); + } +} diff --git a/src/Traits/WithPresetTimeFilters.php b/src/Traits/WithPresetTimeFilters.php new file mode 100644 index 00000000..0eb15689 --- /dev/null +++ b/src/Traits/WithPresetTimeFilters.php @@ -0,0 +1,31 @@ +times['start'] = '09:00:00'; + $this->times['end'] = '17:00:00'; + } + + public function sevenToSevenDay() + { + $this->times['start'] = '07:00:00'; + $this->times['end'] = '19:00:00'; + } + + public function sevenToSevenNight() + { + $this->times['start'] = '19:00:00'; + $this->times['end'] = '07:00:00'; + } + + public function graveyardShift() + { + $this->times['start'] = '22:00:00'; + $this->times['end'] = '06:00:00'; + } +} From d1265f7294fbd11cb40904c2e101baccc288779c Mon Sep 17 00:00:00 2001 From: Mark Salmon Date: Mon, 29 Jun 2020 21:40:08 +0100 Subject: [PATCH 5/8] wip --- resources/views/livewire/datatable.blade.php | 2 +- resources/views/tooltip.blade.php | 2 +- src/Field.php | 7 + src/Fieldset.php | 6 +- src/Http/Livewire/LivewireDatatable.php | 24 +- src/LivewireDatatablesServiceProvider.php | 1 + src/Traits/HandlesProperties.php | 36 +- src/Traits/WithCallbacks.php | 4 +- tests/FieldTest.php | 77 ++++- tests/FieldsetTest.php | 18 +- tests/LivewireDatatableTest.php | 323 ++++++++++++++++++ tests/TestCase.php | 20 ++ tests/classes/DummyTable.php | 43 +++ tests/database/factories/ModelFactory.php | 1 + ...06_25_211600_create_dummy_models_table.php | 1 + 15 files changed, 516 insertions(+), 49 deletions(-) create mode 100644 tests/LivewireDatatableTest.php create mode 100644 tests/classes/DummyTable.php diff --git a/resources/views/livewire/datatable.blade.php b/resources/views/livewire/datatable.blade.php index 5147afc3..275e17ed 100644 --- a/resources/views/livewire/datatable.blade.php +++ b/resources/views/livewire/datatable.blade.php @@ -245,7 +245,7 @@ {{ ucwords(str_replace('_', ' ', $filter['name'])) }}
- +
@endforeach diff --git a/resources/views/tooltip.blade.php b/resources/views/tooltip.blade.php index bfe24276..fdda746e 100644 --- a/resources/views/tooltip.blade.php +++ b/resources/views/tooltip.blade.php @@ -1,4 +1,4 @@ - {{ Str::limit($slot, 16) }} + {{ Str::limit($slot, $length) }} \ No newline at end of file diff --git a/src/Field.php b/src/Field.php index ab0fc21b..14526eb1 100644 --- a/src/Field.php +++ b/src/Field.php @@ -133,6 +133,13 @@ public function linkTo($model, $pad) return $this; } + public function truncate($length = 16) + { + $this->callback = 'truncate'; + $this->params = [$length]; + return $this; + } + public function formatDate($format = null) { $this->callback = 'formatDate'; diff --git a/src/Fieldset.php b/src/Fieldset.php index 8ca8959b..78c7b42b 100644 --- a/src/Fieldset.php +++ b/src/Fieldset.php @@ -6,12 +6,10 @@ class Fieldset { - public $table; public $fields; - public function __construct($table, $fields) + public function __construct($fields) { - $this->table = $table; $this->fields = $fields; } @@ -19,7 +17,7 @@ public static function fromModel($model) { $instance = $model::firstOrFail(); - return new static($instance->getTable(), collect($instance->getAttributes())->keys()->reject(function ($name) use ($instance) { + return new static(collect($instance->getAttributes())->keys()->reject(function ($name) use ($instance) { return in_array($name, $instance->getHidden()); })->map(function ($attribute) use ($instance) { return Field::fromColumn($instance->getTable() . '.' . $attribute); diff --git a/src/Http/Livewire/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php index 18fe7a17..f55541a2 100644 --- a/src/Http/Livewire/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -2,7 +2,6 @@ namespace Mediconesystems\LivewireDatatables\Http\Livewire; -use ReflectionMethod; use Livewire\Component; use Illuminate\Support\Arr; use Livewire\WithPagination; @@ -32,7 +31,7 @@ class LivewireDatatable extends Component public $times; public $perPage = 10; - public function mount($model = null, $except = [], $hidden = [], $uppercase = [], $truncate = [], $formatDates = [], $showHide = null, $header = null, $paginationControls = null, $dateFilters = [], $rename = []) + public function mount($model = null, $except = [], $hidden = [], $uppercase = [], $truncate = [], $formatDates = [], $formatTimes = [], $showHide = null, $header = null, $paginationControls = null, $dateFilters = [], $timeFilters = [], $renames = [], $defaultSort = null) { $this->model = $this->model ?? $model; $this->fields = $this->fields()->map->toArray()->toArray(); @@ -40,8 +39,10 @@ public function mount($model = null, $except = [], $hidden = [], $uppercase = [] $this->addUppercases($uppercase); $this->addTruncates($truncate); $this->addFormatDates($formatDates); + $this->addFormatTimes($formatTimes); $this->addDateFilters($dateFilters); - $this->addRenames($rename); + $this->addTimeFilters($timeFilters); + $this->addRenames($renames); $this->initialiseSort(); } @@ -60,11 +61,6 @@ public function fields() return Fieldset::fromModel($this->model())->fields(); } - public function fieldset() - { - return Fieldset::fromModel($this->model()); - } - public function initialiseSort() { $this->sort = $this->defaultSort() ? $this->defaultSort()['key'] : $this->visibleFields->keys()->first(); @@ -73,13 +69,13 @@ public function initialiseSort() public function defaultSort() { - $fieldIndex = $this->fields()->search(function ($field) { - return is_string($field->defaultSort); + $fieldIndex = collect($this->fields)->search(function ($field) { + return is_string($field['defaultSort']); }); return $fieldIndex ? [ 'key' => $fieldIndex, - 'direction' => $this->fields()[$fieldIndex]->defaultSort + 'direction' => $this->fields[$fieldIndex]['defaultSort'] ] : null; } @@ -121,9 +117,9 @@ public function doSelectFilter($index, $value) $this->page = 1; } - public function doTextFilter($field, $value) + public function doTextFilter($index, $value) { - $this->activeTextFilters[$field] = $value; + $this->activeTextFilters[$index] = $value; $this->page = 1; } @@ -257,7 +253,7 @@ public function addTextFilters($builder) { return $builder->where(function ($query) { foreach ($this->activeTextFilters as $index => $value) { - $query->orWhere($this->getFieldColumn($index), 'like', "%$value%"); + $query->orWhereRaw("LOWER(" . $this->getFieldColumn($index) . ") like ?", [strtolower("%$value%")]); } }); } diff --git a/src/LivewireDatatablesServiceProvider.php b/src/LivewireDatatablesServiceProvider.php index e567e7a6..67eadad7 100644 --- a/src/LivewireDatatablesServiceProvider.php +++ b/src/LivewireDatatablesServiceProvider.php @@ -5,6 +5,7 @@ use Livewire\Livewire; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; +use Mediconesystems\LivewireDatatables\Tests\Classes\DummyTable; use Mediconesystems\LivewireDatatables\Http\Livewire\LivewireDatatable; diff --git a/src/Traits/HandlesProperties.php b/src/Traits/HandlesProperties.php index a2c393d1..a069af3f 100644 --- a/src/Traits/HandlesProperties.php +++ b/src/Traits/HandlesProperties.php @@ -3,7 +3,6 @@ namespace Mediconesystems\LivewireDatatables\Traits; use ReflectionMethod; -use ReflectionFunction; trait HandlesProperties { @@ -23,6 +22,13 @@ public function processProperties($properties) $field['hidden'] = in_array($field['column'], $value); return $field; })->toArray(); + } else if (in_array($name, ['defaultSort']) && $value) {; + $this->fields = collect($this->fields)->map(function ($field) use ($value) { + if ($field['column'] === key($value)) { + $field['defaultSort'] = reset($value); + } + return $field; + })->toArray(); } }); } @@ -79,6 +85,20 @@ public function addFormatDates($fields) })->toArray(); } + public function addFormatTimes($fields) + { + if (!count($fields)) { + return; + } + + $this->fields = collect($this->fields)->map(function ($field) use ($fields) { + if (in_array($field['column'], $fields)) { + $field['callback'] = 'formatTime'; + } + return $field; + })->toArray(); + } + public function addDateFilters($fields) { if (!count($fields)) { @@ -93,6 +113,20 @@ public function addDateFilters($fields) })->toArray(); } + public function addTimeFilters($fields) + { + if (!count($fields)) { + return; + } + + $this->fields = collect($this->fields)->map(function ($field) use ($fields) { + if (in_array($field['column'], $fields)) { + $field['timeFilter'] = true; + } + return $field; + })->toArray(); + } + public function addRenames($fields) { if (!count($fields)) { diff --git a/src/Traits/WithCallbacks.php b/src/Traits/WithCallbacks.php index b2b4f8a5..03bf805c 100644 --- a/src/Traits/WithCallbacks.php +++ b/src/Traits/WithCallbacks.php @@ -37,8 +37,8 @@ public function makeLink($value, $row, $model, $pad = null) ]); } - public function truncate($value) + public function truncate($value, $row, $length = 16) { - return view('livewire-datatables::tooltip', ['slot' => $value]); + return view('livewire-datatables::tooltip', ['slot' => $value, 'length' => $length]); } } diff --git a/tests/FieldTest.php b/tests/FieldTest.php index 72c7be31..6ae1505f 100644 --- a/tests/FieldTest.php +++ b/tests/FieldTest.php @@ -2,18 +2,12 @@ namespace Mediconesystems\LivewireDatatables\Tests; +use Illuminate\Support\Facades\DB; use Mediconesystems\LivewireDatatables\Field; -use Orchestra\Testbench\TestCase; -use Mediconesystems\LivewireDatatables\LivewireDatatablesServiceProvider; +use Mediconesystems\LivewireDatatables\Tests\TestCase; class FieldTest extends TestCase { - - protected function getPackageProviders($app) - { - return [LivewireDatatablesServiceProvider::class]; - } - /** @test */ public function it_can_generate_a_field_from_a_table_column() { @@ -38,9 +32,9 @@ public function it_can_generate_a_field_from_a_scope() */ public function it_sets_properties_and_parameters($method, $value, $attribute) { - $subject = Field::fromColumn('table.column') - ->$method($value); + $subject = Field::fromColumn('table.column')->$method($value); + // dd($value, $subject->$attribute); $this->assertEquals($value, $subject->$attribute); } @@ -55,21 +49,40 @@ public function settersDataProvider() ['withDateFilter', true, 'dateFilter'], ['withTimeFilter', true, 'timeFilter'], ['formatBoolean', 'boolean', 'callback'], - ['linkTo', 'model', 'params'], - ['formatDate', 'd/m/Y', 'params'], - ['formatTime', 'H:i', 'params'], - ['round', 2, 'params'], ['hidden', true, 'hidden'], ]; } + /** + * @test + * @dataProvider presetCallbacksDataProvider + */ + public function it_sets_preset_callbacks($method, $value, $attribute) + { + $subject = Field::fromColumn('table.column')->$method(...$value); + + $this->assertEquals($value, $subject->$attribute); + } + + public function presetCallbacksDataProvider() + { + return [ + ['linkTo', ['model', 'pad'], 'params'], + ['formatDate', ['d/m/Y'], 'params'], + ['formatTime', ['H:i'], 'params'], + ['round', [2], 'params'], + ['truncate', [2], 'params'], + ]; + } + /** @test */ - public function it_returns_an_array() + public function it_returns_an_array_from_column() { $subject = Field::fromColumn('table.column') ->name('Column') ->withSelectFilter(['A', 'B', 'C']) ->hidden() + ->linkTo('model', 8) ->toArray(); $this->assertEquals([ @@ -77,11 +90,41 @@ public function it_returns_an_array() 'name' => 'Column', 'selectFilter' => ['A', 'B', 'C'], 'hidden' => true, - 'callback' => null, + 'callback' => 'makeLink', 'booleanFilter' => null, 'textFilter' => null, 'dateFilter' => null, - 'timeFilter' => null + 'timeFilter' => null, + 'raw' => null, + 'sort' => null, + 'defaultSort' => null, + 'params' => ['model', 8], + ], $subject); + } + + /** @test */ + public function it_returns_an_array_from_raw() + { + $subject = Field::fromRaw('SELECT column FROM table AS table_column') + ->withBooleanFilter() + ->defaultSort('asc') + ->formatDate('yyy-mm-dd') + ->toArray(); + + $this->assertEquals([ + 'column' => null, + 'name' => 'table_column', + 'selectFilter' => null, + 'hidden' => null, + 'callback' => 'formatDate', + 'booleanFilter' => true, + 'textFilter' => null, + 'dateFilter' => null, + 'timeFilter' => null, + 'raw' => 'SELECT column FROM table AS table_column', + 'sort' => DB::raw('SELECT column FROM table'), + 'defaultSort' => 'asc', + 'params' => ['yyy-mm-dd'], ], $subject); } } diff --git a/tests/FieldsetTest.php b/tests/FieldsetTest.php index bb11a5a3..f4a3054f 100644 --- a/tests/FieldsetTest.php +++ b/tests/FieldsetTest.php @@ -16,8 +16,7 @@ public function it_can_generate_an_array_of_fields_from_a_model() $subject = Fieldset::fromModel(DummyModel::class); - $this->assertCount(8, $subject->fields()); - $this->assertEquals('dummy_models', $subject->table); + $this->assertCount(9, $subject->fields()); $subject->fields()->each(function ($field) { $this->assertIsObject($field, Field::class); @@ -51,11 +50,12 @@ public function fieldDataProvider() ['Id', 0, 'dummy_models.id'], ['Relation_id', 1, 'dummy_models.relation_id'], ['Subject', 2, 'dummy_models.subject'], - ['Body', 3, 'dummy_models.body'], - ['Flag', 4, 'dummy_models.flag'], - ['Expires_at', 5, 'dummy_models.expires_at'], - ['Created_at', 6, 'dummy_models.created_at'], - ['Updated_at', 7, 'dummy_models.updated_at'], + ['Category', 3, 'dummy_models.category'], + ['Body', 4, 'dummy_models.body'], + ['Flag', 5, 'dummy_models.flag'], + ['Expires_at', 6, 'dummy_models.expires_at'], + ['Created_at', 7, 'dummy_models.created_at'], + ['Updated_at', 8, 'dummy_models.updated_at'], ]; } @@ -68,10 +68,10 @@ public function it_can_exclude_fields() ->except(['dummy_models.id', 'dummy_models.body']) ->fields(); - $this->assertCount(6, $subject); + $this->assertCount(7, $subject); $this->assertArrayNotHasKey(0, $subject); - $this->assertArrayNotHasKey(3, $subject); + $this->assertArrayNotHasKey(4, $subject); } /** @test */ diff --git a/tests/LivewireDatatableTest.php b/tests/LivewireDatatableTest.php new file mode 100644 index 00000000..71939760 --- /dev/null +++ b/tests/LivewireDatatableTest.php @@ -0,0 +1,323 @@ +create(); + + $subject = Livewire::test(LivewireDatatable::class, ['model' => DummyModel::class]); + + $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); + $this->assertIsArray($subject->fields); + $this->assertEquals([ + 0 => 'Id', + 1 => 'Relation_id', + 2 => 'Subject', + 3 => 'Category', + 4 => 'Body', + 5 => 'Flag', + 6 => 'Expires_at', + 7 => 'Created_at', + 8 => 'Updated_at' + ], collect($subject->fields)->map->name->toArray()); + } + + /** @test */ + public function it_can_mount_using_the_class() + { + factory(DummyModel::class)->create([ + 'subject' => 'How to sell paper in Scranton PA' + ]); + + $subject = Livewire::test(DummyTable::class) + ->assertSee('How to sell paper in Scranton PA'); + + $this->assertIsArray($subject->fields); + $this->assertEquals([ + 0 => 'ID', + 1 => 'Subject', + 2 => 'Category', + 3 => 'Body', + 4 => 'Flag', + 5 => 'Expiry', + ], collect($subject->fields)->map->name->toArray()); + } + + /** @test */ + public function it_can_exclude_fields_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'except' => ['dummy_models.relation_id'] + ]); + + $this->assertIsArray($subject->fields); + $this->assertEquals([ + 0 => 'Id', + 2 => 'Subject', + 3 => 'Category', + 4 => 'Body', + 5 => 'Flag', + 6 => 'Expires_at', + 7 => 'Created_at', + 8 => 'Updated_at' + ], collect($subject->fields)->map->name->toArray()); + } + + /** @test */ + public function it_can_mark_fields_hidden_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'hidden' => ['dummy_models.relation_id', 'dummy_models.created_at'] + ]); + + $this->assertIsArray($subject->fields); + $this->assertFalse($subject->fields[0]['hidden']); + $this->assertTrue($subject->fields[1]['hidden']); + $this->assertFalse($subject->fields[2]['hidden']); + $this->assertFalse($subject->fields[3]['hidden']); + $this->assertFalse($subject->fields[4]['hidden']); + $this->assertFalse($subject->fields[5]['hidden']); + $this->assertFalse($subject->fields[6]['hidden']); + $this->assertTrue($subject->fields[7]['hidden']); + $this->assertFalse($subject->fields[8]['hidden']); + } + + /** @test */ + public function it_can_make_field_names_uppercase_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'uppercase' => ['dummy_models.id', 'dummy_models.flag'] + ]); + + $this->assertIsArray($subject->fields); + $this->assertEquals('ID', $subject->fields[0]['name']); + $this->assertEquals('Relation_id', $subject->fields[1]['name']); + $this->assertEquals('Subject', $subject->fields[2]['name']); + $this->assertEquals('Category', $subject->fields[3]['name']); + $this->assertEquals('Body', $subject->fields[4]['name']); + $this->assertEquals('FLAG', $subject->fields[5]['name']); + $this->assertEquals('Expires_at', $subject->fields[6]['name']); + } + + /** @test */ + public function it_can_marks_fields_for_truncation_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'truncate' => ['dummy_models.body'] + ]); + + $this->assertIsArray($subject->fields); + $this->assertEquals('truncate', $subject->fields[4]['callback']); + } + + /** @test */ + public function it_can_mark_fields_for_date_format_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'formatDates' => ['dummy_models.expires_at'] + ]); + + $this->assertIsArray($subject->fields); + + $this->assertEquals('formatDate', $subject->fields[6]['callback']); + } + + /** @test */ + public function it_can_mark_fields_for_time_format_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'formatTimes' => ['dummy_models.expires_at'] + ]); + + $this->assertIsArray($subject->fields); + + $this->assertEquals('formatTime', $subject->fields[6]['callback']); + } + + /** @test */ + public function it_can_marks_fields_for_date_filter_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'dateFilters' => ['dummy_models.created_at', 'dummy_models.updated_at'] + ]); + + $this->assertIsArray($subject->fields); + + $this->assertCount(2, collect($subject->fields)->filter->dateFilter); + } + + /** @test */ + public function it_can_marks_fields_for_time_filter_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'timeFilters' => ['dummy_models.created_at', 'dummy_models.updated_at'] + ]); + + $this->assertIsArray($subject->fields); + + $this->assertCount(2, collect($subject->fields)->filter->timeFilter); + } + + /** @test */ + public function it_can_rename_fields_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'renames' => ['dummy_models.id' => 'ID'] + ]); + + $this->assertIsArray($subject->fields); + + $this->assertEquals('ID', $subject->fields[0]['name']); + } + + /** @test */ + public function it_can_set_a_default_sort() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + ]); + + $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); + $this->assertIsArray($subject->fields); + + $this->assertEquals(0, $subject->sort); + $this->assertFalse($subject->direction); + } + + /** @test */ + public function it_can_set_sort_from_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'defaultSort' => ['dummy_models.subject' => 'asc'] + ]); + + $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); + $this->assertIsArray($subject->fields); + + $this->assertEquals(2, $subject->sort); + $this->assertTrue($subject->direction); + } + + /** @test */ + public function it_can_hide_a_column() + { + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs']); + + $subject = Livewire::test(LivewireDatatable::class, ['model' => DummyModel::class]) + ->assertSee('Beet growing for noobs') + ->call('toggle', 2) + ->assertDontSee('Beet growing for noobs'); + } + + /** @test */ + public function it_can_show_a_column() + { + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs']); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'hidden' => ['dummy_models.subject'] + ])->assertDontSee('Beet growing for noobs') + ->call('toggle', 2) + ->assertSee('Beet growing for noobs'); + } + + /** @test */ + public function it_can_order_results() + { + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs']); + factory(DummyModel::class)->create(['subject' => 'Advanced beet growing']); + + $subject = new LivewireDatatable(1); + $subject->model = DummyModel::class; + + $this->assertEquals('Beet growing for noobs', $subject->results->getCollection()->map->getAttributes()[0]['subject']); + $this->assertEquals('Advanced beet growing', $subject->results->getCollection()->map->getAttributes()[1]['subject']); + + $subject->forgetComputed(); + $subject->sort = 2; + $subject->direction = true; + + $this->assertEquals('Advanced beet growing', $subject->results->getCollection()->map->getAttributes()[0]['subject']); + $this->assertEquals('Beet growing for noobs', $subject->results->getCollection()->map->getAttributes()[1]['subject']); + } + + /** @test */ + public function it_can_filter_results_based_on_text() + { + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs']); + factory(DummyModel::class)->create(['subject' => 'Advanced beet growing']); + + $subject = Livewire::test(DummyTable::class) + ->assertSee('Results 1 - 2 of 2') + ->call('doTextFilter', 1, 'Advance') + ->assertSee('Results 1 - 1 of 1'); + } + + /** @test */ + public function it_can_filter_results_based_on_boolean() + { + factory(DummyModel::class)->create(['flag' => true]); + factory(DummyModel::class)->create(['flag' => false]); + + $subject = Livewire::test(DummyTable::class) + ->assertSee('Results 1 - 2 of 2') + ->call('doBooleanFilter', 4) + ->assertSee('Results 1 - 1 of 1'); + } + + /** @test */ + public function it_can_filter_results_based_on_selects() + { + factory(DummyModel::class)->create(['category' => 'Schrute']); + factory(DummyModel::class)->create(['category' => 'Scott']); + + $subject = Livewire::test(DummyTable::class) + ->assertSee('Results 1 - 2 of 2') + ->call('doSelectFilter', 2, 'Scott') + ->assertSee('Results 1 - 1 of 1'); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 7b937b02..1702d942 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,8 @@ namespace Mediconesystems\LivewireDatatables\Tests; +use Illuminate\Support\Str; +use Illuminate\Support\Facades\View; use Livewire\LivewireServiceProvider; use Orchestra\Testbench\TestCase as Orchestra; use Mediconesystems\LivewireDatatables\LivewireDatatablesServiceProvider; @@ -20,6 +22,7 @@ public function setUp(): void protected function getPackageProviders($app) { return [ + LivewireServiceProvider::class, LivewireDatatablesServiceProvider::class ]; } @@ -34,4 +37,21 @@ public function getEnvironmentSetUp($app) 'prefix' => '', ]); } + + protected function renderBladeString(string $bladeContent): string + { + $temporaryDirectory = sys_get_temp_dir(); + + if (!in_array($temporaryDirectory, View::getFinder()->getPaths())) { + View::addLocation(sys_get_temp_dir()); + } + + $tempFilePath = tempnam($temporaryDirectory, 'tests') . '.blade.php'; + + file_put_contents($tempFilePath, $bladeContent); + + $bladeViewName = Str::before(pathinfo($tempFilePath, PATHINFO_BASENAME), '.blade.php'); + + return view($bladeViewName)->render(); + } } diff --git a/tests/classes/DummyTable.php b/tests/classes/DummyTable.php new file mode 100644 index 00000000..1cb3e7a2 --- /dev/null +++ b/tests/classes/DummyTable.php @@ -0,0 +1,43 @@ +name('ID') + ->linkTo('dummy_model', 6), + + Field::fromColumn('dummy_models.subject') + ->withTextFilter(), + + Field::fromColumn('dummy_models.category') + ->withSelectFilter(['A', 'B', 'C']), + + Field::fromColumn('dummy_models.body') + ->truncate() + ->withTextFilter(), + + Field::fromColumn('dummy_models.flag') + ->withBooleanFilter() + ->formatBoolean(), + + Field::fromColumn('dummy_models.expires_at') + ->name('Expiry') + ->formatDate('jS F Y') + ->hidden(), + ]); + } +} diff --git a/tests/database/factories/ModelFactory.php b/tests/database/factories/ModelFactory.php index 2936d973..53f313c9 100644 --- a/tests/database/factories/ModelFactory.php +++ b/tests/database/factories/ModelFactory.php @@ -11,6 +11,7 @@ return [ 'relation_id' => $faker->randomNumber(6), 'subject' => $faker->sentence, + 'category' => $faker->word, 'body' => $faker->paragraph, 'flag' => $faker->boolean(), 'expires_at' => $faker->dateTimeBetween('now', '+ 4 weeks') diff --git a/tests/database/migrations/2020_06_25_211600_create_dummy_models_table.php b/tests/database/migrations/2020_06_25_211600_create_dummy_models_table.php index baa8a5cb..86e70414 100644 --- a/tests/database/migrations/2020_06_25_211600_create_dummy_models_table.php +++ b/tests/database/migrations/2020_06_25_211600_create_dummy_models_table.php @@ -12,6 +12,7 @@ public function up() $table->id(); $table->unsignedInteger('relation_id')->index(); $table->string('subject', 64); + $table->string('category', 16); $table->text('body'); $table->boolean('flag')->nullable(); $table->timestamp('expires_at')->nullable(); From 094e2028a248e97152329dce1e789caf3b1a46d0 Mon Sep 17 00:00:00 2001 From: Mark Salmon Date: Tue, 30 Jun 2020 11:50:46 +0100 Subject: [PATCH 6/8] wip --- resources/views/boolean.blade.php | 7 + resources/views/livewire/datatable.blade.php | 17 +- src/Field.php | 12 + src/Fieldset.php | 115 ++++--- src/Http/Livewire/LivewireDatatable.php | 88 +++-- src/Traits/HandlesProperties.php | 127 +------- src/Traits/WithCallbacks.php | 4 +- tests/FieldTest.php | 2 + tests/FieldsetTest.php | 5 +- tests/LivewireDatatableClassTest.php | 133 ++++++++ tests/LivewireDatatableTemplateTest.php | 200 ++++++++++++ tests/LivewireDatatableTest.php | 323 ------------------- tests/classes/DummyTable.php | 5 +- 13 files changed, 517 insertions(+), 521 deletions(-) create mode 100644 resources/views/boolean.blade.php create mode 100644 tests/LivewireDatatableClassTest.php create mode 100644 tests/LivewireDatatableTemplateTest.php delete mode 100644 tests/LivewireDatatableTest.php diff --git a/resources/views/boolean.blade.php b/resources/views/boolean.blade.php new file mode 100644 index 00000000..395a6825 --- /dev/null +++ b/resources/views/boolean.blade.php @@ -0,0 +1,7 @@ +
+@isset($value) + +@else + +@endisset +
\ No newline at end of file diff --git a/resources/views/livewire/datatable.blade.php b/resources/views/livewire/datatable.blade.php index 275e17ed..299992d3 100644 --- a/resources/views/livewire/datatable.blade.php +++ b/resources/views/livewire/datatable.blade.php @@ -1,5 +1,5 @@
- @if($this->showHide) + @unless($this->hideToggles)
@foreach($fields as $index => $field)
@endif
-
+
- @if($this->header) + @unless($this->hideHeader)
@foreach($this->visibleFields as $index => $field)
@@ -35,20 +35,14 @@
@foreach($this->visibleFields as $field)
- @if($result->{$field['name']} === 'check-circle') - - @elseif($result->{$field['name']} === 'x-circle') - - @else {!! $result->{$field['name']} !!} - @endif
@endforeach
@endforeach
- @if($this->paginationControls) + @unless($this->hidePagination)
@@ -189,7 +183,7 @@
@endif - @if(count($this->selectFilters) || count($this->booleanFilters) || count($this->textFilters)) + @if(count($this->selectFilters) || count($this->booleanFilters) || count($this->textFilters) || count($this->numberFilters))
@@ -249,6 +243,7 @@
@endforeach +
@endif diff --git a/src/Field.php b/src/Field.php index 14526eb1..cabff5b3 100644 --- a/src/Field.php +++ b/src/Field.php @@ -16,6 +16,7 @@ class Field public $selectFilter; public $booleanFilter; public $textFilter; + public $numberFilter; public $dateFilter; public $timeFilter; public $hidden; @@ -108,6 +109,12 @@ public function withTextFilter() return $this; } + public function withNumberFilter($range) + { + $this->numberFilter = $range; + return $this; + } + public function withDateFilter() { $this->dateFilter = true; @@ -175,6 +182,11 @@ public function hidden() return $this; } + public function toggleHidden() + { + $this->hidden = !$this->hidden(); + } + public function toArray() { return get_object_vars($this); diff --git a/src/Fieldset.php b/src/Fieldset.php index 78c7b42b..96863d66 100644 --- a/src/Fieldset.php +++ b/src/Fieldset.php @@ -2,13 +2,15 @@ namespace Mediconesystems\LivewireDatatables; +use Illuminate\Support\Str; +use Illuminate\Support\Collection; use Mediconesystems\LivewireDatatables\Field; class Fieldset { public $fields; - public function __construct($fields) + public function __construct(Collection $fields) { $this->fields = $fields; } @@ -24,75 +26,111 @@ public static function fromModel($model) })); } - public function except($fields) + public static function fromArray($fields) { - $fields = is_array($fields) ? $fields : explode(', ', $fields); + return new static(collect($fields)); + } + + // public function except($fields) + // { + // $fields = is_array($fields) ? $fields : explode(', ', $fields); + + // $this->fields = $this->fields->reject(function ($f) use ($fields) { + // return in_array($f->column, $fields); + // }); + + // return $this; + // } + + public function include($include) + { + if (!$include) { + return $this; + } + $include = is_array($include) ? $include : explode(', ', $include); - $this->fields = $this->fields->reject(function ($f) use ($fields) { - return in_array($f->column, $fields); + $this->fields = $this->fields->filter(function ($field) use ($include) { + return in_array(Str::after($field->column, '.'), $include); }); return $this; } - public function hidden($fields) + public function exclude($exclude) { - $fields = is_array($fields) ? $fields : explode(', ', $fields); - - foreach ($fields as $field) { - if ($field = $this->fields->firstWhere('column', $field)) { - $field->hidden = true; - } + if (!$exclude) { + return $this; } + $exclude = is_array($exclude) ? $exclude : explode(', ', $exclude); + + $this->fields = $this->fields->reject(function ($field) use ($exclude) { + return in_array(Str::after($field->column, '.'), $exclude); + }); + return $this; } - public function formatDates($columns, $format = 'd/m/Y') + public function hidden($hidden) { - foreach ($columns as $column) { - if ($field = $this->fields->firstWhere('column', $column)) { - $field->callback = 'formatDate'; - $field->params = [$format]; - } - } + $hidden = is_array($hidden) ? $hidden : explode(', ', $hidden); + + $this->fields->each(function ($field) use ($hidden) { + $field->hidden = in_array(Str::after($field->column, '.'), $hidden); + }); + return $this; } - public function dateFilters($columns) + public function formatDates($dates) { - foreach ($columns as $column) { - if ($field = $this->fields->firstWhere('column', $column)) { - $field->dateFilter = true; + $dates = is_array($dates) ? $dates : explode(', ', $dates); + + foreach ($dates as $date) { + + if ($field = $this->fields->first(function ($field) use ($date) { + return Str::after($field->column, '.') === Str::before($date, '|'); + })) { + $field->callback = 'formatDate'; + $field->params = Str::of($date)->contains('|') ? [Str::after($date, '|')] : []; } } return $this; } - public function uppercase($values) + public function formatTimes($times) { - foreach ($values as $column) { - $field = $this->fields->firstWhere('column', $column); - $field->name = strtoupper($field->name); + $times = is_array($times) ? $times : explode(', ', $times); + + foreach ($times as $time) { + + if ($field = $this->fields->first(function ($field) use ($time) { + return Str::after($field->column, '.') === Str::before($time, '|'); + })) { + $field->callback = 'formatTime'; + $field->params = Str::of($time)->contains('|') ? [Str::after($time, '|')] : []; + } } return $this; } - public function rename($values) + public function rename($names) { - foreach ($values as $column => $newName) { - $this->fields->firstWhere('column', $column)->name = $newName; + foreach ($names as $name) { + $this->fields->first(function ($field) use ($name) { + return Str::after($field->column, '.') === Str::before($name, '|'); + })->name = Str::after($name, '|'); } return $this; } - public function truncate($values) + public function sort($sort) { - foreach ($values as $column) { - $field = $this->fields->firstWhere('column', $column); - $field->callback = 'truncate'; - $field->params = func_get_args(); - }; + if ($sort) { + $this->fields->first(function ($field) use ($sort) { + return Str::after($field->column, '.') === Str::before($sort, '|'); + })->defaultSort = Str::after($sort, '|'); + } return $this; } @@ -100,4 +138,9 @@ public function fields() { return collect($this->fields); } + + public function fieldsArray() + { + return $this->fields()->map->toArray()->toArray(); + } } diff --git a/src/Http/Livewire/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php index f55541a2..debd5999 100644 --- a/src/Http/Livewire/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -23,26 +23,45 @@ class LivewireDatatable extends Component public $activeSelectFilters = []; public $activeBooleanFilters = []; public $activeTextFilters = []; - public $showHide = true; - public $header = true; - public $paginationControls = true; + public $activeNumberFilters = []; + public $hideToggles; + public $hideHeader; + public $hidePagination; public $dates; public $times; - public $perPage = 10; - - public function mount($model = null, $except = [], $hidden = [], $uppercase = [], $truncate = [], $formatDates = [], $formatTimes = [], $showHide = null, $header = null, $paginationControls = null, $dateFilters = [], $timeFilters = [], $renames = [], $defaultSort = null) - { + public $perPage; + + public function mount( + $model = null, + $include = [], + $exclude = [], + $hide = [], + $dates = [], + $times = [], + $renames = [], + $sort = null, + $hideToggles = null, + $hideHeader = null, + $hidePagination = null, + $perPage = 10 + ) { $this->model = $this->model ?? $model; - $this->fields = $this->fields()->map->toArray()->toArray(); - $this->processProperties(func_get_args()); - $this->addUppercases($uppercase); - $this->addTruncates($truncate); - $this->addFormatDates($formatDates); - $this->addFormatTimes($formatTimes); - $this->addDateFilters($dateFilters); - $this->addTimeFilters($timeFilters); - $this->addRenames($renames); + $this->hideToggles = $hideToggles; + $this->hideHeader = $hideHeader; + $this->hidePagination = $hidePagination; + $this->perPage = $perPage; + + $this->fields = $this->fieldset() + ->include($include) + ->exclude($exclude) + ->hidden($hide) + ->formatDates($dates) + ->formatTimes($times) + ->rename($renames) + ->sort($sort) + ->fieldsArray(); + $this->initialiseSort(); } @@ -56,9 +75,9 @@ public function builder() return $this->model()::query(); } - public function fields() + public function fieldset() { - return Fieldset::fromModel($this->model())->fields(); + return Fieldset::fromModel($this->model()); } public function initialiseSort() @@ -81,9 +100,9 @@ public function defaultSort() public function getSortString() { - return $this->fields()[$this->sort]->sort - ?? $this->fields()[$this->sort]->column - ?? $this->fields()[$this->sort]->raw; + return $this->fieldset()->fields()[$this->sort]->sort + ?? $this->fieldset()->fields()[$this->sort]->column + ?? $this->fieldset()->fields()[$this->sort]->raw; } public function sort($index) @@ -123,6 +142,12 @@ public function doTextFilter($index, $value) $this->page = 1; } + public function doNumberFilter($index, $low = 0, $high = 1000000) + { + $this->activeNumberFilters[$index] = [$low, $high]; + $this->page = 1; + } + public function removeSelectFilter($column, $key = null) { unset($this->activeSelectFilters[$column][$key]); @@ -146,6 +171,7 @@ public function clearFilters() $this->activeSelectFilters = []; $this->activeBooleanFilters = []; $this->activeTextFilters = []; + $this->activeNumberFilters = []; } public function clearAllFilters() @@ -155,6 +181,7 @@ public function clearAllFilters() $this->activeSelectFilters = []; $this->activeBooleanFilters = []; $this->activeTextFilters = []; + $this->activeNumberFilters = []; } public function removeBooleanFilter($column) @@ -167,6 +194,11 @@ public function removeTextFilter($column) unset($this->activeTextFilters[$column]); } + public function removeNumberFilter($column) + { + unset($this->activeTextFilters[$column]); + } + public function getVisibleFieldsProperty() { return collect($this->fields)->reject->hidden; @@ -258,6 +290,11 @@ public function addTextFilters($builder) }); } + public function addNumberFilters($builder) + { + return $builder->whereBetween($this->getFieldColumn(key($this->activeNumberFilters)), reset($this->activeNumberFilters)); + } + public function addDateRangeFilter($builder) { return $builder->when(isset($this->dates['start']), function ($query) { @@ -332,6 +369,11 @@ public function getTextFiltersProperty() return collect($this->fields)->filter->textFilter; } + public function getNumberFiltersProperty() + { + return collect($this->fields)->filter->numberFilter; + } + public function getDateFiltersProperty() { return tap(collect($this->fields)->filter->dateFilter, function ($fields) { @@ -379,6 +421,9 @@ public function buildDatabaseQuery() ->when(count($this->activeTextFilters) > 0, function ($query) { return $this->addTextFilters($query); }) + ->when(count($this->activeNumberFilters) > 0, function ($query) { + return $this->addNumberFilters($query); + }) ->when(isset($this->dates['field']) && (isset($this->dates['start']) || (isset($this->dates['end']))), function ($query) { return $this->addDateRangeFilter($query); }) @@ -406,7 +451,6 @@ public function mapCallbacks($paginatedCollection) public function render() { - // dd($this->dateFilters); return view('livewire-datatables::livewire.datatable'); } } diff --git a/src/Traits/HandlesProperties.php b/src/Traits/HandlesProperties.php index a069af3f..e1b2114d 100644 --- a/src/Traits/HandlesProperties.php +++ b/src/Traits/HandlesProperties.php @@ -6,138 +6,23 @@ trait HandlesProperties { - public function processProperties($properties) - { - collect((new ReflectionMethod(static::class, 'mount'))->getParameters())->mapWithKeys(function ($p) use ($properties) { - return [$p->name => $properties[$p->getPosition()]]; - })->each(function ($value, $name) { - if (in_array($name, ['showHide', 'header', 'paginationControls']) && $value) { - $this->{$name} = $value; - } else if (in_array($name, ['except']) && $value) { - $this->fields = collect($this->fields)->reject(function ($field) use ($value) { - return in_array($field['column'], $value); - })->toArray(); - } else if (in_array($name, ['hidden']) && $value) { - $this->fields = collect($this->fields)->map(function ($field) use ($value) { - $field['hidden'] = in_array($field['column'], $value); - return $field; - })->toArray(); - } else if (in_array($name, ['defaultSort']) && $value) {; - $this->fields = collect($this->fields)->map(function ($field) use ($value) { - if ($field['column'] === key($value)) { - $field['defaultSort'] = reset($value); - } - return $field; - })->toArray(); - } - }); - } - public function addExcepts($fields) { - if (!count($fields)) { - return; - } - - $this->fields = collect($this->fields)->reject(function ($field) use ($fields) { - return in_array($field['column'], $fields); - })->toArray(); - } - - public function addUppercases($fields) - { - if (!count($fields)) { - return; - } - $this->fields = collect($this->fields)->map(function ($field) use ($fields) { - if (in_array($field['column'], $fields)) { - $field['name'] = strtoupper($field['name']); - } - return $field; - })->toArray(); - } - - public function addTruncates($fields) - { - if (!count($fields)) { - return; - } - - $this->fields = collect($this->fields)->map(function ($field) use ($fields) { - if (in_array($field['column'], $fields)) { - $field['callback'] = 'truncate'; - } - return $field; - })->toArray(); - } - - public function addFormatDates($fields) - { - if (!count($fields)) { - return; - } - - $this->fields = collect($this->fields)->map(function ($field) use ($fields) { - if (in_array($field['column'], $fields)) { - $field['callback'] = 'formatDate'; - } - return $field; - })->toArray(); + // } - public function addFormatTimes($fields) + public function addDates($dates) { - if (!count($fields)) { - return; - } - - $this->fields = collect($this->fields)->map(function ($field) use ($fields) { - if (in_array($field['column'], $fields)) { - $field['callback'] = 'formatTime'; - } - return $field; - })->toArray(); + // } - public function addDateFilters($fields) + public function addTimes($fields) { - if (!count($fields)) { - return; - } - - $this->fields = collect($this->fields)->map(function ($field) use ($fields) { - if (in_array($field['column'], $fields)) { - $field['dateFilter'] = true; - } - return $field; - })->toArray(); - } - - public function addTimeFilters($fields) - { - if (!count($fields)) { - return; - } - - $this->fields = collect($this->fields)->map(function ($field) use ($fields) { - if (in_array($field['column'], $fields)) { - $field['timeFilter'] = true; - } - return $field; - })->toArray(); + // } public function addRenames($fields) { - if (!count($fields)) { - return; - } - - $this->fields = collect($this->fields)->map(function ($field) use ($fields) { - if (in_array($field['column'], collect($fields)->keys()->toArray())) { - $field['name'] = $fields[$field['column']]; - } - return $field; - })->toArray(); + // } } diff --git a/src/Traits/WithCallbacks.php b/src/Traits/WithCallbacks.php index 03bf805c..574ce49d 100644 --- a/src/Traits/WithCallbacks.php +++ b/src/Traits/WithCallbacks.php @@ -24,9 +24,7 @@ public function round($value, $row, $precision = 0) public function boolean($value) { - return $value - ? 'check-circle' - : 'x-circle'; + return view('livewire-datatables::boolean', ['value' => $value]); } public function makeLink($value, $row, $model, $pad = null) diff --git a/tests/FieldTest.php b/tests/FieldTest.php index 6ae1505f..88ed655b 100644 --- a/tests/FieldTest.php +++ b/tests/FieldTest.php @@ -93,6 +93,7 @@ public function it_returns_an_array_from_column() 'callback' => 'makeLink', 'booleanFilter' => null, 'textFilter' => null, + 'numberFilter' => null, 'dateFilter' => null, 'timeFilter' => null, 'raw' => null, @@ -119,6 +120,7 @@ public function it_returns_an_array_from_raw() 'callback' => 'formatDate', 'booleanFilter' => true, 'textFilter' => null, + 'numberFilter' => null, 'dateFilter' => null, 'timeFilter' => null, 'raw' => 'SELECT column FROM table AS table_column', diff --git a/tests/FieldsetTest.php b/tests/FieldsetTest.php index f4a3054f..dab04477 100644 --- a/tests/FieldsetTest.php +++ b/tests/FieldsetTest.php @@ -65,7 +65,7 @@ public function it_can_exclude_fields() factory(DummyModel::class)->create(); $subject = Fieldset::fromModel(DummyModel::class) - ->except(['dummy_models.id', 'dummy_models.body']) + ->exclude(['id', 'body']) ->fields(); $this->assertCount(7, $subject); @@ -80,10 +80,9 @@ public function it_can_rename_fields_from_the_model() factory(DummyModel::class)->create(); $subject = Fieldset::fromModel(DummyModel::class) - ->rename(['dummy_models.id' => 'ID']) + ->rename(['id|ID']) ->fields(); $this->assertEquals('ID', $subject[0]->name); - $this->assertEquals('dummy_models.id', $subject[0]->column); } } diff --git a/tests/LivewireDatatableClassTest.php b/tests/LivewireDatatableClassTest.php new file mode 100644 index 00000000..2ff502f2 --- /dev/null +++ b/tests/LivewireDatatableClassTest.php @@ -0,0 +1,133 @@ +create([ + 'subject' => 'How to sell paper in Scranton PA' + ]); + + $subject = Livewire::test(DummyTable::class) + ->assertSee('How to sell paper in Scranton PA'); + + $this->assertIsArray($subject->fields); + $this->assertEquals([ + 0 => 'ID', + 1 => 'Subject', + 2 => 'Category', + 3 => 'Body', + 4 => 'Flag', + 5 => 'Expiry', + ], collect($subject->fields)->map->name->toArray()); + } + + /** @test */ + public function it_can_set_a_default_sort() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + ]); + + $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); + $this->assertIsArray($subject->fields); + + $this->assertEquals(0, $subject->sort); + $this->assertFalse($subject->direction); + } + + /** @test */ + public function it_can_show_and_hide_a_column() + { + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs']); + + $subject = Livewire::test(LivewireDatatable::class, ['model' => DummyModel::class]) + ->assertSee('Beet growing for noobs') + ->call('toggle', 2) + ->assertDontSee('Beet growing for noobs') + ->call('toggle', 2) + ->assertSee('Beet growing for noobs') + ->call('toggle', 2) + ->assertDontSee('Beet growing for noobs'); + } + + /** @test */ + public function it_can_order_results() + { + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs']); + factory(DummyModel::class)->create(['subject' => 'Advanced beet growing']); + + $subject = new LivewireDatatable(1); + $subject->model = DummyModel::class; + + $this->assertEquals('Beet growing for noobs', $subject->results->getCollection()->map->getAttributes()[0]['subject']); + $this->assertEquals('Advanced beet growing', $subject->results->getCollection()->map->getAttributes()[1]['subject']); + + $subject->forgetComputed(); + $subject->sort = 2; + $subject->direction = true; + + $this->assertEquals('Advanced beet growing', $subject->results->getCollection()->map->getAttributes()[0]['subject']); + $this->assertEquals('Beet growing for noobs', $subject->results->getCollection()->map->getAttributes()[1]['subject']); + } + + /** @test */ + public function it_can_filter_results_based_on_text() + { + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs']); + factory(DummyModel::class)->create(['subject' => 'Advanced beet growing']); + + $subject = Livewire::test(DummyTable::class) + ->assertSee('Results 1 - 2 of 2') + ->call('doTextFilter', 1, 'Advance') + ->assertSee('Results 1 - 1 of 1'); + } + + /** @test */ + public function it_can_filter_results_based_on_boolean() + { + factory(DummyModel::class)->create(['flag' => true]); + factory(DummyModel::class)->create(['flag' => false]); + + $subject = Livewire::test(DummyTable::class) + ->assertSee('Results 1 - 2 of 2') + ->call('doBooleanFilter', 4) + ->assertSee('Results 1 - 1 of 1'); + } + + /** @test */ + public function it_can_filter_results_based_on_selects() + { + factory(DummyModel::class)->create(['category' => 'Schrute']); + factory(DummyModel::class)->create(['category' => 'Scott']); + + $subject = Livewire::test(DummyTable::class) + ->assertSee('Results 1 - 2 of 2') + ->call('doSelectFilter', 2, 'Scott') + ->assertSee('Results 1 - 1 of 1'); + } + + /** @test */ + public function it_can_filter_results_based_on_numbers() + { + factory(DummyModel::class)->create(['id' => 1]); + factory(DummyModel::class)->create(['id' => 2]); + + $subject = Livewire::test(DummyTable::class) + ->assertSee('Results 1 - 2 of 2') + ->call('doNumberFilter', 0, 2, 10) + ->assertSee('Results 1 - 1 of 1'); + } +} diff --git a/tests/LivewireDatatableTemplateTest.php b/tests/LivewireDatatableTemplateTest.php new file mode 100644 index 00000000..32695786 --- /dev/null +++ b/tests/LivewireDatatableTemplateTest.php @@ -0,0 +1,200 @@ +create(); + + $subject = Livewire::test(LivewireDatatable::class, ['model' => DummyModel::class]); + + $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); + $this->assertIsArray($subject->fields); + $this->assertEquals([ + 0 => 'Id', + 1 => 'Relation_id', + 2 => 'Subject', + 3 => 'Category', + 4 => 'Body', + 5 => 'Flag', + 6 => 'Expires_at', + 7 => 'Created_at', + 8 => 'Updated_at' + ], collect($subject->fields)->map->name->toArray()); + } + + /** @test */ + public function the_show_hide_buttons_can_be_hidden_with_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'hideToggles' => true + ])->assertDontSeeHtml('wire:click.prefetch="toggle'); + } + + /** @test */ + public function the_header_can_be_hidden_with_a_property() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'hideHeader' => true + ])->assertDontSeeHtml(' @endforeach
+ @endif + @if($this->globallySearched()->count()) +
+
+
+ + + +
+ +
+
@endif
diff --git a/src/Field.php b/src/Field.php index cabff5b3..87dc5193 100644 --- a/src/Field.php +++ b/src/Field.php @@ -10,6 +10,7 @@ class Field public $name; public $column; public $raw; + public $globalSearch; public $sort; public $defaultSort; public $callback; @@ -78,6 +79,12 @@ public function defaultSort($direction = 'desc') return $this; } + public function globalSearch() + { + $this->globalSearch = true; + return $this; + } + public function withSelectFilter($selectFilter) { $this->selectFilter = $selectFilter; diff --git a/src/Fieldset.php b/src/Fieldset.php index 96863d66..55f2780d 100644 --- a/src/Fieldset.php +++ b/src/Fieldset.php @@ -73,6 +73,10 @@ public function exclude($exclude) public function hidden($hidden) { + if (!$hidden) { + return $this; + } + $hidden = is_array($hidden) ? $hidden : explode(', ', $hidden); $this->fields->each(function ($field) use ($hidden) { diff --git a/src/Http/Livewire/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php index debd5999..8ea389f4 100644 --- a/src/Http/Livewire/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -18,6 +18,7 @@ class LivewireDatatable extends Component public $model; public $fields; + public $search; public $sort; public $direction; public $activeSelectFilters = []; @@ -36,7 +37,7 @@ public function mount( $model = null, $include = [], $exclude = [], - $hide = [], + $hidden = [], $dates = [], $times = [], $renames = [], @@ -55,7 +56,7 @@ public function mount( $this->fields = $this->fieldset() ->include($include) ->exclude($exclude) - ->hidden($hide) + ->hidden($hidden) ->formatDates($dates) ->formatTimes($times) ->rename($renames) @@ -321,6 +322,13 @@ public function addTimeRangeFilter($builder) }); } + public function globallySearched() + { + return $this->visibleFields->filter(function ($field, $key) { + return isset($field['globalSearch']); + }); + } + public function scopeFields() { return $this->visibleFields->filter(function ($field, $key) { @@ -402,6 +410,13 @@ public function buildDatabaseQuery() return $this->builder() ->select('*') ->addSelect($this->getSelectStatements()->toArray()) + ->when($this->search, function ($query) { + $this->globallySearched()->each(function ($field, $i) use ($query) { + $this->fields[$i]['callback'] = 'highlight'; + $this->fields[$i]['params'] = [$this->search]; + $query->orWhere($field['column'], 'like', "%$this->search%"); + }); + }) ->when(count($this->getRawStatements()), function ($query) { $this->getRawStatements()->each(function ($statement) use ($query) { $query->selectRaw($statement); diff --git a/src/Traits/WithCallbacks.php b/src/Traits/WithCallbacks.php index 574ce49d..989ea702 100644 --- a/src/Traits/WithCallbacks.php +++ b/src/Traits/WithCallbacks.php @@ -2,6 +2,7 @@ namespace Mediconesystems\LivewireDatatables\Traits; +use Exception; use Illuminate\Support\Str; use Illuminate\Support\Carbon; @@ -39,4 +40,11 @@ public function truncate($value, $row, $length = 16) { return view('livewire-datatables::tooltip', ['slot' => $value, 'length' => $length]); } + + public function highlight($value, $row, $string) + { + $output = substr($value, stripos($value, $string), strlen($string)); + + return str_ireplace($string, '' . $output . '', $value); + } } diff --git a/tests/FieldTest.php b/tests/FieldTest.php index 88ed655b..b8950a99 100644 --- a/tests/FieldTest.php +++ b/tests/FieldTest.php @@ -99,6 +99,7 @@ public function it_returns_an_array_from_column() 'raw' => null, 'sort' => null, 'defaultSort' => null, + 'globalSearch' => null, 'params' => ['model', 8], ], $subject); } @@ -126,6 +127,7 @@ public function it_returns_an_array_from_raw() 'raw' => 'SELECT column FROM table AS table_column', 'sort' => DB::raw('SELECT column FROM table'), 'defaultSort' => 'asc', + 'globalSearch' => null, 'params' => ['yyy-mm-dd'], ], $subject); } diff --git a/tests/LivewireDatatableTemplateTest.php b/tests/LivewireDatatableTemplateTest.php index 32695786..404a3c14 100644 --- a/tests/LivewireDatatableTemplateTest.php +++ b/tests/LivewireDatatableTemplateTest.php @@ -128,7 +128,7 @@ public function it_can_hide_fields_from_a_property() $subject = Livewire::test(LivewireDatatable::class, [ 'model' => DummyModel::class, - 'hide' => ['subject'] + 'hidden' => ['subject'] ])->assertDontSee('HIDE-THIS'); $this->assertIsArray($subject->fields); From 4e32bfa7d64cb0abdc8847adfa73ce0a3d1f1c57 Mon Sep 17 00:00:00 2001 From: Mark Salmon Date: Tue, 30 Jun 2020 15:18:35 +0100 Subject: [PATCH 8/8] wip --- resources/views/highlight.blade.php | 1 + src/Http/Livewire/LivewireDatatable.php | 10 ++++++---- src/Traits/WithCallbacks.php | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 resources/views/highlight.blade.php diff --git a/resources/views/highlight.blade.php b/resources/views/highlight.blade.php new file mode 100644 index 00000000..ac397242 --- /dev/null +++ b/resources/views/highlight.blade.php @@ -0,0 +1 @@ +{{ $slot }} \ No newline at end of file diff --git a/src/Http/Livewire/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php index 8ea389f4..eeb42d0b 100644 --- a/src/Http/Livewire/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -411,10 +411,12 @@ public function buildDatabaseQuery() ->select('*') ->addSelect($this->getSelectStatements()->toArray()) ->when($this->search, function ($query) { - $this->globallySearched()->each(function ($field, $i) use ($query) { - $this->fields[$i]['callback'] = 'highlight'; - $this->fields[$i]['params'] = [$this->search]; - $query->orWhere($field['column'], 'like', "%$this->search%"); + $query->where(function ($query) { + $this->globallySearched()->each(function ($field, $i) use ($query) { + $this->fields[$i]['callback'] = 'highlight'; + $this->fields[$i]['params'] = [$this->search]; + $query->orWhere($field['column'], 'like', "%$this->search%"); + }); }); }) ->when(count($this->getRawStatements()), function ($query) { diff --git a/src/Traits/WithCallbacks.php b/src/Traits/WithCallbacks.php index 989ea702..73de0bc8 100644 --- a/src/Traits/WithCallbacks.php +++ b/src/Traits/WithCallbacks.php @@ -45,6 +45,6 @@ public function highlight($value, $row, $string) { $output = substr($value, stripos($value, $string), strlen($string)); - return str_ireplace($string, '' . $output . '', $value); + return str_ireplace($string, view('livewire::datatables.highlight', ['slot' => $output]), $value); } }