Jak vygenerovat formulář s X-inputů, kde X zadá uživatel v šabloně (anketa)?
- Endrju
- Člen | 147
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
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
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)
- srigi
- Nette Blogger | 558
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
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
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
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.
- Ola
- Člen | 385
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
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
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
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
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)
- Endrju
- Člen | 147
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)