edmirkasapi/live-datatable

A simple abstract implementation of a datatable component class for livewire applications.

Maintainers

Package info

github.com/edmir-kasapi/live-datatable

pkg:composer/edmirkasapi/live-datatable

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-08-28 10:50 UTC

This package is auto-updated.

Last update: 2026-08-28 13:50:38 UTC


README

A simple, extensible abstract datatable component for Laravel and Livewire applications.

edmirkasapi/live-datatable provides a reusable base component with built-in support for:

  • Column configuration and ordering
  • Visible/hidden columns
  • Searching
  • Filtering
  • Sorting
  • Pagination
  • Automatic pagination reset when table state changes
  • Automatic navigation to the previous page when the current page becomes empty
  • Configurable pagination theme
  • Configurable default number of results per page

The package is designed to be extended by your own Livewire datatable components.

Requirements

  • PHP 8.2+
  • Laravel 12+
  • Livewire 4.4+

Installation

Install the package using Composer:

composer require edmirkasapi/live-datatable

Laravel will automatically discover the package service provider.

Basic Usage

Create a Livewire component that extends the LiveDatatable base class:

<?php

namespace App\Livewire;

use App\Models\User;
use Edmirkasapi\LiveDatatable\abstracts\LiveDatatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Contracts\View\View;

class UsersTable extends LiveDatatable
{
    protected function columns(): array
    {
        return [
            'name' => [
                'label' => 'Name',
                'sortable' => true,
                'visible' => true,
            ],
            'email' => [
                'label' => 'Email',
                'sortable' => true,
                'visible' => true,
            ],
            'created_at' => [
                'label' => 'Created',
                'sortable' => true,
                'visible' => true,
            ],
        ];
    }

    protected function query(): Builder
    {
        return User::query();
    }

    protected function searchable(): array
    {
        return [
            'name',
            'email',
        ];
    }

    public function render(): View
    {
        return view('livewire.users-table', [
            'data' => $this->getData(),
        ]);
    }
}

The base class handles the query processing pipeline:

Base Query
    ↓
Search
    ↓
Filters
    ↓
Sorting
    ↓
Pagination
    ↓
Paginated Results

Defining Columns

Every datatable must implement the columns() method.

Columns are defined using an associative array where the key represents the database/query column:

protected function columns(): array
{
    return [
        'name' => [
            'label' => 'Name',
            'sortable' => true,
            'visible' => true,
        ],
        'email' => [
            'label' => 'Email',
            'sortable' => true,
        ],
        'created_at' => [
            'label' => 'Created At',
            'sortable' => false,
        ],
    ];
}

Column Visibility

Columns are visible by default.

'email' => [
    'label' => 'Email',
]

is equivalent to:

'email' => [
    'label' => 'Email',
    'visible' => true,
]

To hide a column:

'email' => [
    'label' => 'Email',
    'visible' => false,
]

Retrieve only visible columns using:

$this->getvisiblecolumns();

Note: the method is currently named getvisiblecolumns() with a lowercase v.

Sortable Columns

A column is not sortable unless sortable is explicitly set to true:

'name' => [
    'label' => 'Name',
    'sortable' => true,
]

You can check whether a column is sortable:

$this->isSortable('name');

Column Ordering

The base component supports custom column ordering through the $columnOrder property:

protected array $columnOrder = [
    'email',
    'name',
    'created_at',
];

The order defined here is applied to the columns returned by columns().

Columns specified in $columnOrder that don't exist in columns() are ignored.

Any configured columns that weren't explicitly included in $columnOrder are appended afterward in their original order.

Retrieve the final order with:

$this->getOrderedColumns();

For example:

protected function columns(): array
{
    return [
        'name' => ['label' => 'Name'],
        'email' => ['label' => 'Email'],
        'created_at' => ['label' => 'Created'],
    ];
}

protected array $columnOrder = [
    'email',
    'name',
];

Results in:

email
name
created_at

Query

Every datatable must implement the query() method and return an Eloquent query builder:

protected function query(): Builder
{
    return User::query();
}

The returned query is then passed through the package's search, filter, sorting, and pagination pipeline.

Searching

Override searchable() to define which database columns can be searched:

protected function searchable(): array
{
    return [
        'name',
        'email',
    ];
}

The component exposes the search value through:

public string $search = '';

For example, in a Blade view:

<input
    type="text"
    wire:model.live="search"
    placeholder="Search..."
>

The search is applied using a LIKE query:

WHERE name LIKE '%search term%'
   OR email LIKE '%search term%'

Searching automatically resets the current pagination page.

Resetting Search

Reset the search programmatically:

$this->resetSearch();

This clears the search value and resets the pagination page.

Filtering

Filters are stored in the public $filters property:

public array $filters = [];

A filter can be supplied using the database column name as the key:

$this->filters = [
    'status' => 'active',
];

The resulting query applies:

$query->where('status', 'active');

Only columns defined by columns() are considered valid filters.

Empty filter values and unknown filter columns are ignored.

Example

<select wire:model.live="filters.status">
    <option value="">All</option>
    <option value="active">Active</option>
    <option value="inactive">Inactive</option>
</select>

Changing filters automatically resets the pagination page.

Resetting Filters

Reset all filters with:

$this->resetFilters();

This clears the filters and resets pagination.

Sorting

Sorting is controlled by two public properties:

public ?string $sortColumn = null;
public ?string $sortDirection = null;

A column must be configured as sortable before it can be sorted.

Trigger sorting using:

$this->sortBy('name');

The sorting state cycles through:

ascending
    ↓
descending
    ↓
none

Specifically:

null → asc → desc → null

When a new column is selected, sorting starts with ascending order.

For example:

$this->sortBy('name');

sets:

$sortColumn = 'name';
$sortDirection = 'asc';

Calling it again changes the direction to:

$sortDirection = 'desc';

Calling it a third time removes the sorting.

Blade Example

<button wire:click="sortBy('name')">
    Name
</button>

You can also use the sorting state to display the current direction:

<button wire:click="sortBy('name')">
    Name

    @if ($sortColumn === 'name')
        @if ($sortDirection === 'asc')
            ↑
        @elseif ($sortDirection === 'desc')
            ↓
        @endif
    @endif
</button>

Resetting Sorting

Reset sorting with:

$this->resetSorting();

This sets both sorting properties back to null.

Pagination

The component uses Laravel's LengthAwarePaginator and Livewire's pagination functionality.

The number of results per page is controlled by:

public ?int $perPage = null;

If $perPage has not been explicitly set, the component uses:

config('live-datatable.pagination.per_page')

with a fallback of 10.

Changing Results Per Page

You can change the value:

$this->perPage = 25;

Then reset the pagination:

$this->updatePerPage();

The updatePerPage() method resets the current page.

Reset Pagination

To reset the per-page value and pagination:

$this->resetPagination();

This restores the configured default:

config('live-datatable.pagination.per_page', 10)

and resets the current page.

Automatic Empty Page Handling

The component handles a common pagination problem automatically.

For example:

  1. A user is viewing page 5.
  2. Records are deleted or filtered out.
  3. Page 5 no longer contains any records.
  4. The component automatically moves back to the previous page.

This behavior is handled by:

$this->goToPreviousPageIfEmpty($paginator);

The component will only move backward when the current page is empty and the current page number is greater than 1.

Pagination Theme

The pagination theme is loaded from:

config('live-datatable.theme')

If no theme is configured, the default is:

bootstrap

Retrieve the current theme with:

$this->getPaginationTheme();

For example:

$theme = $this->getPaginationTheme();

Table State

The following public properties are available for controlling the table:

Property Type Default Description
$perPage ?int null Number of records per page
$search string '' Current search term
$filters array [] Active filters
$sortColumn ?string null Currently sorted column
$sortDirection ?string null Current sort direction

Resetting Table State

The component provides separate methods for resetting different parts of the table:

$this->resetSearch();
$this->resetFilters();
$this->resetSorting();
$this->resetPagination();

Pagination is automatically reset when:

  • Search changes
  • Filters change
  • Results-per-page changes
  • Sorting changes

The centralized pagination reset is handled by:

$this->resetTablePage();

The method also listens for the filters-updated Livewire event.

Rendering

The base component's render() method processes the query and provides the resulting paginator as:

$data

The component therefore expects its view to use the $data variable when displaying the results.

A typical table view might look like:

<div>
    <input
        type="text"
        wire:model.live="search"
        placeholder="Search..."
    >

    <table>
        <thead>
            <tr>
                @foreach ($this->getvisiblecolumns() as $key => $column)
                    <th>
                        @if ($this->isSortable($key))
                            <button wire:click="sortBy('{{ $key }}')">
                                {{ $column['label'] ?? $key }}
                            </button>
                        @else
                            {{ $column['label'] ?? $key }}
                        @endif
                    </th>
                @endforeach
            </tr>
        </thead>

        <tbody>
            @foreach ($data as $row)
                <tr>
                    @foreach ($this->getvisiblecolumns() as $key => $column)
                        <td>
                            {{ $row->{$key} }}
                        </td>
                    @endforeach
                </tr>
            @endforeach
        </tbody>
    </table>

    {{ $data->links() }}
</div>

The exact markup is intentionally left to the consuming application, allowing the datatable to be styled with Bootstrap, Tailwind, or another frontend framework.

Example Component

A more complete example:

<?php

namespace App\Livewire;

use App\Models\User;
use Edmirkasapi\LiveDatatable\abstracts\LiveDatatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Contracts\View\View;

class UsersTable extends LiveDatatable
{
    protected array $columnOrder = [
        'name',
        'email',
        'created_at',
    ];

    protected function columns(): array
    {
        return [
            'name' => [
                'label' => 'Name',
                'sortable' => true,
                'visible' => true,
            ],

            'email' => [
                'label' => 'Email',
                'sortable' => true,
                'visible' => true,
            ],

            'created_at' => [
                'label' => 'Created',
                'sortable' => true,
                'visible' => true,
            ],
        ];
    }

    protected function searchable(): array
    {
        return [
            'name',
            'email',
        ];
    }

    protected function query(): Builder
    {
        return User::query();
    }

    public function render(): View
    {
        return view('livewire.users-table', [
            'data' => $this->getData(),
        ]);
    }
}

Development & Testing

The package uses PHPUnit for testing and Orchestra Testbench for package development.

Install development dependencies:

composer install

Run the package test suite:

vendor/bin/phpunit

Development dependencies include:

  • PHPUnit 11
  • Orchestra Testbench 10

Package Structure

live-datatable/
├── src/
│   ├── abstracts/
│   │   └── LiveDatatable.php
│   └── ...
├── resources/
│   └── views/
├── tests/
│   ├── Feature/
│   ├── Unit/
│   └── ...
├── composer.json
└── phpunit.xml

Contributing

Contributions, bug reports, and feature requests are welcome.

When contributing:

  1. Fork the repository.
  2. Create a feature or bug-fix branch.
  3. Make your changes.
  4. Add or update tests where appropriate.
  5. Run the test suite.
  6. Submit a pull request.

License

This package is open-sourced software licensed under the MIT License.