TwiGrid – omlouvám se :-(

Semik
Backer | 135
+
0
-

Tak dle mého názoru zatím nejpovedenější datagrid, z těch co jsem už zkusil.

Semik
Backer | 135
+
0
-

V případě, že vytvořím filtr a zapomenu v šabloně gridu definovat jeho blok, vyskočí pochopitelná, ale matoucí exception: Nette\MemberAccessException - Call to undefined method Nette\Forms\Container::getControl()
Chtělo by to asi upravit.

uestla
Backer | 796
+
0
-

Rozumím… Tohle je ale celkem špatně uchopitelné – to, jestli blok existuje, pohodlně zjistím v šabloně. Ale vyhazovat výjimku v šabloně mi přijde stejně tak nešťastné… Zkusím něco vymyslet, díky za připomínku.

Semik
Backer | 135
+
0
-

Možná by stačila nějaká do očí bijící výchozí hodnota bloku, takže to poznám, když ho nepřepíšu.

uestla napsal(a):

Rozumím… Tohle je ale celkem špatně uchopitelné – to, jestli blok existuje, pohodlně zjistím v šabloně. Ale vyhazovat výjimku v šabloně mi přijde stejně tak nešťastné… Zkusím něco vymyslet, díky za připomínku.

uestla
Backer | 796
+
0
-

To je otázka … Buďto nevykreslit v buňce nic, nebo by default proiterovat kontajnér, a nebo nějak nenásilně upozornit… Osobně jsem pro nevykreslovat nic, příp. připnout warning class buňce…

Semik
Backer | 135
+
0
-

Ano, myslím, že warning class by postačovala.

uestla napsal(a):

To je otázka … Buďto nevykreslit v buňce nic, nebo by default proiterovat kontajnér, a nebo nějak nenásilně upozornit… Osobně jsem pro nevykreslovat nic, příp. připnout warning class buňce…

Semik
Backer | 135
+
0
-

Přemýšlím, zda by nějak nešla nastavit konstantní šířka sloupců. Po aplikaci filtru se většinou rozhejbou.

uestla
Backer | 796
+
0
-

ad chybějící filter-block: nyní se tedy obarvuje buňka lehce do červena na znamení výstrahy…

ad šířka: Šířku si můžeš nastavit podle svého, každá buňka záhlaví má třídu header-cell-$name.

Semik
Backer | 135
+
0
-

Myslel jsem spíš něco jako přidat v kódu sloupci css třídu, aby se dalo aplikovat třeba bootstrap stylování spanX.

Edit: Myslím že stylování v poděděné šabloně gridu bude dostačující. Nechal bych tedy být.

Edit 2: I když v situaci, kdy nepřepisuji bloky s filtry je to celkem práce navíc, než kdyby se ta třída přidala do TwiGrid\Components\Column.

uestla napsal(a):

ad šířka: Šířku si můžeš nastavit podle svého, každá buňka záhlaví má třídu header-cell-$name.

Editoval Semik (10. 6. 2013 18:52)

Semik
Backer | 135
+
0
-

Ve full gridu v examplu bych asi přesunul v bloku ‚row-action-delete‘ šablony u odkazu atribut data-confirm do n:attr. Pokud akci odeberu confirm, tak vyskočí prázdný dialog. Je to logické, spíš jen aby to bylo jasné i méně zdatným.

na1k
Člen | 288
+
0
-

Dovolím si lehké OT

Nemohl by prosím některý mod tohle vlákno přejmenovat? :-)

Asi jsem přecitlivělý a nechápu srandu, ale vždycky když na mě v nepřečtených příspěvcích vyskočí TwiGrid – omlouvám se :-( tak to vypadá strašně divně. Navíc to ani nevypadá jako hlavní vlákno komponenty, ale o nějaký nováčkovský dotaz.

Semik
Backer | 135
+
0
-

Kdyby aspoň smějící se smajlík, že ? :-)

na1k napsal(a):

Nemohl by prosím některý mod tohle vlákno přejmenovat? :-)

Semik
Backer | 135
+
0
-

Jen tak informativně. Proč je v examplu v šabloně u inputů makro {? } ? Není potřeba ne ?

uestla
Backer | 796
+
0
-

Je to kvůli mé posedlosti správným odsazením ve výsledném HTML.

Editoval uestla (11. 6. 2013 17:35)

Reloecc
Člen | 15
+
0
-

uestla napsal(a):

Jelikož do databáze (nebo jinam) sahá náš dataLoader callback, je na nás, na jaké sloupce či co nakonec bude sahat.

Rozhodně děkuji, na to jsem mohl přijít sám, jen jsem uvažoval špatným směrem.. :-[

Semik
Backer | 135
+
0
-

Uvažuje se, ať už obecně nebo u in-line editace podpora pro vložení nového řádku ?

Semik
Backer | 135
+
0
-

U in-line editace je problém s odesláním formuláře enterem. Pokud je vstupním polem textarea, tak logicky místo odřádkování odešlu formulář.

uestla
Backer | 796
+
0
-
  • textarea

    To je dobrá připomínka, díky za ni.

    Odteď funguje Enter uvnitř textarey stejně jak jsme zvyklí, k tomu jsem navíc přidal možnost odeslání formu zevnitř textarey pomocí Ctrl + Enter…

  • nový řádek

    O vložení nového řádku se uvažuje :-)

uestla
Backer | 796
+
0
-

Do ukázky inline editu jsem přidal textareu.

boob
Člen | 21
+
0
-

zdravim,

je mozne nejako vybrat row action na zaklade riadku?
mam napriklad tabulku s riadkami, ktore mozem blokovat, teda pri kazdom riadku chcem na zaciatku „block“ akciu, ale ked je nieco zablokovane, tak by som tam chcel mat samozrejme „unblock“ link, najlepsie aj s inym callbackom

da sa to? :)
dakujem

uestla
Backer | 796
+
0
-

Akci si dám obecně jako toggle:

$grid->addRowAction('toggleBlock', '', function ($id) {
	// get by $id, unblock if blocked, otherwise block
});

V šabloně pak už jde jen o label:

{define row-action-toggleBlock}
	<a href="{$link}" class="btn, btn-info, btn-small, tw-ajax">
		{$record->blocked ? 'Odblokovat' : 'Zablokovat'}
	</a>
{/define}
boob
Člen | 21
+
0
-

Takto som to chcel povodne spravit, ale najradsej by som mal oddelene callbacky pre block a unblock, pretoze takto mi to callbacku pride len id_number a ja neviem, ci je zablokovane alebo nie.

Mozem sa nejako dostat k hodnotam toho riadku?

Pretoze ak si ten grid ulozim do premennej a potom dam napriklad $this->grid->getData(), tak mi to refreshne stranku (asi tam nic nie je a snazi sa tie data nacitat)

Ak mam len id_number, tak by som predtym este musel urobit dalsi request na server, ktory by mi vratil, ci je cislo s takym id blocked alebo unblocked

EDIT // hm, mozem si vlastne tie riadky ulozit do premennej v DataLoader callbacku
EDIT2 // tak asi nie, lebo DataLoader sa vola az po vykonani inlineEditCallbacku

Editoval boob (28. 6. 2013 13:36)

uestla
Backer | 796
+
0
-

Koukni na to z obou pohledů – jak AJAXového, tak neAJAXového požadavku

Základní fakta:

  • všechno je lazy a při zpracování row-action signálu ještě nejsou načtena data (nejsou třeba)
  • nemohu vědět, co row-action se záznamem dělá (jestli ho třeba nemaže)
    • je proto nutné po zprac. signálu přesměrovat/znovu načíst data

neAJAX

  • vyřídí se signál (data nenačtena)
  • přesměruje se na sebe sama
  • tím dojde k překreslení gridu a zobrazení aktuálních dat
  • 2 dotazy do databáze

AJAX

  • vyřídí se signál (data nenačtena)
  • nepřesměrovává se, ale „invalidují“ se data (tj. řekne se, že se mají znovunačíst)
  • jelikož v té době ještě nejsou načtena, nic se neděje
  • v první chvíli, kdy jsou data potřeba, se načtou
    • a jsou aktuální díky tomu, že row-action signál se zpracuje dřív
  • 2 dotazy do databáze

V obou případech stejný počet dotazů (1 na vytažení záznamu podle ID v row-action callbacku, druhý pro získání všech dat aktuální obrazovky).

win-win situation :-)

boob
Člen | 21
+
0
-

Jasne, dik, nieco na tom bude :)) je pravda, ze by bolo zbytocne nacitac znova vsetky data, aj ked by to mozno ponuklo zase ine moznosti (napriklad tu by som usetril tu query na ziskanie zaznamu podla ID)

Nedalo by sa tomu callbacku predat nejaky parameter? Teda pri definovani blocku v sablone by som dal href=„{$link} block“ a href=„{$link} unblock“ podla $record->block a podla toho parametru potom vykonal to, co potrebujem v callbacku

uestla
Backer | 796
+
0
-

Teoreticky můžeš přidat obě akce a až v šabloně se rozhodnout, kterou vykreslit (právě v závislosti na $record->blocked).

S tím ušetřeným dotazem jsem doufal, že jsem to vysvětlil, ale evidentně ne :-D

Kdybych tahal už při zpracování signálu row-action všechna data kvůli jednomu záznamu, pak bych stejně musel zbytek dat zahodit a načíst celá data znovu, protože nemůžu vědět, co v row-action se záznamem děláš (jestli ho náhodou nemažeš, což by dělalo neplechu při stránkování apod.).

uestla
Backer | 796
+
0
-

Ohledně příkladu třeba takhle:

$grid->addRowAction('block', 'Zablokovat', function ($id) { /* ... */ });
$grid->addRowAction('unblock', 'Odblokovat', function ($id) { /* ... */ });

A až pak v šabloně:

{define row-action-block}
	<a href="{$link}" class="btn, btn-info, btn-small, tw-ajax" n:if="!$record->blocked">
		{$action->label}
	</a>
{/define}



{define row-action-unblock}
	<a href="{$link}" class="btn, btn-info, btn-small, tw-ajax" n:if="$record->blocked">
		{$action->label}
	</a>
{/define}
boob
Člen | 21
+
0
-

Vdaka, toto vyzera schopne, asi to vyriesim takto :)
S tymi dotazmi mi to je jasne, ja som to myslel tak, ze by som usetril v mojom pripade 1 dotaz, pretoze teraz by som to robil takto (ak neberiem do uvahy tvoj posledny navrh):

  • zisti podla id, ci je cislo zablokovane alebo nie
  • podl toho ho bud odblokuj, alebo zabloku
  • nacitaj vsetky data a vykresli

by som mal len 2. a 3. dotaz, kedze 1. by som nepotreboval, keby som hned vedel, ci mam zablokovat alebo nie.. ale zase by som tam musel mat dotaz na ziskanie dat pred callbackom, takze by som nakoniec nic neusetril :)

Dakujem, spravim to teda tymto sposobom, dufam, ze to nebude velmi neprehladne pro viacerych takychto toggle buttonoch :)

boob
Člen | 21
+
0
-

zdravim, mal by som po dlhsom case este jednu otazku.

Ked mam komponentu, v ktorej vytvaram grid, teda

<?php

class MyControl
{
	public function createComponentMyGrid()
	{
		$grid = new \TwiGrid\DataGrid($this->context->session);
		...
	}

	public function handleDoSomething()
	{
		...
	}
}

?>

a potreboval by som v stlpci cez custom {define body-cell-mycolumn} vykreslit link na handleDoSomething, teda napriklad

{define body-cell-mycolumn}
<td>
	{$value} <a href="...">do something</a>
</td>
{/define}

da sa toto nejako momentalne docielit?
Dakujem!

uestla
Backer | 796
+
0
-

Mám pocit, že odkazovat z potomka na akci rodičovské komponenty není možné, osobně bych
šel cestou podědění si gridu a přidání akce do něj…

uestla
Backer | 796
+
0
-

Verze 2.0.0

Ať slouží.

ludek
Člen | 83
+
0
-

Zdravím, díky moc za tenhle grid!

Měl jsem trochu co dělat s původním datagridem Romana Sklenáře a s NiftyGridem a tenhle se mi zdá jednoznačně nejlepší. Výborně funguje, výborně vypadá, je připraven pro Nette 2.1. Moc se mi líbí, že např. obsluhu filtrů (v příkladu metoda filterData()) si můžu napsat přesně podle potřeby. Další skvělá věc je nastavení formátování polí v přidružené .latte šabloně. Myslím, že je škoda, že není na addons.nette.org. Narazil jsem na něj jen náhodou na fóru.

Programuju jen občas, nemám moc zkušeností, takže mi dost trvalo, než jsem se v příkladu, který se dost liší od normálního Nette sandboxu, trochu vyznal, ale nakonec se mi podařilo přizpůsobit ho pro svůj projekt.

Dotazy:

  • Je možné ho nějakým způsobem počeštit, aniž bych zasahoval do kódu?
  • Vím, že je to offtopic, ale nedaří se mi pořádně zprovoznit ajax. Myslím, že mám správně načtené všechny js skripty, ale když přepnu třeba filtr v roletovém menu, nic se nestane. Když potom kliknu na tlačítko „Filter“, nejdřív se mi zobrazí alert s hlášením „ahoj“ (? pochází z nette.forms.js) a teprve potom se filtr aplikuje. Kalendáříky datetime pickeru mi také nefungují. I po dlouhém zkoušení pořád nevím, co dělám špatně.

Díky za každou pomoc.

greeny
Člen | 405
+
0
-

Proč když kliknu kamkoliv na vrchní panel, tak se mi vyberou všechny položky v gridu? (co když se netrefím na odkaz na řazení?)

uestla
Backer | 796
+
0
-

@ludek:

  • ad počeštění:
    • můžeš využít překladač $grid->setTranslator($translator) a jednotlivé popisky přeložit…
  • ad AJAX:
    • v nette.forms.js jsem žádný podobný alert nenašel, nemáš nějakou starou verzi? Popřípadě vypisuje něco chybová konzole?

@greeny: tohle je vlastnost gridu, kterou považuju za příjemnou, ale chápu, že nemusí vyhovovat všem…

ludek
Člen | 83
+
0
-

uestla napsal(a):

  • ad AJAX:
    • v nette.forms.js jsem žádný podobný alert nenašel, nemáš nějakou starou verzi? Popřípadě vypisuje něco chybová konzole?

Díky. Bylo to tím, že jsem neměl natažený highlight.pack.js, protože jsem se domníval, že ho nepotřebuju. Ve script.js se ale hned na začátku volá hljs.initHighlightingOnLoad();, což způsobilo, že pak nefungovalo nic. Stačilo zakomentovat.

Jinak v nette.forms.js, který je součástí Vašeho příkladu je to na řádku 137:

Nette.validateForm = function(sender) {
	var form = sender.form || sender, scope = false;
	alert('ahoj');

V NetteFramework-2.1.1/client-side/netteForms.js to není.

Editoval ludek (6. 3. 2014 13:48)

ludek
Člen | 83
+
0
-

Jak udělat klikatelný text (odkaz) v buňce?.

Chtěl bych udělat toto:

{define body-cell-name}
    <td><a n:href="Dashboard:detail $id">{$value}</a></td>
{/define}

Ale jednak obdržím chybu „Component with name 'Dashboard' does not exist“, a také nevím jak se dostat k hodnotě $id aktivního řádku.

uestla
Backer | 796
+
0
-

Atribut n:href se překládá na odkaz na komponentě, kdežto ty chceš zřejmě odkaz na presenter, čili musíš odkaz vytvořit pomocí makra {plink Dashboard:detail}.

Uvnitř bloku je definována proměnná $record, ve které je uložen daný záznam.

ludek
Člen | 83
+
0
-
{define body-cell-name}
    <td><a href="{plink Dashboard:detail $record->id}">{$value}</a></td>
{/define}

Funguje. Díky.

tom
Člen | 171
+
0
-

Funguje TwiGrid i s dibi? Existuje nekde priklad pouziti napr. s dibi DataSoure? me to ted vyhazuje hlasku „Given value is not a callable type.“

Diky

uestla
Backer | 796
+
0
-

To vypadá buď na špatně nastavený data loader nebo value getter, mohl bys ukázat kód definice gridu?

tom
Člen | 171
+
0
-

uestla napsal(a):

To vypadá buď na špatně nastavený data loader nebo value getter, mohl bys ukázat kód definice gridu?

Zkoušel jsem to takto …

<?php
    $grid = new \TwiGrid\DataGrid($this->context->session);
    $grid->setPrimaryKey('id_role');
    $grid->addColumn('name', 'Name');

    $grid->setDataLoader($this->cRolesRepository->vratDataSource());
    return $grid;
?>
uestla
Backer | 796
+
0
-

To je právě to, data loader musí být volatelná věc, obecně s ničím typu data source grid nepřichází do přímého styku …

zapp
Člen | 32
+
0
-

Ahoj,
bohužel se mi nedaří zprovoznit řazení, ani filtrování. Pokud nepoužiju ajax, vše funguje, jinak při překreslení laděnka píše, že nezná proměnnou „form“. Může mě někdo nakoupnout prosím?

default.latte – volá componentu

{block content}
	<p>
		<a href="{plink Contracts:add}" class="btn btn-primary">Přidat zakázku</a>
	</p>

	{control contracts}

ContractsPresenter.php – výtažek

class ContractsPresenter extends BasePresenter {
		public $contracts;

		public function actionDefault () {
			$this->contracts = $this->model->getContracts();
		}

		public function createComponentContracts () {
			return $this->context->createService('contractsGrid');
		}
}

ContractsGrid.php

class ContractsGrid extends DataGrid {
		/** @var \Model @inject */
		public $model;

		protected function build () {
			$this->setTemplateFile(APP_DIR . '/templates/Grid/contracts.latte');

			$this->setPrimaryKey('id');

			$this->addColumn('no', 'Číslo')->setSortable();
			$this->addColumn('name', 'Název')->setSortable();
			$this->addColumn('companies_id', 'Firma')->setSortable();
			$this->addColumn('delivery_date', 'Termín objednání')->setSortable();
			$this->addColumn('text', 'Popis');

			$this->addRowAction('edit', 'Upravit', $this->edit);
			$this->addRowAction('delete', 'Smazat', $this->delete)
				->setConfirmation('Opravdu smazat?');

			$this->setDefaultOrderBy('delivery_date', 'ASC');

			$this->setFilterFactory($this->filterFactory);
			$this->setDataLoader($this->dataLoader);
		}

		public function dataLoader ($grid, array $columns, array $filters, array $order) {
			$contracts = $this->model->getContracts();

			// sorting
			foreach ($order as $column => $direction) {
				$contracts->order($column . ($direction === Column::DESC ? ' DESC' : ''));
			}

			// filtering
			foreach ($filters as $column => $value) {
				if ($column == 'companies_id') {
					$companies = $this->model->getCompanies()->where('name LIKE ?', '%'.$value.'%')->fetchPairs('id', 'id');

					$contracts->where('companies_id', array_values($companies));
				}
				else {
					$contracts->where($column.' LIKE ?', '%'.$value.'%');
				}
			}

			return $contracts;
		}

		public function filterFactory () {
			$container = new Container;

			$container->addText('no');
			$container->addText('name');
			$container->addText('companies_id');

			return $container;
		}

		public function edit ($id) {
			$this->presenter->redirect('Contracts:edit');
		}

		public function delete ($id) {

		}
	}
uestla
Backer | 796
+
0
-

Ahoj, projevuje se ta chyba i na nové verzi? Používáš defaultní, nebo vlastní šablonu?

jarks
Člen | 94
+
0
-

Dobrý den, velmi pěkný grid – alespoň demo vypadá skvěle. Také velmi pěkný návod na zprovoznění. Když ho ale projdu a za bodem 8 chci spustit výsledek, dostanu chybu headers already sent a bohužel se mi nedaří zjistit proč. Nebylo by prosím úplně celé kompletní demo včetně použité Nette atd. ke stažení, že bych ho zkusil rozběhnout na lokálu a snažil se najít v čem to je?

Nette\InvalidStateException
session_start(): Cannot send session cache limiter - headers already sent (output started at C:\web\www\twigrid-quickstart\temp\cache\latte\www-twigrid-quickstart-app-templates-layout-latte-7cfc6ddb213bd27e9582d09c56d86154.php:57)
//---------------------------------
...\vendor\uestla\twigrid\src\TwiGrid\Helpers.php:144

144: ? static::getCsrfTokenSession($session, $namespace)->token = NRandom::generate(10)

Arguments
$name	"token" (5)
$value	"xwle1qftzq" (10)

//-----------
...\temp\cache\latte\www-twigrid-quickstart-app-templates-layout-latte-7cfc6ddb213bd27e9582d09c56d86154.php:79

79:    <?php Latte\Macros\BlockMacros::callBlock($_b, 'content', $template->getParameters()) ?>
uestla
Backer | 796
+
0
-

Zkus prosím, jestli pomůže do config.local.neon přidat

nette:
	session:
		autoStart: TRUE

Ta chyba souvisí s pozdním startem session, což by toto mělo řešit. Jestli to pomůže, přidám to do tutoriálu, díky za report.

jarks
Člen | 94
+
0
-

Pomohlo, díky. To mě taky mohlo napadnout. Zjistil, že se to projevuje ve Firefoxu. V Chrome ne.

A ještě: pokud se postupuje jen podle tutorialu, nefunguje ajax, protože chybí inicializace

$(function () {
    $.nette.init();
});

což je v demu v souboru script.js. Dá se vyřešit připsáním do main.js (který je v sandboxu prázdný) jak je popsáno v Simple Ajax Example a nalinkováním v @layout.latte, pokud už to tam není:

<script src="{$basePath}/js/main.js"></script>

Editoval jarks (5. 9. 2014 16:25)

jarks
Člen | 94
+
0
-

Třeba se bude hodit začátečníkům:

instalace TwiGrid DEMO na lokální webserver:

předpoklad: nainstalovaný composer

postup:

  • https://github.com/…twigrid-demo
  • [Download ZIP] (https://github.com/…e/master.zip)
  • rozbalit např. do ~/www/twigrid-demo nebo C:\www\twigrid-demo
  • přejít do adresáře s rozbaleným TwiGridem
  • otevřít konzolu (ve Windows CMD), zadat: composer update – přiinstaluje se Nette
  • Linux: nastavit práva k adresářům temp, log (chmod -R 777 log/ temp/) a jestli nechcete chybové hlášky, tak ještě k js a css, kam se pokouší při spuštění zapisovat
  • v prohlížeči zadat adresu např. http://localhost/twigrid-demo

Editoval jarks (30. 3. 2015 12:53)

ludek
Člen | 83
+
0
-

Jestli při vícenásobném otevření okna (prostřední tlačítko) se řádkovou akcí dostanete "Security token does not match. Please try again." a nechcete to tak, je třeba doplnit vypnutí ochrany proti CSRF, takto:

protected function build()
{
		//...
		$this->addRowAction('edit', 'Upravit', $this->editItem)
	    	 ->setProtected(FALSE); //<< tady to je
		//...
	}

	function editItem(Nette\Database\Table\ActiveRow $record) {
    $this->getPresenter()->redirect("Homepage:edit", $record->id);
}

Editoval ludek (14. 10. 2014 16:42)

uestla
Backer | 796
+
0
-

@ludek Ovšem pouze v případě, že se jedná o akci, kterou není třeba zabezpečit (typicky odkaz na jiné view, stažení, apod.). U akce jako mazání je naopak CSRF ochrana žádoucí (a tedy i expirace tokenu).

amik
Člen | 118
+
0
-

Podle mě nejlepší datagrid pro Nette, hlavně mě fascinuje napojení na model přes callbacky. Od gridu čekám maximální customizovatelnost, což twigrid nepochybně má :) pár věcí mi ještě v jeho jádru chybělo, mám je zatím dobastlené u sebe na projektu, ale chystám se udělat si fork a rozumně to rozházet do commitů, a případně hodit pull request když se to bude líbit :)