Own widgets
php spark make:anvil-widget OpenInvoicescreates App\Widgets\OpenInvoices and the view
app/Views/widgets/open_invoices.php. Options: --namespace, --suffix
(class name OpenInvoicesWidget), --force. See
Commands.
A widget implements Anvil\Widget\Widget:
| Method | Purpose |
|---|---|
canView(Gate $gate): bool |
whether the current user sees the widget |
columnSpan(): int |
width on extra large screens (the grid clamps it to 2–12) |
render(): string |
the HTML of the widget; it only runs for widgets that are shown |
namespace App\Widgets;
use Anvil\Security\Authorization\Gate;use Anvil\Widget\Widget;use App\Models\InvoiceModel;
final class OpenInvoices implements Widget{ public function canView(Gate $gate): bool { return $gate->allows('invoices.view'); }
public function columnSpan(): int { return 6; }
public function render(): string { // Load data here: render() runs only when the widget is shown. $invoices = (new InvoiceModel())->where('paid_at', null)->orderBy('due_on')->findAll(5);
return view('widgets/open_invoices', ['title' => 'Open invoices', 'invoices' => $invoices], ['saveData' => false]); }}<?php // app/Views/widgets/open_invoices.php ?><div class="card h-100"> <div class="card-body"> <h2 class="anvil-widget-title"><?= esc($title) ?></h2> <ul class="list-unstyled mb-0"><?php foreach ($invoices as $invoice): ?> <li><?= esc($invoice['number']) ?> · <?= esc($invoice['due_on']) ?></li><?php endforeach ?> </ul> </div></div>Return it from Panel::widgets() (new OpenInvoices()) or
Resource::listWidgets().
- Escape every value; no inline scripts,
styleattributes or event attributes (CSP), see the rules for extensions. card h-100gives the same look and height as the built-in widgets;anvil-widget-titleis the heading style.- A widget that renders a
<canvas data-anvil-chart="…">gets the chart script loaded likeChartWidget; reuseChartWidgetinside your widget rather than building the attribute yourself. - Implement
Anvil\Widget\Layout\LayoutWidget(layoutKey(),layoutLabel()) – or use the traitHasLayoutKeywith a$labelproperty – so users can arrange the widget by a stable key; others are keyed by their position. - Widgets are not interactive by themselves. For buttons or forms that change data, render a component inside the widget.
Following the list
Section titled “Following the list”An own widget follows the search and filters of a list by implementing
Anvil\Widget\ListAware as well:
| Method | Purpose |
|---|---|
followsList(): bool |
true if the widget uses the records of the list |
forList(Closure $records): static |
receives fn (): Model, a fresh query of the list for every call; return a copy of the widget that uses it |
use Anvil\Data\Model;use Anvil\Security\Authorization\Gate;use Anvil\Widget\ListAware;use Anvil\Widget\Widget;use Closure;
final class ListedAmount implements ListAware, Widget{ /** @var (Closure(): Model)|null */ private ?Closure $records = null;
public function followsList(): bool { return true; }
public function forList(Closure $records): static { $copy = clone $this; $copy->records = $records;
return $copy; }
public function canView(Gate $gate): bool { return $gate->allows('invoices.view'); }
public function columnSpan(): int { return 4; }
public function render(): string { $row = $this->records === null ? null : ($this->records)()->selectSum('amount', 'total')->asArray()->first(); $total = number_format((float) ($row['total'] ?? 0), 2);
return view('widgets/listed_amount', ['title' => 'Amount in this list', 'total' => $total], ['saveData' => false]); }}