Skip to content

Instantly share code, notes, and snippets.

@rogercbe
Last active February 9, 2022 16:26
Show Gist options
  • Select an option

  • Save rogercbe/ccb6f29678469050057a08172736415c to your computer and use it in GitHub Desktop.

Select an option

Save rogercbe/ccb6f29678469050057a08172736415c to your computer and use it in GitHub Desktop.
Sortable trait for sorting tables with relationships in Laravel with a table helper class for displaying the links as an extension of the Laravel Paginator
<thead>
<tr>
@foreach($headers as $key => $header)
<th>
@if(! $header['sortable'])
{{ $header['title'] }}
@else
@if(request('sort') == $key)
{{ $header['title'] }}
@if(request('direction') == 'asc')
<span>
<a href="?sort={{ $key }}&direction=desc">
&#x25B2
</a>
</span>
@else
<span>
<a href="?sort={{ $key }}&direction=asc">
&#x25BC
</a>
</span>
@endif
@else
<a href="?sort={{ $key }}&direction=asc">
{{ $header['title'] }}
</a>
@endif
@endif
</th>
@endforeach
</tr>
</thead>
<?php
namespace App\Support;
use Illuminate\Database\Eloquent\Relations\HasOne;
trait Sortable
{
public function scopeSortable($query, $defaultSort = null, $direction = 'asc')
{
if ($this->sortingIsActive()) {
return $this->buildQuery(
$query,
collect(request()->only(['sort', 'direction']))
);
}
if (! is_null($defaultSort)) {
return $query->orderBy($defaultSort, $direction);
}
return $query;
}
private function buildQuery($query, $sortParams)
{
$column = $sortParams->get('sort');
$direction = $sortParams->get('direction');
if ($this->columnIsRelated($column)) {
$relatedModel = $this->getRelatedModel($column);
$parameters = collect([
'column' => $this->getRelatedSortColumn($column),
'direction' => $direction
]);
$relation = $query->getRelation($relatedModel);
return $query->join(
$this->relatedTable($relation),
$this->parentPrimaryKey($relation),
'=',
$this->relatedPrimaryKey($relation)
)
->select(
$this->parentTable($relation) . '.*',
$this->getRelatedSelectAttribute($relation, $column)
)
->orderBy(
$parameters->get('column'),
$parameters->get('direction')
);
}
return $query->orderBy($column, $direction);
}
private function getRelatedSelectAttribute($relation, $column)
{
return implode('.', [
$this->relatedTable($relation),
$this->getSortColumn($column)
]) . ' as ' . $this->getRelatedSortColumn($column);
}
private function parentTable($relation)
{
return $relation->getParent()->getTable();
}
private function relatedTable($relation)
{
return $relation->getRelated()->getTable();
}
private function relatedPrimaryKey($relation)
{
if ($relation instanceof HasOne) {
return $relation->getQualifiedForeignKeyName();
}
return $relation->getQualifiedOwnerKeyName();
}
private function parentPrimaryKey($relation)
{
if ($relation instanceof HasOne) {
return $relation->getQualifiedParentKeyName();
}
return $relation->getQualifiedForeignKey();
}
private function sortingIsActive()
{
return request()->has('sort') && request()->has('direction');
}
private function getSortColumn($column)
{
return $this->parseRelation($column)->last();
}
private function getRelatedSortColumn($column)
{
return implode('_', [
$this->parseRelation($column)->first(),
$this->parseRelation($column)->last()
]);
}
private function getRelatedModel($column)
{
return $this->parseRelation($column)
->first();
}
private function parseRelation($column)
{
return collect(
explode('.', $column)
);
}
private function columnIsRelated($column)
{
return strpos($column, '.');
}
}
<?php
namespace App;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\HtmlString;
class Table extends LengthAwarePaginator
{
public function __construct($paginator)
{
$this->total = $paginator->total;
$this->perPage = $paginator->perPage;
$this->lastPage = $paginator->lastPage;
$this->path = $paginator->path;
$this->currentPage = $paginator->currentPage;
$this->items = $paginator->items;
}
public function headers($viewPath = 'headers')
{
return new HtmlString(view($viewPath, [
'headers' => $this->headers
])->render());
}
}
<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
<title>Laravel</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body class="container">
<div id="app">
<div class="page-header">
<h1>Users List</h1>
</div>
<div class="panel panel-default">
<div class="panel-body">
<table class="table">
{{ $users->headers() }}
<tbody>
@foreach($users as $user)
<tr>
<td>{{ $user->company->name }}</td>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>{{ $user->created_at }}</td>
<td>
<edit-user-button :id="{{ $user->id }}"></edit-user-button>
</td>
</tr>
@endforeach
</tbody>
</table>
{{ $users->appends(['sort' => request('sort'), 'direction' => request('direction')])->links() }}
</div>
</div>
</div>
</body>
</html>
<?php
namespace App\Http\Controllers;
use App\User;
use App\UserTable;
class UsersController extends Controller
{
public function index()
{
$usersPaginate = User::with('company')
->sortable()
->paginate(5);
$users = new UserTable($usersPaginate);
return view('test', compact('users'));
}
}
<?php
namespace App;
class UserTable extends Table
{
protected $headers = [
'company.name' => [
'sortable' => true,
'title' => 'Company'
],
'name' => [
'sortable' => true,
'title' => 'Name'
],
'email' => [
'sortable' => true,
'title' => 'Email'
],
'created_at' => [
'sortable' => true,
'title' => 'Created at'
],
'options' => [
'sortable' => false,
'title' => 'Options'
],
];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment