Skip to content

Commit

Permalink
Records can now be reordered (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
ramonrietdijk authored Sep 9, 2023
1 parent c17d9ca commit a215fca
Show file tree
Hide file tree
Showing 16 changed files with 362 additions and 26 deletions.
4 changes: 4 additions & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export default {
text: 'Links',
link: '/usage/links'
},
{
text: 'Reordering',
link: '/usage/reordering'
},
{
text: 'Exports',
link: '/usage/exports'
Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ These are some of the highlights which you will get out of the box:
- Selection of the columns you wish to see
- Apply multiple filters at the same time
- Perform actions on the selected records
- Reorder the records in the table
- Export records from the table
- Works perfectly with the `SoftDeletes` trait
- Search columns individually
- Multiple column types supported
Expand Down
23 changes: 23 additions & 0 deletions docs/usage/reordering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Reordering

Reordering records in the table can be implemented if your model has an order column to save its position.

::: info
If you have enabled reordering and no sorting is set by the user, the table will automatically sort its records using the order column.
You don't need an additional column for this.
:::

By default, reordering records is disabled. It can be enabled by adding the following property to your class:

```php
protected bool $useReordering = true;
```

It will use the column with the name of `order` by default but it can be overwitten like so:

```php
protected string $reorderingColumn = 'position';
```

If the reordering functionality has been enabled, a new button will show up. When reordering, the button will be activated
and the rows in the table can be dragged and dropped.
1 change: 1 addition & 0 deletions resources/views/bar/bar.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class="border border-neutral-200 shadow-sm rounded-md outline-none focus:border-
</div>
@include('livewire-table::bar.selection')
<div class="flex gap-3 ml-auto">
@include('livewire-table::bar.buttons.reordering')
@include('livewire-table::bar.dropdowns.polling')
@include('livewire-table::bar.dropdowns.columns')
@include('livewire-table::bar.dropdowns.filters')
Expand Down
16 changes: 16 additions & 0 deletions resources/views/bar/buttons/reordering.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<button
@class([
'flex items-center gap-1 px-3 py-2 bg-white border transition ease-in-out rounded-md shadow-sm h-full text-sm' => true,
'active:bg-neutral-100 dark:bg-neutral-800 dark:active:bg-neutral-900' => true,
'border-neutral-200 text-neutral-800 hover:text-neutral-500 focus:border-blue-300 active:text-neutral-800 dark:border-neutral-700 dark:focus:border-blue-600 dark:text-neutral-300 dark:hover:text-white dark:focus:border-blue-600 dark:active:text-white' => ! $this->reordering,
'border-blue-300 text-blue-500 dark:border-blue-600 dark:text-blue-500' => $this->reordering,
])
title="{{ __('Reordering') }}"
aria-label="{{ __('Reordering') }}"
x-on:click="$wire.set('reordering', ! $wire.reordering)"
>
<!-- Icon "queue-list" (outline) from https://heroicons.com -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z" />
</svg>
</button>
24 changes: 13 additions & 11 deletions resources/views/columns/header/default.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
class="flex items-center gap-1 font-bold text-md px-3 py-2 whitespace-nowrap"
wire:click.prevent="sort('{{ $column->code() }}')">
<span>{{ $column->label() }}</span>
@if($this->sortColumn === $column->code())
@if($this->sortDirection === 'asc')
<!-- Icon "chevron-up" (outline) from https://heroicons.com -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" />
</svg>
@else
<!-- Icon "chevron-down" (outline) from https://heroicons.com -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
@if(! $this->isReordering())
@if($this->sortColumn === $column->code())
@if($this->sortDirection === 'asc')
<!-- Icon "chevron-up" (outline) from https://heroicons.com -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" />
</svg>
@else
<!-- Icon "chevron-down" (outline) from https://heroicons.com -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
@endif
@endif
@endif
</a>
Expand Down
50 changes: 36 additions & 14 deletions resources/views/table/table.blade.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<table class="w-full relative" x-data="{ selected: @entangle('selected') }">
<thead class="border-b border-neutral-200 dark:border-neutral-700">
<tr class="group">
<th class="p-0 text-left text-black bg-neutral-50 dark:text-white dark:bg-neutral-800">
<input type="checkbox" wire:model.live="selectedPage" class="h-4 w-4 mx-3">
</th>
@if(! $this->isReordering())
<th class="p-0 text-left text-black bg-neutral-50 dark:text-white dark:bg-neutral-800">
<input type="checkbox" wire:model.live="selectedPage" class="h-4 w-4 mx-3">
</th>
@endif
@foreach($table['columns'] as $column)
@continue(! in_array($column->code(), $this->columns))
<th class="p-0 text-left text-black bg-neutral-50 dark:text-white dark:bg-neutral-800">
Expand All @@ -12,7 +14,9 @@
@endforeach
</tr>
<tr class="group">
<th class="p-0 text-left text-black bg-neutral-50 dark:text-white dark:bg-neutral-800"></th>
@if(! $this->isReordering())
<th class="p-0 text-left text-black bg-neutral-50 dark:text-white dark:bg-neutral-800"></th>
@endif
@foreach($table['columns'] as $column)
@continue(! in_array($column->code(), $this->columns))
<th class="p-0 text-left text-black bg-neutral-50 dark:text-white dark:bg-neutral-800">
Expand All @@ -34,19 +38,37 @@
</tr>
@else
@forelse($paginator->items() as $item)
<tr class="group" wire:key="row-{{ $item->getKey() }}">
<td class="p-0"
x-bind:class="~selected.indexOf('{{ $item->getKey() }}')
? 'bg-blue-100 group-odd:bg-blue-100 group-hover:bg-blue-200 dark:bg-blue-900 dark:group-odd:bg-blue-900 dark:group-hover:bg-blue-800'
: 'bg-neutral-100 group-odd:bg-white group-hover:bg-neutral-200 dark:bg-neutral-800 dark:group-odd:bg-neutral-900 dark:group-hover:bg-neutral-700'">
<div class="mx-3">
<input type="checkbox" wire:model.live="selected" value="{{ $item->getKey() }}" class="h-4 w-4">
</div>
</td>
<tr class="group"
wire:key="row-{{ $item->getKey() }}"

@if($this->isReordering())
draggable="true"
x-on:dragstart="e => e.dataTransfer.setData('key', '{{ $item->getKey() }}')"
x-on:dragover.prevent=""
x-on:drop="e => {
$wire.call(
'reorderItem',
e.dataTransfer.getData('key'),
'{{ $item->getKey() }}',
e.target.offsetHeight / 2 > e.offsetY
)
}"
@endif
>
@if(! $this->isReordering())
<td class="p-0"
x-bind:class="~selected.indexOf('{{ $item->getKey() }}')
? 'bg-blue-100 group-odd:bg-blue-100 group-hover:bg-blue-200 dark:bg-blue-900 dark:group-odd:bg-blue-900 dark:group-hover:bg-blue-800'
: 'bg-neutral-100 group-odd:bg-white group-hover:bg-neutral-200 dark:bg-neutral-800 dark:group-odd:bg-neutral-900 dark:group-hover:bg-neutral-700'">
<div class="mx-3">
<input type="checkbox" wire:model.live="selected" value="{{ $item->getKey() }}" class="h-4 w-4">
</div>
</td>
@endif
@foreach($table['columns'] as $column)
@continue(! in_array($column->code(), $this->columns))
<td class="p-0"
@if($column->isClickable())
@if($column->isClickable() && ! $this->isReordering())
@if(($link = $this->link($item)) !== null)
x-on:click.prevent="window.location.href = '{{ $link }}'"
@else
Expand Down
93 changes: 93 additions & 0 deletions src/Concerns/HasReordering.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace RamonRietdijk\LivewireTables\Concerns;

use Illuminate\Database\Eloquent\Model;

trait HasReordering
{
public bool $reordering = false;

protected bool $useReordering = false;

protected string $reorderingColumn = 'order';

/** @return array<string, mixed> */
protected function queryStringHasReordering(): array
{
if (! $this->useQueryString) {
return [];
}

return [
'reordering' => [
'as' => $this->getQueryStringName('reordering'),
],
];
}

public function updatedReordering(): void
{
$this->selected = [];
$this->selectedPage = false;
}

public function reorderItem(string $from, string $to, bool $above): void
{
if ($from === $to) {
return;
}

/** @var Model $from */
$from = $this->query()->findOrFail($from);

/** @var Model $to */
$to = $this->query()->findOrFail($to);

$column = $this->reorderingColumn;

/** @var int $currentOrder */
$currentOrder = $from->getAttribute($column) ?? 0;

/** @var int $toOrder */
$toOrder = $to->getAttribute($column) ?? 0;

$up = $toOrder > $currentOrder;

if ($above && $up) {
$newOrder = $toOrder - 1;
} elseif (! $above && ! $up) {
$newOrder = $toOrder + 1;
} else {
$newOrder = $toOrder;
}

if ($newOrder === $currentOrder) {
return;
}

if ($up) {
// The new order is higher, meaning that everything between has to go down by one.
$this
->query()
->where($column, '>', $currentOrder)
->where($column, '<=', $newOrder)
->decrement($column);
} else {
// The new order is lower, meaning that everything between has to go up by one.
$this
->query()
->where($column, '<', $currentOrder)
->where($column, '>=', $newOrder)
->increment($column);
}

$from->setAttribute($column, $newOrder);
$from->save();
}

protected function isReordering(): bool
{
return $this->useReordering && $this->reordering;
}
}
10 changes: 9 additions & 1 deletion src/Concerns/HasSorting.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,15 @@ public function sort(string $column): void
/** @param Builder<Model> $builder */
protected function applySorting(Builder $builder): static
{
if (blank($this->sortColumn) || blank($this->sortDirection)) {
$hasSorting = ! blank($this->sortColumn) && ! blank($this->sortDirection);

if ($this->isReordering() || ($this->useReordering && ! $hasSorting)) {
$builder->orderBy($builder->qualifyColumn($this->reorderingColumn));

return $this;
}

if (! $hasSorting) {
return $this;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Livewire/LivewireTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use RamonRietdijk\LivewireTables\Concerns\HasPolling;
use RamonRietdijk\LivewireTables\Concerns\HasQueryString;
use RamonRietdijk\LivewireTables\Concerns\HasRelations;
use RamonRietdijk\LivewireTables\Concerns\HasReordering;
use RamonRietdijk\LivewireTables\Concerns\HasSearch;
use RamonRietdijk\LivewireTables\Concerns\HasSelect;
use RamonRietdijk\LivewireTables\Concerns\HasSelection;
Expand All @@ -36,6 +37,7 @@ class LivewireTable extends Component
use HasPolling;
use HasQueryString;
use HasRelations;
use HasReordering;
use HasSearch;
use HasSelect;
use HasSelection;
Expand Down
23 changes: 23 additions & 0 deletions tests/Fakes/Livewire/ReorderingBlogLivewireTable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace RamonRietdijk\LivewireTables\Tests\Fakes\Livewire;

use RamonRietdijk\LivewireTables\Columns\Column;
use RamonRietdijk\LivewireTables\Livewire\LivewireTable;
use RamonRietdijk\LivewireTables\Tests\Fakes\Models\Blog;

class ReorderingBlogLivewireTable extends LivewireTable
{
protected string $model = Blog::class;

protected bool $useReordering = true;

protected function columns(): array
{
return [
Column::make(__('Title'), 'title')
->sortable()
->searchable(),
];
}
}
1 change: 1 addition & 0 deletions tests/Fakes/Models/Blog.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* @property int $author_id
* @property ?int $category_id
* @property bool $published
* @property int $order
* @property ?Carbon $created_at
* @property ?Carbon $updated_at
* @property ?Carbon $deleted_at
Expand Down
Loading

0 comments on commit a215fca

Please sign in to comment.