Více formulářů na jedné stránce

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

Ahoj,

setkávám se s dalším „problémem“, na který jsem nenašel na fóru žádné témata. Rovnou k věci. Z databáze si (přes dibi) vytáhnu nějakou hromadu řádků a jako pole (teď tuším DibiRow[]) uložím do proměnné do templatu. Potřebuji mít pro každý ten řádek nový formulář, jenže nevím jak na to? Mám pole napřed projít před uložením do template a vytvořit formuláře pro jednotlivé řádky (..new Form())? Případně jakým způsobem byste to řešili?
Pak ještě předběhnu, dočetl jsem se, že pokud je na stránce více formulářů na jedné stránce, odesílají se všechny – skutečně se ještě odesílají všechny – není to zbytečné?

Děkuju za odpovědi

phx
Člen | 651
+
0
-

Zdravim.

To ze se odesilaji vsechny je dle meho kachna.

Proc potrebujes co radek to formular??? Nestatilo by tam dat jen vice odesilacich tlacitek a pak si v app detekovat jakym tlacitkem ze to byl formular odeslan? I kdyz pravda je ta, ze v tu chvili se odesle jeden veliky form.

joe
Člen | 313
+
0
-

Spíš bych potřeboval každý formulář, tvořím e-shop a na stránce, kde jsou výpisy produktů, bych u každého chtěl input s počtem a tlačítkem přidat do košíku. S jedním formulářem by to asi taky šlo, ale s více se mi to zdá jednodušší, jen nevím jak na to v Nette :)

phx
Člen | 651
+
0
-

Okoukni to v nejakem prikladu. Pouziti AppForm. (Form je v pouziti bez Nette)

V tom pripade ti nezbyde nic jineho, nez porjit pole vyrobku a pro kazdy vytvorit AppForm a ty stradat do nejakeho pole, ktere predas do sablony. Docela by me jak to nette schroupne. Osobne jsem to netestoval.

Ondrej
Člen | 110
+
0
-

phx napsal(a):

V tom pripade ti nezbyde nic jineho, nez porjit pole vyrobku a pro kazdy vytvorit AppForm a ty stradat do nejakeho pole, ktere predas do sablony. Docela by me jak to nette schroupne. Osobne jsem to netestoval.

AppForm bych na to nepouzil. Muze nastat situace, ze po odeslani(pridani do kosiku) nebude jiz vyrobek na aktualni strance, pokud se mezitim prida do databaze vyrobek, ktery ovlivni vypis. V tom pripade by doslo k vyjimce signal pro neexistujici komponentu

Pro tento priklad bych zkusil pouzit normalni Form a jako action nastavit signal pro presenter nebo signal pro komponentu kosik

joe
Člen | 313
+
0
-

Ondrej napsal(a):

Muze nastat situace, ze po odeslani(pridani do kosiku) nebude jiz vyrobek na aktualni strance, pokud se mezitim prida do databaze vyrobek, ktery ovlivni vypis. V tom pripade by doslo k vyjimce signal pro neexistujici komponentu

Tak abych pravdu napsal, tohle jsem moc nepochopil, nevím proč by výrobek neměl být na aktuální stránce. Nicméně jsem to udělal pomocí AppForm, funguje mi to a to je v tuhle chvíli hlavní (věřte, že nedělám žádnou důležitou aplikaci, to je jen do školy… a tak jsem se chtěl trošku naučit s Nette :-)).

V čem vidím ale problém je ten výpis formulářů, s tim se asi neskamaradím. Raději bych vypisoval formuláře normálně „ručně“ v šabloně v cyklu u výpisu prodůktů – například (i když tady jsem to udělal už kódem), ale například u výpisu košíku bych potřeboval vypisovat formulář jiným způsobem – tj. na jednu řádku input, vedle něj další případné odkazy… věřím, že to jde nějak přepsat, aby mi to vypisovalo tak jak chci, ale myslím si, že lepší by bylo, kdybych to měl rovnou v šabloně. Je pak nějaký způsob, pokud bych to tak udělal, jak napojit ten natvrdo napsaný formulář na nějaký handler v presenteru?

Ondrej
Člen | 110
+
0
-

joe napsal(a):

Tak abych pravdu napsal, tohle jsem moc nepochopil, nevím proč by výrobek neměl být na aktuální stránce.

Protoze jej muze administrator e-shopu v databazi upravit (vymazat, prejmenovat) prave v dobe, kdy si prohlizis nactenou stranku.

joe
Člen | 313
+
0
-

Už chápu, ale

  1. Je to málo pravděpodobné, i když stát se to může.
  2. Při každém výpisu košíku vybírám z databáze právě ty produkty, které v košíku jsou. Pokud nějaký neodpovídá skutečnému produktu z db, není problém ho z košíku odstranit a ohlásit to uživateli.

… ale to už je mimo téma.

romansklenar
Člen | 655
+
0
-

joe napsal(a):

Už chápu, ale

1. Je to málo pravděpodobné, i když stát se to může.

Počítat bys s tím měl (popíšu níže).

2. Při každém výpisu košíku vybírám z databáze právě ty produkty, které v košíku jsou. Pokud nějaký neodpovídá skutečnému produktu z db, není problém ho z košíku odstranit a ohlásit to uživateli.

Při každém výpisu košíku vybírej z db jen ty produkty, které jsou validní (je možné je objednat). Možná jsi to tak myslel jen ses překlep. Když má někdo košík nebo výpisu zboží v kategorii 15 minut, už se může stát, že se změní stav (dosupnost) zboží (ať už ho koupí nějaký jiný zákazník, nebo ho admin umaže), takže když zboží do košíku přidá a odešle tak formulář, je třeba tyhle věci kontrolovat.

Pokud bych to měl udělat nějak univerzálně komponentu výpisu zboží v kategorii, tak mě napadá takovédle řešení:

  1. Vykreslitelná komponenta (Control) – Zboží: bude reprezentovat zboží (výrobek) – máš tím vyřešen ten formulář (input) s polem pro počet kusů (ještě bych zvážil signály místo formulářů, když jsme v komponentě – dalo by se to hezky a jednoduše zajaxovaťet). Komponenta bude mít tu svou logiku (lze ji ještě objednat? atd co je třeba) a zároveň si můžeš udělat více vykreslení (všechny jak budeš potřebovat, včetně manuálního vykreslení formuláře v šabloně) například pro zobrazení ve 3 sloupcích, jen řádky, malé náhledy/velké náhledy, zobrazení v košíku, detail výrobku
  2. Vykreslitelná komponenta ala DataGrid – Kategorie: můžeš si pro jednoduchost představit, že vypisuje zboží – co řádek to jeden Control Zboží – a zaobaluje je těma obvyklýma věcma okolo (řazení dle ceny, filrt výrobců, zobrazení náhledů, atd). Komponenta může v sobě vykreslit další komponentu, takže není problém. Výhodu vidím v tom, že správnou definicí rout si můžeš tyhle uživatelovy požadavky (řazení, filtry) předávat jako parametry presentru této komponentě a ta už se o všechno hezky postará. Komponenty zboží bych pojmenovával podle jejich tvaru v URL adrese.
  3. Vykreslitelná komponent – Košík: opět stejný princip jako přehled kategorie akorát s jinou funkčností okolo výpisu.

Teďkom si už jen nadefinovat routy, napsat pro ně pohledy, které budou dělat to co chceš (detail výrobku / výpis) kde budeš registrovat komponenty a je to :) jak krásně a jednoduše to zní :) až mě mrzí, že v Nette nemám momentálně žádnou práci a musím dělat v takovém něčem (nechci říkat v čem), kde jsou tyhle věci otázkou ne hodin ale světelných let. :(

EDIT: Jak to celé může třeba vypadat ve výsledku.

Editoval romansklenar (9. 1. 2009 23:14)

joe
Člen | 313
+
0
-

Díky, tak tahle odpověď mi moc pomohla. Jak jsem psal, s Nette se začínám učit a taky nedělám žádnou důležitou aplikaci, na které když se něco udělá špatně, tak to vadí. I teď jsem si vědom, že tam mám plno věcí špatným způsobem, ale jde mi o to, to udělat funkční.

Dal jsem na radu a vytvořil si tedy komponentu pro jeden produkt. Takže můj postup je následující, z db vyberu nějaký result set, který projdu cyklem a pro každý řádek DibiRow vytvořím komponentu a tu uložím do pole, které předám šabloně.

Problém mám ale v tom, že jsem si do té komponenty dal i to vytváření formuláře a mám tam i metodu, která by měla přijmout požadavek po odeslání formuláře, až sem to funguje. V té metodě mám pak přesměrování a to je můj problém. Používám řádku

$this->redirect('Nazevpresenteru:view', array('parametr' => 'hodnota'));

Jenže právě na tomto řádku mi to ztroskotá, píše chybu
Unknown signal ‚Nazevkomponenty:Nazevpresenteru:view!‘.

Vůbec nevím jak to opravit, aby to fungovalo, bez toho se dál nehnu. Kdyby mi někdo mohl pomoc, byl bych rád, děkuju.

David Grudl
Nette Core | 8218
+
0
-

Asi by to chtělo

$this->presenter->redirect('Nazevpresenteru:view', array('parametr' => 'hodnota'));
phx
Člen | 651
+
0
-

Ondrej napsal(a):

AppForm bych na to nepouzil. Muze nastat situace, ze po odeslani(pridani do kosiku) nebude jiz vyrobek na aktualni strance, pokud se mezitim prida do databaze vyrobek, ktery ovlivni vypis. V tom pripade by doslo k vyjimce signal pro neexistujici komponentu

Timto si nejsem uplne jist. Na strance bude pokazce 20 formularu, ktere se budou lisit akorat nejakou hodnootu v hiddne polozce. Vsechny formy bude obsluhovat jedna a ta sama metoda. Jediny problem by mohl nastat pokud David kontroluje hodnotxy v hidden polozkach zda je nekdo nezmenil. Coz na jednu stranu by mohly, ale nadruhou stranu hidden polozky vyuziva treba JS na odesilani nejakych hodnot.

Ondrej
Člen | 110
+
0
-

phx napsal(a):

Timto si nejsem uplne jist. Na strance bude pokazce 20 formularu, ktere se budou lisit akorat nejakou hodnootu v hiddne polozce. Vsechny formy bude obsluhovat jedna a ta sama metoda. Jediny problem by mohl nastat pokud David kontroluje hodnotxy v hidden polozkach zda je nekdo nezmenil. Coz na jednu stranu by mohly, ale nadruhou stranu hidden polozky vyuziva treba JS na odesilani nejakych hodnot.

Nevim, jestli pises o variante 20× AppForm, ale formulare musis nejak unikatne pojmenovat, takze je nejspis budes pojmenovavat form-id_vyrobku. A po odeslani zase vytvoris 20× AppForm ale nezarucis, ze budou mit stejny nazev form-id_vyrobku jako minule. Sekvencni pojmenovani form-position by mohlo fungovat jen v pripade, ze na strance bude vzdy 20 formularu a ne jen 19, pokud se jeden proda.

phx
Člen | 651
+
0
-

Mas pravdu. Bylo by nutne jmena formularu cislovat vzdy 1–20. A byl by tam problem se zmizenim (prodanimm, …) vyrobku. Leda pokazde generovat vsech 20 AppForm, coz bude obcas neekonomicke (posledni stranka vypisu)

joe
Člen | 313
+
0
-

David Grudl
Děkuju. To je pak jednoduché, když vím co kde je a jak funguje :) Skoro jsem k tomu už došel.

Ondrej napsal(a):
… ale formulare musis nejak unikatne pojmenovat, takze je nejspis budes pojmenovavat form-id_vyrobku. A po odeslani zase vytvoris 20× AppForm ale nezarucis, ze budou mit stejny nazev form-id_vyrobku jako minule.

Přesně tak formuláře pojmenovávám, i když teď už to dělám v komponentě pro zboží. Jenom nechápu čemu by mělo vadit, pokud bych je pojmenoval stejně jako na předcházející stránce? A nevytvořil bych jich vždycky 20, ale pokaždé ten počet, kolik mám zrovna získaných produktů snad, ne?

tom
Člen | 171
+
0
-

joe napsal(a):

Dal jsem na radu a vytvořil si tedy komponentu pro jeden produkt. Takže můj postup je následující, z db vyberu nějaký result set, který projdu cyklem a pro každý řádek DibiRow vytvořím komponentu a tu uložím do pole, které předám šabloně.

Problém mám ale v tom, že jsem si do té komponenty dal i to vytváření formuláře a mám tam i metodu, která by měla přijmout požadavek po odeslání formuláře, až sem to funguje.

Muzu se zeptat jakym zpusobem pak ten formular zpracovavas? Delam to same a nedari se mi ziskat ty hodnoty z formulare … snazim se o to pomoci handleru. Nevim ale zda je to to prave.

joe
Člen | 313
+
0
-

tom napsal(a):

Muzu se zeptat jakym zpusobem pak ten formular zpracovavas? Delam to same a nedari se mi ziskat ty hodnoty z formulare … snazim se o to pomoci handleru. Nevim ale zda je to to prave.

Napíšu jak to přesně mam, třeba to k něčemu bude. Nevím jestli to tak je správně, ale fungovalo mi to.

Takže, mam nějakou komponentu, v ní mam veřejný parametr $form, do kterého přidávám nový AppForm. Přidávám do něj různý inputy, hodnoty… apod. a na konci mam

$this->form->onSubmit[] = array($this, 'addToCartSubmitted');

Ten formulář ($form) si pak předávám někde do šablony. To znamená, že po odeslání toho formuláře se zavolá metoda addToCartSubmitted v ty samý třídě (komponentě).

Na tu metodu pak mam kód

public function addToCartSubmitted(AppForm $form) { ... }

Kde jako jeden z posledních řádků je přesměrování na nějaký presenter.

No, nevím jestli ti to pomohlo :-)

tom
Člen | 171
+
0
-

Ten formulář ($form) si pak předávám někde do šablony. To znamená, že po odeslání toho formuláře se zavolá metoda addToCartSubmitted v ty samý třídě (komponentě).

Já se snažím hodit komponentu (Produkt), která má formulář s inputem a odesílacím tlačítkem do řádku komponenty Grid, která vypisuje všechny produkty. A na každém řádku bych právě potřeboval mít tu jednu komponentu s tím formem. Což mi zatím nějak nejde :(

tom
Člen | 171
+
0
-

Mám control ProduktVKosiku, ktery ma v sobe formular slouzici k manipulaci s danym produktem.

<?php
class ProduktVKosiku extends Control
{
    /** @var int */
    protected $pid;         // produkt id
    protected $iterator;

    public $form;

    public function bindData($id, $iterator)
	{
        $this->pid = $id;
        $this->iterator = $iterator;
        $this->form = new AppForm($this, 'form'.$this->iterator);
        $this->form->addText('pocet', 'Počet:');
        $this->form->addSubmit('submit'.$this->iterator, 'Odeslat');
        $this->form->onSubmit[] = array($this, 'productSubmitted');
    }

    public function productSubmitted(AppForm $form)
    {
        ...
    }

    public function renderProduktVKosiku()
    {
    	$this->form->render();
    }

}
?>

Tento control umistuju do dalsiho controlu DataGridKosik, ktery vykresluje datovy grid, kde na kazdem radku chci mit onen control ProduktVKosiku

<?php
class DataGridKosik extends Control
{
...
protected $produkt;
...

	public function bindData()
	{
		...
        	$this->produkt = new ProduktVKosiku($this, 'pvk');

	}

	public function renderGrid()
	{
		//vykresli datagrid
		...
		$template->produkt = $this->produkt;
		...
		$template->setFile(dirname(__FILE__) . '/datagridkosik.phtml');
		...
	}

}

?>

a k tomu datagridkosik.phtml

<table class="grid" border="1">
<tr>
	{foreach $columns as $column}

	<th><a href="{link order $column}"
	{if isset($order[$column])} class="{$order[$column][0] === 'a' ? 'asc' : 'desc'}"{/if}
	>{$column}{if count($order) > 1 && isset($order[$column])}&nbsp;<span>{$order[$column][1]}</span>{/if}</a></th>

	{/foreach}
    <th>&nbsp;</th>
</tr>

{foreach $rows as $row}
<tr{!$iterator->isEven() ? ' class="alt"' : ''}>
    <td>{$row['id']}</td>
    <td>{$row['Název']}</td>
    <td>{$row['Počet']}</td>
    <td>{$row['Cena']|currency}</td>
    <td>{?$produkt->bindData($row['id'], $iterator->getCounter());$produkt->renderProduktVKosiku()}</td>


</tr>
{/foreach}
</table>

Nedaří se mi ale, pri kliknuti na tlacitku v libovolnem formulari (na radcich toho gridu) dochazi k chybe InvalidArgumentException
Component with name ‚form1‘ does not exist.
. Číslo formuláře záleží na čísle řádku, na kterém ten formulář je (form1, form2, form3, …)

Tusim v cem je asi chyba, akorat nevim jak ji odstranit, poradil by mi nekdo prosim? Diky. Nebo pokud ma nekdo lepsi reseni … Diky moc

vlki
Člen | 218
+
0
-

Pravděpodobně nemáš ty formuláře a komponenty zaregistrované před fází zpracování signálů. Toto se dá řešit tak, že všechny registrace komponent uvedeš vždy v metodě createComponent. Lazy loading ti pak zajistí to, že pokud bude komponenta při zpracování signálu potřeba, inicializuje se. Viz https://forum.nette.org/…iewtopic.php?…

tom
Člen | 171
+
0
-

vlki napsal(a):

Pravděpodobně nemáš ty formuláře a komponenty zaregistrované před fází zpracování signálů. Toto se dá řešit tak, že všechny registrace komponent uvedeš vždy v metodě createComponent. Lazy loading ti pak zajistí to, že pokud bude komponenta při zpracování signálu potřeba, inicializuje se. Viz https://forum.nette.org/…iewtopic.php?…

a kde ty formulare zaregistrovat a jak?

komponenta ProduktVKosiku obsahujici formular je vlozena do komponenty DataGridKosik (do kazdeho radku gridu jedna komponenta ProduktVKosiku s tim formem) a tato je v presenteru KosikPresenter … hrozne by mi pomohla nejaka ukazka. diky moc

vlki
Člen | 218
+
0
-

Oukej, zkusim ti to tady rozepsat. V presenteru KosikPresenter si vytvoris, podobne jako v „posvätnem akrabate“, metodu createComponent, ktera bude registrovat tu komponentu DataGridKosik. Asi takto…

<?php
presenter KosikPresenter extends Presenter
{
...

	protected function createComponentKosik($name)
	{
				return new DataGridKosik();
	}
...
}
?>

V komponente DataGridKosik by pak, podle nazvu melo byt tolik komponent ProduktVKosiku, kolik je produktu, ne? Takhle to vypada, ze je tam jen jedna komponenta ProduktVKosiku a pod ni vsechny formy…? Zkusim to nastinit tak, aby ProduktVKosiku byl tolikrat, kolik je druhu produktu…

<?php
class DataGridKosik extends Control
{
...

	public function __construct($parent = NULL, $name = NULL)
	{
		// tady se lazy loading nehodi, stejne musime vsechny produkty vypsat
		$products = Products::get(); // pseudokod - ziskani produktu pro vypsani
		foreach($products as $p) {
			$produkt = new ProduktVKosiku($this, 'produkt' . $p['id']);
			$produkt->pid = $p['id'];
		}

	}

        public function renderGrid()
        {
                //vykresli datagrid
                ...
                $template->setFile(dirname(__FILE__) . '/datagridkosik.phtml');
                ...
        }

}
?>

A nakonec samotny ProduktVKosiku

<?php
class ProduktVKosiku extends Control
{
	/** @var int */
	public $pid;         // produkt id

	protected function createComponentForm($name)
	{
		// tady se lazy loading hodi... treba nekdy nebude potreba zrovna vypsat do radku formular,
				$form = new AppForm();
				$form->addText('pocet', 'Počet:');
				$form->addSubmit('odeslat', 'Odeslat');
				$form->onSubmit[] = array($this, 'productSubmitted');
				return $form;
	}

	public function productSubmitted(AppForm $form)
	{
		$produkt = $form->getParent();
		$produkt->pid; // takhle bys mel ziskat pid pri zpracovani formulare
        	...
	}

	public function renderProduktVKosiku()
	{
        	$this->getComponent('form')->render();
	}

}
?>

A nakonec sablona…

<table class="grid" border="1">
<tr>
        {foreach $columns as $column}

        <th><a href="{link order $column}"
        {if isset($order[$column])} class="{$order[$column][0] === 'a' ? 'asc' : 'desc'}"{/if}
        >{$column}{if count($order) > 1 && isset($order[$column])}&nbsp;<span>{$order[$column][1]}</span>{/if}</a></th>

        {/foreach}
    <th>&nbsp;</th>
</tr>

{foreach $rows as $row}
<tr{!$iterator->isEven() ? ' class="alt"' : ''}>
    <td>{$row['id']}</td>
    <td>{$row['Název']}</td>
    <td>{$row['Počet']}</td>
    <td>{$row['Cena']|currency}</td>
    <td>{$component->getComponent('produkt' . $row['id'])->renderProduktVKosiku()}</td>


</tr>
{/foreach}
</table>

Tohle by ti melo zajistit hladky prubeh. Doufam, ze jsem tou zmenou moc nenarusil tvou predstavu. Ty jsi totiz predtim mel registrovanou jen jednu komponentu ProduktVKosiku, ktere jsi menil promennou pid. A presny duvod, proc ti to predtim nejelo? Registroval jsi formular az pri samotnem renderovani stranky. A to uz je pozde.

Jinak jsem to netestoval, takze je mozne, ze pri copy&paste to nepojede. Ale chtel jsem to hlavne demonstrovat.

Editoval vlki (14. 3. 2009 20:51)

tom
Člen | 171
+
0
-

vlki napsal(a):

Tohle by ti melo zajistit hladky prubeh. Doufam, ze jsem tou zmenou moc nenarusil tvou predstavu. Ty jsi totiz predtim mel registrovanou jen jednu komponentu ProduktVKosiku, ktere jsi menil promennou pid. A presny duvod, proc ti to predtim nejelo? Registroval jsi formular az pri samotnem renderovani stranky. A to uz je pozde.

Jinak jsem to netestoval, takze je mozne, ze pri copy&paste to nepojede. Ale chtel jsem to hlavne demonstrovat.

Diky moc jsi mi pomohl, uz mi to funguje :)

Santas
Člen | 11
+
0
-

Mať teda na jednej stránke viac obyčajných Formov nejde a odchytávať ich klasickým isSubmitted()? (V prípade, že by sme ich neoverovali, že boli poslané tým a tým tlačítkom, keďže užívateľ môže stlačiť Enter)

Edit: nakoniec isSubmittedBy() odchytáva aj Enter :)

Editoval Santas (6. 7. 2009 14:10)

joe
Člen | 313
+
0
-

Takže jsem se dostal do stejné situace jako jsem kdysi psal. Pokud jsem správně pochytil vaše odpovědi, tak mám namísto AppForm použít Form, který nekontroluje, zda se tam bude položka ještě nacházet – pokud by došlo ke smazání produktu.

Další problém je, že pokud pro formuláře vytvořím validační pravidla a těch formulářů v cyklu vypíšu třeba 40 (tj. 40× vypíšu komponentu pro produkt), pak se mi tam i 40× vloží stejný kus kódu v JavaScriptu pro validování. Jde to nějakým způsobem obejít, že bych vypsal jen těch 40 formulářů s jedním klientským skriptem, teď mě napadlo, že validační pravidla přiřadím jen vždy tomu prvnímu formuláři.

Tak teď na to koukám, to by vlastně nešlo. Protože každý form má unikátní název…

Nebo řešíte někdo výpis více formulářů na jedné stránce úplně nějak jinak? Třeba se to za tu dobu trochu změnilo :-)

phx
Člen | 651
+
0
-

Ja tohle resim rucne. V sablone mam obycejny HTML FORM a v Nette si vse ohlidam a data si taham z $request.

public function handleDelete() {
	$request = $this->getRequest();
	if ($request->isMethod('post')) {
		$ids = $request->post['id'];
		// ...
	}
}