Jak spravne vytvaret formulare?
- tomasnikl
- Člen | 137
Ahoj,
chci se zeptat, zda je zpusob, ktery pouzivam na vytvareni formularu spravny. Pro kazdy formular na webu mam vlastni tridu umistenou v
app/FrontModule/forms
a jednoduchy formular vypada takto:
<?php
use Nette\Application\UI,
Nette\ComponentModel\IContainer;
class TestForm extends UI\Form
{
public function __construct(IContainer $parent = NULL, $name = NULL)
{
parent::__construct($parent, $name);
$this->addText('name', 'Jméno:');
$this->addSubmit('submit', 'Odeslat');
}
public function testSubmitted($form)
{
echo 'jo jo, formular byl odeslan';
}
}
v Presenteru mam pote uz jen:
protected function createComponentTest($name)
{
$form = new TestForm();
$form->onSuccess[] = callback($form, 'testSubmitted');
return $form;
}
a v sablone:
{control test}
Je takovyto postup spravny? Mit pro kazdy formular vlastni tridu a v spolu s formularem metodu, ktera se provede po odeslani? Nebo je by bylo lepsi formulare vice „seskupovat“. Tim myslim to, ze pokud se formular tyka napr uzivatele (login, registrace, editace uctu apod) tak vsechny tyto 3 formulare dat do jedne tridy?
Je vubec takovyto zpusob vytvareni formularu spravny? Kdyz jsem je vytvarel primo v presenteru a jeden formular mel napr 20 inputu, tak presenter mel najednou 500 radku a clovek se v nem uz dost ztracel.. rozdeleni formularu do samostatnych souboru mi tedy prislo jako dobre reseni a dokonce to je zminovane i v dokumentaci..
Dekuju za tipy a rady
- Tharos
- Člen | 1030
Z mého pohledu na to jdeš velmi dobře. Já osobně mám vlastní třídy nejen pro formuláře, ale i pro obslužné handlery. To mimo jiné velmi zvyšuje znovupoužitelnost kódu, protože mám například nějaký potvrzovací formulář, kterému jen pokaždé nastavím jinou otázku k potvrzení a jiný handler.
Dávat definice a obsluhy formulářu do Presenteru je z mého pohledu nevhodná praktika, protože to do Presenteru vůbec nepatří a stávají se pak z nich 500+ řádkové třídy, ve kterých se je těžké vůbec vyznat.
Pokud si vyseparuješ samostatné třídy pro definice formulářů (a třeba i pro obsluhu formulářů), je už jenom na Tobě, jak je provážeš dědičností/kompozicí. Uričtě je vhodné neopakovat kód a společné prvky formulářů seskupit do nějaké vlastní třídy. K tomu se dá použít dědičnost (třeba nějaký BaseForm) nebo kompozice. Kompozice je zde flexibilnější, dědičnost asi zase používanější, to už je na Tobě…
- Filip Procházka
- Moderator | 4668
Dědičnost
class PersonForm extends Nette\Application\UI\Form
{
public function __construct()
{
parent::__construct();
$this->addText('name', 'Jméno');
$this->addSubmit('submit', 'Odeslat');
}
}
class PersonWithAddressForm extends PersonForm
{
public function __construct()
{
parent::__construct();
$this->addText('city', 'Město');
$this->addText('street', 'Ulice');
}
}
Kompozice
class PersonContainer extends Nette\Forms\Container
{
public function __construct()
{
parent::__construct();
$this->addText('name', 'Jméno');
$this->addText('surname', 'Příjmení');
}
}
class AddressContainer extends Nette\Forms\Container
{
public function __construct()
{
parent::__construct();
$this->addText('city', 'Město');
$this->addText('street', 'Ulice');
}
}
class PersonWithAddressesForm extends Nette\Application\UI\Form
{
public function __construct()
{
parent::__construct();
$this['user'] = new PersonContainer();
$this['user']['address'] = new AddressContainer();
$this['user']['correspondence'] = new AddressContainer();
$this['user']['billing'] = new AddressContainer();
$this->addSubmit('submit', 'Odeslat');
}
}
Co myslíš, že je lepší? :)
// edit 12.6.2012 – přesunuto do pla.nette
Editoval HosipLan (12. 6. 2012 11:18)
- Tharos
- Člen | 1030
@HosipLan: Díky za doplnění :).
@tomasnikl: Kompozice je skoro všude lepší, jen ji řada vývojářů nemá prostě „moc zažitou“ (a i v důsledku toho se dědičnost dost nadužívá). Kompozice zpravidla vede ke znovupoužitelnějšímu kódu, protože nějakou funkcionalitu můžeš zakomponovat kamkoliv. U dědičnosti musíš pak sáhnout k dědění od nějaké třídy, která třeba ale k té rodičovské vůbec nemá vztah „is a“.
Nejlepší je používat dědičnost skutečně jenom pro případy typu
Škoda Octavia extends Auto
a pochopitelně také tam, kde Tě
k tomu třeba nějaký framework vyloženě nutí.
Ještě bych k tomu dodal, že i některé návrhové vzory, které tak trochu specifickým způsobem využívají (rozuměj zneužívají) dědičnost, jsou dnes některými programátory brány s rezervou a často je doporučují nahradit řešením založeným na kompozici. Já se řadím mezi ně. :)
Editoval Tharos (30. 11. 2011 15:15)
- pawouk
- Člen | 172
Rozhodně bych formulář nedědil přímo z UI\Form ale z nejakeho BaseForm, i kdyz bude prazdny, casem budes treba chcit neco udelat pro vsechny formulare (to muze byt cokoliv) a muze se to hodit. Ja mam svuj baseForm kde jsem si napsal par metod jako addTime, addDate, addAjaxUpload atd. a celkem se mi tim ulehcila prace. Ale jinak si myslim ze tvuj navrh je zcela vporadku.
- Filip Procházka
- Moderator | 4668
@**pawouk**: Tvé addTime
má ovšem zásadní nevýhodu.
Nezavoláš to totiž ve form containeru :)
Správně tedy
Nette\Forms\Container::extensionMethod('addTime', function ($_this, $name, $arg1, ...) {
return $_this[$name] = new TimeControl($arg1, ...);
});
- tomasnikl
- Člen | 137
jeste jeden takovy dotaz, zdrojaky muzu pouzit z meho prvniho prispevku, takze:
<?php
use Nette\Application\UI,
Nette\ComponentModel\IContainer;
class TestForm extends UI\Form
{
public function __construct(IContainer $parent = NULL, $name = NULL)
{
parent::__construct($parent, $name);
$this->addText('name', 'Jméno:');
$this->addSubmit('submit', 'Odeslat');
}
public function testSubmitted(UI\Form $form)
{
if ($form->isSuccess()) {
echo 'jo jo, formular byl odeslan';
}
}
}
me vyhodi chybu:
Call to undefined method FrontModule\TipAddForm::isSuccess().
Jak tento problem prosim opravit?
- bojovyletoun
- Člen | 667
Zkus využít laděnku, umí přece zobrazit call stack a hledat v argumentech issuccess. /Pidle mě buď si špatně nastavil handlery (záměna $this a $form), a nebo ti nějaký formulář nedědí od UI\FOrm ale třeba od containeru.
- joseff
- Člen | 233
HosipLan napsal(a):
@**pawouk**: Tvé
addTime
má ovšem zásadní nevýhodu. Nezavoláš to totiž ve form containeru :)Správně tedy
Nette\Forms\Container::extensionMethod('addTime', function ($_this, $name, $arg1, ...) { return $_this[$name] = new TimeControl($arg1, ...); });
Zajímalo by mě, kde přesně by jsi toto rozšířšní definoval?
- Filip Procházka
- Moderator | 4668
Já bych ho definoval přesně v souboru, ve kterém mám vlastního potomka
Form
, takže v momentě, kdy s ním budu pracovat, načtou se mi
vždy i rozšíření. No a nebo v app/boostrap.php
- joseff
- Člen | 233
HosipLan napsal(a):
Já bych ho definoval přesně v souboru, ve kterém mám vlastního potomka
Form
, takže v momentě, kdy s ním budu pracovat, načtou se mi vždy i rozšíření. No a nebo vapp/boostrap.php
Mě právě přijde ten bootstrap špatně, když máš pak 20 různých rozšíření v různých třídách, tak budeš v kařdém voání includovat 20 souboů ikdyž je nevyužiješ… Lepší mi tedy přijde první možnost, ale kde? To jako v __constructoru?
- Filip Procházka
- Moderator | 4668
Ne. Jako vedle třídy.
class Form extends Nette\Application\UI\Form
{
// ...
}
Nette\Forms\Container::extensionMethod('addTime', function ($_this, $name, $arg1, ...) {
return $_this[$name] = new TimeControl($arg1, ...);
});
- ruppy
- Člen | 2
Dobrý den, vzhledem k tomu že hodně moc oprašuji znalost php a rozhodl
jsem si to „ulehčit“ s Nette :)
tak jsou mi některé věci trošku nejasné. Formuláře už zvladam, vkladat
přez ně a tak, ale když už je mám dělat, chci se to učit rovnou co
nejvhodněji.
Kompozice se mi líbí ale krapet tápu v tom jak to v této podobě oživit.
Formulář mám v samostatném souboru a potřeboval bych poradit jak jej
zavolat z presenteru.
HosipLan napsal(a):
Kompozice
class PersonContainer extends Nette\Forms\Container { public function __construct()
Editoval ruppy (20. 2. 2012 17:06)
- RDPanek
- Člen | 189
HosipLan napsal(a):
Dědičnost
class PersonForm extends Nette\Application\UI\Form { public function __construct() { parent::__construct(); $this->addText('name', 'Jméno'); $this->addSubmit('submit', 'Odeslat'); } } class PersonWithAddressForm extends PersonForm { public function __construct() { parent::__construct(); $this->addText('city', 'Město'); $this->addText('street', 'Ulice'); } }
Kompozice
class PersonContainer extends Nette\Forms\Container { public function __construct() { parent::__construct(); $this->addText('name', 'Jméno'); $this->addText('surname', 'Příjmení'); } } class AddressContainer extends Nette\Forms\Container { public function __construct() { parent::__construct(); $this->addText('city', 'Město'); $this->addText('street', 'Ulice'); } } class PersonWithAddressesForm extends Nette\Application\UI\Form { public function __construct() { parent::__construct(); $this['user'] = new PersonContainer(); $this['user']['address'] = new AddressContainer(); $this['user']['correspondence'] = new AddressContainer(); $this['user']['billing'] = new AddressContainer(); $this->addSubmit('submit', 'Odeslat'); } }
Co myslíš, že je lepší? :)
Moc pěkný
- Filip Procházka
- Moderator | 4668
Díky, ale je nutné, abys to celé citoval?
Editoval HosipLan (20. 2. 2012 20:17)
- MelkorNemesis
- Člen | 36
Urcite bych dal HosipLanuv kod do FAQ, ikdyz ta dedicnost vs kompozice neukazuji stejny vystup, tak ale ukazuji silu kompozice a zanorovani komponent.
- PavelJurasek
- Člen | 39
Rád bych upozornil na to, že pokud chci přiřadit kompozicí container do nějaké Group, musím nejdříve ve formu vytvořit Group a poté ji předat containeru:
$phone = $this->addGroup('phone');
$this['phone'] = new PhoneContainer(…, $phone, …);
který si musí tuto Group nastavit jako současnou
public function __construct(…, \Nette\Forms\ControlGroup $group = null, …) {
if (!is_null($group)) {
$this->setCurrentGroup($group);
}
…
}
Je nějaká možnost, jak to vyřešit?
Nejspíše to bude způsobeno tím, že v containeru nelze zavolat parent::__construct() s parametry a tím pádem se okamžitě nepřipojí k formu. Teda pokud tomu správně rozumím…
Editoval PavelJurasek (26. 2. 2012 12:34)
- Jan Mikeš
- Člen | 771
Libi se mi reseni viz 1. prispevek, davat si vsechny formy do vlastnich trid a souboru. Napada me ale jedna otazka a to, co kdyz budu chtit po odeslani formu volat flashMessage a redirect? Mam si predavat presenter do komponenty? nebo flasMessage a redirect volat pouze v presenteru a nedavat to do externiho souboru, viz:
protected function createComponentTest()
{
$that = $this;
$form = new TestForm();
$form->onSuccess[] = callback($form, 'testSubmitted');
$form->onSuccess[] = function() use ($that){
$that->flashMessage("zprava");
$that->redirect("Homepage:default");
};
return $form;
}
Jak to pripadne udelat jinak a nejlepe?
Editoval Lexi (28. 6. 2012 15:29)
- revoke
- Člen | 36
Mám dotaz začátečníka: Jak v metodě testSubmitted zavolat metodu v patřičném modulu? Zkouším:
// presenter
protected function createComponentUser($id)
{
$form = new UserForm();
$form->onSuccess[] = callback($form, 'userSubmitted');
if(!empty($this->list)){
$form->setDefaults($this->list);
}
return $form;
}
// třída
use Nette\Application\UI,
Nette\ComponentModel\IContainer;
class UserForm extends BaseForm
{
public function __construct(IContainer $parent = NULL, $name = NULL)
{
parent::__construct($parent, $name);
$this->addText('name', 'Jméno:', 40, 100);
$this->addSubmit('submit', 'Odeslat');
}
public function userSubmitted($form)
{
echo 'jo jo, formular byl odeslan';
$this->users->createOrUpdate($_POST);
}
}
ale to mi vrací chybu „Cannot read an undeclared property UserForm::$users“. Díky za pomoc.
- David Matějka
- Moderator | 6445
no to bude tim, ze promenna users ve tride neexistuje :)
pravdepodobne mas na mysli $this->presenter->context->users (nebo
users injectnout do formu) nebo neco na ten zpusob (kde definujes
users
a jak?)
a pri zpracovani formulare nepouzivej $_POST, ale $form->values
Editoval matej21 (29. 8. 2012 13:38)
- Ot@s
- Backer | 476
revoke napsal(a):
Mám dotaz začátečníka: Jak v metodě testSubmitted zavolat metodu v patřičném modulu? Zkouším:
ale to mi vrací chybu „Cannot read an undeclared property UserForm::$users“. Díky za pomoc.
Vždyď to funguje správně. Postarej se o to, abys v
$this->users
(v objektu UserForm
) byl existující
objekt (nejlépe nějaký model).
- Filip Procházka
- Moderator | 4668
Tohle je bezpečnostní díra! **
$this->users->createOrUpdate($_POST);
V Nette proměnnou $_POST
nepoužíváme!
Od čeho vůbec ten formulář tam máš, když ho nepoužíváš? A proč si nezaložíš vlastní téma?
Správně takto:
class UserForm extends BaseForm
{
private $users;
public function __construct(Users $users)
{
parent::__construct(); // předávat $parent a $name je zbytečné
$this->users = $users;
$this->addText('name', 'Jméno:', 40, 100);
$this->addSubmit('submit', 'Odeslat');
$this->onSuccess[] = array($this, 'userSubmitted');
}
public function userSubmitted()
{
$this->users->createOrUpdate($this->values);
}
}
V presenteru
protected function createComponentUser($id)
{
// $this->users nastavíš v setContext() nebo ve startup()
$form = new UserForm($this->users);
if (!empty($this->list)) { // tomuhle by bylo líp v action
$form->setDefaults($this->list);
}
$form->onSuccess[] = function ($form) {
if (!$form->valid) return;
$form->presenter->flashMessage("Formulář byl odeslán");
$form->presenter->redirect('this');
};
return $form;
}
**) Není to úplně bezpečnostní díra, ale mohla by být. A navíc je to v Nette, lidově řešeno, prasárna.
Editoval HosipLan (29. 8. 2012 14:00)
- Patrik Votoček
- Člen | 2221
revoke napsal(a):
Mám dotaz začátečníka: Jak v metodě testSubmitted zavolat metodu v patřičném modulu? Zkouším: …
class UserPresenter extends BasePresenter
{
/** @var MyUberModel */
private $model;
public function setContext(MyUberModel $model)
{
$this->model = $model;
}
protected function createComponentUser()
{
$form = new UserForm($this->model);
$presenter = $this;
$form->onSuccess[] = function($form) use($presenter) {
if ($form->valid) {
$presenter->flashMessage('Fu*k Yea!');
$presenter->redirect('this');
}
};
return $form;
}
public function actionEdit($id)
{
// ....
$this['user']->setDefaults($this->list);
}
}
class UserForm extends BaseForm
{
/** @var MyUberModel */
private $model;
public function __construct(MyUberModel $model)
{
parent::__construct();
$this->model = $model;
$this->addText('name', 'Jméno:', 40, 100);
$this->addSubmit('submit', 'Odeslat');
$this->onSuccess[] = callback($this, 'userSubmitted');
}
public function userSubmitted($form)
{
$this->model->createOrUpdate($this->getValues());
}
}
matej21 napsal(a):
pravdepodobne mas na mysli $this->presenter->context->users
Ou nou!