Přidávání položek za běhu aplikace – dynamické formuláře
- tatyalien
- Člen | 239
Zdravím,
snažím se poslední dobou více hrabat v nette (furt v něm začínám).
Chtěl jsem si přepsat starej projekt co jsem dělal kdysi bráchovi →
vedení reklamací, generování pdf atd. Chtěl bych se zeptat, je někde
nějaká možnost u formulářů, když nevím kolik text inputů bude
uživatel potřebovat, aby se to generovalo až dle potřeby?
Ve své staré verzy (bez Nette) jsem dal na tvrdo, že v jedné reklamaci mohly použít 2 různé položky. (nikdy se nestalo, že toho přinesli lidi více + stejně generovaný protokol pro dodavatele byl uzpůsobovaný po 2 položkách). Ale já bych chtěl zkusit udělat to, že při zadávání nové reklamace, by měl přednastavenou jednu položku a měl u ní tlačítko „přidat další položku“, po kliknutí by se buď znovu načetla stránka se zadanýma údajema, kde by ale už nebyla 1 položka ale dvě… takže by si zadaly co potřebují a hotovo. Ale aby tam kdyžtak pak bylo i odebrat položku (ale kdyby to nešlo, svět se nezboří :-D)
Jak by se to dalo řešit? (omluvte, jsem v tomhle lama)
- Filip Procházka
- Moderator | 4668
Zkus si pohrát s tímhle https://gist.github.com/943164
Není to odladěné, pokud tam budou nějaké chybky, tak je klidně opravím, ale není to pro mě priorita, byl to jen nástřel jak jde udělat dynamický kontejner do formuláře jednoduše.
- tatyalien
- Člen | 239
Tak jsem si rozjel to co jsi posílal (přepsal tam jen aliasy, například: class_alias(‚Nette\Application\UI\Form‘ → ‚Nette\Application\AppForm‘); atd..
Teď se mě zobrazí příklad co tam máš:
- Skupina:
- jméno
- příjmení
To tam mám 2× a talčítko přidat dalšího člověka. Po kliknutí mě vypisuje data dle dumpu, ale jak mám na to napojit, aby se mě znovu vygenerovala mnou zadaná část?
Je tam sice pěkný komentář:
// vyžaduje javascript
// je nutné vytvořit metodu, která bude duplikovat prvky v replicatoru
// formulář je pak v pohodě zpracuje
Ale jak danou metodu napsat (sorry, fakt lama…)
- Filip Procházka
- Moderator | 4668
Aktualizoval jsem příklad použití https://gist.github.com/943164, ale jenom proto, aby byl úplný. Javascript si někde budeš muset vykrást, nechce se mi vymýšlet, jako kopírovat tu groupu prvků univerzálně.
Jinak mi docela udělalo radost, že to funguje, aniž bych to jakkoliv testoval :))
- Filip Procházka
- Moderator | 4668
Já vím, tuhle chybu jsem opravil https://github.com/…dde627d55bb5
Editoval HosipLan (28. 4. 2011 9:17)
- Filip Procházka
- Moderator | 4668
Kdyby se ti chtělo, tak to můžeš opravit, já to udělám nejdřív odpoledne
- tatyalien
- Člen | 239
:-) No, vrtat v kódu se raději nebudu, ale asi bych si to zkusil přepsat asi přez session…
Asi něco v tom smyslu, že formulář budu dělat:
Při prvním načtení si zkontroluji zda mám v proměnné ze session nějaké
data, pokud ne, vypíšu základní formulář.
Dodám si tlačítko, na kterém bude jen uložení dat do sessionu +
vytvoření v sessionu proměnná na přidání prvku + její číslo (pokud
existuje, zvětší se o 1) + redirect na this
V továrniččce budu při vytváření formuláře kontrolovat, zda proměnná v sessionu existuje, pokud ano tak pomocí cyklu si přidám určitý počet formulářů. Pokud ne, nepřidá se navíc ani jeden… + dodání defaultních honot ze sessionu.
Co myslíš?
Editoval tatyalien (28. 4. 2011 10:53)
- Filip Procházka
- Moderator | 4668
To by samozřejmě mělo fungovat, ale nebude to kompatibilní s javascriptovým přidáváním. Budeš to muset vždy odesílat ajaxem, když budeš chtít přidat prvek.
PS: Možná jsem opravil tu tvoji chybu.
- Ot@s
- Backer | 476
tatyalien napsal(a):
Po opravě:
Component with name ‚0‘ already exists.
HosipLan napsal(a):
PS: Možná jsem opravil tu tvoji chybu.
Taky jsem na tu chybu narazil taky (po submitu požadavku na další
blok). Ma to už někdo vyřešené (Nette 2beta)? Díky.
Tak nevím, jestli je to správné místo, ale zafungovalo. V Replicator.php, metoda register:
// nahradit tento radek:
if (is_numeric($createDefault) && $createDefault > 0) {
// za
if (is_numeric($createDefault) && $createDefault > 0 && !Nette\Environment::getHttpRequest()->isPost()) {
Editoval Ot@s (2. 6. 2011 8:50)
- Filip Procházka
- Moderator | 4668
Není to úplně ideální, ale je to dobré řešení :) Diky, opravim
//fixed: https://github.com/…f0ab01d57468
Editoval HosipLan (2. 6. 2011 9:01)
- Ot@s
- Backer | 476
HosipLan napsal(a):
Není to úplně ideální, ale je to dobré řešení :) Diky, opravim
Máš pravdu, nebylo to dobré. Pokud se na stránce používá AJAX,
tak to nefunguje dobře (úvodní render formu s výchozím počtem
dynam.bloků). Bude to chtít domyslet. Díky za promptní řešení.
Taktéž by se mi hodil elegatní způsob, jak odebrat blok formulářových prvků. Resp. jak by mohl vypadat callback metody MyFormRemoveElementClicked. Můžeš prosím nastřelit koncept, aby to bylo co nejčistčí? Můj nástřel, mimo jiné,neodstraní addGroup(zůstane prázdný). Mé nezištné díky a pivko na některé z PS. :-)
// nekde v metode komponentu (presenteru)
$replicator = $form->addDynamic('users', function (Nette\Forms\Container $container) {
// zde definice sub-formu
$container->addSubmit('delete', 'Odebrat')->setValidationScope(NULL)
->onClick[] = callback($container->getForm()->parent, 'MyFormRemoveElementClicked');
}, 2);
// nekde v te same komponente (presenteru)
public function MyFormRemoveElementClicked(Nette\Forms\Controls\SubmitButton $button)
{
$button->form['users']->offsetUnset( $button->parent->getName() );
}
Editoval Ot@s (2. 6. 2011 9:34)
- Filip Procházka
- Moderator | 4668
Až na pár syntaktických cukrlátek, bych to napsal stejně. S těmi groups bude problém, protože ve formulářích není podpora na smazání prvku z groupy.
- Ot@s
- Backer | 476
HosipLan: ještě jednou díky za výše zapracované úpravy.
Dnes nemám svůj den a z toho plynou 3 dotazy :-(
- Jak zajistit, aby se mi dynamické contejnery řadily v pořadí, jak je
vytvářím? Přídáním dynam.kontejneru dojde k zpřeházení pořadí
bloků. Formulář se restauruje v metodě
loadHttpData
, ale v ní nic „závadného“ není. - Díky tomu, že nemám pod kontrolou pořadí vykreslování bloků, tak se pokouším o ruční render formuláře. Vím, že to zní divně, ale stejně nerozumím tomu, proč se nemůžu dostat na formulářové prvky uvnitř dynam. kontejneru.
V komponentě:
$replicator = $form->addDynamic('addresses', function (Nette\Forms\Container $container) {
// ...
}, 1);
Jak by by vypadal zápis pro výpis prvků v šabloně?
- Lze
addDynamic
používat rekurzivně? Tj. potřeboval bych dynamicky přidat kontejner v kontejneru…
Díky za odpovědi.
- Filip Procházka
- Moderator | 4668
- Obnovit po odesláni by se měly tak, jak byly vykresleny. O to se stará
loadHttpData
. Nebo ne? - ruční vykreslení by mohlo vypadat takto:
{* nějak se dostanu k $addresses, což je náš Container *}
<table n:foreach="$addresses->components as $address">
<tr>
<td>{$address['street']->label}</td>
<td>{$address['street']->control}</td>
</tr>
<tr>
<td>{$address['city']->label}</td>
<td>{$address['city']->control}</td>
</tr>
<tr>
<td>{$address['zip']->label}</td>
<td>{$address['zip']->control}</td>
</tr>
...
</table>
- teoreticky by měl jít používat i rekurzivně. Nenapadá mě nic, co by tomu vadilo.
- Ot@s
- Backer | 476
HosipLan napsal(a):
- Obnovit po odesláni by se měly tak, jak byly vykresleny. O to se stará
loadHttpData
. Nebo ne?- ruční vykreslení by mohlo vypadat takto: … viz o příspěvek výše
- teoreticky by měl jít používat i rekurzivně. Nenapadá mě nic, co by tomu vadilo.
- a 2) moje nepozornost :-(
Řeším to k vůli tomu, že mám více osob a kontaktů na ně (tj.
2 dynam. kontejnery). Samozřejmě to pak seskupuje osoby k sobě a kontakty
k sobě (místo toho, aby to bylo logicky u sebe). Vidím to na prasácký
zásah do metody getHttpData (abych znásilnil řazení dle svých potřeb).
Což mi ale neřeší čersvé přidání kontejneru (ten se vždy nalepí
nakonec). To je past vedle pasti :-)
Ideální by byla existence nepovinného parametru konstruktoru
$form->addDynamic, kde by se uvedla vazba/závislost na „parent“
kontejneru (ale jen pro potřeby grupování).
Poprosím Tě Filipe, jestli pro mě nemáš elegantní tip, jak to vyřešit.
Třeba by se to hodilo i ostatním…
- Můžeš prosím nahodit ukázku rekurzivní definice, jak by se to definovalo? Řešilo by to můj problém logického seskupovaání kontejnerů?
Každopádně díky za odpovědi a dělám si další čárku na pivko pro Tebe.
Editoval Ot@s (14. 6. 2011 10:55)
- Filip Procházka
- Moderator | 4668
use Nette\Forms\Container;
$form->addDynamic('users', function (Container $user) {
$user->addTest('name', 'Jmeno');
$user->addDynamic('addresses', function (Container $address) {
$address->addText('street', 'Ulice');
$address->addText('city', 'Město');
$address->addText('zip', 'PSČ');
// ...
}, 1);
// ...
}, 2);
Takový zápis by měl automaticky seskupovat správně ty containery.
Co se týče toho zásahu, pokud chceš upravovat řazení, pak so poděď
Replicator a přepiš metodu loadHttpData
, od toho je protected.
(budeš pak muset registrovat pro addDynamic
svého potomka, místo
Replicator
u)
PS: necituj dlouhé příspěvky
Editoval HosipLan (14. 6. 2011 10:54)
- Ot@s
- Backer | 476
HosipLan napsal(a):
Takový zápis by měl automaticky seskupovat správně ty containery.
HosipLan: Díky za odpověd. Opět máš pravdu. :-)
Trochu se ještě peru s ovládáním buttonů mající nastarosti
„vnořené“ kontejnery, ale nakonec to asi dám (pro tápající
zájemce – v obsluze kliknutí na tento button, např.
addAddressToUserClicked, používám
$button->parent['addresses']
, kde addresses
je
název vnořeného/závislého dynam. kontejneru).
Už jsem se dostával do zběsilé fáze pokus-omyl, což nikam nevedlo. Díky lidem, kteří ví a poradí/nasměrují. To je jeden z velkých benefitů Nette.