Core Components

Table Component

Laravolt provides a powerful Table component for displaying data in a tabular format with features like searching, sorting, and filtering - all without writing JavaScript.

Table Component Example


Introduction

Laravolt Table provides a UI for displaying data in tabular form with supporting features like searching, sorting, and filtering. Built on Livewire, tables can be interacted with without full page refreshes. You don't need to worry about JavaScript because all UI-related aspects are already handled by Laravolt - just focus on writing PHP code.

Creating Tables

Because tables are built on Livewire, all Table classes are stored in the app\Http\Livewire\Table folder. To create a new Table, run the make:table command:

Bash
php artisan make:table UserTable

Then, define your data source and the columns to display:

PHP
use Laravolt\Suitable\Columns\Text;
class UserTable extends TableView
{
public function data()
{
return \App\Models\User::where('status', 'ACTIVE')->paginate();
}
public function columns(): array
{
return [
Text::make('name', 'Name'),
Text::make('email', 'Email'),
];
}
}

Displaying Tables

To display a table, simply call the Blade component or directive from your view:

PHP
<livewire:table.user-table />
// or
@livewire('table.user-table')
// or
@livewire(\App\Http\Livewire\Table\UserTable::class)

Reference

Data Sources

Laravolt Table can process several types of data sources to display in a table. The available options for the data() method return value are:

  • Array
  • Collection
  • Eloquent
  • Query Builder
  • Response from HTTP Client

Array

The simplest data source is an array:

PHP
public function data()
{
return [
['name' => 'Andi', 'email' => 'andi@example.com'],
['name' => 'Budi', 'email' => 'budi@example.com'],
];
}

Collection

You can also use \Illuminate\Support\Collection as a data source:

PHP
public function data()
{
$users = [
['name' => 'Andi', 'email' => 'andi@example.com'],
['name' => 'Budi', 'email' => 'budi@example.com'],
['name' => 'Citra', 'email' => null],
];
return collect($users)->reject(fn($user) => $user->email === null);
}

Eloquent

The most commonly used data source is Eloquent:

PHP
use App\Models\User;
public function data()
{
return User::whereNull('deleted_at')->paginate();
}

If you don't need pagination, you can directly call get():

PHP
use App\Models\User;
public function data()
{
return User::query()->whereNull('deleted_at')->get();
}

Query Builder

If Eloquent isn't your preference and you prefer Query Builder, you can still apply the same approach:

PHP
public function data()
{
return \DB::table('users')->whereNull('email')->paginate();
}

Or without pagination:

PHP
public function data()
{
return \DB::table('users')->get();
}

Response From HTTP Client

To retrieve data directly from an API, you can use Laravel's built-in HTTP Client:

PHP
use Illuminate\Support\Facades\Http;
public function data()
{
return Http::get('https://jsonplaceholder.typicode.com/users');
}

Column Types

Laravolt Table provides various column types to display different kinds of data appropriately:

Avatar

The Avatar column automatically displays avatar images generated from initials. In the example below, the email initials will be used to generate an appropriate avatar image:

PHP
use Laravolt\Suitable\Columns\Avatar;
public function columns(): array
{
return [
Avatar::make('email', 'Avatar'),
];
}

Complete documentation about Avatar can be found at https://github.com/laravolt/avatar.

Boolean

The Boolean column displays true or false values as icons. Non-boolean values will be automatically cast to (bool):

PHP
use Laravolt\Suitable\Columns\Boolean;
public function columns(): array
{
return [
Boolean::make('is_active'),
];
}

Button

The Button column adds buttons that function as navigation to other pages:

PHP
use Laravolt\Suitable\Columns\Button;
public function columns(): array
{
return [
Button::make('permalink', 'Info')->label('Profile')->icon('external link'),
];
}

In the example above, permalink contains a valid URL, either a hardcoded "http://example.com" or the result of calling Laravel's route() or url() function:

PHP
class User extends Model
{
public function getPermalinkAttribute()
{
return route('users.show', $this->id);
}
}

Button::make() also accepts a Closure to dynamically generate URLs:

PHP
use Laravolt\Suitable\Columns\Button;
public function columns(): array
{
return [
// using arrow function syntax
Button::make(fn ($user) => route('users.show', $user['id'])),
// or the longer traditional way
Button::make(
function ($user) {
return route('users.show', $user['id']);
}
),
];
}

Checkall

The Checkall column adds a checkbox to each row that can be used for multiple selection. Selected IDs can then be sent as parameters when defining Table Action:

PHP
use Laravolt\Suitable\Columns\Checkall;
public function columns(): array
{
return [
Checkall::make('id'),
];
}

Date

The Date column converts database dates with format 2021-06-02 to a more human-readable format, like June 2, 2021:

PHP
use Laravolt\Suitable\Columns\Date;
public function columns(): array
{
return [
Date::make('created_at'),
];
}

Additional methods available for the Date column type:

  • format(string $format) where $format is a string that follows the format accepted by Moment.js.
  • timezone(string $timezone) for automatic date conversion to the appropriate timezone. Valid timezones can be found in the official PHP documentation. If timezone() is not explicitly called, the conversion will consider:
    • The timezone attribute of the currently logged-in user: auth()->user()->timezone.
    • If null, the timezone will be taken from config('app.timezone').

DateTime

Similar to the Date column, DateTime changes values like 2021-06-02 22:54:00 to June 2, 2021 at 22:45:

PHP
use Laravolt\Suitable\Columns\DateTime;
public function columns(): array
{
return [
DateTime::make('created_at'),
];
}

The format() and timezone() methods are also available, with the same behavior as the Date column.

Html

The Html column is used when the content to be displayed contains HTML tags, and you want to display it according to HTML format:

PHP
public function data()
{
return [
['bio' => '<b>Strong</b> <i>foo</i>'],
];
}
public function columns(): array
{
return [
Html::make('bio')
];
}

In the example above, the table will display the content as "Strong foo". This column is commonly used when the stored data comes from a WYSIWYG or rich text editor like Redactor or TinyMCE.

Id

The Id column is used to display the primary key of an Eloquent model. Behind the scenes, this column will call getKey(). So make sure to only use this column when the data source comes from Eloquent:

PHP
use Laravolt\Suitable\Columns\Id;
public function columns(): array
{
return [
Id::make(),
];
}

Image

The Image column is used to display images:

PHP
use Laravolt\Suitable\Columns\Image;
public function columns(): array
{
return [
Image::make('profile_picture')
];
}

Additional methods available:

  • height(int $sizeInPixel) to set the image height.
  • width(int $sizeInPixel) to set the image width.
  • alt(string $text) to set the alt attribute (alternate text).

Label

The Label column is used to display a value so it stands out more. Examples of values suitable to be displayed as Label include: status, category, type, and similar attributes:

PHP
use Laravolt\Suitable\Columns\Label;
public function columns(): array
{
return [
Label::make('status'),
];
}

To add CSS classes to a Label, you can use the addClass method:

PHP
use Laravolt\Suitable\Columns\Label;
public function columns(): array
{
return [
Label::make('status')->addClass('green small'),
];
}

There are several built-in classes that can be used to determine the color of a Label: red, orange, yellow, olive, green, teal, blue, violet, purple, pink, brown, grey, and black.

In practice, there's a need to give different colors to each value. This can be done using the map() method:

PHP
use Laravolt\Suitable\Columns\Label;
public function columns(): array
{
return [
Label::make('status')->map([
'active' => 'green',
'banned' => 'red',
]),
];
}

Number

The Number column is used to display a value in a more human-friendly number format. For example, a value of 1000000 will automatically be changed to 1,000,000 and displayed right-aligned:

PHP
use Laravolt\Suitable\Columns\Number;
public function columns(): array
{
return [
Number::make('salary',)
];
}

The number format currently used is the format commonly used in Indonesia.

Raw

The Raw column is used to display a value as is (unescaped), without going through the htmlspecialchars function. Be careful when using Raw for a value that comes from user input, as there's a potential for Cross-Site Scripting (XSS):

PHP
use Laravolt\Suitable\Columns\Raw;
public function data()
{
return [
['bio' => '<b>Strong</b><script>alert("foo")</script>'],
];
}
public function columns(): array
{
return [
Raw::make('bio'),
];
}

In the example above, we'll get a Strong text (in bold) and the JavaScript code to display an alert will be executed by the browser.

If you need more flexible custom logic, the Raw column type can also accept a Closure:

PHP
// Display a list of roles for a user, separated by commas
Raw::make(
function ($user) {
return $user->roles->implode('name', ', ');
},
'Roles'
),

RestfulButton

The RestfulButton column is used to create standard buttons for a create-read-update-delete or CRUD process. You only need to define the resource name to generate three buttons: show, edit, and destroy:

PHP
use Laravolt\Suitable\Columns\RestfulButton;
public function columns(): array
{
return [
RestfulButton::make('users'),
];
}

In the example code above, the three buttons generated will have routes:

  • show: route('users.show', <id>)
  • edit: route('users.edit', <id>)
  • destroy: route('users.destroy', <id>)

<id> is automatically taken from the primary key of the object in question. Therefore, RestfulButton can only be used if the data source comes from Eloquent.

Additional methods available:

  • only($action1, $action2) if you only want to display certain actions, for example only('show').
  • except($action1, $action2) if you want to hide certain buttons, for example except('destroy').

To learn more about resource controllers, please read the official documentation from Laravel.

RowNumber

The RowNumber column is used to display row numbers in sequence, starting from 1:

PHP
use Laravolt\Suitable\Columns\RowNumber;
public function columns(): array
{
return [
RowNumber::make(),
];
}

Text

The Text column is used to display values safely, protected from Cross-Site Scripting (XSS), using PHP's built-in htmlspecialchars function:

PHP
use Laravolt\Suitable\Columns\Text;
public function data()
{
return [
['bio' => '<b>Strong</b><script>alert("foo")</script>'],
];
}
public function columns(): array
{
return [
Text::make('bio'),
];
}

In the example above, "bio" will be displayed as-is according to the written text:

Text
<b>Strong</b><script>alert("foo")</script>

Text vs Html vs Raw

There are three columns that can be used to display content from WYSIWYG editors containing HTML tags (and JavaScript). Here's the difference between the three:

ColumnOutputNotes
Text<b>Strong</b> <script>alert("foo")</script>Safest, displays text as-is.
HtmlStrong <script>alert("foo")</script>If you only want to execute HTML tags.
RawStrong (and alert appears in the browser)If you want to fully execute HTML and JavaScript code.

Url

The Url column is used to automatically display a URL as a clickable link:

PHP
use Laravolt\Suitable\Columns\Url;
public function columns(): array
{
return [
Url::make('website'),
];
}

View

When the display becomes more complex, we can use the View column to move the logic for rendering the contents of a column to a separate blade file:

PHP
use Laravolt\Suitable\Columns\View;
public function columns(): array
{
return [
View::make('profile')
];
}

Then create a blade file profile.blade.php:

HTML
<dl>
<dt>Name</dt>
<dd>{{ $data->name }}</dd>
<dt>Email</dt>
<dd>{{ $data->email }}</dd>
</dl>

Custom Column

If you don't find a suitable column type, then Raw is the first choice to display data with your own custom logic.

If this custom logic is needed in multiple places and you want to apply the Don't Repeat Yourself (DRY) principle, then creating a specific Class for a custom column becomes a wiser choice.

Here's the framework for a custom column that functions to mask the displayed data:

PHP
<?php
namespace App\Tables\Columns;
use Laravolt\Suitable\Columns\Column;
use Laravolt\Suitable\Columns\ColumnInterface;
class MaskColumn extends Column implements ColumnInterface
{
public function cell($cell, $collection, $loop)
{
return \Str::mask($cell->{$this->field});
}
}

All you need to create is a class that extends Column (base class), implements ColumnInterface, and defines logic to display data in the cell($cell, $collection, $loop) function.

$cell is an item from the Collection (can be an Eloquent model or other object) that will currently be displayed in the cell of a table.

$collection is the entire data (Collection).

$loop contains information about looping, according to Laravel's documentation.

In the example above, the custom column is saved in app/tables/columns. This is not a standard rule. Please adjust the namespace according to your application structure.

Searching

Search Query

When users type something in the search field (searchbox), the input will automatically be bound to the $search property, so we can access it by calling $this->search.

Here's an example code to search by name:

PHP
class UserTable extends TableView
{
public function data()
{
return \App\Models\User::where('status', 'ACTIVE')
->whereLike(['name', 'email'], $this->search)
->paginate();
}
}

whereLike is an additional method from Laravolt to search multiple columns at once using the where <column> like "%<keyword>%" mechanism. For large amounts of data, this query may not be optimal. Please create your own search logic as needed.

PHP
class UserTable extends TableView
{
public bool $showSearchbox = false; // default is "true"
}

Search Debounce

Every time a User types something in the searchbox, the search query will automatically run with an AJAX mechanism. Debounce is a mechanism to wait a few moments until the User is considered to have really stopped typing, then the query is executed to update the displayed data.

PHP
class UserTable extends TableView
{
public int $searchDebounce = 1000; // in milliseconds, default is 300
}

Sorting

When defining a table, we can add the sortable() function to any defined column. Automatically, that column will be clickable.

PHP
use Laravolt\Suitable\Columns\Text;
class UserTable extends TableView
{
public function data()
{
return \App\Models\User::where('status', 'ACTIVE')->paginate();
}
public function columns(): array
{
return [
Text::make('name', 'Name')->sortable(),
Text::make('email', 'Email')->sortable(),
Raw::make(
fn ($data) => DateHelper::formatDate($data->birth_date),
'Birth Date'
)->sortable('birth_date'),
];
}
}

But don't forget, we also need to modify the query so that the resulting data is also sorted. Laravolt has provided the AutoSort Trait to handle this need.

First, make sure the Model that is the data source is already using AutoSort:

PHP
namespace App\Models;
use Laravolt\Suitable\AutoSort;
class User extends Model
{
use AutoSort;
}

Next, we just need to call the autoSort() function when querying the data:

PHP
class UserTable extends TableView
{
public function data()
{
return \App\Models\User::where('status', 'ACTIVE')
->autoSort($this->sortPayload())
->paginate();
}
}

Filtering

Filtering functions to perform more exact searches. When adding Filtering, try to ensure the table data source is still in the form of a Query Builder. This is necessary because the Filtering logic will be applied after calling data().

PHP
class UserTable extends TableView
{
public function data()
{
// NOT OK
return \App\Models\User::get();
// NOT OK
return \App\Models\User::paginate();
// OK
return \App\Models\User::query();
// OK
return \App\Models\User::where('status', 'ACTIVE');
}
}

To add a Filter, create a class with a skeleton like the following:

PHP
<?php
namespace App\Tables\Filters;
use Laravolt\Ui\Filters\TextFilter;
class EmailFilter extends TextFilter
{
protected string $label = 'Email';
public function apply($data, $value)
{
if ($value) {
$data->where('email', $value);
}
return $data;
}
}

You can change the query logic in the apply method as needed. In the example above, filtering is done with an exact match.

Then add that class to the filters() method of the Table:

PHP
use App\Models\User;
use App\Tables\Filters\EmailFilter;
class UserTable extends TableView
{
public function data()
{
return User::where('status', 'ACTIVE'); // don't forget to import the class on top with "use"
}
public function filters(): array
{
return [
new EmailFilter(), // don't forget to import the class on top with "use"
];
}
}

The result will look something like this:

Filter Example

Built-in Filters

There are several types of Filters already available and ready to be extended, namely:

  • TextFilter
  • DropdownFilter
  • CheckboxFilter
  • DateFilter

Text Filter

PHP
<?php
namespace App\Tables\Filters;
use Laravolt\Ui\Filters\TextFilter;
class EmailFilter extends TextFilter
{
protected string $label = 'Email';
public function apply($data, $value)
{
if ($value) {
$data->where('email', $value);
}
return $data;
}
}

Dropdown Filter Example

PHP
<?php
namespace App\Tables\Filters;
use App\Models\Role;
use Laravolt\Ui\Filters\DropdownFilter;
class RoleFilter extends DropdownFilter
{
protected string $label = 'Roles';
public function apply($data, $value)
{
if ($value) {
$data->whereHas('roles', fn ($query) => $query->where('id', $value));
}
return $data;
}
public function options(): array
{
return Role::query()->pluck('name', 'id')->prepend('All Roles', '0')->toArray();
}
}

Checkbox Filter

Checkbox Filter Example

PHP
<?php
namespace App\Tables\Filters;
use Laravolt\Ui\Filters\CheckboxFilter;
class StatusFilter extends CheckboxFilter
{
protected string $label = 'Status';
public function apply($data, $value)
{
$status = collect($value)->filter()->values()->toArray();
if (! empty($status)) {
$data->whereIn('status', $status);
}
return $data;
}
public function options(): array
{
return [
'ACTIVE' => 'ACTIVE',
'PENDING' => 'PENDING',
];
}
}

Date Filter

Date Filter Example

PHP
<?php
namespace App\Tables\Filters;
use Laravolt\Ui\Filters\DateFilter;
class RegisteredFilter extends DateFilter
{
protected string $label = 'Registered At';
public function apply($data, $value)
{
if ($value) {
$data->where('created_at', $value);
}
return $data;
}
}

Pagination

Pagination will be displayed automatically according to the data source returned in the data() method. Pagination customization can be done by changing several properties:

PHP
class UserTable extends TableView
{
// change the default number of data shown per page
private const DEFAULT_PER_PAGE = 15;
// change the options for data shown per page
protected array $perPageOptions = [5, 15, 30, 50, 100, 250];
// if you want to change the view
protected $paginationView = 'laravolt::pagination.livewire.simple';
}

Query String

Every time a User interacts with the Table, the current state will be saved as a query string. For example, after searching for data with the keyword "foo", the browser URL will automatically change to:

Http
/users?search=foo

The advantages of explicitly storing state in the URL include:

  1. Users can easily share the current URL.
  2. When Users refresh their browser, the displayed data will match the last state.

Here are the states that will automatically change the query string in the browser:

PHP
protected $queryString = [
'page' => ['except' => 1],
'search' => ['except' => ''],
'sort' => ['except' => null],
'direction',
'perPage' => ['except' => self::DEFAULT_PER_PAGE],
];

Please study Livewire's official documentation regarding query strings.

However, if for some reason you don't want the above behavior (for example when having to display 2 tables on the same page), simply empty the $queryString property:

PHP
class UserTable extends TableView
{
protected $queryString = [];
}

Afterwards, whenever Users interact with the table (searching, sorting, filtering, pagination), the browser URL will not change.

Previous
Form
Next
Menu