Core
Dashboardy
Skládání widgetů na komponentě přes `WithWidgets` a deklarace dashboardu jako vlastníka, kterého najde menu i router.
Na této stránce
- Dashboard layout (WithWidgets)
- Použití
- Blade šablona
- WithWidgets API
- Stav drží hostitel, widgety žádný
- Rozhraní HasWidgets
- Dashboard (deklarovaný, ne komponenta)
- Kde je aplikace registruje
- Jak se vykresluje
- Jak se dostane do menu
- Dashboard API
- Jak nechat každého uživatele přeskládat si ho
- Filtry nad celým dashboardem
- DashboardFilter API
- Přidání widgetu na dashboard, který nevlastníte
- Související
Dostat widgety na obrazovku jde dvěma způsoby a je to totéž dělení, jaké dělá resource: napsat si je na komponentu, nebo deklarovat dashboard, který je jmenuje, a nechat ho vykreslit stránkou. To druhé je to, co dá položku do menu a URL do routeru, aniž by se kdokoli z nich dozvěděl, co je widget.
Dashboard layout (WithWidgets)
Použijte trait WithWidgets na Livewire komponentě pro složení widgetového dashboardu.
use NyonCode\WireCore\Widgets\ChartWidget;use NyonCode\WireCore\Widgets\Concerns\WithWidgets;use NyonCode\WireCore\Widgets\Contracts\HasWidgets;use NyonCode\WireCore\Widgets\Stat;use NyonCode\WireCore\Widgets\StatsOverviewWidget;use NyonCode\WireTable\Table;use NyonCode\WireTable\Widgets\TableWidget;
TableWidget jako jediný není z wire-core. Kreslí řádky tabulky uvnitř
karty a engine, který je kreslí, žije ve wire-table — takže tam žije i ta
třída. Aplikace bez toho balíčku skládá ostatní druhy widgetů přesně jako níž.
Viz Tabulky a vlastní pohledy.
Použití
class Dashboard extends Component implements HasWidgets{ use WithWidgets; protected function getWidgets(): array { return [ StatsOverviewWidget::make() ->columns(4) ->stats([ Stat::make('Users', (string) User::count()), Stat::make('Orders', (string) Order::count()), Stat::make('Revenue', "$" . number_format(Order::sum('total'), 2)), Stat::make('Products', (string) Product::count()), ]), ChartWidget::make() ->heading('Monthly Revenue') ->type('line') ->columnSpan(2) ->labels($this->getMonthLabels()) ->datasets($this->getRevenueDatasets()), TableWidget::make() ->heading('Recent Orders') ->limit(5) ->table(fn (Table $table): Table => $this->configureRecentOrdersTable($table)), ]; } protected function getWidgetColumns(): int { return 2; // 2-sloupcový grid layout }}
Blade šablona
Vykreslete dashboard komponentou <x-wire::widget-grid>.
getVisibleWidgets() je veřejná a vrací jen widgety, které projdou svými
kontrolami viditelnosti a autorizace; každý widget ctí svůj vlastní columnSpan()
a polling interval uvnitř gridu:
<div> <x-wire::widget-grid :widgets="$this->getVisibleWidgets()" :columns="2" /></div>
Každý widget je také Htmlable, takže můžete komponentu přeskočit a rozložit je
sami: @foreach ($this->getVisibleWidgets() as $widget) {{ $widget }} @endforeach.
WithWidgets API
abstract protected function getWidgets(): array // definovat widgetyprotected function getWidgetColumns(): int // sloupce gridu (výchozí: 2)public function getVisibleWidgets(): array // filtrované podle viditelnosti + autorizacepublic function refreshWidget(string $key): void // tik pollingu, zodpovězený tím widgetempublic function filterWidget(string $key, string $value): void // výběr filtrupublic function callWidgetAction(string $key, string $name): void // akce v hlavičcepublic function loadWidget(string $key): void // první render odloženého widgetupublic array $widgetFilters // jaký filtr který widget ukazujepublic array $loadedWidgets // klíče, které už byly staženyprotected function getDashboardFilters(): array // filtry nad celým dashboardem (výchozí: žádné) public function dashboardFilter(string $name): ?string // hodnota jednoho filtru, pro getWidgets()public function setDashboardFilter(string $name, string $value): void // co volá lišta filtrůpublic function resetDashboardFilters(): void // všechny filtry zpět na výchozípublic array $dashboardFilters // výběr, v adrese jako ?dashboard[…]
Stav drží hostitel, widgety žádný
Widget je obyčejný objekt, který se z getWidgets() staví znovu při každém
requestu, takže si přes round trip nemůže pamatovat nic. Hostitel může, a dělá
to: zapíše si, jaký filtr který widget ukazuje a které odložené widgety už byly
staženy, a obojí do widgetů vrátí v getVisibleWidgets() — až po orazítkování
klíčů a ještě před dotazem na viditelnost, aby closure ve visible(fn () => …)
mohla přečíst filtr, který uživatel zvolil.
Všechny čtyři metody výše odpovídají markupem jednoho widgetu, stejnou
wire:partial oblastí:
| Spouštěč | Volá | Odpoví |
|---|---|---|
refreshWidget() |
wire:poll na obalu widgetu |
ten widget, překreslený |
filterWidget() |
wire:change na selectu widgetu |
ten widget, znovu vyřešený novým klíčem |
callWidgetAction() |
wire:click na tlačítku v hlavičce |
ten widget, poté co akce doběhla — nebo plný render, když ho akce odstranila |
loadWidget() |
wire:init na obalu odloženého widgetu |
ten widget, poprvé vykreslený |
Klíč, který nic nepojmenuje — widget skrytý od vykreslení stránky, widget bez filtru, widget, který zmizel — nezařadí žádnou oblast a request spadne zpět na plný render, místo aby odpověděl půlkou stránky.
Rozhraní HasWidgets
interface HasWidgets{ public function getWidgets(): array;}
Dashboard (deklarovaný, ne komponenta)
WithWidgets výše dává dashboard dovnitř Livewire komponenty, což stačí,
dokud ho nepotřebuje něco dalšího: komponenta se nedá zaregistrovat, vypsat
v menu ani použít na druhé stránce a její widgety nejsou odnikud jinud
dosažitelné.
Dashboard drží deklaraci sám, stejně jako Resource drží tabulku:
use NyonCode\WireCore\Widgets\Dashboard; final class SalesDashboard extends Dashboard{ public function widgets(): array { return [ StatsOverviewWidget::make()->stats([Stat::make('Tržby', '1.2M')]), ChartWidget::make()->heading('Posledních 30 dní')->type('line'), ]; } public function columns(): int { return 3; }}
php artisan make:wire-dashboard Sales vygeneruje dva soubory:
app/Dashboards/SalesDashboard.php deklaraceapp/Livewire/Dashboards/ShowSales.php stránka, která ji mountuje
Dva proto, že jeden z nich nejde otevřít. Dashboard, který nedeklaruje žádné stránky, není nikam routovaný — v menu je z něj položka bez odkazu a na každé adrese odpovídá 404 — takže generátor, který psal jen deklaraci, vracel něco, co vypadalo hotově a hotové nebylo.
Stránka je třída z wire-panels, takže ji generují panels: příkaz zavolá
make:wire-dashboard-page, pokud je ten balíček nainstalovaný, a jinak řekne, co
doinstalovat. --no-page zapíše jen deklaraci; deklarované pages() tam zůstane
tak jako tak, protože je nikdo nečte, dokud se neroutuje.
Obě šablony jsou stuby, které jde publikovat a upravit:
php artisan vendor:publish --tag=wire-core::stubs # dashboardphp artisan vendor:publish --tag=wire-panels::stubs # stránka
Publikovaný stubs/wire-core/dashboard.stub má přednost před balíčkovým, stejně
jako to dělá Laravelí stub:publish.
Registruje se stejně jako resources:
// config/wire-core.php'dashboards' => [ App\Dashboards\SalesDashboard::class,],
Kde je aplikace registruje
Dashboardy ani navigační skupiny nepatří žádnému resource ani dashboardu — jsou to věci, které skládá aplikace. wire-core na to dodává provider, stejně jako to dělá Cashier a Fortify:
php artisan vendor:publish --tag=wire-core::providers
Publikuje se jako app/Providers/WireDashboardServiceProvider.php a je váš
k úpravám: zaregistrujte ho v bootstrap/providers.php a deklarujte v něm své
dashboardy i skupiny menu na jednom místě.
Jak se vykresluje
WirePanels\Resources\Pages\DashboardPage ho vykreslí přesně tak, jako
ListPage vykresluje tabulku resource — skládá WithWidgets, takže mřížka,
pravidla viditelnosti i polling jednotlivých widgetů výše zůstávají beze změny:
use NyonCode\WirePanels\Resources\Pages\DashboardPage; class SalesDashboardPage extends DashboardPage{ protected static ?string $dashboard = SalesDashboard::class;}
Obě cesty zůstávají rovnocenné: stránka, která si deklaruje vlastní
getWidgets() a žádný dashboard nejmenuje, funguje jako dřív. Stránka, která
nedeklaruje ani jedno, se odmítne vykreslit místo prázdné mřížky — prázdná se
čte jako „žádné widgety“, ne jako chyba.
Jak se dostane do menu
Dashboard se do menu dostane stejně jako resource — implementací
ProvidesNavigation:
public static function navigation(): NavigationItem{ return NavigationItem::make('Prodej')->icon('outline:chart-bar')->group('insights');}
Workspace přitom vůbec neví, co je dashboard. DashboardRegistry je
RegistrySource a workspace vypisuje to, co mu katalog podá — díky tomu může
menu míchat resources, dashboardy a cokoli, co aplikace zaregistruje později.
Router čte tentýž katalog, takže dashboard, který deklaruje pages(), zaroutuje
Route::wireResources() stejně jako resource.
Viz Resources.
Dashboard API
| Metoda | Vrací | K čemu |
|---|---|---|
widgets(): array |
array<int, Widget> |
Widgety v pořadí rozložení. Povinné |
columns(): int |
int |
Sloupce mřížky; výchozí 2 |
customisable(): bool |
bool |
Jestli si ho každý uživatel smí přeskládat; false, a to je celé opt-in |
savedLayouts(): bool |
bool |
Jestli si každý uživatel smí držet víc uspořádání, každé pod jménem; potřebuje i customisable() |
defaultLayout(): ?array |
array|null |
Co uživatel vidí, než si cokoli uspořádá; zbytek začíná v zásobníku. Null umístí vše deklarované |
autosave(): bool |
bool |
Jestli se změna v režimu úprav uloží hned; false ponechá Uložit a Zrušit |
maxWidgets(): ?int |
int|null |
Nejvíc widgetů, které si uživatel smí umístit; null bez limitu |
filters(): array |
array<int, DashboardFilter> |
Filtry nad celým dashboardem, čtené ve widgets() přes filter() |
withFilterState(DashboardFilterState $state): static |
static |
Nastaví stránka před widgets(); deklarace ho jen čte |
filter(string $name): ?string |
string|null |
Chráněná. Hodnota jednoho filtru — jeho výchozí, dokud stránka neřekne jinak |
static key(): string |
string |
Identita, odvozená z názvu třídy bez Dashboard |
static label(): string |
string |
Lidský název; výchozí nadpis stránky |
Jak nechat každého uživatele přeskládat si ho
customisable() je tady jedna věta a za ní celá funkce: widgety, které si
uživatel položil, v jeho pořadí a v jeho velikostech, uložené per uživatel a per
dashboard. DashboardPage z toho udělá klíč, pod kterým se layout ukládá, a
přidá ovládání Přizpůsobit / Uložit / Zrušit / Obnovit — deklarovaný dashboard
tak nepotřebuje vlastní view. Každý widget na něm potřebuje vlastní key().
Tu funkci tvarují defaultLayout(), autosave() a maxWidgets() — co uvidí
nováček, jestli se změna uloží hned, kolik widgetů se vejde. Úložiště, zásobník,
group(), sizes() i ty tři popisují
Widgety → Přizpůsobitelné dashboardy.
Filtry nad celým dashboardem
Vlastní filter() widgetu zužuje ten jeden widget. Dashboard čtený jako jeden
obraz chce opak: jeden výběr — „tento měsíc“, „tento zákazník“ — na který
odpovídá každý widget, takže dvě čísla vedle sebe odpovídají na tutéž otázku.
use NyonCode\WireCore\Widgets\Dashboard;use NyonCode\WireCore\Widgets\DashboardFilter; final class ProductionDashboard extends Dashboard{ public function filters(): array { return [ DashboardFilter::make('period')->label('Období')->buttons() ->options(['week' => 'Tento týden', 'month' => 'Tento měsíc', 'all' => 'Vše']) ->default('month'), DashboardFilter::make('customer')->label('Zákazník')->placeholder('Všichni zákazníci') ->options(fn (): array => Customer::withWorkInProgress()->pluck('name', 'id')->all()), ]; } public function widgets(): array { $metrics = ProductionMetrics::for($this->filter('period'), $this->filter('customer')); return [ StatsOverviewWidget::make()->key('made')->heading('Vyrobeno') ->stats([Stat::make('Kusů', (string) $metrics->made())]), StatsOverviewWidget::make()->key('server')->heading('Účetní server') ->ignoresDashboardFilters() ->stats([Stat::make('Stav', $this->serverStatus())]), ]; }}
Výběr žije na stránce a v adrese. DashboardPage ho drží v
$dashboardFilters, který je v query stringu jako ?dashboard[period]=week —
dashboard se tak dá poslat odkazem přesně tak, jak ho odesílatel viděl, a
obnovení stránky ho zachová. Drží se jen hodnoty, které se liší od výchozích,
takže nefiltrovaný dashboard má čistou adresu. Vlastnost je zamčená: lišta
filtrů ji mění přes setDashboardFilter() a cokoli přišlo v adrese se ověří
dřív, než to kdokoli přečte.
Hodnota zůstane, jen když ji filtr nabídl. Všechno ostatní — starý odkaz,
překlep, id z doby před změnou voleb — spadne na výchozí hodnotu filtru, místo
aby se dostalo k dotazu, který ji čte. Výchozí null znamená „nezužuje nic“ a
kreslí se jako placeholder.
Stránka předá vyhodnocené hodnoty dashboardu dřív, než běží widgets()
(withFilterState()), a filter() je čte; dashboard postavený bez stránky čte
své výchozí hodnoty. Ručně psaný hostitel WithWidgets deklaruje
getDashboardFilters() a v getWidgets() čte $this->dashboardFilter('period')
stejně.
Widget, který zúžit nejde, to řekne. ignoresDashboardFilters() na něj dá
značku „Bez filtru“, dokud se kterýkoli filtr liší od výchozího — stav externího
serveru nemá zákazníka a čtenář, který ho porovnává s filtrovanými sousedy,
to potřebuje vědět. Značka leží přes spodní okraj karty a nezabírá místo, takže
kartu nikdy neposune mimo řadu s kartami vedle ní.
Kde se kreslí. DashboardPage dá lištu filtrů vedle ovládání rozložení.
Vlastní hostitel ji vloží tam, kam ji chce jeho layout:
@include('wire-core::widgets.partials.widget-filters') {{-- bez filtrů nevykreslí nic --}}@include('wire-core::widgets.widget-grid')
Filtr deklarovaný buttons() — pár období čitelných na první pohled — je řada
tlačítek, ostatní jsou select, kde volby jsou záznamy. Odkaz „Zrušit filtr“ se
objeví, jen dokud je něco zúžené.
DashboardFilter API
DashboardFilter::make(string $name)->label(string|Closure|null $label) // výchozí je jméno jako nadpis->options(array|Closure $options) // hodnota => popisek; closure se vyhodnotí, až je potřeba->default(int|string|null $default) // hodnota, když není nic vybráno; null nezužuje nic->placeholder(string|Closure|null $placeholder) // prázdná volba selectu — výchozí: popisek->buttons(bool $buttons = true) // řada tlačítek místo selectu->getOptions(): array // klíčované hodnotou tak, jak ji nese adresa->getDefault(): ?string->isButtons(): bool->resolve(mixed $value): ?string // nabídnutá hodnota, jinak výchozí
Přidání widgetu na dashboard, který nevlastníte
Dashboard z nainstalovaného modulu deklaruje svoje
widgety uvnitř balíčku, takže aplikace k nim přidá přes
hook widget.configuring:
$manager->hook(Hook::WidgetConfiguring, function (WidgetConfiguringPayload $payload) { $payload->widgets[] = StatsOverviewWidget::make()->stats([Stat::make('Otevřené', '12')]); return $payload;}, for: 'sales');
for: pojmenuje klíč, pod kterým se dashboard zaregistroval — DashboardPage jím
odpoví. Stránka, která si widgety deklaruje sama, neukazuje nic registrovaného
a zúží se podle třídy.
Běží dřív, než se orazítkují klíče, a dřív, než seznam profiltruje viditelnost
— a obojí je podstatné: klíč widgetu se odvozuje z jeho indexu v neprofiltrovaném
seznamu, takže widget přidaný později by žádný neměl (a poll tick by ho nedosáhl),
nebo by si vzal ten, na který už jiný widget slyší; a visible() přidaného widgetu
platí, místo aby se přeskočilo.
Související
- Widgety — to, co se skládá
- Panely: Stránky —
DashboardPage, komponenta, která deklarovaný dashboard vykreslí - Panely: Navigace — jak se dashboard dostane do menu
- Panely: Moduly — deklarace dashboardů oblasti spolu s jejími resourcy