Přidávání položek za běhu aplikace – dynamické formuláře

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
tatyalien
Člen | 239
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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 :))

tatyalien
Člen | 239
+
0
-

No něco jsem rozjel, ale je to asi nad mé síly… tak to asi udělám zase na tvrdo…

Rozjel jsem, že se přidá jeden prvek, druhý už ne, při odeslání formuláře se nevypíšou data z přidaných a někdy skočí laděnka Component with name ‚1‘ already exists… jen pro info ;)

Filip Procházka
Moderator | 4668
+
0
-

Já vím, tuhle chybu jsem opravil https://github.com/…dde627d55bb5

Editoval HosipLan (28. 4. 2011 9:17)

tatyalien
Člen | 239
+
0
-

Po opravě:
Component with name ‚0‘ already exists.

Filip Procházka
Moderator | 4668
+
0
-

Kdyby se ti chtělo, tak to můžeš opravit, já to udělám nejdřív odpoledne

tatyalien
Člen | 239
+
0
-

:-) 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
+
0
-

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.

22
Člen | 1478
+
0
-

…nemůže ještě někdo prosím opravit titulek toho vlákna – aby to našli i ti, co použvají ‚y‘ ve slově dynamické?

tatyalien
Člen | 239
+
0
-

Oki, takže mám stáhnout novou verzy.

No nebylo by to kompatabilní s javascriptem to ano, ale alespoň by mě to fungovalo ;)

Editoval tatyalien (28. 4. 2011 11:33)

22
Člen | 1478
+
+1
-

ty voe dělej s tou češtinou něco…

tatyalien
Člen | 239
+
0
-

22: Vrozená vada, dislektik+disgrafik. Pokud tě to bije moc do očí, tak se ti výslovně omlouvám.

Editoval tatyalien (28. 4. 2011 12:11)

PetrP
Člen | 587
+
+1
-

22 napsal(a):

…nemůže ještě někdo prosím opravit titulek toho vlákna – aby to našli i ti, co použvají ‚y‘ ve slově dynamické?

Done

22
Člen | 1478
+
0
-

tatyalien napsal(a):

22: Vrozená vada, dislektik+disgrafik. Pokud tě to bije moc do očí, tak se ti výslovně omlouvám.

v pohodě :-) nikdo není dokonalý…

Ot@s
Backer | 476
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

HosipLan: ještě jednou díky za výše zapracované úpravy.

Dnes nemám svůj den a z toho plynou 3 dotazy :-(

  1. 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í.
  2. 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ě?

  1. 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
+
0
-
  1. Obnovit po odesláni by se měly tak, jak byly vykresleny. O to se stará loadHttpData. Nebo ne?
  2. 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>
  1. teoreticky by měl jít používat i rekurzivně. Nenapadá mě nic, co by tomu vadilo.
Ot@s
Backer | 476
+
0
-

HosipLan napsal(a):

  1. Obnovit po odesláni by se měly tak, jak byly vykresleny. O to se stará loadHttpData. Nebo ne?
  2. ruční vykreslení by mohlo vypadat takto: … viz o příspěvek výše
  3. teoreticky by měl jít používat i rekurzivně. Nenapadá mě nic, co by tomu vadilo.
  1. 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…

  1. 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
+
0
-
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 Replicatoru)

PS: necituj dlouhé příspěvky

Editoval HosipLan (14. 6. 2011 10:54)

Ot@s
Backer | 476
+
0
-

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.