Jak vygenerovat formulář s X-inputů, kde X zadá uživatel v šabloně (anketa)?

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

Zdravím :).

Vytvářím anketu ale mám problémek a rád bych si nechal poradit. Samotnou anketu jsem rozchodil bez problémů.

Chci teď vytvořit formulář, ve kterém budu zadávat otázku a odpovědi. Odpovědi budou vždy minimálně dvě, ale jejich maximum stanoveno není a je na uživateli, kolik odpovědí do své ankety chce.

Tedy v sabloně (akci edit) chci mít odkaz (nebo tlačítko) pro přidání inputu pro odpověď a pro odebrání inputu pro odpověď. Odebrání bude aktivní pouze když jsou zobrazeny 3 a více odpovědí. Přidání bude aktivní dokud nedosáhnu nějakého maxima odpovědí.

Problém je, že nevím jak to v Nette udělat. Navíc, aby na vygenerovaná políčka fungovaly validátory (ty budou stejné jako pro první 2 defaultně vygenerované inputy pro odpovědi). Napadlo mě předávat továrničce createComponentPoolForm() nějaký parametr podle kterého by generovala počet inputů pro odpovědi. Ale zároveň bych nechtěl, aby se mi při přidání nové odpovědi vymazaly obsahy inputů, které už na stránce mám.

Poradili by jste mi prosím někdo? Na fóru jsem takové téma nenašel..

Jan Endel
Člen | 1016
+
0
-

Něco podobného právě řeším, mám dva handly add a delete, v každém načtu sešnu v add přičtu jedničku a v delete odečtu jedničku. No a pak v renderu mám ve zkratce toto (count se načítá právě z té sešny):

$form = $this->getComponent('formEdit');
       for($i=0;$i<$count;$i++)
       {

           $form->addText('name'.$i, 'Název');
	}
Endrju
Člen | 147
+
0
-

Takhle jsem to taky zamýšlel a řekněme, že logicky vím jak to udělat. Prakticky ale ne. Povedlo se mi, že se přidávají inputy a odebírají, validace také probíhá, ale po vyplnění nějakých hodnot a přidání/odebrání inputu se vyplněné hodnoty ztratí. Spíše bych raději napsal, že se mi nepovedlo udělat nic, protože prakticky nevím, jak docílit toho co potřebuju.

Nemám s handlery žádnou zkušenost a chci se to naučit a porozumět tomu na konkrétním příkladu. Díval jsem na na fifteen (jediný příklad, kde se handlery využívají), ale to je na mě složité. Chci tomu první porozumět na něčem jednoduchším jako je tohle..


K věci:

  • na stránce mám staticky 2 inputy s názvem (answer1 a answer2), které tam budou pořád
  • po kliknutí na tlačítko Přidat odpoveď se vloží nový input (zůstanou zachována data, která jsem do formuláře již napsal)
  • po kliknutí na tlačítko Ubrat odpoveď se ubere přidaný input (zůstanou zachována data, která jsem do formuláře již napsal)
  • Celé by to mělo fungovat asi tak jako, když na email.seznam.cz píšete email a chcete připojit nějaké přílohy. Tam lze přidat nový input aniž by to změnilo aplikaci a také ubrat jakýkoli input (nehledě na pořadí). 

Chtěl bych, aby to bylo funkční ajaxově i neajaxově (kvůli (ne)podpory JS).

A abych po vás nechtěl vymýšlet i ten začatek, tak úvádím kód, který zatím mám (vím, že ty handlery asi nebudou OK a asi ani v továrničce to nebude úplně košér), ale rád bych kdyby jste mi řekli co a proč má být jinak a jak. Ubírám se směrem, kteým mě „nakopnete“ :), takže uvítám i úplně jiné řešení, když se bude více hodit.

Kód presenteru:

public function renderDefault()
{
	$poolAnswers = $this->getSession('poolAnswers');
	$this->template->poolAnswers = $poolAnswers->count;
}

public function handleAddAnswer()
{
	$poolAnswers = $this->getSession('poolAnswers');
	if (!isset($poolAnswers->count) || $poolAnswers->count !== (int)$poolAnswers->count) {
		$poolAnswers->count = 4;
	}
	else {
		$poolAnswers->count++;
	}

	$this->invalidateControl('poolAnswers');

	if(!$this->isAjax()) {
		$this->redirect('this');
	}
}

public function handleDeleteAnswer()
{
	$poolAnswers = $this->getSession('poolAnswers');
	if (!isset($poolAnswers->count) || $poolAnswers->count !== (int)$poolAnswers->count) {
		$poolAnswers->count = 4;
	}
	else {
		if ($poolAnswers->count > 3) {
			$poolAnswers->count--;
		}
	}

	$this->invalidateControl('poolAnswers');

	if(!$this->isAjax()) {
		$this->redirect('this');
	}
}


protected function createComponentPoolForm()
{
	$form = new AppForm();

	$form->addText('question', 'Otázka:')->addRule(Form::FILLED, 'Vložte otázku.');
	$form->addText('answer1', 'Odpověď #1:')->addRule(Form::FILLED, 'Vložte odpověď.');
	$form->addText('answer2', 'Odpověď #2:')->addRule(Form::FILLED, 'Vložte odpověď.');

	$poolAnswers = $this->getSession('poolAnswers');
	for ($i = 3; $i < $poolAnswers->count; $i++)
	{
		$form->addText('answer'.$i, 'Odpoveď #'.$i.':')->addRule(Form::FILLED, 'Vložte odpověď.');
	}

	$form->addSubmit('save', 'Uložit');
	$form->addProtection('Prosím odešlete přihlašovací údaje znova (vypršla platnost tzv. bezpečnostního tokenu).');
	$form->onSubmit[] = callback($this, 'poolFormSubmitted');
	$form->getRenderer()->setClientScript(new LiveClientScript($form));

	return $form;
}

public function poolFormSubmitted($form)
{
	if ($form['save']->isSubmittedBy()) {
		$this->flashMessage('Anketa byla upravena.<br />'. Debug::dump($form->getValues()), 'updated');
	}

	$this->redirect('default');
}

Krome toho, že to nemám OK, taky nevím proč se mi po odeslání formuláře nevypíší „dumpem“ data z formuláře do té flash message..?

V šabloně pak mám:

{block content}
{widget poolForm}
<a href="{link addAnswer!}" class="ajax">Přidat odpověď</a>
<a href="{link deleteAnswer!}" class="ajax">Ubrat odpověď</a><br />
Odpovedí celkem: {=$poolAnswers-1}

Předem moc děkuju za vaši vztřícnost :).

Editoval Endrju (15. 3. 2010 10:38)

Endrju
Člen | 147
+
0
-

Ozve se nějaká dobrá duše :)?

srigi
Nette Blogger | 558
+
0
-

Vzdy sa treba dobre zamysliet nad tym s akou entitiu na stranke manipulujes. V tomto pripade manipulujes s formularom. V tomto svetle je tvoj posledny kod nespravne, lebo signaly posielas presenteru a nie formularu. Urob to tak, ze si spravis novy php subor v ktorom podedis AppForm a v nom nadefinuj handlery (prijimace signalov) pre tlacidla add/delete. Tie budu potom robit pripadne zmeny formulara nad $this, cize aktualnou instanciou – tym padom si nic nemusis ukladat do session, lebo $this predstavuje aktualnu podobu formulara.

Editoval srigi (15. 3. 2010 19:28)

Endrju
Člen | 147
+
0
-

To je vyborny napad srigi! Neni nahodou takto nejak udelany ten priklad z examples fifteen? Snazil jsem se probadat ten kod z toho prikladu, kdyz tady nikdo dlouho neodepisoval a vsiml jsem si, ze je tam udelana zvlast v souboru komponenta FifteenControl, ktera ale rozsiruje Control a ne AppForm jak pises. Vim, ze to zavani trochu tim, ze bych chtel aby mi nekdo napsal kod, ale já v podstatě opravdu moc nevím jak to udělat… Jdu dale zkoumat fifteen.. pokud to nejak poresim, tak se pak rad podelim o kod, protoze tohle nikde v tutorialech jeste neni a urcite by neco takoveho bylo fajn.

srigi, myslis, že by jsi mi s naprogramovanim tehle veci mohl trochu pomoct? Mel bys u me virtualni flasku :). Nebo i nekdo jiny :).

bazo
Člen | 620
+
0
-

ak podedis AppForm tak tie signaly musis navesit na submit buttony, ale ak by si vytvoril novy Control, ktory bude obsahovat ten formular, tak mozes mat tie signaly navesene na odkazy, tak ako to mas v doterajsom kode. ale asi by sa ti takto neukladali hodnoty uz vyplnenych policiek

Endrju
Člen | 147
+
0
-

bazo, taky díky za tip. V tom případě se obejdu bez klikatelných odkazů (i když by byly lepší). Zamýšlel jsem že v případě, kdy už nebude možné odebírat odkazy to už nebude klikatelný odkaz ale jen nějaký naformátovaný <span>. Nicméně něco podobného se snad dá udělat i s buttonem (enabled/disabled), hm?

Jinak pořád se s tím trápim a nikam jsem se nepohnul. Uvítam jakoukoli pomoc, díky.

srigi
Nette Blogger | 558
+
0
-

Az sa najde trocha casu (asi az v druhej polovici tyzdna), skusim na to pozriet. Skvor mi to bohuzial nevyjde P(

Ola
Člen | 385
+
0
-

IMHO si poděděním AppFormu nepomůžeš, někde stejně musíš mít uložený počet políček (session, url (get), hidden pole (post)). Zde se zdá jako ideální url, protože můžeš odkázat tak, že presenter vykreslí právě 5 políček, navíc nemusíš zastřešovat funkčnost jako u session. Udělat to můžeš i v presenteru, pokud ten Tvůj kód trochu upravím:

<?php
class SomethingPresenter extends BasePresenter
{

	/** @persistent */
	public $answersCount = 4;

	/** @persistent */
	public $answers = array();

	public function renderDefault()
	{
		$this->template->poolAnswers = $this->answersCount;
	}

	public function handleAddAnswer()
	{
		$this->invalidateControl('poolAnswers');
		$this->answersCount++;

		if(!$this->isAjax()) {
		        $this->redirect('this');
		}
	}

	public function handleDeleteAnswer()
	{
		$this->invalidateControl('poolAnswers');
		$this->answersCount--;

		if(!$this->isAjax()) {
		        $this->redirect('this');
		}
	}

	protected function createComponentPoolForm()
	{
		$form = new AppForm;

		$form->addText('question', 'Otázka:')->addRule(Form::FILLED, 'Vložte otázku.');
		$form->addText('answer1', 'Odpověď #1:')->addRule(Form::FILLED, 'Vložte odpověď.');
		$form->addText('answer2', 'Odpověď #2:')->addRule(Form::FILLED, 'Vložte odpověď.');

		if($this->answersCount >= 3) {
			for ($i = 3; $i < $this->answersCount; $i++) {
			        $form->addText('answer'.$i, 'Odpoveď #'.$i.':')->addRule(Form::FILLED, 'Vložte odpověď.');
			}
		}

		$form->addSubmit('save', 'Uložit');
		$form->addProtection('Prosím odešlete přihlašovací údaje znova (vypršla platnost tzv. bezpečnostního tokenu).');
		$form->onSubmit[] = callback($this, 'poolFormSubmitted');
		$form->getRenderer()->setClientScript(new LiveClientScript($form));

		return $form;
	}

	public function poolFormSubmitted($form)
	{
		if ($form['save']->isSubmittedBy()) {
		        $this->flashMessage('Anketa byla upravena.<br />'. Debug::dump($form->getValues()), 'updated');
		}

		$this->redirect('default');
	}
}
?>

Editoval Ola (15. 3. 2010 23:19)

Endrju
Člen | 147
+
0
-

Díky srigi, to bys byl moc hodný!

Zkousim zatím různé nápady, ale zatím pokazdé na něčem ztroskotám.. Co třeba něco takového?

class Admin_PoolPresenter extends Admin_SecuredBasePresenter
{
	public function renderDefault()
	{
		// co tady..? je zde treba neco momentalne psat?
	}

	// handlery jsem zakomentoval

	protected function createComponentPoolForm()
	{
		$form = new AppForm();

		// nevim jak nastavit pocet inputu z hodnoty ve formulari..
+		if (!isset($count) || $count !== (int)$count) {
+			$count = 2; // prednastaveni defaultniho poctu odpovedi
+		}

+		$form->addHidden('count')->setValue($count);

		// otazka
		$form->addText('question', 'Otázka:')->addRule(Form::FILLED, 'Vložte otázku.');
		// odpovedi
+/-		for ($i = 0; $i < $count; $i++) {
+/-			$form->addText('answer'.$i, 'Odpoveď #'.$i.':')->addRule(Form::FILLED, 'Vložte odpověď #'.$i);
+/-		}

		// tlacitka pridat a ubrat odpoved
+		$form->addSubmit('answerAdd', '+')->setValidationScope(NULL);
+		$form->addSubmit('answerDel', '-')->setValidationScope(NULL);

		$form->addSubmit('save', 'Uložit');
		$form->addSubmit('cancel', 'Zrušit')->setValidationScope(NULL);

		$form->addProtection('Prosím odešlete přihlašovací údaje znova (vypršla platnost tzv. bezpečnostního tokenu).');
		$form->onSubmit[] = callback($this, 'poolFormSubmitted');

		return $form;
	}



	public function poolFormSubmitted($form)
	{
		if ($form['save']->isSubmittedBy()) {
			// ....
		}
+		else if ($form['answerAdd']->isSubmittedBy()) {
+			$values = $form->getValues();
+			$values['count']++; // zde bych zvysil pocet v hidden poli
+
+			$form->setDefaults($values); // nastavila by se data formulari
+			// a pak by se melo presmerovat zase na formular, ale tak aby mu zustala data
+		}
+		else if ($form['answerDel']->isSubmittedBy()) {
+			$values = $form->getValues();
+			$values['count']--; // zde bych snizil pocet v hidden poli
+
+			$form->setDefaults($values);
+			// a pak by se melo presmerovat zase na formular, ale tak aby mu zustala data
+		}

		$this->redirect('default');
	}

}

V sablone by se kliklo na tlacitko + nebo -, form by se odeslal (bez validace) a znova by se prekreslil s polickem o jedno mene nebo vice a data by zustala zachovana.
Nevim.. co si o tom myslite? Nevim jak to dotahnout do konce… Nechci ale predhazovat nejake reseni, ktere neni od navrhu uplne OK, abychom se ho pak nechytnuli a reseni, ktere by mohlo byt lepsi by se ani nezkusilo… Diky

Endrju
Člen | 147
+
0
-

Ola: jeste jsem to zkusil prepsat tak jak pises.. inputy se pridavaji/odebiraji. Data ale nezustavaji.. Mozna proto, ze to nemam v ajaxu?

Zkousel jsem to v sablone obalit a sablone layout pridat zavinac pred include content, ale zda se ze se to ajaxove porad nechova. Jednak data nezustavaji a druhak se prekresluje cela stranka… Ajax ale spise az k zaveru.. Melo by to preci jit bez JS i s JS.. Viz. ten priklad fifteen (funguje i kdyz vypnete JS)

Viz.

@{block content}
<script type="text/javascript" src="{$basePath}/js/LiveFormValidation.js"></script>

{snippet poolAnswers}
{widget poolForm}
<a href="{link addAnswer!}" class="ajax">Přidat odpověď</a>
<a href="{link deleteAnswer!}" class="ajax">Ubrat odpověď</a><br />
{/snippet}

{/block}

Editoval Endrju (15. 3. 2010 23:57)

Ola
Člen | 385
+
0
-

Na AJAX se zatím vykašli – důležitý je, že se nepředávaj data. Máš nainstalovaný firebug? zkus vyhodit přesměrování po odeslání formuláře a dát sem URL, na které se nacházíš a kolik si tam měl addTextů.

Pokud už ho řešíš, zavináče musíš mít všude, kde je to potřeba. Tedy i před {include #content} v layoutu, i před {/block}.

Endrju
Člen | 147
+
0
-

Neboj, Ajax zatim resit nechci.. Firebug mam.

Zakomentoval jsem presmerovani po odeslani formulare… Zkusil jsem tam do te casti kodu (hned na zacatek) dat Debug::dump($form->getValues());. Zkusil jsem ten dump dat do flash message, ale vypise se to stejne nad layoutem a obsah flash message se zobrazi prazdny.. wierd?

URL po odeslani formulare: http://localhost/<NAZEV PROJEKTU>/document_root/admin/pool/?answersCount=3&do=poolForm-submit

POST data:

_token_		0bff4f78826ad0d7a4271c932db1f1f1
answer1		1
answer2		2
answer3		3
question	q
save		Uložit

view je default.phtm

{block content}
{widget poolForm}
<a href="{link addAnswer!}">Přidat odpověď</a> <a href="{link deleteAnswer!}">Ubrat odpověď</a>
{/block}

Editoval Endrju (16. 3. 2010 22:28)

Ola
Člen | 385
+
0
-

To není URL po odeslání formuláře. Musíš zakomentovat redirect.

bazo
Člen | 620
+
0
-

do Debug::dump($form->getValues()); daj druhy parameter true, inak to robi echo a flash message vypisuje len string. staci sa pozriet do api

Endrju
Člen | 147
+
0
-

Ola: updatnul jsem post (prehlednul jsem zakomentovani redirectu v poolFormSubmitted() metode..)

bazo: jo diky, nevedel jsem ze ma dump jeste dalsi paramert, ale ja vim nevedomost neomlouva :P. Sice mi to ted do flashmessage vypise, ale escapuje to HTML, takze ta informace z dump neni moc prehledna a menit sablonu, jen kvuli toho, abych videl pekny dump ve flash message nebudu, necham to vypisovat jen jako echo tedy.. Ale jsem zase o neco chytrejsi :).

No a co ted dale? Mam zakomenentovany redirect v poolFormSubmitted() a kdyz odeslu formular, tak data zustanou.. kdyz ale kliknu na ten odkaz pro pridani/odebrani policka, tak se vlastne stranka znova vykresli a data se nezachovaji… coz by se v pripade handleru dit asi nemelo ne? nebo alespon ja bych to takto nechtel..

Mohl bych to sice udelat tak, ze bych misto odkazu dal do formulare submit buttony pro pridani a odebrani inputu a v submit metode neprovadel redirect pri submitu pomoci techo dvou tlacitek (viz. muj post #11), ale radeji bych to udelal pomoci tech odkazu, ktere nenalezi formulari… a po jejich stisku data zachoval.. pokud by se ale musela posilat, tak radeji pres POST.. nejsou to sice nejak moc tajna data, ale kyby se to posilalo pres GET, tak by se posilalo i ID ankety, obsah tokenu a pripadna dalsi hidden data a to by se mi moc nelibilo. Je tady nejaky jiny zbusoub nez AJAX? Nebo bych tohle musel udelat s temi submit tlacitky primo ve formulari a jejich stisk odchytavat v submit metode?


srigi napsal(a):

Az sa najde trocha casu (asi az v druhej polovici tyzdna), skusim na to pozriet. Skvor mi to bohuzial nevyjde P(

srigi, asi to reseni zustane na tobe. Zkousel jsem co me napadlo nebo mi zkusili poradit (mozna jsem je nepochopil spravne), ale pokazde skoncim na tom, ze kdyz v handleru nebo onSubmit metode provedu redirect (aby se vykreslil formular se spravnym poctem policek), tak tim prijdu o data, ktera si poslu. Data jsem posilal tak, ze jsem ve formulari pridal 2 submit tlacitka na pridani/odebrani inputu a navesil na ne onClick udalost, ktera vola handlery. V handleru pak dokazu ziskat data z formulare $this['poolForm']->setDefaults($this['poolForm']->getValues());, ale naslednym $this->redirect('this'); je zrusim. Pokud redirect neuvedu, neprekresli se formular a nezobrzi se spravny pocet inputu..

Napadlo me.. Jak to dela validace na strane serveru? Kdyz neni form validni a vypnu JavaScript, tak se formular vrati s vyplnenyma hodnotama a vykresli chyby. Kdyz pred tim vykreslenim vedel kolik inputu mam vykreslit, tak by to slo, hm? Zkousel jsem proto debugovat nevalidni formular, ale nevim kde az je to zakopane, takze jsem se k tomu nedostal.

Jsem da se rici ve slepe ulicce. Budu moc vdecny za tvou pomoc srigi.

Editoval Endrju (17. 3. 2010 6:06)

Endrju
Člen | 147
+
0
-

oživuju téma..