Nové formuláře Nette\Forms

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8218
+
0
-

Do repozitáře jsem nahrál důležitou součást Nette Frameworku – formuláře. Společně s tím se symbolicky verze přehoupla na 0.8.

Nové formuláře svou koncepcí vycházejí ze svého osvědčeného předchůdce, dva roky staré třídy NForm, nicméně nejsou zpětně kompatibilní.

Co je nového?

  • uživatelská validační pravidla
  • vlastní obsluha na straně JavaScriptu
  • jednodušší zápis formuláře (fluent interfaces)
  • nekonfliktní ID položky při generování částí formuláře (např. přes AJAX)
  • výchozí chybové hlášky u validačních pravidel
  • jmenné kontejnery (pro tzv. array notation)
  • internacionalizace a automatické překlady

Důležitou změnou je, že větev validačních pravidel se neodvíjí od formuláře, ale od každého ovládacího prvku zvlášť. Tedy metody addRule() a addCondition() se nevolají nad formulářem, ale nad prvkem, ke kterému se vztahují. Validace pak probíhá v pořadí, v jakém jsou definovány prvky formuláře.

$form = new Form();
$form->addText('name', 'Your name:')
	->addRule(Form::FILLED, 'Enter your name');

$form->addText('age', 'Your age:', 5)
	->addRule(Form::FILLED, 'Enter your age')
	->addRule(Form::NUMERIC, 'Age must be numeric')
	->addRule(Form::RANGE, 'Age must be in range from %d to %d', array(10, 100));

$form->addCheckbox('send', 'Shipping address:')
	->addCondition(Form::EQUAL, TRUE)
		->toggle('sendBox'); // toggle HTML element 'sendBox'

$form->addText('email', 'Email:', 35)
	->setEmptyValue('@')
	->addCondition(Form::FILLED) // conditional rule: if is email filled, ...
		->addRule(Form::EMAIL, 'E-mail is not valid'); // ... then check email

$form->addText('city', 'City:', 35)
	->addConditionOn($form['send'], Form::EQUAL, TRUE) // if $form['send'] is checked
		->addRule(Form::FILLED, 'Enter your shipping address'); // $form['city'] must be filled

Dále metody addRule() a addCondition() jako název validační operace akceptují callback nebo jméno statické funkce, díky čemuž je možné používat vlastní validační pravidla.

$form = new Form();
$form->addText('name', 'Text:', 10)
	->addRule('MyClass::myValidator', 'Value %d is not allowed!', 11)

Veškerá JavaScriptová podpora byla vyseparována do samostatné třídy. Díky tomu je možné vytvořit vlastní JavaScriptový validátor nebo obsluhu událostí, lze snadno propojit vygenerovaný formulář s nějakým JavaScriptovým frameworkem a podobně.

Každý HTML prvek formuláře lze před vykreslením libovolně upravit. Přístup k němu zajišťují metody getControlPrototype() a getLabelPrototype(), které vrací objekt typu Nette\Web\Html.

$form->addText('name', 'Text:', 10);
$form['name']->getControlPrototype()->style = "background: blue";

Ve webových aplikacích s Presenterem používejte zděděnou třídu Nette\Application\AppForm.

Nové formuláře se nacházejí v beta verzi, mám v plánu některé další vlastnosti doprogramovat. Především se chci zaměřit na spolupráci s AJAXem.

David Grudl
Nette Core | 8218
+
0
-

ps. způsob, jak snadno upravit definice formulářů do nové podoby:

zaměňte

$form->addRule('name', Forms::FILLED, 'Zadejte prosím Vaše ID');
$form->addRule('name', Forms::NUMERIC, 'ID musí být číslo');
...

za

$form['name']->addRule(Form::FILLED, 'Zadejte prosím Vaše ID');
$form['name']->addRule(Form::NUMERIC, 'ID musí být číslo');
...

Jiný příklad:

$cond = $form->addCondition('submit', Forms::SUBMITTED);
$cond->addRule('note', Forms::REGEXP, 'Poznámka musí být ve tvaru %s', '/\d{3}/');

zaměňte za

$form['submit1']->addCondition(Form::SUBMITTED)
    ->addRuleFor($form['note'], Form::REGEXP, 'Poznámka musí být ve tvaru %s', '/\d{3}/');
LM
Člen | 206
+
0
-

Jak je to se skrytými inputy, dřív je to automaticky renderovalo na konci formuláře, nyní se tak neděje a metoda renderHiddens už není, takže renderovat manuálně? Možná by stálo za to na to taky upozornit.

Editoval LM (11. 8. 2008 14:21)

David Grudl
Nette Core | 8218
+
0
-

LM napsal(a):

Jak je to se skrytými inputy, dřív je to automaticky renderovalo na konci formuláře, nyní se tak neděje a metoda renderHiddens už není, takže renderovat manuálně? Možná by stálo za to na to taky upozornit.

Mělo by to (snad) zařídit $form->render('body').

LM
Člen | 206
+
0
-

David Grudl napsal(a):
Mělo by to (snad) zařídit $form->render(‚body‘).

Aha super, akorát ta prázdná tabulka je tam nepěkná :-)

pmg
Člen | 372
+
0
-

Díky za vylepšení! Warning: Komentář pro PHP < 5.3 by měl být ještě před apostrofy in ‚Nette/Form.php‘ on line 55.

Panda
Člen | 569
+
0
-

David Grudl napsal(a):

ps. způsob, jak snadno upravit definice formulářů do nové podoby: zaměňte

$form->addRule('name', Forms::FILLED, 'Zadejte prosím Vaše ID');
$form->addRule('name', Forms::NUMERIC, 'ID musí být číslo');
...

na

$form['name']->addRule(Forms::FILLED, 'Zadejte prosím Vaše ID');
$form['name']->addRule(Forms::NUMERIC, 'ID musí být číslo');
...

Jiný příklad:

$cond = $form->addCondition('submit', Forms::SUBMITTED);
$cond->addRule('note', Forms::REGEXP, 'Poznámka musí být ve tvaru %s', '/\d{3}/');

zaměňte na

$form['submit1']->addCondition(Forms::SUBMITTED)
    ->addRuleFor($form['note'], Forms::REGEXP, 'Poznámka musí být ve tvaru %s', '/\d{3}/');

Ještě se musí u konstant v argumentu $operation u metod FormControl::addCondition a Form::addRule zaměnit třída Forms (již neexistující) za třídu Form (kam se konstanty pčesunuly), tzn. Forms::SUBMITTED → Form:SUBMITTED, Forms::FILLED → Form::FILLED, atd., jinak to skončí parádní chybou o neexistující třídě. :)

Uvedené příklady pak budou vypadat takto:

$form['name']->addRule(Form::FILLED, 'Zadejte prosím Vaše ID');
$form['name']->addRule(Form::NUMERIC, 'ID musí být číslo');
...

a

$form['submit1']->addCondition(Form::SUBMITTED)
    ->addRuleFor($form['note'], Form::REGEXP, 'Poznámka musí být ve tvaru %s', '/\d{3}/');

Jinak nové formy vypadají a jsou naprosto báječné, Nette dál kvete…

David Grudl
Nette Core | 8218
+
0
-

Panda napsal(a):

Ještě se musí u konstant v argumentu $operation u metod FormControl::addCondition a Form::addRule zaměnit třída Forms (již neexistující) za třídu Form (kam se konstanty pčesunuly), tzn. Forms::SUBMITTED → Form:SUBMITTED, Forms::FILLED → Form::FILLED, atd., jinak to skončí parádní chybou o neexistující třídě. :)

To je pravda, post upravím.

Jinak nové formy vypadají a jsou naprosto báječné, Nette dál kvete…

Díky!

David Grudl
Nette Core | 8218
+
0
-

pmg napsal(a):

Díky za vylepšení! Warning: Komentář pro PHP < 5.3 by měl být ještě před apostrofy in ‚Nette/Form.php‘ on line 55.

fixed

LM napsal(a):

David Grudl napsal(a):
Mělo by to (snad) zařídit $form->render(‚body‘).

Aha super, akorát ta prázdná tabulka je tam nepěkná :-)

fixed

Spikee
Člen | 2
+
0
-

Ahoj. Ak chcem používať formuláre v presenteri, tak platí ešte toto riešenie?

LM
Člen | 206
+
0
-

Spikee napsal(a):

Ahoj. Ak chcem používať formuláre v presenteri, tak platí ešte toto riešenie?

Koukni do prvního příspěvku, akorát místo Form použit AppForm.

Panda
Člen | 569
+
0
-

Spikee napsal(a):

Ahoj. Ak chcem používať formuláre v presenteri, tak platí ešte toto riešenie?

Platí, ale použij třídu AppForm, která je speciálně pro použití v presenteru – má obslužné mechanismy na zpracování signálů. Od třídy Form se liší také tím tím, že má převrácené argumenty v konstruktoru (AppForm je má stejně jako staré Forms, Form má nyní jako první argument jméno a až jako druhý rodičovský IComponentContainer), takže když to použiješ přesně podle udaného řešení, připomene Ti nutnost použití AppForm chybové hlášení (tzn. prostě to nebude fungovat).

//Doplnění: LM byl o něco rychlejší, omlouvám se…

Editoval Panda (11. 8. 2008 21:36)

David Grudl
Nette Core | 8218
+
0
-

Panda napsal(a):

Od třídy Form se liší také tím tím, že má převrácené argumenty v konstruktoru (AppForm je má stejně jako staré Forms, Form má nyní jako první argument jméno a až jako druhý rodičovský IComponentContainer), takže když to použiješ přesně podle udaného řešení, připomene Ti nutnost použití AppForm chybové hlášení (tzn. prostě to nebude fungovat).

To mě nenapadlo, jde o docela příjemný vedlejší efekt :-) Třída Form má parametr $parent uvedený až „bokem“ proto, že tam, kde se používá, obvykle žádný parent není potřeba a ani neexistuje.

phx
Člen | 651
+
0
-

Zdravim… Takze trida Form funguje samostatne, ale AppForm je vylepsena pro pouziti v Presenteru.

Snad bych jen rad upozornil, ze se docela zasadne zmenila metoda Form::isSubbmited().

  • nevraci bool, ale objekt s rozhanim ISubmitterControl
  • dale nema parametr na dotaz zda odeslano tim nebo tim tlacitkem

Uprimne prijde mi kapku divna tato konstrukce, ke ktery jsem ted nucen:

$btn = $form->isSubmitted();
if($btn->name=='save') {
	$this->redirect('default', true);
}
else {
	$this->redirect('new', true);
}

Osobne bych ji opravil at vraci bool (kdyz uz se jmenuje isXYZ()) a pridal getSubmittedBy(). Originalni parametr v isSubmitedBy($by) byl uzasny napad, ktery se ted nekam vytratil.

ViliamKopecky
Nette hipster | 230
+
0
-

Souhlasím s phx. isSubmitted() by určitě měla vracet bool.

phx
Člen | 651
+
0
-

Jeste jedna vec:
onSubmit se provede pouze kdyz fyl formular uspesne a validne odeslan?

Rad bych udelal jeste nekde jinde na strance upozorneni, ze form je chybne vyplnen, ale nevim kde neco takoveho zachytit. Mozna by stalo za to udelat neco jako onValidationError a nebo onSubmit udelat pokazde pri odeslani a onSuccessSubmit pri validnim odeslani.

Hmm?

phx
Člen | 651
+
0
-

Mam prosbu o opetovne zavadeni renderHiddens(). Kdyz kreslim form rucne tak to usnadni praci. DEKUJI

BERU ZPET… render(‚body‘) jiz renderuje jen zbytky;) Krasa…

Editoval phx (13. 8. 2008 14:10)

Panda
Člen | 569
+
0
-

phx napsal(a):

Jeste jedna vec:
onSubmit se provede pouze kdyz fyl formular uspesne a validne odeslan?

Pokud je jeho vlastnost onlyValid nastavena na true (jako že defaultně je), tak ano.

Rad bych udelal jeste nekde jinde na strance upozorneni, ze form je chybne vyplnen, ale nevim kde neco takoveho zachytit. Mozna by stalo za to udelat neco jako onValidationError a nebo onSubmit udelat pokazde pri odeslani a onSuccessSubmit pri validnim odeslani.

Pokud je formulář chybně vyplněn, tak si chyby zpracuje sám a vykreslí je hned před tělíčko. Stačí nakouknout do kousku metody ConventionalRenderer::render:

	$this->render('begin'); // renderuje počáteční <form ...>
	$this->render('errors'); // renderuje chyby - jako ul
	$this->render('body');
	$this->render('end'); // renderuje </form>

Chybu také můžeš sám přidat metodou addError, nebo FormControl::addError.

Pokud by jsi chtěl chyby kreslit ještě někde jinde, tak doporučuji kouknout na metody hasErrors a getErrors.

phx
Člen | 651
+
0
-

Diky… uz jsem si nasle cestu:)

Panda
Člen | 569
+
0
-

phx napsal(a):

Zdravim… Takze trida Form funguje samostatne, ale AppForm je vylepsena pro pouziti v Presenteru.

Snad bych jen rad upozornil, ze se docela zasadne zmenila metoda Form::isSubbmited().

  • nevraci bool, ale objekt s rozhanim ISubmitterControl
  • dale nema parametr na dotaz zda odeslano tim nebo tim tlacitkem

Uprimne prijde mi kapku divna tato konstrukce, ke ktery jsem ted nucen:

$btn = $form->isSubmitted();
if($btn->name=='save') {
	$this->redirect('default', true);
}
else {
	$this->redirect('new', true);
}

Osobne bych ji opravil at vraci bool (kdyz uz se jmenuje isXYZ()) a pridal getSubmittedBy(). Originalni parametr v isSubmitedBy($by) byl uzasny napad, ktery se ted nekam vytratil.

Vytratil se do rozhraní ISubmitterControl:

if($form['save']->isSubmittedBy()) {
	$this->redirect('default', true);
} else {
	$this->redirect('new', true);
}

Myslím, že tato konstrukce je ještě o něco lepší, než původní řešení s metodou Form::isSubmittedBy.

Mě osobně vůbec nevadí, že metoda Form::isSubmitted() nevrací bool, v podmínce jí mohu použít tak jako tak a navíc se dozvím, čím že to vlastně byl formulář odeslán. A to mi umožňuje použití jiné konstrukce:

if($submit = $form->isSubmitted())
{
	// možnost použít proměnnou $submit
}

To by se jinak psalo takto:

if($form->isSubmitted())
{
	$submit = $form->getSubmittedBy();
	// možnost použít proměnnou $submit
}

Tím ušetřím celý jeden řádek kódu s celým jedním voláním funkce. :)

phx
Člen | 651
+
0
-

A nebo:

if($submit = $form->getSubmittedBy())
{
        // možnost použít proměnnou $submit
}

isXYZ() by dle me melo vracet bool.

Vytratil se do rozhraní ISubmitterControl:

if($form['save']->isSubmittedBy()) {
	$this->redirect('default', true);
} else {
	$this->redirect('new', true);
}

DIKY to jsem nenasel.

David Grudl
Nette Core | 8218
+
0
-

phx napsal(a):

Snad bych jen rad upozornil, ze se docela zasadne zmenila metoda Form::isSubmitted().

  • nevraci bool, ale objekt s rozhanim ISubmitterControl
  • dale nema parametr na dotaz zda odeslano tim nebo tim tlacitkem

Uprimne prijde mi kapku divna tato konstrukce, ke ktery jsem ted nucen:

$btn = $form->isSubmitted();
if($btn->name=='save') {
	$this->redirect('default', true);
}
else {
	$this->redirect('new', true);
}

Použití by mohlo vypadat spíš takto:

if ($form->isSubmitted()) { ... }

nevadí, že vrácená hodnota je objekt, podmínka se vyhodnotí korektně.

A dotaz přímo na tlačítko:

if ($form['preview']->isSubmittedBy()) { ... }

Tímto způsobem je možné postihnout případné složitější struktury:

if ($form['subform']['preview']->isSubmittedBy()) { ... }

Co bych navrhl k diskusi, je název metody isSubmittedBy u prvků. Ten název není úplně šťastný, formulář má isSubmitted, jenže použit isSubmitted na tlačítku mi připadá divné – „odeslán“ je formulář, nikoliv tlačítko. Rozdíl mezi 1. a 7. pádem.

David Grudl
Nette Core | 8218
+
0
-

Než jsem si udělal kafe, předběhli jste mě :-)

phx
Člen | 651
+
0
-

David Grudl napsal(a):

if ($form->isSubmitted()) { ... }

Ale tohle zase vypada nelogicky a priserne:

$btn = $form->isSubmitted();

Editoval phx (13. 8. 2008 16:46)

David Grudl
Nette Core | 8218
+
0
-

phx napsal(a):
Ale tohle zase vypada nelogicky a priserne:

$btn = $form->isSubmitted();

Jasně – nepoužívej to ;-)

pmg
Člen | 372
+
0
-

Dá se nějak získat vyrenderovaný formulář jako Nette\Web\Html? Aby šlo něco takového:

$form = new Form;

$container = Html::el('div');
$container->add($form);

echo $container;

Nebo je to zbytečné? Stačila by i metoda __toString.

OT. Nebudu šetřit díky za Nette.compact. Vidím stránku dřív, než stačím zadat adresu.

David Grudl
Nette Core | 8218
+
0
-

Proč ne, napiš si vlastní IFormRenderer::render() které to tak poskládá.

Nedělám to tak standardně kvůli limitu velikosti paměti. Komplikovaný formulář ve formě objektů totiž zabírá mnohem víc paměti, než výstupní řetězec.

MartinJanda
Člen | 60
+
0
-

Lze jednoduše vložit více tlačítek ve vykreslované tabulce formuláře pomocí AppForm?

phx
Člen | 651
+
0
-

Prvek formulare jako prvek. Kdyz tam das X tlacitek tak se vsechny vykresli, ale budou kazde na samostatnem radku:(

MartinJanda
Člen | 60
+
0
-

Ano, o to mi přesně šlo, jen jsem to zapomněl upřesnit.

Hm, díky za info.

phx
Člen | 651
+
0
-

Ja osobne tedy mam slacitka save a saveNew. A pote but se vratim na vypis zaznamu a nebo na moznost vlozeni noveho zaznamu. Jen form musim vykreslovat rucne, protoze tyto tlacitka jsou az nakonci a nelibi se mi, kdyz jsou pod sebou.

David Grudl
Nette Core | 8218
+
0
-

Zavoláním $form['save']->setRendered(TRUE) je vynecháte z vykreslování $form->render('body') a pak je můžete vykreslit ručně.

MartinJanda
Člen | 60
+
0
-

Díky. A jak lze zasáhnout do vykreslovaného formu ručně?

phx
Člen | 651
+
0
-

Do sablony si posles samotny fomrular. (jde to i jinak???)

A pak napriklad:

		<fieldset style="margin: 0 auto 1em auto; width: 260px;">
			<legend>Heslo</legend>
			<?php $form->render('errors'); ?>
			<?php $form->render('begin'); ?>
			<table>
				<tr>
					<th><?= $form['pass1']->label ?></th>
					<td><?= $form['pass1']->control ?></td>
				</tr>
				<tr>
					<th><?= $form['pass2']->label ?></th>
					<td><?= $form['pass2']->control ?></td>
				</tr>
				<tr>
					<td></td>
					<td><?= $form['start']->control ?></td>
				</tr>
			</table>
			<?php $form->render('body'); ?>
			<?php $form->render('end'); ?>
		</fieldset>
MartinJanda
Člen | 60
+
0
-

Perfektní. Díky.

pmg
Člen | 372
+
0
-

Emailová adresa se v současné době validuje výrazem /^[^@]+@[^@]+\.[a-z]{2,6}$/i. Sice dobře chrání před překlepy při psaní, bojím se ale především možného podstrčení víceřádkového textu. Podobné riziko hrozí také u URL.

Bezpečný výraz před časem sestavil Jakub Vrána.

David Grudl
Nette Core | 8218
+
0
-

To je fakt, whitespaces ošetřím.

Jakubův výraz je příliš přísný, selže například na adresách s diakritikou.

pmg
Člen | 372
+
0
-

To je fakt, whitespaces ošetřím.

Děkuji!

Jakubův výraz je příliš přísný, selže například na adresách s diakritikou.

Je těžké udělat kompromis. Adresy s diakritikou nebo s mezerou („Lorem Ipsum“@lipsum.com) jsou platné, ale zatím asi špatně podporované. Pokud se diakritika vyskytne v doménové části, asi by bylo nejlepší doménu převést na její ascii ekvivalent.