EN
Getting started
78 · DataTable

Listing page

A complete list page of the panel: toolbar, filter button, bulk selection bar, sortable table and pagination. The look comes from Acun UI components, the behavior (search, sorting, selection, pagination) from the WithListing trait.

PRO WithListing
01

Look

The parts of a list page: acun:list-toolbar (search on the left, actions on the right), the Filters panel opened with acun:filter-button, acun:selection-bar, which shows while something is selected, a table with acun:sortable-th headers and the pagination bar. The live example has no Livewire, so clicks do not reach the server.

LIVE EXAMPLE
<div class="space-y-4">
    <acun:list-toolbar>
        <acun:search name="list_search" placeholder="Product name or SKU" />
        <acun:slot:actions>
            <acun:filter-button drawer="catalog-listing-filters" :count="2" />
            <acun:button>New Product</acun:button>
        </acun:slot:actions>
    </acun:list-toolbar>

    <acun:selection-bar :count="2" clear-action="clearSelection">
        <acun:button variant="white" size="sm">Activate</acun:button>
        <acun:button variant="danger" size="sm">Delete</acun:button>
    </acun:selection-bar>

    <acun:table>
        <table class="ui-table">
            <thead>
                <tr>
                    <th class="w-10"><acun:checkbox id="list-all" aria-label="Select all" /></th>
                    <acun:sortable-th field="name" sort-field="name" sort-direction="asc">Product Name</acun:sortable-th>
                    <acun:sortable-th field="sku" sort-field="name" sort-direction="asc">SKU</acun:sortable-th>
                    <th class="text-center">Stock</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody>
                <tr><td><acun:checkbox id="list-1" checked /></td><td class="font-medium text-gray-900 dark:text-zinc-100">Wireless headphones</td><td>WH-01</td><td class="text-center tabular-nums">312</td><td><acun:badge variant="success">Active</acun:badge></td></tr>
                <tr><td><acun:checkbox id="list-2" checked /></td><td class="font-medium text-gray-900 dark:text-zinc-100">Mechanical keyboard</td><td>MK-04</td><td class="text-center tabular-nums">148</td><td><acun:badge variant="success">Active</acun:badge></td></tr>
                <tr><td><acun:checkbox id="list-3" /></td><td class="font-medium text-gray-900 dark:text-zinc-100">USB-C charger</td><td>UC-02</td><td class="text-center tabular-nums">96</td><td><acun:badge variant="danger">Inactive</acun:badge></td></tr>
            </tbody>
        </table>
    </acun:table>

    {{ $examplePaginator->links('acun-ui::components.ui.pagination') }}
</div>

<acun:drawer name="catalog-listing-filters" docked title="Filters" compact>
    <acun:select name="list_status" :options="['' => 'All Statuses', '1' => 'Active', '0' => 'Inactive']" />
</acun:drawer>
PropValues
acun:list-toolbarDefault slot: search · actions slot: the buttons on the right
acun:filter-buttondrawer: name of the drawer to open · count: number of active filters · label: text (default Filters, in the app language)
acun:selection-barcount · all-matching · total · clear-action
02

Livewire component

Add WithListing to the component and write the two abstract methods: defaultSortField() returns the default sort field, currentPageKeys() the row keys of the visible page. Enable sort fields other than the default with sortableFields(); fields not in the list are ignored by sortBy() and the URL. Call fillPageSelection() at the start of the with() method.

PHP
use Acun\Ui\Livewire\Concerns\WithListing;
use Acun\Ui\Search\SearchField;
use Acun\Ui\Search\SearchMode;

new class extends Component
{
    use WithListing;

    public string $status = '';

    protected function defaultSortField(): string
    {
        return 'name';
    }

    protected function sortableFields(): array
    {
        return ['name', 'sku'];
    }

    protected function currentPageKeys(): array
    {
        return $this->baseQuery()->forPage($this->getPage(), $this->perPage)->pluck('id')->all();
    }

    public function updatedStatus(): void
    {
        $this->resetPage();
        $this->clearSelection();
    }

    // A word anywhere in the product name, the SKU from the start (name_search, sku_search columns).
    protected function searchFields(): array
    {
        return [SearchField::make('name', SearchMode::Contains), SearchField::make('sku')];
    }

    private function baseQuery(): Builder
    {
        return $this->applySearch(Product::query())
            ->when($this->status !== '', fn ($q) => $q->where('is_active', $this->status))
            ->orderBy($this->sortField, $this->sortDirection);
    }

    public function with(): array
    {
        $this->fillPageSelection();

        return ['products' => $this->baseQuery()->paginate($this->perPage)];
    }
};
PropValues
defaultSortField()Required: the default sort field
currentPageKeys()Required: the row keys of the visible page
sortableFields()Sortable fields (default: only defaultSortField)
perPageOptions()Page sizes (default: 10 | 25 | 50 | 100)
03

Blade template

Bind the parts of the view to the trait's properties: the search to search, the header checkbox to pageSelected and the row checkboxes to selected. acun:sortable-th calls sortBy() when clicked. For pagination, links() is enough; the trait picks the Acun UI pagination as the view.

BLADE
<acun:list-toolbar>
    <acun:search wire:model.live.debounce.400ms="search" placeholder="Product name or SKU" />
    <acun:slot:actions>
        <acun:filter-button drawer="product-filters" :count="$status !== '' ? 1 : 0" />
    </acun:slot:actions>
</acun:list-toolbar>

<acun:selection-bar :count="count($selected)" :all-matching="$allMatchingSelected" :total="$products->total()">
    <acun:modal.trigger name="bulk-delete-products"><acun:button variant="danger" size="sm">Delete</acun:button></acun:modal.trigger>
</acun:selection-bar>

<acun:table>
    <table class="ui-table">
        <thead>
            <tr>
                <th class="w-10"><acun:checkbox wire:model.live="pageSelected" aria-label="Select all on this page" /></th>
                <acun:sortable-th field="name" :sort-field="$sortField" :sort-direction="$sortDirection">Product Name</acun:sortable-th>
                <acun:sortable-th field="sku" :sort-field="$sortField" :sort-direction="$sortDirection">SKU</acun:sortable-th>
            </tr>
        </thead>
        <tbody>
            @foreach ($products as $product)
                <tr wire:key="product-{{ $product->id }}">
                    <td><acun:checkbox wire:model.live="selected" value="{{ $product->id }}" /></td>
                    <td>{{ $product->name }}</td>
                    <td>{{ $product->sku }}</td>
                </tr>
            @endforeach
        </tbody>
    </table>
</acun:table>

{{ $products->links() }}
04

Selecting everything that matches the filters

When the header checkbox is checked, the keys of the visible page are selected and allMatchingSelected turns on; acun:selection-bar then shows the total value as "all matching the filters". If the user unchecks a row, this mode turns off on its own and the selection narrows to the checked rows. The selection is cleared when the search changes; remember to call clearSelection() in your own filters.

LIVE EXAMPLE
<acun:selection-bar :count="25" :all-matching="true" :total="148" clear-action="clearSelection">
    <acun:button variant="white" size="sm">Deactivate</acun:button>
</acun:selection-bar>
PropValues
all-matchingtrue | false
totalTotal number of records matching the filters
clear-actionLivewire method that clears the selection (default clearSelection; when null, the button is hidden)
05

Search columns

The search runs on a normalized column that the database generates from the source column. It ignores Turkish characters and letter case: "ayse" finds Ayşe, "ismail isik" finds İsmail Işık. Add the column in a migration with searchColumn(); first and last names in separate columns can be combined into one column. The default mode is prefix search, which uses the index; Contains reads every row. % and _ in the term are treated as plain text. Details: the Search guide.

PHP
// Migration
Schema::table('customers', function (Blueprint $table) {
    $table->searchColumn('tax_number');                                 // tax_number_search + index
    $table->searchColumn(['first_name', 'last_name'], 'full_name_search'); // "Ayşe Yılmaz" is searched as a whole
});

// Component
protected function searchFields(): array
{
    return [
        SearchField::make('tax_number', SearchMode::Exact),
        SearchField::column('full_name_search', SearchMode::Contains),
    ];
}
PropValues
SearchModePrefix (default, indexed) | Exact | Contains (scan) | FullText (MySQL FULLTEXT)
SearchField::make(source, mode, column)The source_search column; in a joined table, qualify it like users.email
SearchField::column(column, mode)A column generated from several sources
API

Props and slots

The values the component accepts.

PropDefaultDescription
$search''Search term · URL: ?q= · when it changes, the page resets to the first and the selection is cleared
$sortField''URL: ?sort= · a value that is not allowed becomes defaultSortField()
$sortDirectionascasc · desc · URL: ?direction=
$perPage25A value outside perPageOptions() falls back to the first option
$selected[]Keys of the selected rows (wire:model.live="selected")
$pageSelectedfalseHeader checkbox; when true, currentPageKeys() are selected
$allMatchingSelectedfalseEverything matching the filters is selected; becomes false when a row is unchecked
defaultSortField() — Required (abstract)
currentPageKeys() — Required (abstract)
sortableFields()[defaultSortField()]Sortable fields
perPageOptions()[10, 25, 50, 100]Allowed page sizes
sortBy($field) — Flips the direction on the same field, starts with asc on a new field; resets to the first page
clearSelection() — Resets selected, pageSelected and allMatchingSelected
fillPageSelection() — Called at the start of with(); while "all selected" is on, adds the keys of the new page
applySearch($q, $term = null, $fields = null) — Searches the searchFields() fields with $search; insensitive to Turkish characters and letter case
searchFields() — Search fields (a list of SearchField)
paginationView()acun-ui::components.ui.paginationlinks() uses this view
Acun UI · Listing pageDetailed usage and examples