EN | CS | Přihlásit | Registrovat

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 NumericColumn filtrovat data pomocí operátorů >, >=, <, <=, =, <>
  • pro textové datové typy umožňuje TextColumn filtrovat 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:

  1. vykreslení řádku
  2. vykreslení datové buňky tabulky
  3. 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 Html objekt 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 class s 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


Komentáře Comments feed

mlha | 29. 4. 2010, 12:23 | question

Bylo by možné upravit toto i pro PHP 5.3?

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; ?

Login to submit a comment