From 59f4c6ea7b5842bdcd46023b1990c3bb922bd457 Mon Sep 17 00:00:00 2001 From: Michalis Antoniou Date: Fri, 17 Dec 2021 01:27:35 -0600 Subject: [PATCH 01/15] Adds ability to sort by multiple columns (multisort) * Adds ability to sort by multiple columns (multisort) Authored-by: Antonis Antoniou and Michalis Antoniou --- README.md | 21 +- .../livewire/datatables/datatable.blade.php | 8 + .../datatables/header-no-hide.blade.php | 58 ++++-- src/Column.php | 2 +- src/ColumnSet.php | 11 +- src/Http/Livewire/LivewireDatatable.php | 184 ++++++++++++++--- tests/LivewireDatatableClassTest.php | 75 ++++++- ...DatatableMultisortableQueryBuilderTest.php | 188 ++++++++++++++++++ tests/LivewireDatatableQueryBuilderTest.php | 2 +- tests/LivewireDatatableTemplateTest.php | 82 +++++++- tests/PutSortToSessionTest.php | 60 ++++++ 11 files changed, 630 insertions(+), 61 deletions(-) create mode 100644 tests/LivewireDatatableMultisortableQueryBuilderTest.php create mode 100644 tests/PutSortToSessionTest.php diff --git a/README.md b/README.md index d905b1f7..d41501d9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ### Features - Use a model or query builder to supply data - Mutate and format columns using preset or custom callbacks -- Sort data using column or computed column +- Sort data using one or more columns or computed columns - Filter using booleans, times, dates, selects or free text - Create complex combined filters using the [complex query builder](#complex-query-builder) - Show / hide columns @@ -77,7 +77,8 @@ somewhere in your CSS |**dates**|*String\|Array* of column definitions [ and optional format in \| delimited string]|column values are formatted as per the default date format, or format can be included in string with \| separator | ```:dates="['dob\|lS F y', 'created_at']"```| |**times**|*String\|Array* of column definitions [optional format in \| delimited string]|column values are formatted as per the default time format, or format can be included in string with \| separator | ```'bedtime\|g:i A'```| |**searchable**|*String\|Array* of column names | Defines columns to be included in global search | ```searchable="name, email"```| -|**sort**|*String* of column definition [and optional 'asc' or 'desc' (default: 'desc') in \| delimited string]|Specifies the column and direction for initial table sort. Default is column 0 descending | ```sort="name\|asc"```| +|**sort**|*String, int or array* of column(s) definition [and optional 'asc' or 'desc' (default: 'desc') Specifies the column and direction for initial table sort. Default is column 0 descending | ```sort="name\|asc"```| +|**multisort**|*Boolean default: false*|When set to true, sort is done using multiple columns. |**hide-header**|*Boolean* default: *false*|The top row of the table including the column titles is removed if this is ```true```| | |**hide-pagination**|*Boolean* default: *false*|Pagination controls are removed if this is ```true```| | |**per-page**|*Integer* default: 10|Number of rows per page| ```per-page="20"``` | @@ -87,6 +88,22 @@ somewhere in your CSS |**afterTableSlot**| _String_ |blade view to be included immediately after the table in the component, which can therefore access public properties| [demo](https://livewire-datatables.com/complex) | --- +## Sorting by multiple columns (multisort) +![Multisort Demo](http://g.recordit.co/u5nCxb7hTp.gif "Multisort Demo") +### Enable multisort +Multisort is disabled by default. To enable it set ```multisort = true``` on your ```livewire-datatable``` component. +```html +... + + + +... +``` +### How does it work? +There's 3 possible states a column can have when multisort is enabled. Clicking on a column will advance its state. +- 1st click: column added to the sort with direction `desc`. +- 2nd click: direction changes to `asc`. +- 3rd click: column removed from sort. ## Component Syntax diff --git a/resources/views/livewire/datatables/datatable.blade.php b/resources/views/livewire/datatables/datatable.blade.php index bd854f69..db86ce89 100644 --- a/resources/views/livewire/datatables/datatable.blade.php +++ b/resources/views/livewire/datatables/datatable.blade.php @@ -6,6 +6,14 @@ @endif
+ @if(isset($this->multisort) && $this->multisort === true && count($this->sort) > 1) + + @endif
@if($this->searchableColumns()->count())
diff --git a/resources/views/livewire/datatables/header-no-hide.blade.php b/resources/views/livewire/datatables/header-no-hide.blade.php index 6ceddb9e..4c2c044c 100644 --- a/resources/views/livewire/datatables/header-no-hide.blade.php +++ b/resources/views/livewire/datatables/header-no-hide.blade.php @@ -1,22 +1,44 @@ @unless($column['hidden']) -
- @if($column['unsortable']) -
- {{ str_replace('_', ' ', $column['label']) }} -
- @else - - @endif -
+ + @endif +
@endif diff --git a/src/Column.php b/src/Column.php index 8578111e..c10b7916 100644 --- a/src/Column.php +++ b/src/Column.php @@ -120,7 +120,7 @@ public function sortBy($column) return $this; } - public function defaultSort($direction = true) + public function defaultSort(?string $direction = 'desc') { $this->defaultSort = $direction; diff --git a/src/ColumnSet.php b/src/ColumnSet.php index 5b4a9a92..119c7121 100644 --- a/src/ColumnSet.php +++ b/src/ColumnSet.php @@ -135,8 +135,15 @@ public function search($searchable) public function sort($sort) { - if ($sort && $column = $this->columns->first(function ($column) use ($sort) { - return Str::after($column->name, '.') === Str::before($sort, '|'); + if (is_array($sort)) { + foreach ($sort as $arg) { + $this->sort($arg); + } + return $this; + } + + if ($sort && $column = $this->columns->first(function ($column, $key) use ($sort) { + return Str::after($column->name, '.') === ($sort = Str::before($sort, '|')) || $sort === $key; })) { $column->defaultSort(Str::of($sort)->contains('|') ? Str::after($sort, '|') : null); } diff --git a/src/Http/Livewire/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php index 6d76e41d..10beed36 100644 --- a/src/Http/Livewire/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Query\Expression; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use Illuminate\View\View; @@ -29,11 +30,12 @@ class LivewireDatatable extends Component use WithPagination, WithCallbacks, WithPresetDateFilters, WithPresetTimeFilters; const SEPARATOR = '|**lwdt**|'; + const DEFAULT_DIRECTION = 'desc'; + const ORDER_BY_DIRECTION_STATES = [self::DEFAULT_DIRECTION, 'asc', null]; public $model; public $columns; public $search; public $sort; - public $direction; public $activeDateFilters = []; public $activeTimeFilters = []; public $activeSelectFilters = []; @@ -66,6 +68,7 @@ class LivewireDatatable extends Component public $persistSort = true; public $persistPerPage = true; public $persistFilters = true; + public $multisort; /** * @var array List your groups and the corresponding label (or translation) here. @@ -166,6 +169,7 @@ public function mount( $times = [], $searchable = [], $sort = null, + $multisort = false, $hideHeader = null, $hidePagination = null, $perPage = null, @@ -175,7 +179,7 @@ public function mount( $afterTableSlot = false, $params = [] ) { - foreach (['model', 'include', 'exclude', 'hide', 'dates', 'times', 'searchable', 'sort', 'hideHeader', 'hidePagination', 'exportable', 'hideable', 'beforeTableSlot', 'afterTableSlot'] as $property) { + foreach (['model', 'include', 'exclude', 'hide', 'dates', 'times', 'searchable', 'sort', 'multisort', 'hideHeader', 'hidePagination', 'exportable', 'hideable', 'beforeTableSlot', 'afterTableSlot'] as $property) { $this->$property = $this->$property ?? $$property; } @@ -474,8 +478,12 @@ public function getSessionStoredSort() return; } - $this->sort = session()->get($this->sessionStorageKey() . $this->name . '_sort', $this->sort); - $this->direction = session()->get($this->sessionStorageKey() . $this->name . '_direction', $this->direction); + if (! $this->multisort) { + $this->sort = session()->get($this->sessionStorageKey() . $this->name . '_sort', $this->sort); + return; + } + + $this->sort = session()->get($this->sessionStorageKey() . $this->name . '_multisort', $this->sort) ?? []; } public function getSessionStoredPerPage() @@ -493,7 +501,12 @@ public function setSessionStoredSort() return; } - session()->put([$this->sessionStorageKey() . $this->name . '_sort' => $this->sort, $this->sessionStorageKey() . $this->name . '_direction' => $this->direction]); + if (! $this->multisort) { + session()->put([$this->sessionStorageKey() . $this->name . '_sort' => $this->sort]); + return; + } + + session()->put([$this->sessionStorageKey() . $this->name . '_multisort' => implode(',', $this->sort)]); } public function setSessionStoredFilters() @@ -516,14 +529,28 @@ public function setSessionStoredFilters() public function initialiseSort() { - $this->sort = $this->defaultSort() - ? $this->defaultSort()['key'] - : collect($this->freshColumns)->reject(function ($column) { - return in_array($column['type'], Column::UNSORTABLE_TYPES) || $column['hidden']; - })->keys()->first(); + $default = $this->defaultSort(); + $direction = self::DEFAULT_DIRECTION; + if ($default->isNotEmpty()) { + $this->sort = $default->transform(function ($column) { + return $column['key'] . '|' . $column['direction']; + })->values()->toArray(); + $this->getSessionStoredSort(); + return; + } + + if (is_int($this->sort) || is_string($this->sort)) { + $columnIndex = $this->getIndexFromValue($this->sort); + $direction = $this->getColumnDirection($this->sort); + } else { + $columnIndex = collect($this->freshColumns)->reject(function ($column) { + return in_array($column['type'], Column::UNSORTABLE_TYPES) || $column['hidden']; + })->keys()->first(); + } + + $this->sort = [$columnIndex . '|' . $direction]; $this->getSessionStoredSort(); - $this->direction = $this->defaultSort() && $this->defaultSort()['direction'] === 'asc'; } public function initialiseHiddenColumns() @@ -577,19 +604,21 @@ public function initialiseFilters() public function defaultSort() { - $columnIndex = collect($this->freshColumns)->search(function ($column) { + $columns = collect($this->freshColumns)->filter(function ($column) { return is_string($column['defaultSort']); }); - return is_numeric($columnIndex) ? [ - 'key' => $columnIndex, - 'direction' => $this->freshColumns[$columnIndex]['defaultSort'], - ] : null; + return $columns->map(function ($column, $index) { + return is_numeric($index) ? [ + 'key' => $index, + 'direction' => $this->freshColumns[$index]['defaultSort'], + ] : null; + }); } - public function getSortString() + public function getSortString(int $index) { - $column = $this->freshColumns[$this->sort]; + $column = $this->freshColumns[$index]; $dbTable = DB::connection()->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME); switch (true) { @@ -627,6 +656,19 @@ public function refreshLivewireDatatable() $this->page = 1; } + public function getColumnDirection(string $sortString): string + { + $direction = self::DEFAULT_DIRECTION; + if (Str::contains($sortString, '|')) { + $direction = Str::after($sortString, '|'); + if (! in_array($direction, self::ORDER_BY_DIRECTION_STATES)) { + throw new \Exception("Invalid direction $direction given in getColumnDirection() method. Allowed values: asc, desc."); + } + } + + return $direction; + } + /** * Order the table by a given column index starting from 0. * @@ -636,28 +678,61 @@ public function refreshLivewireDatatable() */ public function sort($index, $direction = null) { - if (! in_array($direction, [null, 'asc', 'desc'])) { + if (! in_array($direction, self::ORDER_BY_DIRECTION_STATES)) { throw new \Exception("Invalid direction $direction given in sort() method. Allowed values: asc, desc."); } + $key = Str::snake(Str::afterLast(get_called_class(), '\\')); + + if ($this->multisort) { + if (($columnsWithDirection = $this->getColumnsFromSort($this->sort, $index))->isEmpty()) { + if ($direction === null) { + $sort = $index . '|' . $this->getColumnDirection($index); + } else { + $sort = $index . '|' . $direction; + } + $this->sort[] = $sort; + } else { + $sortIndex = $columnsWithDirection->keys()->first(); + if ($direction === null) { + $toggledDirection = $this->toggleMultisortDirection($this->getColumnDirection($this->sort[$sortIndex])); + unset($this->sort[$sortIndex]); + $this->sort = array_values($this->sort); + $sort = $index . '|' . $toggledDirection; + if ($toggledDirection === null) { + return; + } + $this->sort[] = $sort; + } else { + $this->sort[$sortIndex] = + $index . '|' . $direction; + } + } + + $this->page = 1; + session()->put([$key . $this->name . '_multisort' => $this->sort]); + return; + } - if ($this->sort === (int) $index) { - if ($direction === null) { // toggle direction - $this->direction = ! $this->direction; + if (in_array($index . '|' . $this->getColumnDirection($index), $this->sort)) { + if ($direction === null) { + $sort = [$index . '|' . $this->toggleSortDirection($this->getColumnDirection($index))]; } else { - $this->direction = $direction === 'desc' ? false : true; + $sort = [$index . '|' . $direction]; } } else { - $this->sort = (int) $index; + $direction = $direction ?? self::DEFAULT_DIRECTION; + $sort = [$index . '|' . $direction]; } + + $this->sort = $sort; $this->page = 1; - $key = Str::snake(Str::afterLast(get_called_class(), '\\')); - session()->put([$key . $this->name . '_sort' => $this->sort, $key . $this->name . '_direction' => $this->direction]); + session()->put([$key . $this->name . '_sort' => $this->sort, $key . $this->name . '_direction' => Str::after($this->sort[0], '|')]); } public function toggle($index) { - if ($this->sort == $index) { + if (in_array($index, $this->sort)) { $this->initialiseSort(); } @@ -1346,8 +1421,18 @@ public function addTimeRangeFilter() */ public function addSort() { - if (isset($this->sort) && isset($this->freshColumns[$this->sort]) && $this->freshColumns[$this->sort]['name']) { - $this->query->orderBy(DB::raw($this->getSortString()), $this->direction ? 'asc' : 'desc'); + if (!empty($this->sort)) { + foreach ($this->sort as $sort) { + $index = Str::before($sort, '|'); + if (!is_numeric($index) + && !is_null(($index = optional(collect($this->freshColumns)->where('name', $index))->keys()->first()))) { + $columnName = Str::after($this->getSortString($index), '.'); + } else { + $columnName = $this->getSortString($index); + } + + $this->query->orderByRaw($columnName . ' ' . ($direction = Str::after($sort, '|') == $index ? self::DEFAULT_DIRECTION : Str::after($sort, '|'))); + } } return $this; @@ -1563,4 +1648,45 @@ public function cellClasses($row, $column) // Override this method with your own method for adding classes to a cell return config('livewire-datatables.default_classes.cell', 'text-sm text-gray-900'); } + + public function toggleSortDirection(string $direction): string + { + if (! in_array($direction, self::ORDER_BY_DIRECTION_STATES)) { + throw new \Exception("Invalid direction $direction given in toggleSortDirection() method. Allowed values: asc, desc."); + } + + switch ($direction) { + case $direction === self::DEFAULT_DIRECTION : + return 'asc'; + default : + return self::DEFAULT_DIRECTION; + } + } + + public function toggleMultisortDirection(string $direction): ?string + { + $directionState = array_search($direction, ($directions = self::ORDER_BY_DIRECTION_STATES)); + if ($directionState === false) { + throw new Exception('Undefined direction index in togglemultisortDirection()'); + } + return $directions[$directionState + 1]; + } + + public function getColumnsFromSort(array $sort, $index): Collection + { + return collect($sort)->filter(function ($value) use ($index) { + return $this->getIndexFromValue($value) == $index; + }); + } + + public function getIndexFromValue($q): ?int + { + return (int) Str::before($q, '|'); + } + + public function forgetSortSession() + { + session()->forget($this->sessionStorageKey() . $this->name . '_multisort'); + $this->sort = []; + } } diff --git a/tests/LivewireDatatableClassTest.php b/tests/LivewireDatatableClassTest.php index 8b7182e8..d199c373 100644 --- a/tests/LivewireDatatableClassTest.php +++ b/tests/LivewireDatatableClassTest.php @@ -40,8 +40,7 @@ public function it_can_set_a_default_sort() $this->assertIsArray($subject->columns); - $this->assertEquals(0, $subject->sort); - $this->assertFalse($subject->direction); + $this->assertEquals(['0|desc'], $subject->sort); } /** @test */ @@ -60,7 +59,7 @@ public function it_can_show_and_hide_a_column() } /** @test */ - public function it_can_order_results() + public function it_can_order_results_for_a_column() { factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs']); factory(DummyModel::class)->create(['subject' => 'Advanced beet growing']); @@ -71,13 +70,79 @@ public function it_can_order_results() $this->assertEquals('Advanced beet growing', $subject->results->getCollection()[1]->subject); $subject->forgetComputed(); - $subject->sort = 1; - $subject->direction = true; + $subject->sort = ['1|asc']; $this->assertEquals('Advanced beet growing', $subject->results->getCollection()[0]->subject); $this->assertEquals('Beet growing for noobs', $subject->results->getCollection()[1]->subject); } + /** @test */ + public function it_can_order_results_for_multiple_columns_using_column_index() + { + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs', 'category' => 'A']); + factory(DummyModel::class)->create(['subject' => 'Advanced beet growing', 'category' => 'A']); + factory(DummyModel::class)->create(['subject' => 'Advanced beet growing', 'category' => 'B']); + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs', 'category' => 'B']); + + $subject = new DummyTable(1); + + $this->assertEquals(['Beet growing for noobs', 'A'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); + $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); + $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[2]->subject, $subject->results->getCollection()[2]->category]); + $this->assertEquals(['Beet growing for noobs', 'B'], [$subject->results->getCollection()[3]->subject, $subject->results->getCollection()[3]->category]); + + $subject->forgetComputed(); + $subject->multisort = true; + $subject->sort = ["1|asc", "2|desc"]; + + $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); + $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); + $this->assertEquals(['Beet growing for noobs', 'B'], [$subject->results->getCollection()[2]->subject, $subject->results->getCollection()[2]->category]); + $this->assertEquals(['Beet growing for noobs', 'A'], [$subject->results->getCollection()[3]->subject, $subject->results->getCollection()[3]->category]); + + $subject->forgetComputed(); + $subject->sort = ["1|asc", "2|asc"]; + + $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); + $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); + $this->assertEquals(['Beet growing for noobs', 'A'], [$subject->results->getCollection()[2]->subject, $subject->results->getCollection()[2]->category]); + $this->assertEquals(['Beet growing for noobs', 'B'], [$subject->results->getCollection()[3]->subject, $subject->results->getCollection()[3]->category]); + + } + + /** @test */ + public function it_can_order_results_for_multiple_columns_using_column_name() + { + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs', 'category' => 'A']); + factory(DummyModel::class)->create(['subject' => 'Advanced beet growing', 'category' => 'A']); + factory(DummyModel::class)->create(['subject' => 'Advanced beet growing', 'category' => 'B']); + factory(DummyModel::class)->create(['subject' => 'Beet growing for noobs', 'category' => 'B']); + + $subject = new DummyTable(1); + + $this->assertEquals(['Beet growing for noobs', 'A'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); + $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); + $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[2]->subject, $subject->results->getCollection()[2]->category]); + $this->assertEquals(['Beet growing for noobs', 'B'], [$subject->results->getCollection()[3]->subject, $subject->results->getCollection()[3]->category]); + + $subject->forgetComputed(); + $subject->multisort = true; + $subject->sort = ["subject|asc", "category|desc"]; + + $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); + $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); + $this->assertEquals(['Beet growing for noobs', 'B'], [$subject->results->getCollection()[2]->subject, $subject->results->getCollection()[2]->category]); + $this->assertEquals(['Beet growing for noobs', 'A'], [$subject->results->getCollection()[3]->subject, $subject->results->getCollection()[3]->category]); + + $subject->forgetComputed(); + $subject->sort = ["subject|asc", "category|asc"]; + + $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); + $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); + $this->assertEquals(['Beet growing for noobs', 'A'], [$subject->results->getCollection()[2]->subject, $subject->results->getCollection()[2]->category]); + $this->assertEquals(['Beet growing for noobs', 'B'], [$subject->results->getCollection()[3]->subject, $subject->results->getCollection()[3]->category]); + } + /** @test */ public function it_can_filter_results_based_on_text() { diff --git a/tests/LivewireDatatableMultisortableQueryBuilderTest.php b/tests/LivewireDatatableMultisortableQueryBuilderTest.php new file mode 100644 index 00000000..d99c35d3 --- /dev/null +++ b/tests/LivewireDatatableMultisortableQueryBuilderTest.php @@ -0,0 +1,188 @@ +create(); + + $subject = new LivewireDatatable(1); + $subject->multisort = true; + $subject->mount(DummyModel::class, ['id', 'subject', 'category']); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models" order by `id` desc', $subject->getQuery()->toSql()); + + $subject->sort(0); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models" order by `id` asc', $subject->getQuery()->toSql()); + + $subject->sort(0); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models"', $subject->getQuery()->toSql()); + + $subject->sort(0); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models" order by `id` desc', $subject->getQuery()->toSql()); + + } + + /** @test */ + public function it_creates_a_multisort_query_builder_for_base_columns() + { + factory(DummyModel::class)->create(); + $subject = new LivewireDatatable(1); + $subject->multisort = true; + $subject->mount(DummyModel::class, ['id', 'subject', 'category']); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models" order by `id` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models" order by `id` desc, `subject` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models" order by `id` desc, `subject` asc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models" order by `id` desc', $subject->getQuery()->toSql()); + + $subject->sort(2); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models" order by `id` desc, `category` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_models"."subject" as "subject", "dummy_models"."category" as "category" from "dummy_models" order by `id` desc, `category` desc, `subject` desc', $subject->getQuery()->toSql()); + } + + /** @test */ + public function it_creates_a_multisort_query_builder_for_has_one_relation_columns() + { + factory(DummyModel::class)->create()->dummy_has_one()->save(factory(DummyHasOneModel::class)->make()); + + $subject = new LivewireDatatable(1); + $subject->multisort = true; + $subject->mount(DummyModel::class, ['id', 'dummy_has_one.name', 'dummy_has_one.category']); + + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_has_one_models"."name" as "dummy_has_one.name", "dummy_has_one_models"."category" as "dummy_has_one.category" from "dummy_models" left join "dummy_has_one_models" on "dummy_has_one_models"."dummy_model_id" = "dummy_models"."id" order by `id` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + $subject->forgetComputed(); + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_has_one_models"."name" as "dummy_has_one.name", "dummy_has_one_models"."category" as "dummy_has_one.category" from "dummy_models" left join "dummy_has_one_models" on "dummy_has_one_models"."dummy_model_id" = "dummy_models"."id" order by dummy_models.id desc, dummy_has_one_models.name desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + $subject->forgetComputed(); + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_has_one_models"."name" as "dummy_has_one.name", "dummy_has_one_models"."category" as "dummy_has_one.category" from "dummy_models" left join "dummy_has_one_models" on "dummy_has_one_models"."dummy_model_id" = "dummy_models"."id" order by dummy_models.id desc, dummy_has_one_models.name asc', $subject->getQuery()->toSql()); + + $subject->sort(2); + $subject->forgetComputed(); + $this->assertEquals('select "dummy_models"."id" as "id", "dummy_has_one_models"."name" as "dummy_has_one.name", "dummy_has_one_models"."category" as "dummy_has_one.category" from "dummy_models" left join "dummy_has_one_models" on "dummy_has_one_models"."dummy_model_id" = "dummy_models"."id" order by dummy_models.id desc, dummy_has_one_models.name asc, dummy_has_one_models.category desc', $subject->getQuery()->toSql()); + } + + /** @test */ + public function it_creates_a_multisort_query_builder_for_has_many_relation_columns() + { + factory(DummyModel::class)->create()->dummy_has_many()->saveMany(factory(DummyHasManyModel::class, 2)->make()); + + $subject = new LivewireDatatable(1); + $subject->multisort = true; + $subject->mount(DummyModel::class, ['id', 'dummy_has_many.name']); + + $this->assertEquals('select (select group_concat(REPLACE(DISTINCT(dummy_has_many_models.name), \'\', \'\') , \', \') from "dummy_has_many_models" where "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id") as `dummy_has_many.name`, "dummy_models"."id" as "id" from "dummy_models" order by `id` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select (select group_concat(REPLACE(DISTINCT(dummy_has_many_models.name), \'\', \'\') , \', \') from "dummy_has_many_models" where "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id") as `dummy_has_many.name`, "dummy_models"."id" as "id" from "dummy_models" order by `id` desc, `dummy_has_many.name` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select (select group_concat(REPLACE(DISTINCT(dummy_has_many_models.name), \'\', \'\') , \', \') from "dummy_has_many_models" where "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id") as `dummy_has_many.name`, "dummy_models"."id" as "id" from "dummy_models" order by `id` desc, `dummy_has_many.name` asc', $subject->getQuery()->toSql()); + + $subject->sort(0); + + $this->assertEquals('select (select group_concat(REPLACE(DISTINCT(dummy_has_many_models.name), \'\', \'\') , \', \') from "dummy_has_many_models" where "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id") as `dummy_has_many.name`, "dummy_models"."id" as "id" from "dummy_models" order by `dummy_has_many.name` asc, `id` asc', $subject->getQuery()->toSql()); + } + + /** @test */ + public function it_creates_a_multisort_query_builder_for_has_many_relation_column_with_specific_aggregate() + { + factory(DummyModel::class)->create()->dummy_has_many()->saveMany(factory(DummyHasManyModel::class, 2)->make()); + + $subject = new LivewireDatatable(1); + $subject->multisort = true; + $subject->mount(DummyModel::class, ['id', 'dummy_has_many.id:avg']); + + $this->assertEquals('select (select COALESCE(avg(dummy_has_many_models.id),0) from "dummy_has_many_models" where "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id") as `dummy_has_many.id:avg`, "dummy_models"."id" as "id" from "dummy_models" order by `id` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select (select COALESCE(avg(dummy_has_many_models.id),0) from "dummy_has_many_models" where "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id") as `dummy_has_many.id:avg`, "dummy_models"."id" as "id" from "dummy_models" order by `id` desc, `dummy_has_many.id:avg` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select (select COALESCE(avg(dummy_has_many_models.id),0) from "dummy_has_many_models" where "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id") as `dummy_has_many.id:avg`, "dummy_models"."id" as "id" from "dummy_models" order by `id` desc, `dummy_has_many.id:avg` asc', $subject->getQuery()->toSql()); + } + + /** @test */ + public function it_creates_a_multisort_query_builder_for_belongs_to_relation_columns() + { + factory(DummyModel::class)->create()->dummy_has_many()->saveMany(factory(DummyHasManyModel::class, 2)->make()); + + $subject = new LivewireDatatable(1); + $subject->multisort = true; + $subject->mount(DummyHasManyModel::class, ['id', 'dummy_model.name']); + + $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_has_many_models"."dummy_model_id" = "dummy_models"."id" order by `id` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + $subject->forgetComputed(); + + $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_has_many_models"."dummy_model_id" = "dummy_models"."id" order by dummy_has_many_models.id desc, dummy_models.name desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + $subject->forgetComputed(); + + $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_has_many_models"."dummy_model_id" = "dummy_models"."id" order by dummy_has_many_models.id desc, dummy_models.name asc', $subject->getQuery()->toSql()); + + $subject->sort(0); + $subject->forgetComputed(); + + $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_has_many_models"."dummy_model_id" = "dummy_models"."id" order by dummy_models.name asc, dummy_has_many_models.id asc', $subject->getQuery()->toSql()); + } + + /** @test */ + public function it_creates_a_multisort_query_builder_for_belongs_to_many_relation_columns() + { + factory(DummyModel::class)->create()->dummy_belongs_to_many()->attach(factory(DummyBelongsToManyModel::class)->create()); + + $subject = new LivewireDatatable(1); + $subject->multisort = true; + $subject->mount(DummyModel::class, ['id', 'dummy_belongs_to_many.name']); + + $this->assertEquals('select (select group_concat(REPLACE(DISTINCT(dummy_belongs_to_many_models.name), \'\', \'\') , \', \') from "dummy_belongs_to_many_models" inner join "dummy_belongs_to_many_model_dummy_model" on "dummy_belongs_to_many_models"."id" = "dummy_belongs_to_many_model_dummy_model"."dummy_belongs_to_many_model_id" where "dummy_models"."id" = "dummy_belongs_to_many_model_dummy_model"."dummy_model_id" and "dummy_belongs_to_many_models"."deleted_at" is null) as `dummy_belongs_to_many.name`, "dummy_models"."id" as "id" from "dummy_models" order by `id` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select (select group_concat(REPLACE(DISTINCT(dummy_belongs_to_many_models.name), \'\', \'\') , \', \') from "dummy_belongs_to_many_models" inner join "dummy_belongs_to_many_model_dummy_model" on "dummy_belongs_to_many_models"."id" = "dummy_belongs_to_many_model_dummy_model"."dummy_belongs_to_many_model_id" where "dummy_models"."id" = "dummy_belongs_to_many_model_dummy_model"."dummy_model_id" and "dummy_belongs_to_many_models"."deleted_at" is null) as `dummy_belongs_to_many.name`, "dummy_models"."id" as "id" from "dummy_models" order by `id` desc, `dummy_belongs_to_many.name` desc', $subject->getQuery()->toSql()); + + $subject->sort(1); + + $this->assertEquals('select (select group_concat(REPLACE(DISTINCT(dummy_belongs_to_many_models.name), \'\', \'\') , \', \') from "dummy_belongs_to_many_models" inner join "dummy_belongs_to_many_model_dummy_model" on "dummy_belongs_to_many_models"."id" = "dummy_belongs_to_many_model_dummy_model"."dummy_belongs_to_many_model_id" where "dummy_models"."id" = "dummy_belongs_to_many_model_dummy_model"."dummy_model_id" and "dummy_belongs_to_many_models"."deleted_at" is null) as `dummy_belongs_to_many.name`, "dummy_models"."id" as "id" from "dummy_models" order by `id` desc, `dummy_belongs_to_many.name` asc', $subject->getQuery()->toSql()); + + $subject->sort(0); + + $this->assertEquals('select (select group_concat(REPLACE(DISTINCT(dummy_belongs_to_many_models.name), \'\', \'\') , \', \') from "dummy_belongs_to_many_models" inner join "dummy_belongs_to_many_model_dummy_model" on "dummy_belongs_to_many_models"."id" = "dummy_belongs_to_many_model_dummy_model"."dummy_belongs_to_many_model_id" where "dummy_models"."id" = "dummy_belongs_to_many_model_dummy_model"."dummy_model_id" and "dummy_belongs_to_many_models"."deleted_at" is null) as `dummy_belongs_to_many.name`, "dummy_models"."id" as "id" from "dummy_models" order by `dummy_belongs_to_many.name` asc, `id` asc', $subject->getQuery()->toSql()); + } +} diff --git a/tests/LivewireDatatableQueryBuilderTest.php b/tests/LivewireDatatableQueryBuilderTest.php index 6d839891..ae95781f 100644 --- a/tests/LivewireDatatableQueryBuilderTest.php +++ b/tests/LivewireDatatableQueryBuilderTest.php @@ -187,7 +187,7 @@ public function it_creates_a_query_builder_for_belongs_to_many_relation_columns( } /** @test */ - public function it_creates_a__where_query_for_belongs_to_many_relation_columns() + public function it_creates_a_where_query_for_belongs_to_many_relation_columns() { factory(DummyModel::class)->create()->dummy_belongs_to_many()->attach(factory(DummyBelongsToManyModel::class)->create()); diff --git a/tests/LivewireDatatableTemplateTest.php b/tests/LivewireDatatableTemplateTest.php index 3457b2a4..e42a4b51 100644 --- a/tests/LivewireDatatableTemplateTest.php +++ b/tests/LivewireDatatableTemplateTest.php @@ -2,6 +2,7 @@ namespace Mediconesystems\LivewireDatatables\Tests; +use Illuminate\Support\Str; use Livewire\Livewire; use Mediconesystems\LivewireDatatables\Http\Livewire\LivewireDatatable; use Mediconesystems\LivewireDatatables\Tests\Models\DummyModel; @@ -152,7 +153,7 @@ public function it_can_mark_columns_for_time_format_from_a_property() } /** @test */ - public function it_can_set_sort_from_a_property() + public function it_can_set_sort_from_property_using_column_name_and_direction() { factory(DummyModel::class)->create(); @@ -164,7 +165,82 @@ public function it_can_set_sort_from_a_property() $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); $this->assertIsArray($subject->columns); - $this->assertEquals(1, $subject->sort); - $this->assertTrue($subject->direction); + $this->assertEquals(['1|asc'], $subject->sort); + } + + /** @test */ + public function it_can_set_sort_from_property_using_column_index() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'sort' => 1, + ]); + + $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); + $this->assertIsArray($subject->columns); + + $this->assertEquals(['1|desc'], $subject->sort); + } + + /** @test */ + public function it_can_set_sort_from_property_using_column_index_and_direction() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'sort' => '1|asc', + ]); + + $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); + $this->assertIsArray($subject->columns); + + $this->assertEquals(['1|asc'], $subject->sort); + } + + /** @test */ + public function it_can_set_multisort_from_property_using_array() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'multisort' => true, + 'sort' => [ + 'subject|asc', + 'category|desc', + ], + ]); + + $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); + $this->assertIsArray($subject->columns); + + $this->assertEquals(['1|asc', '2|desc'], $subject->sort); + $this->assertEquals($subject->freshColumns[1]['defaultSort'], "asc"); + $this->assertEquals($subject->freshColumns[2]['defaultSort'], "desc"); + $this->assertNull($subject->direction); + } + + /** @test */ + public function it_can_forget_multisort_session_on_demand() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'multisort' => true, + ]); + + $this->assertEquals('Mediconesystems\LivewireDatatables\Tests\Models\DummyModel', $subject->model); + $this->assertIsArray($subject->columns); + + $subject->assertDontSee('Reset Columns Sort'); + $subject->call('sort', 1); + $subject->assertSee('Reset Columns Sort'); + $subject->assertSessionHas($multisortSessionKey = Str::snake(Str::afterLast(LivewireDatatable::class, '\\')) . '_multisort'); + $subject->call('forgetSortSession'); + $subject->assertSessionMissing($multisortSessionKey); } } diff --git a/tests/PutSortToSessionTest.php b/tests/PutSortToSessionTest.php new file mode 100644 index 00000000..575e0819 --- /dev/null +++ b/tests/PutSortToSessionTest.php @@ -0,0 +1,60 @@ +create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + ]); + + $subject->call('sort', 1); + $this->assertSession(1, ['1|desc'], $sortSessionKey = Str::snake(Str::afterLast(LivewireDatatable::class, '\\')) . '_sort'); + + $subject->call('sort', 1); + $this->assertSession(1, ['1|asc'], $sortSessionKey); + + $subject->call('sort', 0); + $this->assertSession(1, ['0|desc'], $sortSessionKey); + } + + /** @test */ + public function it_saves_sort_to_session_when_in_multisort() + { + factory(DummyModel::class)->create(); + + $subject = Livewire::test(LivewireDatatable::class, [ + 'model' => DummyModel::class, + 'multisort' => true + ]); + + $subject->call('sort', 1); + $this->assertSession(2, ['0|desc', '1|desc'], $multisortSessionKey = Str::snake(Str::afterLast(LivewireDatatable::class, '\\')) . '_multisort'); + + $subject->call('sort', 1); + $this->assertSession(2, ['0|desc', '1|asc'], $multisortSessionKey); + + $subject->call('sort', 2); + $this->assertSession(3, ['0|desc', '1|asc', '2|desc'], $multisortSessionKey); + } + + private function assertSession(int $expectedCount, array $columnsIndexDirection, string $sessionKey) + { + $session = session()->get($sessionKey); + $this->assertCount($expectedCount, $session); + foreach ($columnsIndexDirection as $columnIndexDirection) { + $this->assertTrue(in_array($columnIndexDirection, $session)); + } + + } +} From 39c723e1d3e122f26159c42d0a2bad2996ce50b7 Mon Sep 17 00:00:00 2001 From: Antonis Antoniou Date: Fri, 17 Dec 2021 10:11:47 +0200 Subject: [PATCH 02/15] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d41501d9..a136c7f2 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ somewhere in your CSS --- ## Sorting by multiple columns (multisort) -![Multisort Demo](http://g.recordit.co/u5nCxb7hTp.gif "Multisort Demo") +![Multisort Demo](http://g.recordit.co/V0FOLGGPjO.gif "Multisort Demo") ### Enable multisort Multisort is disabled by default. To enable it set ```multisort = true``` on your ```livewire-datatable``` component. ```html From fde2f914276653af56549f82fd2845bb6f13adc2 Mon Sep 17 00:00:00 2001 From: Michalis Antoniou Date: Fri, 17 Dec 2021 03:34:18 -0600 Subject: [PATCH 03/15] StyleCI fixes --- src/ColumnSet.php | 1 + src/Http/Livewire/LivewireDatatable.php | 14 +++++++++----- tests/LivewireDatatableClassTest.php | 8 ++++---- ...ewireDatatableMultisortableQueryBuilderTest.php | 1 - tests/LivewireDatatableTemplateTest.php | 4 ++-- tests/PutSortToSessionTest.php | 2 +- 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/ColumnSet.php b/src/ColumnSet.php index 119c7121..0377e426 100644 --- a/src/ColumnSet.php +++ b/src/ColumnSet.php @@ -139,6 +139,7 @@ public function sort($sort) foreach ($sort as $arg) { $this->sort($arg); } + return $this; } diff --git a/src/Http/Livewire/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php index 10beed36..ba33538e 100644 --- a/src/Http/Livewire/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -480,6 +480,7 @@ public function getSessionStoredSort() if (! $this->multisort) { $this->sort = session()->get($this->sessionStorageKey() . $this->name . '_sort', $this->sort); + return; } @@ -503,6 +504,7 @@ public function setSessionStoredSort() if (! $this->multisort) { session()->put([$this->sessionStorageKey() . $this->name . '_sort' => $this->sort]); + return; } @@ -537,6 +539,7 @@ public function initialiseSort() return $column['key'] . '|' . $column['direction']; })->values()->toArray(); $this->getSessionStoredSort(); + return; } @@ -710,6 +713,7 @@ public function sort($index, $direction = null) $this->page = 1; session()->put([$key . $this->name . '_multisort' => $this->sort]); + return; } @@ -1421,11 +1425,11 @@ public function addTimeRangeFilter() */ public function addSort() { - if (!empty($this->sort)) { + if (! empty($this->sort)) { foreach ($this->sort as $sort) { $index = Str::before($sort, '|'); - if (!is_numeric($index) - && !is_null(($index = optional(collect($this->freshColumns)->where('name', $index))->keys()->first()))) { + if (! is_numeric($index) + && ! is_null(($index = optional(collect($this->freshColumns)->where('name', $index))->keys()->first()))) { $columnName = Str::after($this->getSortString($index), '.'); } else { $columnName = $this->getSortString($index); @@ -1656,9 +1660,9 @@ public function toggleSortDirection(string $direction): string } switch ($direction) { - case $direction === self::DEFAULT_DIRECTION : + case self::DEFAULT_DIRECTION: return 'asc'; - default : + default: return self::DEFAULT_DIRECTION; } } diff --git a/tests/LivewireDatatableClassTest.php b/tests/LivewireDatatableClassTest.php index d199c373..18296403 100644 --- a/tests/LivewireDatatableClassTest.php +++ b/tests/LivewireDatatableClassTest.php @@ -93,7 +93,7 @@ public function it_can_order_results_for_multiple_columns_using_column_index() $subject->forgetComputed(); $subject->multisort = true; - $subject->sort = ["1|asc", "2|desc"]; + $subject->sort = ['1|asc', '2|desc']; $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); @@ -101,7 +101,7 @@ public function it_can_order_results_for_multiple_columns_using_column_index() $this->assertEquals(['Beet growing for noobs', 'A'], [$subject->results->getCollection()[3]->subject, $subject->results->getCollection()[3]->category]); $subject->forgetComputed(); - $subject->sort = ["1|asc", "2|asc"]; + $subject->sort = ['1|asc', '2|asc']; $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); @@ -127,7 +127,7 @@ public function it_can_order_results_for_multiple_columns_using_column_name() $subject->forgetComputed(); $subject->multisort = true; - $subject->sort = ["subject|asc", "category|desc"]; + $subject->sort = ['subject|asc', 'category|desc']; $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); @@ -135,7 +135,7 @@ public function it_can_order_results_for_multiple_columns_using_column_name() $this->assertEquals(['Beet growing for noobs', 'A'], [$subject->results->getCollection()[3]->subject, $subject->results->getCollection()[3]->category]); $subject->forgetComputed(); - $subject->sort = ["subject|asc", "category|asc"]; + $subject->sort = ['subject|asc', 'category|asc']; $this->assertEquals(['Advanced beet growing', 'A'], [$subject->results->getCollection()[0]->subject, $subject->results->getCollection()[0]->category]); $this->assertEquals(['Advanced beet growing', 'B'], [$subject->results->getCollection()[1]->subject, $subject->results->getCollection()[1]->category]); diff --git a/tests/LivewireDatatableMultisortableQueryBuilderTest.php b/tests/LivewireDatatableMultisortableQueryBuilderTest.php index d99c35d3..1477d1cc 100644 --- a/tests/LivewireDatatableMultisortableQueryBuilderTest.php +++ b/tests/LivewireDatatableMultisortableQueryBuilderTest.php @@ -2,7 +2,6 @@ namespace Mediconesystems\LivewireDatatables\Tests; -use Illuminate\Contracts\Session\Session; use Mediconesystems\LivewireDatatables\Http\Livewire\LivewireDatatable; use Mediconesystems\LivewireDatatables\Tests\Models\DummyBelongsToManyModel; use Mediconesystems\LivewireDatatables\Tests\Models\DummyHasManyModel; diff --git a/tests/LivewireDatatableTemplateTest.php b/tests/LivewireDatatableTemplateTest.php index e42a4b51..ef3748ba 100644 --- a/tests/LivewireDatatableTemplateTest.php +++ b/tests/LivewireDatatableTemplateTest.php @@ -218,8 +218,8 @@ public function it_can_set_multisort_from_property_using_array() $this->assertIsArray($subject->columns); $this->assertEquals(['1|asc', '2|desc'], $subject->sort); - $this->assertEquals($subject->freshColumns[1]['defaultSort'], "asc"); - $this->assertEquals($subject->freshColumns[2]['defaultSort'], "desc"); + $this->assertEquals($subject->freshColumns[1]['defaultSort'], 'asc'); + $this->assertEquals($subject->freshColumns[2]['defaultSort'], 'desc'); $this->assertNull($subject->direction); } diff --git a/tests/PutSortToSessionTest.php b/tests/PutSortToSessionTest.php index 575e0819..5bedb958 100644 --- a/tests/PutSortToSessionTest.php +++ b/tests/PutSortToSessionTest.php @@ -35,7 +35,7 @@ public function it_saves_sort_to_session_when_in_multisort() $subject = Livewire::test(LivewireDatatable::class, [ 'model' => DummyModel::class, - 'multisort' => true + 'multisort' => true, ]); $subject->call('sort', 1); From 5cc3a25c4caca3f8319e52a88262ac7b5894205c Mon Sep 17 00:00:00 2001 From: kountouris7 Date: Wed, 7 Sep 2022 23:26:16 +0300 Subject: [PATCH 04/15] Resolving LivewireDatatable conflicts --- .../LivewireDatatableMultisortableQueryBuilderTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/LivewireDatatableMultisortableQueryBuilderTest.php b/tests/LivewireDatatableMultisortableQueryBuilderTest.php index 1477d1cc..db157a29 100644 --- a/tests/LivewireDatatableMultisortableQueryBuilderTest.php +++ b/tests/LivewireDatatableMultisortableQueryBuilderTest.php @@ -8,7 +8,7 @@ use Mediconesystems\LivewireDatatables\Tests\Models\DummyHasOneModel; use Mediconesystems\LivewireDatatables\Tests\Models\DummyModel; -class LivewireDatatableMultisortQueryBuilderTest extends TestCase +class LivewireDatatableMultisortableQueryBuilderTest extends TestCase { /** @test */ public function it_toggles_sort_status_on_each_sort_trigger() @@ -143,22 +143,22 @@ public function it_creates_a_multisort_query_builder_for_belongs_to_relation_col $subject->multisort = true; $subject->mount(DummyHasManyModel::class, ['id', 'dummy_model.name']); - $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_has_many_models"."dummy_model_id" = "dummy_models"."id" order by `id` desc', $subject->getQuery()->toSql()); + $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id" order by `id` desc', $subject->getQuery()->toSql()); $subject->sort(1); $subject->forgetComputed(); - $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_has_many_models"."dummy_model_id" = "dummy_models"."id" order by dummy_has_many_models.id desc, dummy_models.name desc', $subject->getQuery()->toSql()); + $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id" order by dummy_has_many_models.id desc, dummy_models.name desc', $subject->getQuery()->toSql()); $subject->sort(1); $subject->forgetComputed(); - $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_has_many_models"."dummy_model_id" = "dummy_models"."id" order by dummy_has_many_models.id desc, dummy_models.name asc', $subject->getQuery()->toSql()); + $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id" order by dummy_has_many_models.id desc, dummy_models.name asc', $subject->getQuery()->toSql()); $subject->sort(0); $subject->forgetComputed(); - $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_has_many_models"."dummy_model_id" = "dummy_models"."id" order by dummy_models.name asc, dummy_has_many_models.id asc', $subject->getQuery()->toSql()); + $this->assertEquals('select "dummy_has_many_models"."id" as "id", "dummy_models"."name" as "dummy_model.name" from "dummy_has_many_models" left join "dummy_models" on "dummy_models"."id" = "dummy_has_many_models"."dummy_model_id" order by dummy_models.name asc, dummy_has_many_models.id asc', $subject->getQuery()->toSql()); } /** @test */ From 327fc03083f81d041e53f76e7fb9e4f35702d161 Mon Sep 17 00:00:00 2001 From: kountouris7 Date: Wed, 7 Sep 2022 23:26:43 +0300 Subject: [PATCH 05/15] Resolving LivewireDatatable conflicts --- src/Http/Livewire/LivewireDatatable.php | 48 ++++++++++++------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/Http/Livewire/LivewireDatatable.php b/src/Http/Livewire/LivewireDatatable.php index 687e2680..a0611e60 100644 --- a/src/Http/Livewire/LivewireDatatable.php +++ b/src/Http/Livewire/LivewireDatatable.php @@ -648,47 +648,28 @@ public function defaultSort() }); } - public function getSortString(int $index) + public function getSortString(int $index, string $dbTable) { $column = $this->freshColumns[$index]; - $dbTable = DB::connection()->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME); + switch (true) { case $column['sort']: return $column['sort']; - break; case $column['base']: return $column['base']; - break; case is_array($column['select']): return Str::before($column['select'][0], ' AS '); - break; case $column['select']: return Str::before($column['select'], ' AS '); - break; - default: + default: return $dbTable == 'pgsql' || $dbTable == 'sqlsrv' ? new Expression('"' . $column['name'] . '"') : new Expression('`' . $column['name'] . '`'); - break; - } - } - - /** - * @return bool has the user defined at least one column to display a summary row? - */ - public function hasSummaryRow() - { - foreach ($this->columns as $column) { - if ($column['summary']) { - return true; - } } - - return false; } /** @@ -705,6 +686,20 @@ public function summarize($column) } } + /** + * @return bool has the user defined at least one column to display a summary row? + */ + public function hasSummaryRow() + { + foreach ($this->columns as $column) { + if ($column['summary']) { + return true; + } + } + + return false; + } + public function updatingPerPage() { $this->refreshLivewireDatatable(); @@ -1508,20 +1503,21 @@ public function addTimeRangeFilter() public function addSort() { if (! empty($this->sort)) { + $dbTable = $this->query->getConnection()->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME); foreach ($this->sort as $sort) { $index = Str::before($sort, '|'); if (! is_numeric($index) && ! is_null(($index = optional(collect($this->freshColumns)->where('name', $index))->keys()->first()))) { - $columnName = Str::after($this->getSortString($index), '.'); + $columnName = Str::after($this->getSortString($index, $dbTable), '.'); } else { - $columnName = $this->getSortString($index); + $columnName = $this->getSortString($index, $dbTable); } if (isset($this->pinnedRecords) && $this->pinnedRecords) { $this->query->orderBy(DB::raw('FIELD(id,' . implode(',', $this->pinnedRecords) . ')'), 'DESC'); } - $this->query->orderBy(DB::raw($this->getSortString($this->query->getConnection()->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME))), $this->direction ? 'asc' : 'desc'); + $this->query->orderByRaw($columnName . ' ' . ($direction = Str::after($sort, '|') == $index ? self::DEFAULT_DIRECTION : Str::after($sort, '|'))); } } @@ -1737,6 +1733,8 @@ public function rowClasses($row, $loop) return config('livewire-datatables.default_classes.row.odd', 'divide-x divide-gray-100 text-sm text-gray-900 bg-gray-50'); } } + + $this->setSessionStoredHidden(); } public function cellClasses($row, $column) From 50e68dca7ab7fae84ca16a2787f988057dbd5b9a Mon Sep 17 00:00:00 2001 From: kountouris7 Date: Wed, 7 Sep 2022 23:26:54 +0300 Subject: [PATCH 06/15] Resolving LivewireDatatable conflicts --- .../datatables/header-no-hide.blade.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/resources/views/livewire/datatables/header-no-hide.blade.php b/resources/views/livewire/datatables/header-no-hide.blade.php index 47abc01d..526e461f 100644 --- a/resources/views/livewire/datatables/header-no-hide.blade.php +++ b/resources/views/livewire/datatables/header-no-hide.blade.php @@ -1,7 +1,13 @@ @unless($column['hidden']) - +
@if($column['sortable']) +
+ {{ str_replace('_', ' ', $column['label']) }} +
+ @else @else -
- {{ str_replace('_', ' ', $column['label']) }} -
+ @endif
@endif From 0b3c93566828373421212ba6d9d12bd27eaed52f Mon Sep 17 00:00:00 2001 From: kountouris7 Date: Wed, 7 Sep 2022 23:31:04 +0300 Subject: [PATCH 07/15] CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5e189b1..3ec076db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,3 +10,8 @@ All notable changes to `livewire-datatables` will be documented in this file - Breaking Change: 'unsortable' has been renamed to 'sortable', which is more intuitive. Please adjust your overwritten views, if any (thyseus). + +## 0.9.0 ( 2022-03-22 ) + +- Breaking Change: 'unsortable' has been renamed to 'sortable', which is more intuitive. Please adjust your overwritten views, if any (thyseus). + From 66f10d7442d715c7ca6e7a2dc206c10108a2daee Mon Sep 17 00:00:00 2001 From: kountouris7 Date: Wed, 7 Sep 2022 23:37:55 +0300 Subject: [PATCH 08/15] merges in master --- .../livewire/datatables/datatable.blade.php | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/resources/views/livewire/datatables/datatable.blade.php b/resources/views/livewire/datatables/datatable.blade.php index 52dce8aa..f72b34e5 100644 --- a/resources/views/livewire/datatables/datatable.blade.php +++ b/resources/views/livewire/datatables/datatable.blade.php @@ -69,6 +69,32 @@ class="flex items-center px-4 py-2 text-xs font-medium tracking-wider text-green
@endif + @if(count($this->massActionsOptions)) +
+ + + +
+ @endif + @if($exportable)
- + pinnedRecords)) checked @endif + class="w-4 h-4 mt-1 text-blue-600 form-checkbox transition duration-150 ease-in-out" + />
diff --git a/resources/views/livewire/datatables/delete.blade.php b/resources/views/livewire/datatables/delete.blade.php index 56aff510..55fd679c 100644 --- a/resources/views/livewire/datatables/delete.blade.php +++ b/resources/views/livewire/datatables/delete.blade.php @@ -44,7 +44,7 @@ class="text-gray-400 hover:text-gray-500 focus:outline-none focus:text-gray-500 - diff --git a/resources/views/livewire/datatables/editable.blade.php b/resources/views/livewire/datatables/editable.blade.php index 925cc050..3faad52d 100644 --- a/resources/views/livewire/datatables/editable.blade.php +++ b/resources/views/livewire/datatables/editable.blade.php @@ -2,8 +2,8 @@ edit: false, edited: false, init() { - window.livewire.on('fieldEdited', (id) => { - if (id === '{{ $rowId }}') { + window.livewire.on('fieldEdited', (id, column) => { + if (id === '{{ $rowId }}' && column === '{{ $column }}') { this.edited = true setTimeout(() => { this.edited = false @@ -11,7 +11,7 @@ } }) } -}" x-init="init()" :key="{{ $rowId }}"> +}" x-init="init()" wire:key="{{ $rowId }}_{{ $column }}"> diff --git a/resources/views/livewire/datatables/filters/checkbox.blade.php b/resources/views/livewire/datatables/filters/checkbox.blade.php new file mode 100644 index 00000000..15dcd6ab --- /dev/null +++ b/resources/views/livewire/datatables/filters/checkbox.blade.php @@ -0,0 +1,13 @@ +
+
{{ __('SELECT ALL') }}
+
+ results->total()) checked @endif + /> +
+
diff --git a/resources/views/livewire/datatables/header-inline-hide.blade.php b/resources/views/livewire/datatables/header-inline-hide.blade.php index 14b86c83..041d71d0 100644 --- a/resources/views/livewire/datatables/header-inline-hide.blade.php +++ b/resources/views/livewire/datatables/header-inline-hide.blade.php @@ -1,43 +1,43 @@ -