# Tables

# Overview

Tables are the backbone of the hub and how data is displayed. Therefore it's important they can be extended when needed and also allow you to create your own tables when developing add-ons.

All tables use a TableBuilder class to add columns, filters and actions. It's these classes that can be extended in order to provider a richer experience in the hub when displaying data.

# Available builders

  • Lunar\Hub\Tables\Builders\CustomersTableBuilder
  • Lunar\Hub\Tables\Builders\OrdersTableBuilder
  • Lunar\Hub\Tables\Builders\ProductsTableBuilder
  • Lunar\Hub\Tables\Builders\ProductTypesTableBuilder
  • Lunar\Hub\Tables\Builders\ProductVariantsTableBuilder

# Getting the builder instance

You can resolve each builder class as a singleton from the container. Generally this can be done via a service provider:

use Lunar\Hub\Tables\Builders\ProductsTableBuilder;

public function boot(ProductsTableBuilder $productTableBuilder)
{
    // ...
}

# Adding columns

$productsTableBuilder->addColumn($columnClass);

When adding columns, there is some flexibility on how they are rendered.

# Standard

This is the default behaviour and will use the view provided by Lunar.

# Laravel Component

You can render your own Laravel component

TextColumn::make('status')->viewComponent('my-column');

This will pass through the instance of the row so it's available:

<?php

namespace App\Views;

use Illuminate\View\Component;

class MyColumn extends Component
{
    public function __construct($record)
    {
        // ...
    }

    // ...
}

# Livewire Component

TextColumn::make('status')->livewire('livewire.component.reference');

When rendered, the current record will be passed through via a prop.


<?php

namespace App\Http\Livewire\Components;

use Livewire\Component;
use Illuminate\Database\Eloquent\Model;

class CustomColumnComponent extends Component
{
    public Model $record;

    // ...
}

# Positioning

You can specify a position for the new column by defining which column it should appear after.

TextColumn::make('status')->after('name');

# TextColumn

use Lunar\LivewireTables\Components\Columns\TextColumn;

// Specify an existing column
$productsTableBuilder->addColumn(
    TextColumn::make('status')
);

// Reference a relationship
$productsTableBuilder->addColumn(
    TextColumn::make('productType.name')
);

// Use a callback to return the value for each row
$productsTableBuilder->addColumn(
    TextColumn::make('store', function ($product) {
        return $product->store;
    })->heading('Store')
);

# BadgeColumn

use Lunar\LivewireTables\Components\Columns\BadgeColumn;

BadgeColumn::make('status', function ($record) {
    return $record->status;
})->states(function ($record) {
    return [
        'success' => $record->status == 'published' && ! $record->deleted_at,
        'warning' => $record->status == 'draft' && ! $record->deleted_at,
        'danger' => (bool) $record->deleted_at,
    ];
});

# ImageColumn

use Lunar\LivewireTables\Components\Columns\ImageColumn;

ImageColumn::make('thumbnail', function ($record) {
    return $record->thumbnail->getUrl('small');
})->heading(false);

TIP

Calling heading(false) will prevent heading text from being displayed.

# AvatarColumn

use Lunar\LivewireTables\Components\Columns\AvatarColumn;

AvatarColumn::make('avatar', function ($record) {
    return $record->email;
})->gravatar()->heading(false);

By calling gravatar() we're telling the column to convert it to a hash that Gravatar can understand and render.

# StatusColumn

use Lunar\LivewireTables\Components\Columns\StatusColumn;

StatusColumn::make('active', function ($record) {
    return ! $record->deleted_at;
});

# Adding Filters

$productsTableBuilder->addFilter($filterClass);

# SelectFilter

use Lunar\LivewireTables\Components\Filters\SelectFilter;

SelectFilter::make('status')->options(function () {
    return collect([
        null => 'All Statuses',
        'payment-received' => 'Payment Received',
    ])->merge($statuses);
})->query(function ($filters, $query) {
    $value = $filters->get('status');

    if ($value) {
        $query->whereStatus($value);
    }
});

# DateFilter

use Lunar\LivewireTables\Components\Filters\DateFilter;

DateFilter::make('placed_at')
    ->heading('Placed at')
    ->query(function ($filters, $query) {
        $value = $filters->get('placed_at');

        if (! $value) {
            return $query;
        }

        $parts = explode(' to ', $value);

        if (empty($parts[1])) {
            return $query;
        }

        $query->whereBetween('placed_at', [
            $parts[0],
            $parts[1],
        ]);
    });

# Actions

Single actions will populate a dropdown on each row that will display the registered actions when clicked.

$productsTableBuilder->addAction($actionClass);

# Action

use Lunar\LivewireTables\Components\Actions\Action;

Action::make('view')->label('View Order')->url(function ($record) {
    return route('hub.orders.show', $record->id);
});

# Bulk Actions

Bulk actions are available when one or more rows are selected on the table. When an action is clicked the underlying Livewire component will be called with the selected ID's.

$productsTableBuilder->addAction($actionClass);

# Bulk Action

BulkAction::make('update_status')
    ->label('Update Status')
    ->livewire('hub.components.tables.actions.update-status')

The underlying Livewire component

<?php

namespace Lunar\Hub\Http\Livewire\Components\Tables\Actions;

use Livewire\Component;
use Lunar\Hub\Http\Livewire\Traits\Notifies;
use Lunar\Models\Order;

class UpdateStatus extends Component
{
    use Notifies;

    /**
     * The array of selected IDs
     *
     * @var array
     */
    public array $ids = [];

    public $status = null;

    /**
     * {@inheritDoc}
     */
    public function getListeners()
    {
        return [
            'table.selectedRows' => 'setSelected',
        ];
    }

    /**
     * {@inheritDoc}
     */
    public function rules()
    {
        return [
            'status' => 'required',
        ];
    }

    public function getStatusesProperty()
    {
        return config('lunar.orders.statuses');
    }

    /**
     * Set the selected ids
     *
     * @param  array  $rows
     * @return void
     */
    public function setSelected(array $rows)
    {
        $this->ids = $rows;
    }

    /**
     * Save the updated status
     *
     * @return
     */
    public function updateStatus()
    {
        Order::whereIn('id', $this->ids)->update([
            'status' => $this->status,
        ]);

        $this->notify('Order statuses updated');
        $this->emit('bulkAction.complete');
    }

    /**
     * Render the livewire component.
     *
     * @return \Illuminate\View\View
     */
    public function render()
    {
        return view('adminhub::livewire.components.tables.actions.update-status')
            ->layout('adminhub::layouts.base');
    }
}

# Customising the query

If you add custom columns which reference relations, or want to make some performance improvements, it can be useful to customise the underlying query.

$productsTableBuilder->extendQuery(function ($query) {
    $query->withCount('variants');
});

# Creating a new table

If you need to create a table for your own add-on, simply create a Livewire component which extends the Table component.

# Table component

<?php

namespace App\Http\Livewire;

use Lunar\LivewireTables\Components\Table;

class OrdersTable extends Table
{
    /**
     * {@inheritDoc}
     */
    public function build()
    {
        $this->tableBuilder->baseColumns([
            TextColumn::make('id'),
        ]);
    }
    
    /**
     * Return the search placeholder.
     *
     * @return string
     */
    public function getSearchPlaceholderProperty(): string
    {
        return 'Search by keyword';
    }
    
    /**
     * {@inheritDoc}
     */
    public function getData()
    {
        return Mode::get();
    }
}

# Table builder class

Out the box, the table will be loaded with a base TableBuilder class. In most cases this should be enough, however you are free to add your own if needed.

# Create a new table builder class

<?php

namespace App\Tables;

use Lunar\Hub\Tables\TableBuilder;

class CustomTableBuilder extends TableBuilder
{
    // ...
}

Then update the reference on your Table component.

<?php

namespace App\Http\Livewire;

use Lunar\LivewireTables\Components\Table;

class OrdersTable extends Table
{
    /**
     * The binding to use when building out the table.
     *
     * @var string
     */
    protected $tableBuilderBinding = CustomTableBuilder::class;
    
    // ...
}