ublaboo/datagrid: mocný, rychlý, rozšiřitelný, hezký, anglicky dokumentovaný datagrid
- rkor
- Člen | 62
Zdravím,
mám problém, se kterým si nevím rady a rád bych se zeptal, zda se s tím
už někdo nesetkal.
Mám datagrid v treeview s možností sortable. Pokud začnu táhnout parent
prvek, je vše v pořádku https://ctrlv.cz/7wke. Pokud začnu táhnout první child
prvek, opět se začne přesunovat celý parent, což je špatně https://ctrlv.cz/avQS. Pokud
začnu táhnout jakýkoli jiný child prvek, tak se přesunuje jen on, což je
opět správně https://ctrlv.cz/UksK.
Zkoušel jsem vyhodit z layoutu všechny nepotřebné (pro datagrid) CSS a JS, ale chování se nezměnilo. Pokud by někoho napadlo, čím by to mohlo být, budu vděčen za každou pomoc
- jurajvt
- Člen | 18
Ahoj.
Dve otázky.
1. Odkaz na signál v presenter-i
Definíciu grid-u mám vo factory triede. V stĺpci akcií je akcia Editovať.
$grid->addAction('edit', 'Editovať', 'edit!')
V presenter-i mám funkciu handleEdit()
Grid zobrazujem vo view list.latte
, takže url v prehliadači
je /request/list
. Link v tlačidle Editovať je
teda /request/list?id=21&do=edit
.
Ako docielim to, aby som vytvoril link
/request/?id=21&do=edit
?
Štandardne by som to spravil cez makro n:href takto:
<a n:href="Request:default do => edit, id => $request->id">Editovať</a>
2. Hodnota parametra – keď stĺpec neexistuje v databáze
Štandardne nie je možné dať do link-u hodnotu parametra, keď takýto stĺpec neexistuje. Ako sa to dá obísť?
V tomto prípade si len chcem do link-u pridať vlastný parameter, stĺpec
car
v databáze nie je.
$grid->addAction('car', 'Avízo pre dovoz autom', 'create!', ['id', 'type' => 'car'])
Vďaka.
Editoval jurajvt (14. 7. 2017 12:05)
- theo
- Člen | 57
Ahoj, narazil jsem na podivný problém, který se možná tak docela netýká Ublaboo\Datagridu, nicméně mohl bych zde dostat aspoň nějakou nápovědu jak to řešit.
Mám databázi PostgreSQL a používám jako datasource Dibi\Fluent
rozhraní. Vlastností Postgresu je, že očekává stringy v
'jednoduchých uvozovách'
a je-li to nutné, pak identifikátory
(názvy sloupců) ve "dvojitých úvozovkách"
. Datagrid si při
filtrování dělá do databáze např. takovýto dotaz:
SELECT u.*, r.description AS role_description
FROM users u JOIN roles r USING (role)
WHERE ('login' LIKE '%ad%')
ORDER BY "login"
LIMIT 10
V tomto dotazu je ale jádro pudla, které tkví v tom (mimochodem úplně
zbytečně) špatně escapovaném řetězci 'login'
ve
WHERE
podmínce. Vzniká z tohoto kódu:
$this->users->getDataSource()
->select('u.*, r.description AS role_description')
->from('users u')
->join('roles r')->using('(role)')
->orderBy('login')
ke kterému si Datagrid něco přidává fluent rozhraním. O tom co
přidává moc pochybností nemám, protože tam to je escapované správně,
nicméně po nějakém pátrání jsem došel na to, že někde uvnitř metody
Dibi\Translator::translate()
se to celé přeloží znovu a
tentokrát ze správného "login"
na špatné 'login'
.
Postgres tak srovnává dva řetězce a ne řetězec s obsahem identifikátoru
a tudíž nic nevyfiltruje (výsledek filtru je prázdný).
Tušíte někdo proč by se to mělo dít, resp. co s tím? Je to totiž dost zákeřná chyba, a nenašel jsem žádný způsob jak ji obejít (a to jsem si už hrál i s tím Dibi\Translatorem)
- jurajvt
- Člen | 18
V oboch prípadoch asi len cez callback:
$grid->addActionCallback('edit', 'Editovať')
->setClass('btn btn-xs btn-success')
->onClick[] = function($item_id) use ($container) {
$container->redirect('Request:default', ['id' => $item_id, 'do' => 'edit']);
};
Alebo druhý prípad vyriešiť inak – do dotazu pridám explicitne potrebné stĺpce:
$orders = this->orders->findAll()->select('code, name, "car"')
Alebo upraviť funkciu a poslať PR.
jurajvt napsal(a):
Ahoj.
Dve otázky.
1. Odkaz na signál v presenter-i
Definíciu grid-u mám vo factory triede. V stĺpci akcií je akcia Editovať.
$grid->addAction('edit', 'Editovať', 'edit!')
V presenter-i mám funkciu
handleEdit()
Grid zobrazujem vo view
list.latte
, takže url v prehliadači je/request/list
. Link v tlačidle Editovať je teda/request/list?id=21&do=edit
.Ako docielim to, aby som vytvoril link
/request/?id=21&do=edit
?Štandardne by som to spravil cez makro n:href takto:
<a n:href="Request:default do => edit, id => $request->id">Editovať</a>
2. Hodnota parametra – keď stĺpec neexistuje v databáze
Štandardne nie je možné dať do link-u hodnotu parametra, keď takýto stĺpec neexistuje. Ako sa to dá obísť?
V tomto prípade si len chcem do link-u pridať vlastný parameter, stĺpec
car
v databáze nie je.$grid->addAction('car', 'Avízo pre dovoz autom', 'create!', ['id', 'type' => 'car'])
Vďaka.
Editoval jurajvt (24. 7. 2017 9:53)
- sevca79
- Člen | 55
Ahoj,
nevím jak vybruslit z následujícího začátečnického problému…určitě
jednoduché řešení,ale už nad tím dumám přes půl hodky a nevím jak
na to
mám grid např. Autobazary kde je primární klíč id. Mám tam akci detail, která mi otevře novou stránku s detailem autobazaru s url autobazary/detail/5
Uvnitř této stránky mám mimo jiné grid se seznamem aut, kde je primární klíč taktéž id, avšak když na tento grid navážu akci např. delete,
$grid->addAction('delete', '', 'deleteCar!')
tak se mi to přesměrovává na stránku např. autobazary/detail/1234?do=deleteCar
jak mám zanechat původní url i s detailem autobazaru a id auta, aby se mi posílalo jako další parameter
díky za nakopnutí ;)
- Pavel Janda
- Člen | 977
@sevca79 persistent parameters in presenter + jiné jméno sloupce než ID, které je matoucí pro presenter.. :)
Editoval Pavel Janda (24. 7. 2017 12:54)
- sevca79
- Člen | 55
Pavel Janda napsal(a):
@sevca79 persistent parameters in presenter + jiné jméno sloupce než ID, které je matoucí pro presenter.. :)
a nejde to nějak jinak, než přejmenovat jméno sloupce?? U inlineEditu to nějak funguje, že se id u seznamuAut, nejak prejmenuje na nazevGridu-id a původní id (detailu autobazaru) zůstane…jen sem celkem začátečník a ze zdrojáků inlineEditu na to nemůžu přijít
- Pavel Janda
- Člen | 977
@sevca79 Jenže akce inlineEdit je v komponentě-gridu. Akce deleteCar je v tvém presenteru.
- Jarek92
- Člen | 91
Ahoj,
mám v datagridu jako sourceData obyčejné pole. Zkouším si pak nastavit na filtr typu DateRange vlastní podmínku (která by měla vzít rozsah datumu from a to a z databáze vyhledat vyhovující), nicméně se mi to nedaří.
$grid->addFilterDateRange("created", "")
->setAttribute("class", "form-control input-sm datepicker")
->setCondition(function($fluent, $value) {
$fluent = [];
$fluent[$value] = [
"attributte1" => "value1",
"attributte2" => "value2",
"attributte3" => "value3",
];
});
Zkoušel jsem pouze resetovat pole a přidat do něj jednu položku, samozřejmě bez úspěchu. Řešil už někdo obecně problém, ve kterém je v DB velké množství dat, a chce jej při filtrování tahat z DB, aby nazačátku nemusel přenášet všechny řádky?
- sibka
- Člen | 24
Používám history.nette.ajax.js a celou aplikaci zajaxovnou přes snippet content. Pokud ale použiji tento skvělý grid, tak se mi po každém kliknutí na pole filtru překreslí celý snippet content. A když třeba klikdu na očičko pro detail, tak se mi do obsahu gridu zobrazí právě jen ta šblona detailu. Čím to?
- Pavel Janda
- Člen | 977
@sibka To je zvláštní chování. Vážně nepoužíváš žádnou další knihovnu, která by to mohla ovlivnit? Děje se to pouze při přidání nette.ajax.js?
- sibka
- Člen | 24
@PavelJanda Udělala jsem si nový projekt a v @layout.latte
<script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://nette.github.io/resources/js/netteForms.min.js"></script>
<script src="{$basePath}/js/nette.ajax.js"></script>
<script src="{$basePath}/js/history.ajax.js"></script>
<script src="{$basePath}/js/bootstrap.min.js"></script>
<script src="{$basePath}/js/datagrid.js"></script>
<script src="{$basePath}/js/main.js"></script>
</script>
V BasePresenteru
<?php
public function beforeRender() {
if ($this->isAjax()) {
$this->redrawControl('content');
}
}
?>
A děje se to pořád. Přidala jsem si do šablony proměnnou s časem, a když třeba filtruji v gridu, tak se neustále refreshuje celá stránka a tím pádem mi z pole filtru vyhazuje kurzor. Item detail opět zobrazuje v oblast gridu, bez hlavního obsahu.
- Pavel Janda
- Člen | 977
@sibka Hm, tak to těžko říct, kde se to bude mlátit. Nepoužívám nette-ajax-history. Pokud někdo ano, rád si nechám poradit, co pozměnit, aby vše fungovalo, jak má – rád uvítám PR. :)
- andros
- Člen | 145
Ahoj,
v gridu mám inline přidávání nového záznamu s takto:
$grid->addInlineAdd()
->setPositionTop()
->onControlAdd[] = function($container) {
$container->addText('id', '')->setAttribute('readonly');
$container->addText('code', '')
->setRequired('Kód měny je potřeba vyplnit.');
$container->addText('name', '')
->setRequired('Název měny je potřeba vyplnit.');
$container->addText('unit', '')
->setRequired('Počet je potřeba vyplnit.');
};
Na jeden sloupec mám nastavený filtr:
$grid->addFilterText('code', 'Kód');
Pokud chci změnit počet řádků na stránce, „vylítne alert okno“, že je potřeba nejprve vyplnit položky u nového záznamu. To samé se zobrazí při pokusu o vyhledávání. Když vyplním povinné položky u nového záznamu, je vše v pořádku. Ten samý problém se zde už řešil cca před rokem, ale řešení jsem nikde nenašel. Budu rád za každou radu, co dělám špatně. Děkuji moc.
- pavelinnuendo
- Člen | 33
Ahoj, mohu nějak změnit ikonu pro inline editaci? Ta se objevuje automagicky s addInlineEdit() a nepřišel jsem na to, jak ji změnit a odstranit class btn-xs. Díky.
- uncza
- Člen | 2
andros napsal(a):
Ahoj,
v gridu mám inline přidávání nového záznamu s takto:$grid->addInlineAdd() ->setPositionTop() ->onControlAdd[] = function($container) { $container->addText('id', '')->setAttribute('readonly'); $container->addText('code', '') ->setRequired('Kód měny je potřeba vyplnit.'); $container->addText('name', '') ->setRequired('Název měny je potřeba vyplnit.'); $container->addText('unit', '') ->setRequired('Počet je potřeba vyplnit.'); };
Na jeden sloupec mám nastavený filtr:
$grid->addFilterText('code', 'Kód');
Pokud chci změnit počet řádků na stránce, „vylítne alert okno“, že je potřeba nejprve vyplnit položky u nového záznamu. To samé se zobrazí při pokusu o vyhledávání. Když vyplním povinné položky u nového záznamu, je vše v pořádku. Ten samý problém se zde už řešil cca před rokem, ale řešení jsem nikde nenašel. Budu rád za každou radu, co dělám špatně. Děkuji moc.
Chtěl jsem se tu zeptat na to samé … Nevěděl by někdo, jak na to?
- elring
- Člen | 6
theo napsal(a):
Ahoj, narazil jsem na podivný problém, který se možná tak docela netýká Ublaboo\Datagridu, nicméně mohl bych zde dostat aspoň nějakou nápovědu jak to řešit.
Mám databázi PostgreSQL a používám jako datasource Dibi\Fluent rozhraní. Vlastností Postgresu je, že očekává stringy v
'jednoduchých uvozovách'
a je-li to nutné, pak identifikátory (názvy sloupců) ve"dvojitých úvozovkách"
. Datagrid si při filtrování dělá do databáze např. takovýto dotaz:SELECT u.*, r.description AS role_description FROM users u JOIN roles r USING (role) WHERE ('login' LIKE '%ad%') ORDER BY "login" LIMIT 10
V tomto dotazu je ale jádro pudla, které tkví v tom (mimochodem úplně zbytečně) špatně escapovaném řetězci
'login'
veWHERE
podmínce. Vzniká z tohoto kódu:$this->users->getDataSource() ->select('u.*, r.description AS role_description') ->from('users u') ->join('roles r')->using('(role)') ->orderBy('login')
ke kterému si Datagrid něco přidává fluent rozhraním. O tom co přidává moc pochybností nemám, protože tam to je escapované správně, nicméně po nějakém pátrání jsem došel na to, že někde uvnitř metody
Dibi\Translator::translate()
se to celé přeloží znovu a tentokrát ze správného"login"
na špatné'login'
. Postgres tak srovnává dva řetězce a ne řetězec s obsahem identifikátoru a tudíž nic nevyfiltruje (výsledek filtru je prázdný).Tušíte někdo proč by se to mělo dít, resp. co s tím? Je to totiž dost zákeřná chyba, a nenašel jsem žádný způsob jak ji obejít (a to jsem si už hrál i s tím Dibi\Translatorem)
Ahoj,
toto jsem také řešil. Napsal jsem na to i issue #496 na githubu.
Jestli jsem datagrid dobře pochopil, take podle mě je to trochu chyba
gridu(?). Ten totiž vše (včetně názvu sloupců) escapuje (snaží se
o nativní escapování podle db). Jenže dibi dle dokumentace očekává
nikoliv nativní, ale jeho speciální escapování, takže to dopadne, tak jak
to dopadlo. S PostgreSQL to nefunguje. Ve svých projektech to (provizorně)
řeším zakomentováním escapování v metodě applyFilterText
v DibiFluentDataSource.php. Správné řešení by asi bylo vždy escapovat
pomocí `identifier
` nebo [identifier]
,
'string'
nebo "string"
…
- Pavel Janda
- Člen | 977
@uncza Je potřeba upravit scopy formuláře u těch inline editací/přidávání. Dostanu se k tomu o víkendu a už to snad konečně fixnu. :)
@elring Nemám k dispozici testovací postgre, mohl by jeden z vás
poslat případně PR? :) Třeba s novým
DibiFluentPostgreDataSource
?
- elring
- Člen | 6
@elring Nemám k dispozici testovací postgre, mohl by jeden z vás poslat případně PR? :) Třeba s novým
DibiFluentPostgreDataSource
?
Já ten PR klidně udělám. Už jsem ho měl i připravený. Je to jen
nahrazení escapování pomocí dibi helperu za
$column = '[' . $column . ']'
. Jen nevím, jestli to dávat do
speciální verze pro Postgres. V podstatě by to mělo být společné pro
všechny db. Musel bych to ale otestovat na mysql…
Ale samostatný DibiFluentPostgreDataSource
by měl výhodu, že
by se tam ve filtrování dalo propašovat nahrazení LIKE za iLIKE, což je
opravdu specialita Postgresu pro case insensitive hledání… To je ale asi na
jiný PR :)
- andros
- Člen | 145
Ahoj, nevím si rady s následujícím problémem. V datagridu mám u sloupce status nastavený callback, který zruší select u aktuálně přihlášeného uživatele. Viz. kód níže. Pokud ale nastavím Big innline editaci pouze na jméno a email, po kliknutí na tlačítko edit mi grid dovolí editovat i status. Ten bych rád editovat pouze v případě, že se nejedná o aktuálně přihlášeného uživatele.
Díky za každou radu
public function createComponentUsersGrid($name) {
$grid = new DataGrid($this, $name);
$grid->setDataSource($this->usersRepository->getList());
$grid->setDefaultSort(['id' => 'ASC']);
$grid->addColumnNumber('id', 'ID')
->setSortable();
$grid->addColumnText('name', 'Jméno')
->setSortable();
$grid->addColumnText('email', 'Email')
->setSortable();
$grid->addColumnStatus('status', 'Stav')
->setAlign('center')
->addOption(0, 'Neaktivní')
->setClass('btn-success')
->endOption()
->addOption(1, 'Aktivní')
->setClass('btn-primary')
->endOption()
->onChange[] = [$this, 'userStatusChange'];
$grid->addColumnCallback('status', function($column, $item) {
if ($item->id === $this->presenter->user->getId()) {
$column->setTemplate(NULL);
$column->setReplacement(["1" => 'Aktivní', 0 => 'Neaktivní']);
}
});
$grid->addInlineEdit()
->onControlAdd[] = function($container) {
$container->addText('name', '')
->setRequired('Vyplňte jméno');
$container->addText('email', '')
->setRequired('Email');
};
- radas
- Člen | 224
Ahoj,
pokud vytvářím grid v komponentě přes továrničku, dá se definovat akce
na signál v presenteru? Protože syntaxe ‚Presenter:signal!‘ skončí
výjimkou Nette\InvalidArgumentException, jelikož ‚Presenter‘ je v tomto
případě komponenta a metoda handleSignal() v komponentě doposud
neexistuje. Díky.
- Pavel Janda
- Člen | 977
@radas Signál na presenter asi ne, komponenta by podle mého názoru
neměla vědět o žádném handleru v presenteru. Buď si to vyvolej
postupně (handleFoo v komponentě, kde zavoláš presenter), nebo to udělej
akcí ($grid->addAction('foo', 'Foo', 'Homepage:foo')
)
- nocturne32
- Člen | 21
Zdravím,
dalo by se udělat, aby se sloupec Action zobrazoval vlevo?
Nebo jestli by šlo v jednom sloupečku mít text a k němu dodat dva
odkazy/tlačítka
Například:
TITLE | AUTHORS |
........................|.........|
Titulek (odkaz1, odkaz2)| authors |
Případně nějaký příklad? Děkuju.
Editoval nocturne32 (23. 8. 2017 12:12)
- Pavel Janda
- Člen | 977
@nocturne32 To druhé by šlo:
1, Můžeš si podědit třeba ColumnText a přidat ho do gridu (pro tuto
variantu bys musel podědit i DataGrid, aby sis přidal metodu ekvivalentní k
addColumnText()
) – samozřejmě budeš muset implementovat
funkcionalitu spjatou s vykreslením textu a tlačítek, ale podle mě je to
easy peasy
2, Nastavit datagridu šablonu (https://ublaboo.org/…rid/template#…) a v ní si udělej
block, ve kterém naprosto jednoduše vypíšeš text + nějaká tvá
tlačítka
Editoval Pavel Janda (23. 8. 2017 16:26)
- Croc
- Člen | 270
Zdravím. Pokud to dobře chápu, tak DataGrid načte všechny data nejednou a pak nad nima hledá pomocí JS. Je možné grid přepnou do režimu „serverSide“? Aby se načítaly jen ty data co jsou zobrazeny a při vyhledávání se ptalo do DB?
Mám hodně dat v gridu a načtení samotného gridu trvá dlouho (včetně cca 300MB paměti) a vyhledávání trvá také dlouho.
- Pavel Janda
- Člen | 977
@Croc :) :) DataGrid vyhledává server side – vše je server side. Nepoužíváš náhodou jako datasource hoooodně velké pole?
- Peca
- Člen | 2
Ahoj,
dá se nějak docílit toho abych při SmallInline editaci nastavil výchozí
hodnotu pro selectbox dle aktuální hodnoty, takhle je nastavena jako výchozí
vždy první položka?
Lze rozšířit nějak následující kód nebo je řešením jen nějaký JavaScript?
$grid->addColumnText('name', 'Name')
->setEditableCallback(/**...*/)
->setEditableInputTypeSelect([
0 => 'Offline',
1 => 'Online',
2 => 'Standby'
]);
- marten_cz
- Člen | 10
1. Jde jednoduse mit jiny text pro filter a pro sloupecek? Neco jako
$grid->addColumnText(…, ‚Text‘)->setFilterLabel(‚Text 2‘)
2. Neplanuje se podpora pro seskupovani sloupecku? Moznost ze sloupecky pridam do groupy a ta se bude zobrazovat nad nima? Neco takoveho, ale ne az tak slozite https://www.accelebrate.com/…image013.jpg .
- nocturne32
- Člen | 21
@PavelJanda Tak jsem to nakonec vymyslel vše přes render metodu a na šablonu nebylo třeba sahat :-) Děkuju.
- nocturne32
- Člen | 21
Ještě bych se vlastně mohl zeptat, jestli nemáš nějak v plánu třeba
označování (barevně) hledaných výrazů z filtru.
Snažím se to udělat přes jquery, ale pořád mi to funguje špatně, když
se to následně překreslí. Neovládám to tak moc. :-D
Každopádně děkuju za parádní datagrid. :-)
- Pavel Janda
- Člen | 977
@nocturne32 Hmm. Sice to neplánuji, ale mohla by to být zajímavá nette.ajax.js extension. Ani by to asi nebylo tak složité.
- BigCharlie
- Člen | 283
Je možné nějak ovlivnit podobu action tlačítek? Jde mi konkrétně o MultiAction:
- v datagridu je řešeno jako „dropdown“, tj. po kliknutí se rozbalí volby
- rád bych si přidal něco jako split button dropdown, tj. mám výchozí akci a nebo si rozbalím ostatní akce (ubyde mi tím jedno kliknutí)
Jak toho nejjednodušeji dosáhnout?
- Pavel Janda
- Člen | 977
@BigCharlie Úplně nejrychleji – vlastní šablonkou akce :) Ale šlo by to i lépe a složitěji.. Vlastní akce apod.
- chap
- Člen | 81
Ahoj, mám trochu problém s ajaxem v Datagridu. Používám v aplikaci https://github.com/…-validation/ … Pokud si přidám do datagridu inlineAdd, ve kterém mám validační pravidla, tak mi to zablokuje inputy na datagridu (filtrování, počet záznamů). Neřešil to někdo prosím?
- Pavel Janda
- Člen | 977
@chap Toto issue je už dlouho známé. Zmínil jsem to na Nette Campu.. Vysvětlím:
1, Inline editace se vyrenderuje do formuláře rovnou (pak se jen
zviditelní při kliknutí na button)
2, Pokud je tam něco required, začne celý formulář při jakékoliv akci
křičet, protože má formulář obalující datagrid blbě nastavený
validation scope – zahrnuje InlineEdit/Add form (tuším, že v tom tkví
jádro pudla).
3, Fix by měl být jednoduchý..
Jenže!
Přesto, že si na to lidé stěžují už delší dobu, ani jeden z těch cca
20 lidí, kteří o tom mluvili, neposlal PR, nepřišel diskutovat fix,
nic.
Takže!
Kdyby byl alespoň nějaký náznak spolupráce, náznak toho, že chce do
problému někdo strkat prstíky, budu moc rád nápomocen. Aktuálně to však
na žádném svém projektu nepotřebuji, takže nechám toto issue ještě
chvilku (třeba měsíc a kus) hnít jako takový průzkum opensource
společnosti. Zpětně i předem se za to omlouvám a doufám, že se třeba
najde někdo, kdo zlomek času, který třeba ušetřil použitím datagridu,
věnuje pokusu o fixnutí validation scope.
- BigCharlie
- Člen | 283
Je možné nějak ovlivnit podobu action tlačítek? Jde mi konkrétně o MultiAction:
- v datagridu je řešeno jako „dropdown“, tj. po kliknutí se rozbalí volby
- rád bych si přidal něco jako split button dropdown, tj. mám výchozí akci a nebo si rozbalím ostatní akce (ubyde mi tím jedno kliknutí)
Jak toho nejjednodušeji dosáhnout?
Pavel Janda napsal(a):
@BigCharlie Úplně nejrychleji – vlastní šablonkou akce :) Ale šlo by to i lépe a složitěji.. Vlastní akce apod.
@PavelJanda Dík za popostrčení.
Připadá mi, že vlastní šablonou to nejde. MultiAction::renderButton definuje vykreslení tlačítka, navíc není kde vzít výchozí akci, tudíž je třeba to vyřešit buď přes vlastní action, nebo úpravou MultiAction.
Zajímal by tě PR, nebo to neužiješ?