Sortable table partials
These partials follow the shared rules in Server templates. The
tabs remember your framework, and a link can choose it with ?server-framework=laravel.
The server sorts the rows, so each sortable header is a link to the same page
with the other sort order. The partial renders one header cell: its
data-rc-sort-key, aria-sort when the table is sorted by that column, and a
link that asks for the next direction. That is the reverse direction on the
sorted column, and on any other column its first direction, ascending unless
first is descending. The page wraps the table in <rc-data-table> with the
current sort-key and sort-direction.
- Rails
- Laravel
- Razor
<%# locals: (key:, label:, sort:, direction:, href:, first: "ascending") -%>
<th scope="col" <%= tag.attributes(data: { rc_sort_key: key, rc_sort_first: (first if first == "descending") }, aria: { sort: (direction if key == sort) }) %>>
<a href="<%= href.call(key, rc_next_sort_direction(key, sort, direction, first: first)) %>"><%= label %></a>
</th>
<% sort_href = ->(key, direction) { members_path(sort: key, dir: direction) } %>
<rc-data-table sort-key="<%= @sort %>" sort-direction="<%= @direction %>">
<table>
<caption>Members. Column headers with links sort the table.</caption>
<thead>
<tr>
<%= render "rc/sort_header", key: "name", label: "Name",
sort: @sort, direction: @direction, href: sort_href %>
<%= render "rc/sort_header", key: "joined", label: "Joined", first: "descending",
sort: @sort, direction: @direction, href: sort_href %>
<th scope="col">Role</th>
</tr>
</thead>
<tbody>
<%= render @members %>
</tbody>
</table>
</rc-data-table>
@props(['key', 'label', 'sort', 'direction', 'href', 'first' => 'ascending'])
<th
scope="col"
data-rc-sort-key="{{ $key }}"
@if ($first === 'descending') data-rc-sort-first="descending" @endif
@if ($key === $sort) aria-sort="{{ $direction }}" @endif
>
<a href="{{ $href($key, \App\Support\Rc::nextSortDirection($key, $sort, $direction, $first)) }}">{{ $label }}</a>
</th>
@php($sortHref = fn (string $key, string $direction) => route('members.index', ['sort' => $key, 'dir' => $direction]))
<rc-data-table sort-key="{{ $sort }}" sort-direction="{{ $direction }}">
<table>
<caption>
Members. Column headers with links sort the table.
</caption>
<thead>
<tr>
<x-rc.sort-header
key="name"
label="Name"
:sort="$sort"
:direction="$direction"
:href="$sortHref"
/>
<x-rc.sort-header
key="joined"
label="Joined"
first="descending"
:sort="$sort"
:direction="$direction"
:href="$sortHref"
/>
<th scope="col">Role</th>
</tr>
</thead>
<tbody>
@each('members.row', $members, 'member')
</tbody>
</table>
</rc-data-table>
<th scope="col" data-rc-sort-key="@Key" data-rc-sort-first="@(First == "descending" ? First : null)" aria-sort="@(Key == SortKey ? SortDirection : null)">
<a href="@Href(Key, NextDirection)">@Label</a>
</th>
@code {
[Parameter, EditorRequired] public string Key { get; set; } = "";
[Parameter, EditorRequired] public string Label { get; set; } = "";
[Parameter, EditorRequired] public string? SortKey { get; set; }
[Parameter, EditorRequired] public string SortDirection { get; set; } = "ascending";
[Parameter, EditorRequired] public Func<string, string, string> Href { get; set; } = default!;
[Parameter] public string First { get; set; } = "ascending";
/// <summary>
/// The direction the link asks for: the reverse of the current direction
/// on the sorted column, else the column's first direction.
/// </summary>
private string NextDirection => Key != SortKey
? First
: SortDirection == "ascending" ? "descending" : "ascending";
}
<rc-data-table sort-key="@sort" sort-direction="@direction">
<table>
<caption>Members. Column headers with links sort the table.</caption>
<thead>
<tr>
<RcSortHeader Key="name" Label="Name" SortKey="@sort" SortDirection="@direction" Href="SortHref" />
<RcSortHeader Key="joined" Label="Joined" First="descending" SortKey="@sort" SortDirection="@direction" Href="SortHref" />
<th scope="col">Role</th>
</tr>
</thead>
<tbody>
@foreach (var member in members)
{
<MemberRow Member="member" />
}
</tbody>
</table>
</rc-data-table>
@code {
private static string SortHref(string key, string direction) => $"/members?sort={key}&dir={direction}";
}
Read the sort key and direction from the query string through an allowlist before they reach the database query, and fall back to the default order for any other value:
SORTS = { "name" => :name, "joined" => :created_at }.freeze
@sort = SORTS.key?(params[:sort]) ? params[:sort] : "name"
@direction = params[:dir] == "descending" ? "descending" : "ascending"
@members = Member.order(SORTS[@sort] => @direction == "ascending" ? :asc : :desc)