This guide covers sorting a collection. For a first collection, see Getting Started.

Sortable Columns

Add sort to a column to sort by it. In a table, users click its heading; in a list or grid, they click its button in the row of sort buttons above the items:

<Cinder.collection resource={MyApp.User} actor={@current_user}>
  <:col :let={user} field="name" sort>{user.name}</:col>
  <:col :let={user} field="email">{user.email}</:col>
  <:col :let={user} field="inserted_at" sort>{user.inserted_at}</:col>
</Cinder.collection>

In a list or grid, change the sort buttons' label with sort_label, or hide them with show_sort={false}.

A column can sort by an attribute, a field through relationships (artist.name), a field of an embedded resource (profile__last_name), an aggregate, or a calculation the data layer can run (an expression calculation). A column with sort on anything else, like a calculation run in Elixir or one that needs arguments, isn't sortable and logs a warning. In 1.0 this raises.

Sort Cycles

Each click moves a column on to its next direction: unsorted, then ascending, then descending, then unsorted again. Change the order with cycle:

<!-- Newest first -->
<:col field="inserted_at" sort={[cycle: [:desc, :asc, nil]]} />

<!-- Always sorted, never unsorted -->
<:col field="name" sort={[cycle: [:asc, :desc]]} />

<!-- With Ash's directions for where empty values go -->
<:col field="completed_at" sort={[cycle: [:desc_nils_last, :asc_nils_first, nil]]} />

A cycle without nil can't be unsorted, so the collection starts sorted by that column, in the cycle's first direction. All of Ash's directions work: :asc, :desc, :asc_nils_first, :asc_nils_last, :desc_nils_first and :desc_nils_last.

Sorting by Several Columns

Clicking a second column adds to the sort: sorted by the first column, then by the second. With sort_mode={:exclusive} (or "exclusive"), clicking a column replaces the sort instead:

<Cinder.collection resource={MyApp.User} actor={@current_user} sort_mode={:exclusive}>
  <:col :let={user} field="name" sort>{user.name}</:col>
  <:col :let={user} field="inserted_at" sort>{user.inserted_at}</:col>
</Cinder.collection>

Tiebreakers

then lists fields to sort by after a column, to put the records it leaves tied into a predictable order:

<!-- Stops in the same position stay in the order they were added -->
<:col field="position" sort={[then: ["inserted_at"]]} />

They follow the column's direction, and go after all the user's sorts, so they only order what those leave tied. A tiebreaker the user sorts by themselves keeps the user's direction. One that can't be sorted by is left out, with a warning.

Default Sort

To sort a collection before the user does, give it a query with a sort:

<Cinder.collection query={Ash.Query.sort(MyApp.User, inserted_at: :desc)} actor={@current_user}>
  <:col :let={user} field="name" sort>{user.name}</:col>
  <:col :let={user} field="inserted_at" sort>{user.inserted_at}</:col>
</Cinder.collection>

If the query sorts by a sortable column's field, ascending or descending, that column shows as sorted, and the user's clicks carry on from there. Once the collection has a sort of its own, from the user or from such a column, it replaces the query's sorts entirely.

To sort by an embedded field by default, use Cinder.QueryBuilder.apply_sorting/2, which knows the __ notation:

<Cinder.collection query={Cinder.QueryBuilder.apply_sorting(MyApp.User, [{"profile__last_name", :asc}])}>
  <:col :let={user} field="profile__last_name" sort>{user.profile.last_name}</:col>
</Cinder.collection>

Sorting in the URL

With URL sync, the sort is kept in the URL as sort=-inserted_at,name: fields in order, with - for descending. Ash's other directions use -- (descending, empty last), ++ (ascending, empty first), -+ (ascending, empty last) and +- (descending, empty first). Fields that aren't sortable columns are ignored.