ComfyCoders TranquilTools
Docs /Laravel Vue TableBuilder
Repository
v1

Totals

Add a footer row with a total per column. Totals are computed on the server, over every row that matches the current search and filters, not only the rows on the current page:

$table
    ->column('description')
    ->column('hours', 'Hours')
    ->column('amount', 'Amount', as: fn (int $cents) => Money::format($cents))
    ->total('hours')
    ->total('amount', format: fn (int $cents) => Money::format($cents))
    ->totalsLabel('Total')
    ->paginate(25);

Signature

total(
    string $key,
    string|Closure $using = 'sum',
    ?Closure $format = null,
    ?string $column = null,
    bool $pageOnly = false,
): self

totalsLabel(?string $label): self
Parameter Description
key The column the total is shown under
using sum, count, avg, min, max, or a callback that returns the value
format Turns the value into the text shown, for currencies, units and rounding
column The database column (or item key) to aggregate. Defaults to key
pageOnly Total only the rows on the current page instead of every matching row

totalsLabel() shows a text such as "Total" in the first visible column, when that column has no total of its own.

How They Are Computed

On a query table each total is one aggregate query on the table's query with the active search and filters applied and the ordering removed, so ->total('amount') runs select sum("amount") over all matching rows. A table built from a collection totals every row of the collection that matches the search.

With pageOnly: true the total is computed from the rows on the current page.

Callbacks

A callback receives the filtered query (a clone, free to change) and returns the value:

$table->total('amount', using: fn (Builder $query) => $query->sum(DB::raw('quantity * unit_price')));

$table->total('hours', using: fn (Builder $query) => $query->where('billable', true)->sum('hours'));

With pageOnly: true, or on a collection table, it receives the rows as a Collection instead:

$table->total('name', using: fn (Collection $rows) => $rows->count().' bookings', pageOnly: true);

Formatting

format receives the computed value. A string it returns is escaped like a cell; return an Illuminate\Support\HtmlString for markup. Without format the raw value is sent: a number, or null when there are no rows to average.

$table->total('amount', format: fn (?int $cents) => '€ '.number_format(($cents ?? 0) / 100, 2, ',', '.'));

Frontend

The component renders the totals in a <tfoot> under the matching columns, and hides the footer while the table is empty. A total-{key} slot renders a total yourself:

<TableBuilder :table="table">
    <template #total-hours="{value}">
        <strong>{{ value }} h</strong>
    </template>
</TableBuilder>

The slot also receives the column and the whole totals payload:

interface Totals {
    label: string | null
    values: Record<string, string | number | null>
}

With the HTTP transport the totals come from the fetched response; the default adapter reads a totals key in the same shape.