Jak spravne vytvaret formulare?

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

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

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ě…

tomasnikl
Člen | 137
+
0
-

Dekuju za nazor,

mel bych k tomu dotaz. Co myslis tou Kompozici?
…K tomu se dá použít dědičnost (třeba nějaký BaseForm) nebo kompozice…
Muzes me nasmerovat nekam, kde bych si o tom mohl precist, co je tim mysleno?

Dekuju

Filip Procházka
Moderator | 4668
+
0
-

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)

tomasnikl
Člen | 137
+
0
-

Na prvni pohled se mi libi vic skladani jednotlivych prvku formulare pomoci kompozice, ale urcite to ma nejake nevyhody (vzdycky nejake ALE je :o)).

Tharos
Člen | 1030
+
0
-

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

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

@**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, ...);
});
Nox
Člen | 378
+
0
-

Ad HosipLan: BaseForm by se dalo snad využít na @method, aby byla takhle přidaná metoda v našeptávači (snad ty magický metody umí IDE i dědit)

Editoval Nox (1. 12. 2011 8:20)

pawouk
Člen | 172
+
0
-

@HosipLan Souhlas, to by asi bylo lepší.

Filip Procházka
Moderator | 4668
+
0
-

Ano máš pravdu, @method annotace se tam výborně hodí :)

tomasnikl
Člen | 137
+
0
-

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

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

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

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

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 v app/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
+
0
-

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

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)

Jan Endel
Člen | 1016
+
0
-

S presenteru jednoduše, vytvoříš ho v továrničce takto:

protected function createComponentMyForm()
{
	return new TestForm();
}

a pak v šabloně vykreslíš:

{control myForm}
RDPanek
Člen | 189
+
0
-

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

Díky, ale je nutné, abys to celé citoval?

Editoval HosipLan (20. 2. 2012 20:17)

Ivorius
Nette Blogger | 119
+
0
-

HosipLanova ukázka by měla jít do FAQ ne?

MelkorNemesis
Člen | 36
+
0
-

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

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)

joseff
Člen | 233
+
0
-

@PavelJurasek: To řešení se mi moc nelíbí, přijde mi takové krkolomné, nicméně řeším stejný problém a nevím jak to lépe vyřešit, věděl by někdo jak na to?

Jan Mikeš
Člen | 771
+
0
-

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)

vvoody
Člen | 910
+
0
-

Ak to je Form podedeny od Nette\Application\UI\Form tak presenter mas hned po ruke:

$form->presenter->redirect(...);

samozrejme ze v kontexte triedy TestForm to bude $this->presenter->…

Jan Mikeš
Člen | 771
+
0
-

Super, diky

revoke
Člen | 36
+
0
-

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

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

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

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

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!

David Matějka
Moderator | 6445
+
0
-

rikam, co ma asi na mysli, ne co by mel pouzit :)