Přeskočit obsah
V
Pro vývojáře
Architektura, konvence, jádro systému a bezpečnost
UI vrstva / Práce s tabulkami

Práce s tabulkami#

DataGrid je komponenta výpisu v administraci: sloupce, filtry, řazení, stránkování, akce na řádku, hromadné akce a volitelně strom s přesouváním. Kód: app/UI/Admin/System/Components/DataGridComponent/.

Zdrojem dat je manager, ne dotaz — grid si sám poskládá filtry, řazení i stránkování a předá je do findAll().

Z čeho se grid skládá#

Prvek Složka K čemu
Column, MultiColumn, RelationColumn Columns/ jeden sloupec, víc hodnot v jednom sloupci, sloupec nad vazbou
RowAction, GroupAction Actions/ akce na řádku, hromadná akce nad zaškrtnutými
TextFilter, SelectFilter, MultiSelectFilter, RangeFilter, MappedSelectFilter, PrecomputedIdsFilter Filters/ filtr v hlavičce sloupce
22 rendererů (BoolRenderer, PriceRenderer, ImageRenderer, ToggleRenderer, …) Renderers/ jak se hodnota vykreslí

Postup: zakládám nový výpis#

  1. Továrnu gridu injektuj do presenteru (DataGridFactory) a napiš createComponentGrid(): DataGrid.
  2. Zdroj a základ výpisu:
$grid = $this->dataGridFactory->create();
$grid->setDataSource($this->tagManager)
    ->setIncludes(['translations'])
    ->setItemsPerPage($this->getAdminItemsPerPage())
    ->setDefaultOrder('id', 'desc')
    ->setAddLink('add');
  1. Sloupce přes Column::create(); k čemu se má filtrovat, dostane setFilter():
$grid->addColumn(
    Column::create('name')
        ->setLabel($t('columns.name'))
        ->setProperty('translations.name')   // tečková cesta přes vazbu
        ->setSortable()
        ->setWidth(60)
        ->setFilter(TextFilter::create())
);
  1. Akce na řádku — buď odkaz (setLink()), nebo signál (setSignal()):
$grid->addRowAction(
    RowAction::create('edit')->setIcon('edit')->setVariant('success')
        ->setLink('edit', ['id' => 'id'])
);
$grid->addRowAction(
    RowAction::create('delete')->setIcon('cross')->setVariant('danger')
        ->setSignal('delete')->setConfirm($t('confirmDelete'))
);
  1. Hromadné akce přes GroupAction::create() — vlastní chování dostane setCallback().
  2. Šablona obsahuje jen {control grid}.
  3. Prokliknij filtr, řazení, stránkování, obě akce a prázdný stav.

setIncludes() musí pokrývat všechno, co grid zobrazuje

Sloupec s tečkovou cestou (translations.name) potřebuje tu vazbu načtenou. Bez ní se nevypíše chyba — jen prázdná buňka, což vypadá jako chybějící data.

Tečkové cesty joiny doplní samy, ale hloubka něco stojí

category.parent.name funguje, jen se to promítne do dotazu. U výpisu, který zobrazuje stovky řádků, je levnější denormalizovaný sloupec.

Šířky sloupců#

setWidth(X) dá hlavičce třídu width-X. SCSS generuje pravidla jen pro násobky pěti od 5 do 100 (@for $i from 1 through 20 { th.width-#{$i * 5} }).

Jiná hodnota než násobek pěti se tiše zahodí

setWidth(12) vyrobí třídu width-12, ke které žádné CSS pravidlo neexistuje — sloupec zůstane bez nastavené šířky. Nespadne to a v kódu to vypadá správně; pozná se to jen pohledem na rozvržení. (V kódu takové případy dnes jsou.)

Řazení#

setDefaultOrder() bere směr jako řetězec ('asc' / 'desc') a převede si ho sám. První volání zahodí vestavěné id DESC, další volání přidávají další úroveň:

$grid->setDefaultOrder('type')->setDefaultOrder('name');   // ORDER BY type ASC, name ASC

V surovém poli orderBy je směr BOOL, ne řetězec

Když řazení skládáte ručně pro findAll(), platí ['id' => true]ASC, ['id' => false]DESC. ['id' => 'DESC'] seřadí VZESTUPNĚ, protože neprázdný řetězec je truthy. Nikde to nespadne, výpis vypadá seřazeně — jen obráceně, takže „posledních deset“ ukáže deset nejstarších.

Bez zahození vestavěného id DESC by nastavení tiše nedělalo nic

Tak se to kdysi chovalo: setDefaultOrder('reports', 'desc') vyrobilo ORDER BY id DESC, reports DESC a obrazovka nahlášených hodnocení neplnila svůj hlavní účel. Dnes je to opravené, ale je dobré vědět, proč to tak je.

Vlastní obsah buňky#

Vykreslení se řeší rendererem, ne anonymní funkcí v definici sloupce. Renderery jsou hotové pro většinu typů: BoolRenderer, DateRenderer, PriceRenderer, ImageRenderer, EmailRenderer, PhoneRenderer, BadgeRenderer, ToggleRenderer, LinkRenderer, PdfDownloadRenderer a další.

Pro odkazy z buňky existuje setLink() přímo na sloupci; pro úpravu celé stránky výsledků naráz je setRowDecorator(), který se zavolá jednou po načtení.

Renderer se dá napsat vlastní

Stačí implementovat CellRenderer. Je to lepší než HTML v šabloně gridu — ten je sdílený všemi výpisy v administraci.

Stromový výpis#

setTreeStructure() přepne grid na strom (nested set): výpis se řadí do hloubky, buňka se odsazuje podle úrovně a řádky dostanou šipky pro přesun mezi sourozenci. Přesun i mazání dělá modelová vrstva set-based SQL nad lft/rgt, grid jen zavolá moveUp() / moveDown() / deleteNode().

setTreeScope(['menuId' => $id]) řeší víc stromů v jedné tabulce — přidá se do filtrů výpisu i do přesunových dotazů, aby přepočet lft/rgt nezasáhl cizí strom.

Bez setTreeScope() přepočet zasáhne i cizí stromy v téže tabulce

Přesun položky v jednom menu přečísluje i ostatní menu. Neprojeví se to chybou — projeví se to rozsypaným pořadím jinde, než kde jste pracovali.

Smazání uzlu odstraní celý podstrom

A zacelí po něm mezeru v lft/rgt. V seznamu to vypadá jako smazání jednoho řádku.

Přesun na kořen stromu je nejrizikovější operace

Přepočítává se celá struktura. U velkého stromu to trvá a při přerušení zůstane strom nekonzistentní — a nekonzistentní nested set se projeví jako náhodně mizející větve, ne jako chyba.

Mazání#

Signál i hromadná akce volají na manageru metodu delete. setDeleteMethod('softDelete') to přepne na jinou — e-shopové gridy to tak dělají.

Hromadná akce dostává POLE identifikátorů

Obsluha, která čeká jedno id, spadne. A protože je základ sdílený všemi výpisy v administraci, spadnou tím naráz všechny — ne jen ten, který jste upravovali.

Stav výpisu#

Grid si pamatuje stránku, řazení i filtry.

„Výpis nic neukazuje“ bývá zapamatovaný filtr z minula

Zkuste ho vyresetovat dřív, než začnete hledat chybu v kódu nebo v datech.

Režim výběru#

Grid se umí vykreslit jako obsah Pickeru — bez přidání, editace, mazání a zaškrtávátek, s tlačítkem „Vybrat“ na řádku. Akci, která má být vidět i tam, označte showInPickerMode().

Podrobně Admin presenter, oddíl o režimu Pickeru.

Kam sáhnout#

Chci Kde
API gridu app/UI/Admin/System/Components/DataGridComponent/DataGrid.php
definici sloupce …/Columns/BaseColumn.php, Column.php, MultiColumn.php
filtry …/Filters/
renderery …/Renderers/
šablonu gridu …/Templates/Default/
SQL pro strom app/Core/Traits/Api/Models/Repositories/TreeTrait.php
směr řazení v SQL app/Core/Utils/Helpers/Sqls/OrderByHelper.php
hotový vzor app/UI/Admin/Blog/Presenters/TagPresenter.php

Navazující kapitoly: Admin presenter · Picker · Filtry · Formuláře