Nové formuláře Nette\Forms
- David Grudl
- Nette Core | 8218
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
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}/');
- David Grudl
- Nette Core | 8218
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')
.
- Panda
- Člen | 569
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
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
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
- LM
- Člen | 206
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
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
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
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.
- phx
- Člen | 651
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?
- Panda
- Člen | 569
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
.
- Panda
- Člen | 569
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
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
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
phx napsal(a):
Ale tohle zase vypada nelogicky a priserne:$btn = $form->isSubmitted();
Jasně – nepoužívej to ;-)
- pmg
- Člen | 372
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
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
Lze jednoduše vložit více tlačítek ve vykreslované tabulce formuláře pomocí AppForm?
- David Grudl
- Nette Core | 8218
Zavoláním $form['save']->setRendered(TRUE)
je vynecháte
z vykreslování $form->render('body')
a pak je můžete
vykreslit ručně.
- phx
- Člen | 651
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>
- David Grudl
- Nette Core | 8218
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
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.