Nextras\Datagrid – datagrid se vsim jak ma byt

hrach
Člen | 1838
+
+6
-

Nextras\Datagrid

Výhody oproto ostatním:

  • konečně použitelné API
  • vzhled, filtry a helpery píšete v šabloně, ne v PHP
  • complete lazy – používá callbacky
  • inline editace!
  • full AJAX podpora (přenáší se jen to, co je třeba, tzn. třeba jen jeden řádek tabulky.) – podpora moderniho nette.ajax.js
  • plna podpora Nette\Database pristupu – tedy zadne joiny!

Odkazy:

Demo je zatim male, protoze nevyuziva dalsi komponenty. V tom je sila Nextras\Datagridu – mate vlastni komponentu na datum? Proc ne, pouzijte si ji. Mate vlastni komponentu na Ajax vyhledavani? Proc ne, pouzijte si ji. Nextras\Datagrid vam neklade zadny odpor. Pouzvejte sve vlastni komponenty! Definujte si svuj vlastni vzhled.

Editoval hrach (21. 3. 2013 9:50)

David Ďurika
Člen | 328
+
0
-

mozem potvrdit je to Datagrid 2.0 :)
super napad s tym setDataSourceCallback takto som tam vedel jednoducho rozbehat aj doctrine2!

o5
Člen | 416
+
0
-

Pěknej design, ty data v tom demu jsou mi taky nějaké povědomé ;)

David Ďurika
Člen | 328
+
0
-

@o5 design je s bootstrapu…

@hrach bolo by fajn kedy si do zakladnej sablony zapracoval hned tie bootstrap prvky, potom by si nic s tohto robit a nam zjednodusil zivot :) a ak niekto nebude chciet pouzit bootstrap (tak ho vystavis do muzia) tak si to jednoducho prepise…

Editoval achtan (30. 12. 2012 18:29)

o5
Člen | 416
+
0
-

achtan napsal(a):

design je s bootstrapu…

To těžko, bootstrap tabulky vypadají takto

hrach
Člen | 1838
+
0
-
  • Ne, urcite nechci aby bootstrap vzhled byl vychozi.
  • Tuto nevyhodu castecne chcu vyresit tim, ze si budes moci nadefinovat vice sablon s temi blocky, takze muzes mit vlastni komponentu, ktera bude automaticky includovat definice pro bootstrap tabulku.
  • vzhled tabulky je vicemene bootstrapovsky. To, ze @o5 pouziva v gridu nesmyslne tridy .even na tr a pak to styluje bokem v .grido tbody tr.even je sice vysledkem stejne, ale mne stacilo pridat table-striped. To stejne jako table-condensed a teoreticky i table-hover, ktery tam neni. Jedine co je navic a musim se pravda priznat, ze inspirovane z grida je gradient v <thead>. Na opaktu se doporucuji inspirovat v reseni width=100% za pomoci box-sizing, misto toho indentu ;)
secmi
Člen | 19
+
0
-

Ahoj,
snažím se pomocí toho datagridu přidat sloupec, který bude zobrazovat sloupec z tabulky propojené pomocí cizího klíče, ale nedaří se mi.

Pokud použiji

$grid->addColumn('vozidla->rz', 'rz');

dostanu chybu PDOException – No reference found for $zakazky->vozidla->rz

Pokud však stejnou věc udělám takto

$grid->addColumn('vozidla', 'rz');

a pak v šabloně

{define col-vozidla}
<td>
    {$row->vozidla->rz}
</td>
{/define}

tak je vše v pořádku.

Datacallback je

$selection = $this->connection->table('zakazky');

Tabulky mám provázané cizím klíčem z tabulky zakázky (CONSTRAINT zakazky_ibfk_4 FOREIGN KEY (vozidla_id) REFERENCES vozidla (vozidla_id)), takže se obávám, že v tom prvním případě na to jdu špatně…

Verze Nette je 2.0.8

Editoval secmi (5. 1. 2013 17:18)

hrach
Člen | 1838
+
0
-

secmi napsal(a):
Pokud však stejnou věc udělám takto

$grid->addColumn('vozidla', 'rz');

a pak v šabloně

{define col-vozidla}
<td>
    {$row->vozidla->rz}
</td>
{/define}

tak je vše v pořádku.

Přesně tak (a jen tak) to má fungovat.

secmi
Člen | 19
+
0
-

Super, díky moc!

V souvislosti s tím bych se ještě zeptal na jednu věc, jak vypsat více sloupců z tabulky vozidel (třeba vin, tp), tak aby sloupec měl normálně záhlaví, případně se mu dal přidat filtr…

Pokud přidám znovu

$grid->addColumn('vozidla', 'rz');

tak budu dva sloupce se stejným jménem, tzn. potřeboval bych přidat nějaký fiktivní sloupec, tak aby se nezařadil do selectu a já si ho následně upravil v šabloně. Nebo na to jdu úplně špatně?

hrach
Člen | 1838
+
0
-

Pokud má sloupec definovaný block, tak je uplně jedno, jak se jmenuje. Klidně to může být

$grid->addColumn('rz', 'RZ');

Takovému sloupci jde přiřadit filtrovací Control, nicméně obsah je právě naplněn/předefinovám blockem define. (Samozřejmě daný block je patřičně pojmenován {define col-rz})

secmi
Člen | 19
+
0
-

To je právě to co mi nefunguje :(

Přidám sloupec, který je v provázané tabulce

$grid->addColumn('vin', 'vin')->enableSort();

v šabloně pak definuji co se má vypsat

{define col-vin}
<td>
    {$row->vozidla->vin}
</td>
{/define}

ale skončím chybou PDOException – No reference found for $zakazky->vin.

Trošku jsem pátral a problém je podle mě na řádku 73 v Datagrid.latte

{var $cell = $control->getter($row, $column->name)}

kde probíhá pokus získání sloupce bez ohledu na to jaká je definice v šabloně a pokud tedy sloupec neexistuje, jako v tomto případě, skončí skript výše uvedenou výjimkou.

Takže kdyby se funkce getter v Datagrid.php upravila nějak takto

	public function getter($row, $column)
	{
		if ($this->columnGetterCallback) {
			return $this->columnGetterCallback->invokeArgs(array($row, $column));
		} else {
			try {
				return $row->$column;
			}
			catch(\PDOException $e) {
				return NULL;
			}
		}
	}

měl by se celý problém vyřešit…

Editoval secmi (5. 1. 2013 21:51)

hrach
Člen | 1838
+
0
-

Jasný, díky za info, to mi nedošlo, to zkusím nějak pořešit :|

secmi
Člen | 19
+
0
-

Super, díky moc!

A také díky za tenhle datagrid :)

miler
Člen | 75
+
0
-

Ahoj,

co můžu dělat s IOException not found, prosím?

...\Datagrid\Datagrid.php   Line: 203
Class 'IOException' not found

203: throw new \IOException("Cells template '{$this->cellsTemplate}' does not exists.");
hrach
Člen | 1838
+
0
-
  • @secmi: implementováno
  • @miler: zrejme mas spatne nahranou componentu, nebo proste chybi sablona. chtelo by to asi celou ladenku.
miler
Člen | 75
+
0
-

Hrach:
Jasně, bylo to tím že jsem nastavoval šablonu se špatnou cestou, ale zmátlo mě že neexistuje třída \IOException. Díky.

hrach
Člen | 1838
+
0
-

Jasny, diky, fixed :)

Filip111
Člen | 244
+
0
-

Ahoj, zprovoznil jsem grid a snažím se ušetřit si práci v administraci.
Každá tabulka v administraci má stejné akce + něteré umí i něco navíc. Mám tedy základní šablonu s akcemi
columns-default.latte:

{define col-actions}
    <a href="{plink edit $primary}" data-datagrid-edit class="ajax btn btn-small btn-primary">Edit</a>
    <a href="{plink delete! $primary}" class="btn btn-small btn-info">Smazat</a>
{/define}

a pak můžu mít u některých tabulek akce upravené a chtěl bych aby přepsali tu půvdní definici, např.
columns-specific.latte:

{define col-actions}
    <a href="{plink edit $primary}" data-datagrid-edit class="ajax btn btn-small btn-primary">Edit</a>
    <a href="{plink delete! $primary}" class="btn btn-small btn-info">Smazat</a>
    <a href="{plink setDef! $primary}" class="btn btn-small btn-info">Nastavit defaultní</a>
{/define}

Přidám je do cellTemplates:

$grid->addCellsTemplate('columns-default.latte');
$grid->addCellsTemplate('columns-specific.latte');

Problém je, že vidím stále akce definované v první šabloně, tedy columns-default.latte a nikoliv z té druhé. Definice bloku v latte se nedá přepsat pozdější deklarací stejného bloku? (pokud přehodím pořadí nastavení cellTemplates, zobrazí se správné akce…zkrátka vždy z té první definice define)

Pořadí ale změnit nemůžu:
V ModelAdminPresenteru si předpřipravím základní kompoentu:

$grid = new \Nextras\Datagrid\Datagrid;
$grid->setDataSourceCallback($this->getListData);
$grid->addCellsTemplate('columns-default.latte');
$this->grid = $grid;
// casem chci pridat spolecne prvky a funkce

a pak v konkrétním v presenteru, který dědí ModelAdminPresenter, ji jen doupravím:

public function createComponentListGrid() {
	$grid = $this->grid;
	$grid->addColumn('status', 'S');
	$grid->addColumn('title', 'Název');
	$grid->addColumn('weight', 'Hmotnost');
	$grid->addColumn('categoryId', 'Kategorie');
	$grid->addCellsTemplate('columns-specific.latte');
	return $grid;
	}

Editoval Filip111 (10. 1. 2013 11:52)

hrach
Člen | 1838
+
0
-

define blocky nelze bohuzel prepisovat ani dedit, nicmene nic ti nebrani si vytvorit vlastni block a ten si pak includnout.

{define col-actions}
	{include #col-default-actions primary => $primary, row => $row}
{/define}

Bohužel je to omezeni nette, které aktuálně nechci nějak v Datagridem řešit.

Filip111
Člen | 244
+
0
-

Tohle bohatě stačí – sice to bude víc práce ale to se dá přežít.
Díky.

miler
Člen | 75
+
0
-

Ještě jeden dotaz – používám gettextové překlady z plaNette, ty potřebuji nasadit např. na buttony u filtrů.

Zkoušel v setFilterFormFactoey jsem něco jako:

$form->getForm()->setTranslator($this->translator);

Při úpravách @Datagrid.latte zase pro změnu:

Call to undefined method Nette\Templating\FileTemplate::translate()

Děkuju moc za radu.

hrach
Člen | 1838
+
0
-

Tak, určitě si můžeš nadefinovat vlasntí edit tlačítko, tím padem v kodu muzes nastavit u jeho prelozeny label. Chapu ze to neni idealni. Jakym zpusobem propasovat tam translator, zamyslim se.
Stejne tak premyslim,jakym zpusobem nechat dodefinovat filtry a helpery pro sablonu. Diky za feedback.

lunak83
Člen | 47
+
0
-

Vím že se tu psalo, že je to antipattern, ale nerozjeli jste si už do něj někdo ten paginator?

hrach
Člen | 1838
+
0
-

Hele, uplne jednoduse, vytvor si v presenteru normalne beznou komponent paginator a generuj ji pod datagridem, v callbacku datasource si pak normalne muzes sahnout do presenteru na tu komponentu paginatoru a aktualni stranu.

lunak83
Člen | 47
+
0
-

Aha, ono to bylo tak jednoduche! Diky!

I když teď jsem narazil na malý problém a to že při filtrování paginator zůstává stejný, tedy že jsem na straně 2/3 i když položka je prakticky jen jedna. Zároveň musím předat i řazení a filtr další stránce.

public function getDataLight($filter, $order) {
        $filters = array();
        foreach ($filter as $k => $v) {
            $filters[$k . ' LIKE ?'] = "%$v%";
        }

        $selection = $this->table->where($filters);

        $paginator = $this['paginator']->getPaginator();
        $paginator->itemCount = $selection->count();
        $paginator->itemsPerPage = 25;

        if ($order) {
            $selection->order(implode(' ', $order));
        }

        return $selection->limit($paginator->getLength(), $paginator->getOffset());
    }

Editoval lunak83 (15. 1. 2013 16:47)

David Ďurika
Člen | 328
+
0
-

no to si uz budes musiet nastavit ze ak sa zmeni filter tak resetnes (nastavis na prvu stranu) paginator

lunak83
Člen | 47
+
0
-

Chtěl jsem přenášet filtry a pořadí pro řazení mezi stránkami, takže jsem v presenteru udělal persistentní $filters a $order. Když ale pak v callbacku zavolám $this->filters = $filters; tak mám pocit že se nestane nic, protože odkazy paginatoru se po zadání filtrů vůbec nemění.

secmi
Člen | 19
+
0
-

Ahoj,
pokouším se pomocí kontejneru přidat do filtru dvě pole, minimální a maximální datum.

Definice filtru je následující:

$grid->setFilterFormFactory(function() {
	$form = new Nette\Forms\Container;
	...
	$form->addContainer('end');
	$form['end']->addDatePicker('min');
	$form['end']->addDatePicker('max');
	...
	return $form;
});

ale pokud se pokusím datagrid vypsat, dostanu následující chybu

Call to undefined method Nette\Forms\Container::getControl().
 <?php if (isset($_form[$column->name])): $_input = (is_object($column->name) ? $column->name : $_form[$column->name]); echo $_input->getControl()->addAttributes(array()) ;endif ?>

Dělám něco špatně, nebo jde o chybu v datagridu?

Verze Nette 2.0.8

hrach
Člen | 1838
+
0
-

Hm, to je proto, ze pouzivam formularova makra z nette, ktery neumi castecny automaticky rendering :| Premyslim jak to resit. Samozrejme bych mohl jednou checknout na Container a proiterovat to, na druhou stranu to neni univerzalni reseni a to se mi nelibi (zanoreni muze byt vice). Otazka je pak, ale ja to jinak resit. Bug je to nekde na pomezi :D

hrach
Člen | 1838
+
0
-

@secmi konečně mě napadlo jednoduché řešení, které se mi zamlouvá. Implementoval jsem možnost vlastního bloko pro input filter. Přidej do šablony pro grid:

{define col-filter-end}
	{formContainer end}
		{input min}
		{input max}
	{/formContainer}
{/define}
ZZromanZZ
Člen | 87
+
0
-

+ 1

Výborný grid. Už se těším na podporu hromadných akcí.

buffus
Člen | 101
+
0
-

Díky za parádní datagrid. Mohl bych poprosit o malý příklad, co doplnit do kódu

public function saveData($data)
{

	$this->flashMessage('Saving data: ' . json_encode($data->getValues()));
	$this->invalidateControl('flashes');

}

aby se po zavolání metody zapsala data z editovaného pole formuláře do databáze?

Editoval buffus (5. 3. 2013 23:15)

ZZromanZZ
Člen | 87
+
0
-

@buffus, v poli $data už máš vyplněné hodnoty uživatelem(příp. je to JSON), indexované dle názvu komponent formulářových prvků. Tzn. už to rovnou můžeš poslat modelu(službě), který to uloží do db.

Editoval ZZromanZZ (5. 3. 2013 23:49)

hrach
Člen | 1838
+
0
-

$data je IFormContainer, takze to zpracuj dle libosti, podobne, jak zpracovavas svoje formulare.

buffus
Člen | 101
+
0
-

Díky za rady, ale já se špatně zeptal. Mám rozchozené Basic demo a po kliknutí na Edit->Save mi
Firebug Post hlásí OK např.:
Parameters application/x-www-form-urlencoded

edit[firstname]	Martha
edit[id]	77
edit[save]	Save
edit[surname]	Yohannes
...

ale $data zůstává NULL a neumím ji naplnit. Formulář mám jako v demo BasicPresenter.php.

Editoval buffus (6. 3. 2013 10:01)

hrach
Člen | 1838
+
0
-

Hmhm, asi ukaz presenter…
Edit: chces rict, ze mas rozhozeny demos, bez zadne zmeny a nefunguje to?

Editoval hrach (6. 3. 2013 10:18)

buffus
Člen | 101
+
0
-

Nee rozhozený, rozchozený, chodí to :)

buffus
Člen | 101
+
0
-
<?php

use Nette\Application\UI\Form;



class ZakazkaPresenter extends BasePresenter
{


	/** @var Nette\Database\Connection */
	private $connection;

	public function injectConnection(Nette\Database\Connection $connection)
	{
		$this->connection = $connection;
	}


	protected function startup()
	{
		parent::startup();

		if (!$this->getUser()->isLoggedIn()) {
			$this->redirect('Sign:in');
		}
	}



	public function createComponentDatagrid()
	{
		$grid = new Nextras\Datagrid\Datagrid;
		$grid->addColumn('id');
		$grid->addColumn('zakazkanazevdilu')->enableSort();
		$grid->addColumn('neco')->enableSort();
		$grid->addColumn('virtual-neco', 'Operace');

		$grid->setDataSourceCallback($this->getData);

		$grid->setFilterFormFactory(function() {
			$form = new Nette\Forms\Container;
			$form->addText('zakazkanazevdilu');
			$form->addSelect('neco', NULL, array(
				'male' => 'železo',
				'female' => 'dřevo',
			))->setPrompt('---');

			return $form;
		});

		$grid->setEditFormFactory(function($row) {
			$form = new Nette\Forms\Container;
			$form->addText('zakazkanazevdilu');
			!$row ?: $form->setDefaults($row);
			return $form;
		});



		$grid->setEditFormCallback($this->saveData);

		$grid->addCellsTemplate(__DIR__ . '/../../libs/Nextras/datagrid/bootstrap-style/@bootstrap3.datagrid.latte');
		$grid->addCellsTemplate(__DIR__ . '/../../libs/Nextras/datagrid/bootstrap-style/@cells.latte');
		return $grid;
	}




	public function getData($filter, $order)
	{
		$filters = array();
		foreach ($filter as $k => $v) {
			if ($k === 'neco')
				$filters[$k] = $v;
			else
				$filters[$k. ' LIKE ?'] = "%$v%";
		}

		$selection = $this->connection->table('zakazka')->where($filters);
		if ($order) {
			$selection->order(implode(' ', $order));
		}

		return $selection->limit(30);
	}



	public function saveData($data)
	{

		$this->flashMessage('Saving data: ' . json_encode($data->getValues()));
		$this->invalidateControl('flashes');

	}




	public function renderDefault()
	{


	}

}
hrach
Člen | 1838
+
0
-

Divne. Verze nette?

buffus
Člen | 101
+
0
-

PHP/5.3.18 | Nette Framework 2.0.8

buffus
Člen | 101
+
0
-

Na Basic demo také nejde nic změnit. Předpokládám, že to tak je v online demu schválně. O to naplnění $data by se měl postarat datagrid tak jak to mám v prezenteru sám?

Pokud např. doplním do

public function saveData($data)
{

$datatest = array ("zakazkanazevdilu" => 'ddd', "uzivatelzavedl_id" => '2',  "uzivatelzadal_id" => '3');
	$this->connection->table('zakazka')->insert($datatest);

	$this->flashMessage('Saving data: ' . json_encode($data->getValues()));
	$this->invalidateControl('flashes');

}

tak se mi do db po kliknutí na Save normálně řádek přidává…

Editoval buffus (6. 3. 2013 11:32)

hrach
Člen | 1838
+
0
-

Tak si rikam, ze mas asi nejake zacatecnicke problemy. V examplu ktery si poslal zadne ukladani udelane nemas. Pak mi pises, ze v demu nejde nic zmenit – vzdyt to je jasne a je to tak schvalne.

$this->connection->table('zakazka')->where('id', $data->id)->update($data);
buffus
Člen | 101
+
0
-

jj, jsem začátečník… Pokud saveData upravím na

public function saveData($data)
{
	$this->connection->table('zakazka')->where('id', $data->id)->update($data);
	$this->flashMessage('Saving data: ' . json_encode($data->getValues()));
	$this->invalidateControl('flashes');
}

tak se v db nic nezmění a Firebug Lite v Chrome vyhodí
POST /…/is/zakazka/?do=datagrid-form-submit 500 Internal Server Error 88ms

Parametersapplication/x-www-form-urlencoded
edit[id]	3
edit[save]	Save
edit[zakazkanazevdilu]	chtelBychZmenit-nejde
filter[neco]
filter[zakazkanazevdilu]

a když si dumpnu $data má hodnotu NULL

Přitom např. Short nebo Filter mi v datagridu funguje, tak snad jsem doplněk nějak blbě nenainstaloval…

Editoval buffus (6. 3. 2013 12:34)

hrach
Člen | 1838
+
0
-

Poslal jsem spatnou ukazku. Ma to byt nejak takto:

public function saveData($data)
{
    $data = $data->getValues();
    $this->connection->table('zakazka')->where('id', $data->id)->update($data);
    $this->flashMessage('Data ulozena.');
    $this->invalidateControl('flashes');
}
buffus
Člen | 101
+
0
-

Super, funguje. Moc moc děkuju za ukončení mého noc a půl dne dlouhého trápení. Mám se ještě hodně co učit… Díky!

sepo
Člen | 69
+
0
-

je možné v cells template gridu používať helpery ?
mám definovaný Helper Loader (ktorý v štandartnom template funguje)
ale cells template gridu už dáva

Call to undefined method Nette\Templating\FileTemplate::test()
hrach
Člen | 1838
+
0
-

hmhm, premyslim, jestli to nejde nejak zaregistrovat do vychozi makra pres config. Komponenta pouziva latte z DICu. Zkusil bych pohledat, zeptat se na „registraci helperu v neonu“. Registrace maker jde, tak snad i toto, ale ted z hlavy nevim.

buffus
Člen | 101
+
0
-

Ahoj, ještě se zeptám: Funkce Sort, Edit a Filter (nebo některá z nich) by měly fungovat v datagridu i nad sloupci z jiných tabulek (provázaných cizím klíčem)?

Editoval buffus (11. 3. 2013 22:59)

hrach
Člen | 1838
+
0
-

implementace na datasource je ciste tvoje zalezitost.