Obsah
DataGrid
Komponenta výrazně zjednodušuje tvorbu přehledových tabulek a zajišťuje vizuální prezentaci těchto dat uživateli. Umožňuje data rychle třídit, filtrovat a manipulovat s nimi uživatelsky definovanými rutinami. A to vše s plnou AJAXovou podporou pro maximální uživatelský požitek.
| Verze | 1.0-alpha |
| Download | github |
| Demo | http://demo.datagrid.romansklenar.cz |
| Autor | Roman Sklenář |
| Licence | New BSD License |
Ukázka
Ukázka uživatelského rozhraní
Použití
DataGrid vytvoříme nejlépe továrničkou na komponenty.
Vytvoření komponenty
protected function createComponentGrid($name)
{
$grid = new DataGrid;
$model = new Model;
$grid->bindDataTable($model->getDataSource('customers'));
$this->addComponent($grid, $name);
}
Tímto jsme si nadefinovali jednoduchý datagrid. Jediné, co nutně
potřebuje, je zdroj dat, ze kterých má čerpat. Ten mu předáme metodou
bindDataTable(), která přijme data ve formě DibiDataSource.
Příklad výše předpokládá třídu Model s metodou
getDataSource($table) vracející DibiDataSource
tabulky ze zadané databáze. DibiDataSource může mít více
podob, proto je možné zobrazit data ze složených dotazů včetně
spojování tabulek. Konkrétní implementace modelů i podoby datasourců
jsou tedy plně na vás.
Šablona si datagrid vykreslí pomocí makra widget. AJAXovou
funkčnost získáte klasickým aplikováním zavináče před volání této
funkce.
{* vykreslení v šabloně *}
@{control grid}
Tento datagrid automaticky vykreslí všechny sloupce z předaného datasourcu, podle kterých umožní vícenásobné řazení a zobrazí 15 položek (řádků) na stránku.
Chceme-li řadit pouze podle jednoho sloupce a zobrazit na stránku například pouze 10 položek, upravíme si definici datagridu v továrničce.
$grid->itemsPerPage = 10; // výchozí počet řádků na stránku
$grid->multiOrder = FALSE; // pro řazení použij vždy jen jeden sloupec
Dále můžeme dát uživateli možnost výběru počtu zobrazených řádků na stránce nebo úplně zakázat řazení.
$grid->displayedItems = array(10, 20, 50); // roletka pro výběr počtu řádků na stránku
$grid->disableOrder = TRUE; // zrušení možnosti řazení nad všemi sloupci
Nicméně jsme stále na začátku. Takovýto datagrid bude jistě obsahovat sloupce, které nechceme uživateli zobrazit, například primární a cizí klíče. To se dá lehce změnit.
Sloupce
Třída DataGrid obsahuje sadu továrniček, které vytvoří
instance tříd reprezentující sloupce, se kterými datagrid vnitřně
pracuje.
Lze takto například přidávat sloupce pro zobrazení atributů textového charakteru či reprezentující čísla, logické hodnoty nebo data.
// sloupce pro textové atributy
$grid->addColumn('customerName', 'Zákazník');
$grid->addColumn('contactLastName', 'Kontaktní osoba');
$grid->addColumn('city', 'Město');
// reprezentace logických hodnot je realizována checkboxy
// číslo větší než 1 je považováno za pravdivou logickou hodnotu
$grid->addCheckboxColumn('orders', 'Má objednávky?');
// sloupec pro zobrazení datumu či času
$grid->addDateColumn('orderDate', 'Datum poslední objednávky');
// sloupec pro číselné atributy
$grid->addNumericColumn('creditLimit', 'Limit kreditu');
Všechny mají společnou syntax prvních dvou parametrů: název atributu z datasource a textový popis. Další parametry jsou volitelné ale stojí také za zmínku. Přepíšeme s nimi tedy úvodní definici.
$grid->addColumn('customerName', 'Zákazník');
$grid->addColumn('contactLastName', 'Kontaktní osoba', 30); // ořež text delší než 30 znaků
$grid->addColumn('city', 'Město');
$grid->addCheckboxColumn('orders', 'Má objednávky?');
$grid->addDateColumn('orderDate', 'Datum poslední objednávky', '%m/%d/%Y'); // český formát: '%d.%m.%Y'
$grid->addNumericColumn('creditLimit', 'Limit kreditu', 0); // počet desetinných míst
Popisky přijímají i objekty Nette\Web\Html, takže můžeme si dovolit i definice, například pro zkrácení názvu u sloupců reprezentující logické hodnoty zobrazující jen checkboxy, u kterých by se zbytečně plýtvalo místem.
$caption = Html::el('span')->setText('O')->title('Má objednávky?')->class('link');
$grid->addCheckboxColumn('orders', $caption)
Filtry
Abychom mohli data dohledávat lépe než se k nim proklikávat na určitou stránku, můžeme přidat filtrační pole, které slouží uživateli jako vstupy. Filtry vytváříme na konkrétním sloupci opět továrničkou (tentokrát už továrničku zaštiťuje samotný sloupec, na datagrid) a opět jich máme ve výchozím stavu povícero druhů.
// přidání filtru při definici sloupce (Fluent rozhraní)
$grid->addColumn('customerName', 'Zákazník')->addFilter();
// alternativně při již definovaném sloupci (přes ArrayAccess)
$grid['customerName']->addFilter();
// tam kde to má smysl, můžeme filtrovat pomocí selectboxu nebo checkboxu
$grid['city']->addSelectboxFilter();
$grid['orders']->addCheckboxFilter();
Občas ale není filtrování pomocí checkboxu vhodné, má totiž jen 2 stavy (odškrtnuto/zaškrtnuto), a je lepší použít selectbox a předat mu parametrem asociativní pole hodnot, kterých filtr může nabývat.
// druhý parametr je jen zkratka pro známé skipFirst() z Nette\Forms\SelectBox
$grid['orders']->addSelectboxFilter(array('?' => '?', '0' => "Nemá objednávky", '1' => "Má objednávky"), TRUE);
Položky selectboxů jsou automaticky překládány translátorem pokud je
definován (o tom níže), takže pokud je filtr selectboxu číselník jako
sloupec city (narozdíl od sloupce orders, který
obsahuje námi definované hodnoty), tak se nám nevyplatí jej překládat a
může to být v jistých případech brzda aplikace. Proto můžeme volitelně
automatické překládání vypnout.
$grid['city']->addSelectboxFilter()->translateItems(FALSE);
Použitý sloupec definuje i jakým způsobem lze data filtrovat.
- pro číselné datové typy umožňuje
NumericColumnfiltrovat data pomocí operátorů>, >=, <, <=, =, <> - pro textové datové typy umožňuje
TextColumnfiltrovat za použití syntaxe jednoduchých regulárních výrazů s podporou automatického přepisu hvězdiček (auto*→^auto.*,*mobil→.*mobil$) pro netechnicky založené uživatele, kteří znají použití hvězdičky jako zástupný znak pro cokoliv například z Windows či Wordu
Databáze SQLite2 nepodporuje klíčové slovo
REGEXP, proto je pro podporu této funkčnosti nutné zaregistrovat
vlastní funkci. Konkrétní ukázku je možné nalézt v modelu
demo aplikace.
Pokud vám chování nějakého filtru nevyhovuje, můžete jej podědit či
rovnou vytvořit nový, aniž byste přišli o luxus továrničky tím, že
pomocí extension
metody tuto továrničku ke třídě DataGridColumn
připojíte.
Automatické ukládání stavů komponenty
Pokud s DataGridem na stránce pracujeme (stránkujeme, filtrujeme, řadíme) a chceme, aby se nám stav komponenty ukládal a zobrazil ve stejném stavu i po zavření a znovuotevření prohlížeče, můžeme toho jednoduše docílit:
$grid->rememberState = TRUE; // povolí ukládání stavů komponenty do session
$grid->timeout = '+ 7 days'; // prodloužení expirace této session o dobu jednoho týdne
Nastavení výchozího stavu komponenty
Další možností je, že nechceme aby se nám stav komponenty ukládal a při příštím zobrazení ji vykreslil tak jak byla vykreslena naposled, ale chceme pro každé nové zobrazení vykreslit komponentu v nějakými výchozími filtry nebo řazením. Typickým příkladem může být například seřazení objednávek od nejnovější po nejstarší. Toho lze docílit jednoduchými příkazy nad konkrétním sloupcem, nad kterým budeme chtít tyto výchozí filtry nebo řazení aplikovat.
// výchozí řazení
$grid['orderDate']->addDefaultSorting('desc');
$grid['city']->addDefaultSorting('asc');
// výchozí filtrování
$grid['country']->addDefaultFiltering('USA'); // výchozí filtrování
// výchozí filtrování na logických a číselných hodnotách
$grid['orders']->addDefaultFiltering(TRUE);
$grid['productsCount']->addDefaultFiltering('>2');
Automatického ukládání stavů a výchozích stavů datagridu lze i vzájemně kombinovat.
Formátování obsahu
Jsou případy, kdy potřebujeme nějakým způsobem upravit hodnoty, které sloupec vypisuje. Je více způsobů jak toho docílit, napřiklad nastavením sloupci nějakou CSS vlastnost (zarovnání, šířka, …) nebo můžeme hodnoty sloupce před vypsáním prohnat přes nadefinované funkce či případně nastavit výrazy, které se mají nahrazovat určitým řetězcem.
Ovlivnění obsahu přes CSS funguje na stejném principu jako ve formulářích.
// ovlivnění formátování obsahu při definici sloupce (Fluent rozhraní)
$grid->addColumn('customerName', 'Zákazník')->getHeaderPrototype()->style('width: 180px');
$grid->addNumericColumn('creditLimit', 'Limit kreditu', 0)->getCellPrototype()->style('text-align: center');
// alternativně při již definovaném sloupci (přes ArrayAccess)
$grid['customerName']->getHeaderPrototype()->style('width: 180px');
$grid['creditLimit']->getCellPrototype()->style('text-align: center');
$grid['actions']->getHeaderPrototype()->style('width: 100px'); // pro sloupec akcí (viz. níže)
Uživatelské funkce (callbacky) ke sloupci můžeme přiřadit následujícím způsobem.
// předpokládejme sloupec, jehož obsahem je například velikost souboru
$grid['size']->formatCallback[] = 'TemplateHelpers::bytes';
V PHP 5.3 můžeme použít i ananymní funkce:
$grid['creditLimit']->formatCallback[] = function ($value) {
return Html->el('a')->href($value);
}
A nakonec nahrazování určitých výrazů je realizováno asociativním
polem, který pojme i objekty Html.
// předpokládejme sloupec se statusem objednávky
$el = Html::el('span')->style('margin: 0 auto');
$grid['status']->replacement['Shipped'] = clone $el->class("icon icon-shipped")->title("Doručeno");
$grid['status']->replacement['Resolved'] = clone $el->class("icon icon-resolved")->title("Vyřešeno");
$grid['status']->replacement['Cancelled'] = clone $el->class("icon icon-cancelled")->title("Zrušeno");
Vše je prováděno přesně v tomto pořadí.
To bylo ke sloupcům, ale jiná je situace, potřebujeme-li změnit způsob
vykreslování samotného datagridu. Jelikož DataGrid patří mezi
vykreslitelné komponenty tak není třeba se zatěžovat jeho vykreslováním,
což ale pro někoho nemusí být ideální. Vykreslení je realizováno
podobně jako u Nette\Forms vlastní
třídou DataGridRenderer obsahující taktéž pole wrapperů pro
základní úpravy obsahu bez nutnosti dědit a psát vlastní vykreslovač. Pro
jednoduché úpravy tedy dokáže dobře posloužit.
$renderer = $grid->getRenderer();
$renderer->paginatorFormat = '%input%'; // upravíme formát výpisu footeru datagridu
$renderer->wrappers['error']['container'] = 'div class=error';
$renderer->wrappers['error']['item'] = 'span';
$grid->setRenderer($renderer);
Další možností jak ovlivnit vykreslování přímo v rendereru je pomocí událostí. Renderer rozlišuje až 3 události:
- vykreslení řádku
- vykreslení datové buňky tabulky
- vykreslení akce
Události jsou klasickou vlastností Nette Frameworku, proto se o nich zde nebudu rozepisovat a jen odkážu na dokumentaci třídy Nette\Object a přidám ukázku syntaxe a příklad. callbacku.
$renderer->onRowRender[] = callback(Html $row, DibiRow $data);
$renderer->onCellRender[] = callback(Html $cell, string $column, mixed $value);
$renderer->onActionRender[] = callback(Html $action, DibiRow $data)
Chceme-li například ovlivnit formátování buňky (např. barvu) podle její hodnoty, tak nadefinujeme standardním php zápisem callback, který událost vyvolá.
$renderer->onCellRender[] = array($this, 'gridOnCellRendered');
V kontextu presenteru nebo komponenty, ve které byl datagrid definován pak implementujeme callback, který buňce přidá css třídu, podle toho jaký je zákazníkův kredit.
/**
* 'grid' onCellRender event.
* @param Html
* @param string
* @param mixed
* @return Html
*/
public function gridOnCellRendered(Html $cell, $column, $value)
{
if ($column === 'creditLimit') {
if ($value < 30000) $cell->addClass('money-low');
elseif ($value >= 100000) $cell->addClass('money-high');
}
return $cell;
}
Pro další uživatelské úpravy je možné vykreslit datagrid i manuálně v šabloně.
{control grid begin}
{control grid errors}
{control grid body}
{control grid paginator}
{control grid end}
{control grid info}
Akce
Akce zajišťují manipulaci s datagridem na úrovni jednoho záznamu. Jako příklad uvedu vytvoření a smazání záznamu. Aby se se záznamy dalo manipulovat, musí být určen nějaký klíč (pro zjednodušení a v mnohých případech jím bude primární klíč), podle kterého se bude k záznamům přistupovat a který je i součástí datasourcu předaného datagridu.
// nastavíme klíč pro akce (a také i pro operace, o těch později)
$grid->keyName = 'customerNumber';
// přidáme sloupec pro akce (sloupců může být i více)
$grid->addActionColumn('Actions');
// a naplníme datagrid akcemi pomocí továrničky
$grid->addAction('Nový', 'Customer:new', Html::el('span')->class('icon icon-new'), $useAjax = FALSE, $type = DataGridAction::WITHOUT_KEY);
$grid->addAction('Smaž', 'customerDelete!', Html::el('span')->class('icon icon-del'), $useAjax = TRUE);
Nyní si parametry továrničky trošku rozebereme:
addAction($title, $signal, $icon = NULL, $useAjax = FALSE, $type =
DataGridAction::WITH_KEY)
- textový popisek odkazu nebo-li html atribut
title - cíl odkazu zapsaný klasickým způsobem, jakým se v Nette tvoří odkazy
- obsah odkazu, nejčastěji
Htmlobjekt reprezentující nějakou ikonku akce, pokud není uveden vygeneruje se obyčejný odkaz s textem, který je zadán v prvním parametru - pokud se má použít AJAX, přidá se do vygenerovaného odkazu atribut
classs obsahem proměnnéDataGridAction::$ajaxClass - má-li být součástí vygenerovaného odkazu i klíč záznamu (logická hodnota nebo konstanta)
Mohli jste si všimnout, že akce vede buď na signál nebo třeba na úplně
jiný presenter. V připadě signálu customerDelete! bude při
zpracování volána metoda handleCustomerDelete($customerNumber),
kde obsah parametru $customerNumber bude právě klíč daného
záznamu (nebo-li hodnota atributu customerNumber záznamu).
Název proměnné v handleru se tedy musí jmenovat stejně,
jako název sloupce definovaný v proměnné keyName.
Operace
Dostáváme se k případům, kdy potřebujeme manipulovat s daty datagridu na úrovni více záznamů (hromadné operace se záznamy) jako například mazání více záznamů. Záznamy, chcete-li řádky, si označíme přes checkboxy, vybereme operaci, která se bude provádět, odešleme a uživatelsky nadefinovaný handler tuto operaci zpracuje, pokud je vhodně napsán tak i AJAXově. Jak tedy na to?
// nadefinujeme si operace, tyto hodnoty je možno nechat překládat translatorem
$operations = array('delete' => 'smaž', 'deal' => 'vyřiď objednávky', 'print' => 'tiskni faktury');
// poté callback(y), které mají operaci zpracovat
$callback = array($this, 'gridOperationHandler'); // $this je presenter
// povolíme operace
$grid->allowOperations($operations, $callback, 'customerNumber');
// pozn: pokud je již uveden $grid->keyName není třeba poslední parametr udávat
Zbývá zajistit zpracování handlerem gridOperationHandler(),
jehož parametrem je tlačítko, kterým byla operace odeslána.
public function gridOperationHandler(SubmitButton $button)
{
$form = $button->getParent();
$grid = $this->getComponent('dg');
$values = $form->getValues();
// ... provedeme zpracování operace
// název operace získáme z $values['operations']
// a zda-li byl checkbox zaškrtnut zjistíme přes $values['checker'][123] => bool(TRUE)
$grid->invalidateControl();
// $this může být v kontextu komponenta i presenter
if (!Environment::getHttpRequest()->isAjax()) $this->redirect('this');
}
Při zaplém JavaScriptu v prohlížeči klienta, podporuje datagrid
označení více položek pomocí kliknutí a podržení klávesy
SHIFT nebo CTRL, konkrétně kliknutí →
SHIFT nebo CTRL + další kliknutí
(SHIFT označuje v prohlížečích text, proto je možnost
používat i CTRL, které je pro tyto případy vhodnější).
Lokalizace
Samozřejmostí je i podpora lokalizace. Třída DataGrid
disponuje potřebnými metodami, takže ho můžete bez obav použít i ve
vícejazyčných systémech. Překládány jsou i titulky včetně
Html objektů. V demo aplikaci je připravena ukázka konkrétní
implementace překladače
využívající GetTextových souborů.
$translator = new Translator(Environment::expand('%templatesDir%/datagrid.cs.mo'));
$grid->setTranslator($translator);
Historie
Starší verze komponenty a minimální potřebné verze Nette Frameworku a dibi:
| Nette | dibi | Odkaz |
|---|---|---|
| Nette 0.8 | dibi 1.1 | DataGrid 0.8 |
| Nette 0.9.0 | dibi 1.1 | DataGrid 0.9.0 |
| Nette 0.9.2 | dibi 1.2 | DataGrid 0.9.2 |
| Nette 0.9.3 | dibi 1.3 | DataGrid 0.9.3 |
| Nette 0.9.4 | dibi 1.3 | DataGrid 0.9.4 |
Připojené soubory
- datagrid-0.9.3.png 100 kB
Komentáře 
mlha | 29. 4. 2010, 12:35 | question
A pro Nette 1.0dev
mlha | 26. 5. 2010, 10:06 | comment
O doplňku se diskutuje v tomto vlákně: http://forum.nette.org/…469-datagrid
romansklenar | 26. 5. 2010, 11:44 | comment
Nikoliv, diskuse je přímo ve vlákně komponenty. Update přijde až s finální verzí Nette 1.0. Vydávat updaty na vývojové verze nemá smysl, protože bych nedělal nic jiného.
matata | 12. 7. 2010, 7:55 | comment
Co kdyby když nejsou data, aby datagrid do buňky tabulky nacpal & nbsp; ?




mlha | 29. 4. 2010, 12:23 | question
Bylo by možné upravit toto i pro PHP 5.3?