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, SelectLikeFilter, PresetSelectFilter Filters/ filtr v hlavičce sloupce
23 rendererů (BoolRenderer, PriceRenderer, QuantityRenderer, 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()). Upravit dostane setAlwaysVisible(), ostatní akce skončí v nabídce „⋯“ (viz Akce na řádku):
$grid->addRowAction(
    RowAction::create('edit')->setIcon('edit')->setVariant('success')
        ->setAlwaysVisible()
        ->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(). Vykreslí se v panelu „Hromadné akce“ pod tabulkou.
  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.

Akce na řádku#

Šablona Parts/_row_actions.latte rozdělí akce řádku do tří skupin:

Akce Kde se vykreslí
s RowAction::setAlwaysVisible() přímo v řádku jako tlačítko s popiskem
ostatní v rozbalovací nabídce „⋯“ na konci řádku
nebezpečné (varianta danger nebo ikona cross) vždy na konci nabídky, oddělené čarou a červeně, bez ohledu na setAlwaysVisible()

Stromový grid přidává do nabídky ještě „Ve stromu“ (plochý pohled) a „Přesunout“. Nabídka je Bootstrap dropdown (klávesnice, Escape, klik mimo) se strategií fixed, takže ji vodorovně posuvný obal tabulky neořízne. Po AJAX překreslení řádku funguje dál.

Konvence: akce Upravit dostává ->setAlwaysVisible(), mají ji tak všechny výpisy v administraci. Další akci nechte v řádku jen tehdy, když ji uživatel potřebuje u většiny řádků.

Bez setAlwaysVisible() zmizí Upravit z řádku

Akce se nevytratí, jen se schová do „⋯“. Výpis vypadá funkčně, ale hlavní akce je o klik dál a v řádku zůstane jen nabídka.

Mazání do řádku nedostanete

setAlwaysVisible() na akci s variantou danger nebo ikonou cross nemá účinek. Je to záměr: smazání nesmí být jeden klik vedle editace.

Hromadné akce#

Hromadné akce (GroupAction) se vykreslí v trvale viditelném panelu Hromadné akce pod tabulkou (Parts/_foot.latte). Panel ukazuje stav výběru a vedle akcí gridu má dvě tlačítka:

Tlačítko Atribut Co dělá
Označit zobrazené data-grid-select-page zaškrtne všechny řádky aktuální stránky
Zrušit výběr data-grid-select-none odškrtne vše

Obsluhuje je datagrid.js (delegovaně, takže přežije AJAX překreslení) přes kořen panelu [data-grid-bulk]. Bez výběru jsou akční tlačítka vypnutá, „Označit zobrazené“ se vypne, až je označeno všechno. Texty stavu výběru jsou v NEONu (data-text-none, data-text-some s %count%), ne v JS.

Vypnuté tlačítko není kontrola

Bez JavaScriptu zůstanou akce aktivní. Obsluha hromadné akce proto dál musí sama ověřit, že dostala neprázdné pole id, a server výběr ověřuje jako dřív.

Styl filtrů#

Jak se filtry a řazení vykreslí, určuje nastavení administrace admin.dataGrid.filterStyle (modul 1, upravuje se v Nastavení administrace vedle počtu položek na stránku). Čte ho DataGrid::getFilterStyle() ze SettingContext, takže nestojí dotaz navíc.

Hodnota Konstanta Vzhled
style_1 (výchozí) FILTER_STYLE_COLUMNS řádek filtrů pod nadpisy sloupců, šipky řazení v nadpisu
style_2 FILTER_STYLE_PANEL samostatný panel „Vyhledávání a řazení“ nad tabulkou, po načtení zavřený
style_3 FILTER_STYLE_HEADER řádek filtrů pod nadpisy, řazení klikem na nadpis (další klik obrátí směr)

Kořen gridu nese data-filter-style="style_…", podle něj se dá stylovat. Chybějící nebo neznámá hodnota a grid v režimu Pickeru dají style_1. Mění se jen vykreslení: jména polí filtrů, submit search, persistentní parametry i signály jsou ve všech stylech stejné, presenter se kvůli stylu nemění. Klíč zakládá migrace sql/2026-10-29_25_admin_datagrid_filter_style.sql.

Vlastní šablona části gridu v tématu#

Patičku a akce řádku si admin téma může nahradit bez kopie celého gridu. DataGrid::render() hledá obě části přes BaseComponent::resolveThemeTemplatePath() a předá cestu do šablony:

Část Proměnná v šabloně Výchozí soubor
akce řádku $rowActionsTemplate Templates/Default/Parts/_row_actions.latte
patička a panel hromadných akcí $footTemplate Templates/Default/Parts/_foot.latte

Obě admin témata dnes používají výchozí části; vlastní verze tématu by ležela v Templates/<Téma>/default/Parts/. Pořadí hledání popisuje Komponenty.

Přepsaná část musí zachovat jména a atributy

Tlačítka hromadných akcí musí zůstat <button n:name="'group__' . …"> (formulář gridu je čte podle jména) a panel potřebuje data-grid-bulk, data-grid-select-page a data-grid-select-none, jinak datagrid.js výběr neobslouží. Akce řádku musí dál volat buildRowActionHref() a POST akce odesílat formulářem gridu.

Šíř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ší. Množství (stav skladu, rezervace, šarže) vykresluj QuantityRenderer::create(): „1,5“ místo 1.500, „5“ místo 5.000, s oddělovačem lokalizace; export CSV dostane strojový tvar s tečkou (1.5).

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.

Odkaz z buňky#

setLink() na sloupci obalí celý obsah buňky odkazem, s jakýmkoli rendererem. Parametry odkazu se mapují z vlastností řádku (URL parametr => cesta k vlastnosti):

Column::create('name')
    ->setLink($canEdit ? ':Admin:System:User:edit' : null, ['id' => 'id']);
Volání Co udělá
setLink(null, …) odkaz vypne; hodí se pro podmínku podle práva
$requiredParams vynechaný (null) odkaz vznikne, jen když jsou vyplněné všechny namapované parametry
$requiredParams = ['id'] povinné jsou jen vyjmenované parametry
$requiredParams = [] odkaz vznikne vždy, i s prázdnými parametry

Výchozí chování chrání před pádem link() na řádku, kterému chybí id: buňka se vypíše jako text.

Na sloupec, jehož renderer sám kreslí <a>, setLink() nedávejte

EmailRenderer, LinkRenderer nebo přepínač by skončily jako odkaz v odkazu. To je neplatné HTML, prohlížeč ho rozdělí po svém a kliknutí vede jinam, než čekáte.

Víc položek v jedné buňce#

Renderer Hodnota sloupce Odkud data
LinkListRenderer pole entit; text, tooltip a parametry odkazu se berou z každé položky dávkově v setRowDecorator() do #[Transient] vlastnosti řádku, vzor app/UI/Admin/Eshop/Presenters/Traits/Lists/OrderListTrait.php
BadgeListRenderer štítky z callbacku nad celou entitou mapa, kterou presenter načte předem; žádný dotaz na řádek

Sloupec s jedním z nich se neřadí ani nefiltruje. Text štítku BadgeListRenderer se nepřekládá, šablona ho jen escapuje (jde o data z databáze, např. název role).

Vlastnost pro LinkListRenderer musí vracet pole, ne Collection

ValueResolver zredukuje kolekci na konci cesty na její první prvek. Buňka pak tiše ukáže jednu položku místo všech.

Filtry, které nejsou rovnost#

SelectFilter porovnává hodnotu sloupce s volbou. Čtyři jeho potomci řeší případy, kdy to nestačí:

Filtr Kdy Co postaví Vzor
MappedSelectFilter volba není hodnotou sloupce (vybírá se sekce, sloupec drží threadId) IN (…) z mapy volba => hodnoty sloupce v setValueMap() app/UI/Admin/Base/Presenters/CommentPresenter.php
PrecomputedIdsFilter ano/ne nad agregací z jiné tabulky („pod minimem“ přes všechny sklady) pole IN (:ids) z setIdsProvider(); provider se zavolá jen u aktivního filtru app/UI/Admin/Store/Presenters/StockItemPresenter.php
SelectLikeFilter sloupec drží seznam hodnot v jednom poli (JSON pole příznaků) LIKE na výskyt volby; projdou jen volby z setOptions() app/UI/Admin/System/Presenters/PaymentPresenter.php
PresetSelectFilter volba je pojmenovaná podmínka: skupina hodnot nebo kombinace sloupců („Po splatnosti“ = nezaplaceno a splatnost před dneškem) hotový Core filtr z mapy volba => filtr v setPresets(); sloupec filtru jen určí místo v hlavičce, jeho cesty se nepoužijí stav faktury app/UI/Admin/Invoicer/Presenters/InvoicePresenter.php, skupiny stavů plateb PaymentPresenter.php
->setFilter(
    PresetSelectFilter::create()
        ->setOptions($statusLabels)                                        // paid / unpaid / overdue
        ->setPresets(InvoiceStatusChoice::buildPresets(new \DateTime('today'))) // volba => Core filtr
)

Hodnota přijde z požadavku (?grid-filters[status]=overdue), takže na předfiltrovaný výpis jde odkázat. Tak odkazují widgety dashboardu i karty „Co čeká na vyřízení“. Neznámá nebo prázdná volba nefiltruje. Podmínky drží jedno místo (InvoiceStatusChoice, PaymentStatusBuckets), ze kterého čte filtr gridu, počty widgetu i jeho odkazy, takže karta a výpis, který otevře, napočítají totéž.

MappedSelectFilter: volba s prázdným seznamem nevrátí nic

Prázdný seznam se filtruje jako IN (0). Je to schválně: „ukaž sekci, kde zatím nic není“ má odpověď prázdný výpis, ne všechno. Volba, která v mapě chybí úplně, filtr nepoužije.

PrecomputedIdsFilter potřebuje setTargetField() na skutečný sloupec

Sloupec, na kterém filtr sedí, obvykle ukazuje odvozenou hodnotu bez sloupce v databázi. Bez setTargetField() míří IN právě na ni a výpis se zapnutým filtrem nefunguje. Cílem je mapovaný sloupec: primární klíč řádku nebo jeho cizí klíč. Prázdná množina id dá prázdný výpis, ne všechno.

Stromový výpis#

setTreeStructure() řekne gridu, že záznamy tvoří strom (nested set). Grid pak nabídne v liště tři pohledy:

Pohled Co ukazuje Jak se načítá
Seznam (výchozí) plochý stránkovaný výpis se sloupcem „Cesta“ (předci řádku) a akcí „Ve stromu“ filtry, řazení a stránkování v SQL, předci řádků stránky jedním dotazem
Strom hierarchie s ▸/▾, šipkami mezi sourozenci a zaměřením na podstrom po rodičích: kořen nebo zaměřený uzel a rozbalené uzly, bez stránkování
Setřídit vnořený seznam s přetahováním celý strom; jen s enableDragDrop() a právem edit

Volba pohledu se pamatuje v session pro každý grid, výchozí změní setTreeDefaultView(DataGrid::VIEW_TREE). Hledání nebo řazení podle sloupce přepne i pohled Strom na plochý výpis, protože hierarchii rozbije.

Stav stromu Kde žije
rozbalené uzly session, zvlášť pro presenter, komponentu a scope
zaměření na podstrom persistentní parametr treeRoot, adresu jde poslat dál
první otevření strom do DataGrid::TREE_FULL_LIMIT (500) uzlů celý rozbalený, větší jen kořeny; „Rozbalit vše“ nad limitem odmítne
děti jednoho rodiče nejvýš setTreeChildrenLimit() (výchozí 500), zbytek jako řádek „… dalších N“

setTreeCollapsible(false) je strom vždy celý rozbalený, bez session a zaměření, pro malé stromy přetahované napříč (menu). Když přeroste limit, chová se z pojistky jako sbalitelný a zapíše varování do logu.

Přesun i mazání dělá modelová vrstva set-based SQL nad lft/rgt, grid jen zavolá metodu manageru:

Přesun Zapnutí Metoda manageru
šipky ↑/↓ setTreeStructure() moveUp() / moveDown()
přetažení v pohledu Setřídit enableDragDrop() moveSubtreeTo() s rodičem a sousedy, jak je klient viděl
jen pořadí sourozenců, bez změny rodiče enableDragDrop(flat: true), vzor AbstractParameterPresenter moveSubtreeTo(), vnořené cíle neaktivní
„Přesunout do…“ (modál s výběrem rodiče a pozice) enableDragDrop() a strom s pravou mezí rgt moveSubtreeTo(), nikdy save() entity
„Seřadit abecedně“ (přímé děti podle popisku v aktuálním jazyce) totéž a manager s reorderChildren() reorderChildren() jednou operací
smazání akce delete deleteNode()

Přetažení i modál posílají rodiče a sousedy, jak je uživatel viděl. Změnil-li se strom mezitím, přesun vrátí TREE_STALE a nezapíše nic; grid ukáže hlášku a obnoví výpis.

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.

Co se při přesunu děje v modelové vrstvě (zámek stromu, kódy TREE_STALE a TREE_LOCKED, změna rodiče z formuláře), popisuje kapitola Stromy.

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.

Práva ve výpisu#

Grid si klíče práv odvozuje z připojeného presenteru: :Admin:<Modul>:<Presenter>:<akce>, stejně jako brány presenteru. Ruční podmínka isAllowed() kolem addRowAction() není potřeba; když tam je, nevadí.

Co Klíč Bez práva
tlačítko „Přidat“ z setAddLink('add') …:add tlačítko se nevykreslí
řádková i hromadná akce delete, signál delete! …:del akce se nevykreslí, signál vrátí 403
hromadná akce s vlastním callbackem přípona z GroupAction::setPrivilege() tlačítko se nevykreslí, odeslání vrátí 403
inline editace, přepnutí vazby, šipky, přetažení, „Přesunout do…“, „Seřadit abecedně“ …:edit signál vrátí 403, pohled Setřídit se nenabídne
export CSV vlastní nemá platí brána čtení stránky

Signál delete! vrátí 403 i tehdy, když grid akci delete vůbec nezaregistroval. setAddLink() odvozuje klíč jen z relativní akce ('add'); u absolutní destinace (':Admin:Jiny:Modul:add') tlačítko neskrývá. Vlastní klíč dejte třetím parametrem, '' odvození vypne. Mimo admin větev (presenter, jehož jméno nezačíná Admin:) se klíč odvodit nedá a grid nic neskrývá ani neblokuje.

Postup: hromadná akce, kterou nesmí každý#

  1. Akci zaregistrujte s příponou práva:
$grid->addGroupAction(
    GroupAction::create('exportXml')
        ->setPrivilege('export')
        ->setCallback(fn(array $ids) => $this->exportXml($ids))
);
  1. Klíč :Admin:<Modul>:<Presenter>:export založte v tabulce práv a povolte ho roli admin.
  2. Ověřte s rolí bez práva: tlačítko chybí a ručně odeslaný formulář vrátí 403.

Obrazovka s mazáním nebo zápisem v gridu potřebuje klíč del a edit

Brány jsou fail-closed. Bez řádku …:del (a …:edit u inline editace a stromu) povoleného roli admin nesmaže ani nepřepne nic ani administrátor. U del to hlídá kontrola 1.7 v tests/Manual/Admin/privilege_enforcement_audit.php.

Zapisující signál gridu přijímá jen POST

inlineUpdate!, relationToggle!, delete!, moveUp!, moveDown!, dragDrop! a sortChildren! mají #[Requires(methods: 'POST')]. Řádková akce se setSignal() je proto tlačítko odesílané POSTem; čtecí signál to vypne setPost(false). GET odkaz by v přihlášené relaci spustil robot nebo přednačtení prohlížeče. Hlídá to GridWritingSignalsRequirePostTest.

setVisibleWhen() jen skrývá tlačítko

Podmínka pro jednotlivý řádek (fn(object $row): bool, vzor app/UI/Admin/Invoicer/Presenters/InvoicePresenter.php) zrcadlí stav entity, brána mazání ji neřeší. Zakázaný stav musí odmítnout manager v beforeDelete(), jinak řádek smaže ručně poslaný signál.

Export do CSV#

enableCsvExport('zakazky-' . date('Y-m-d') . '.csv') přidá do lišty tlačítko exportu. Export vezme aktuální filtry a řazení, ale všechny řádky bez stránkování, načtené z manageru po dávkách 500. Sloupce jsou sloupce gridu bez akcí, text buňky vrací CellRenderer::toCsvValue().

Vlastnost souboru Hodnota
oddělovač středník
kódování UTF-8 s BOM, jinak ho Excel v češtině otevře v jiném kódování
hlavičky odpovědi PrivateDownloadHeaders, soubor nezůstane v cache

Postup: zapínám export

  1. V továrně gridu zavolejte enableCsvExport() se jménem souboru (vzor app/UI/Admin/Company/Presenters/WorkListPresenter.php).
  2. Vlastní renderer, jehož prepare() nevrací klíč text (kreslí HTML, přepínač, ikonu), přepíše toCsvValue(), jinak v exportu bude surová hodnota.
  3. Ověřte export se zapnutým filtrem: obsahuje jen filtrované řádky, i ty za první stránkou.

Vlastní export mimo CsvExportTrait musí volat CsvCellSanitizer

Buňku začínající =, +, -, @, tabulátorem nebo CR spustí Excel jako vzorec, i když ji zadal anonymní návštěvník veřejného formuláře. CsvCellSanitizer před ni přidá apostrof, a to na jediném místě těsně před fputcsv(), ne v rendererech: nový renderer by na ochranu zapomněl. Proto mají apostrof i záporná čísla.

Hromadná akce může vrátit soubor ke stažení

processGroupAction() po callbacku volá redirect('this'), ale sendResponse() v callbacku vyhodí AbortException dřív, takže stažení projde bez dalšího signálu. Formulář gridu se odesílá bez AJAXu, odpověď jde rovnou do prohlížeče. Chybová větev (flash a return) nechá redirect proběhnout normálně. Vzor: CatalogExportDownloadTrait::sendCatalogExport() (CallbackResponse + readfile()).

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/, části v …/Templates/Default/Parts/
akce řádku a nabídku „⋯“ …/Actions/RowAction.php, …/Templates/Default/Parts/_row_actions.latte
panel hromadných akcí …/Templates/Default/Parts/_foot.latte, www/themes/admin/*/desktop/assets/js/datagrid.js
brány práv a signály …/Traits/HandleTrait.php, DataGrid::isActionAllowed()
export CSV …/Traits/CsvExportTrait.php, …/Utils/CsvCellSanitizer.php
pohledy a stav stromu …/Traits/TreeStateTrait.php, …/Utils/TreeRowsBuilder.php, TreePathBuilder.php
„Přesunout do…“ a seřazení dětí …/Traits/TreeMoveTrait.php
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

Nastavení cookies