Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

Formuláře v Nette 2.1

Jaké novinky přináší Nette Framework 2.1 ve formulářích? Začnu perličkou: formuláře byly představeny před sedmi lety, jaksi mimochodem, v komentářích pod jiným článkem, a jejich kód včetně dema se vešel do 800 řádků. Už tehdy šlo o stěžejní část frameworku, která uměla třeba vedle serverové validace generovat i tu JavaScriptovou.

Původní návrh založený na explicitním vyjmenovávání prvků se ukázal jako správný, zejména když propukla kauza se zranitelností Mass Assignment, kterou trpěly weby psané v Rails a jimi inspirovaných frameworcích. Na druhou stranu, tvorbu některých dynamických formulářů to činilo těžkopádnou. Dlouho jsem hledal kompromis a pak to přišlo:

Low-level formuláře

Nyní lze používat i prvky, které zapíšeme pouze v šabloně a nepřidáme je do formuláře některou z metod $form->addXyz(). Když například vypisujeme záznamy z databáze a dopředu nevíme, kolik jich bude a jaké budou mít ID, a chceme u každého řádku zobrazit checkbox nebo radio button, stačí jej nakódovat v šabloně:

{foreach $items as $item}
    <p><input type=checkbox name="sel[]" value={$item->id}> {$item->name}</p>
{/foreach}

A po odeslání hodnotu zjistíme:

$values = $form->getHttpData($form::DATA_TEXT, 'sel[]');

kde první parametr je typ elementu (DATA_FILE pro type=file, DATA_LINE pro jednořádkové vstupy jako text, password, email apod. a DATA_TEXT pro všechny ostatní) a druhý parametr sel[] odpovídá HTML atributu name.

Podstatné je, že getHttpData() vrací sanitizovanou hodnotu, v tomto případě to bude vždy pole validních UTF-8 řetězců, ať už se pokusíte serveru podstrčit cokoliv. Jde o obdobu přímé práce s $_POST nebo $_GET avšak s tím podstatným rozdílem, že vždy vrací čistá data, tak, jak jste zvyklí u standardních prvků Nette formulářů.

CheckboxList

Nový prvek pro výběr z více možností je CheckboxList. Stejně jako v případě selectboxů nebo radiolistů kontroluje, zda odeslané hodnoty jsou z těch, které nabízíme:

$form = new Form;
$form->addCheckboxList('colors', 'Favorite colors:', array(
    'r' => 'red',
    'g' => 'green',
    'b' => 'blue',
));

Multiple file upload

Najednou lze uploadovat i více souborů, všimněte si TRUE:

$form = new Form;
$form->addUpload('avatar', 'Picture:', TRUE);

Zároveň formuláře mají integrovanou kontrolu, zda nebyl překročen povolený limit velikosti odesílaných dat.

Nové vykreslovací zbraně

Velmi snadno můžete propojit formulář s existující šablonou. Stačí jen doplnit atributy n:name:

function createComponentSignInForm()
{
    $form = new Form;
    $form->addText('user')->setRequired();
    $form->addPassword('password')->setRequired();
    $form->addSubmit('send');
    return $form;
}
<form n:name=signInForm class=form>
    <p><label n:name=user>Username: <input n:name=user size=20></label>
    <p><label n:name=password>Password: <input n:name=password></label>
    <p><input n:name=send class="btn btn-default">
</form>

Atribut n:name lze používat i s elementy <select>, <button> nebo <textarea>.

Dále můžete vykreslovat prvky jako je RadioList, Checkbox nebo nový CheckList pěkně po jednotlivých HTML elementech. Říká se tomu partial rendering:

{foreach $form[gender]->items as $key => $label}
    <label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>
{/foreach}

Nebo lze použít klasická makra {input gender:$key} a {label gender:$key}, trik je tom názvu s dvojtečkou.

S tím úzce souvisí i aktualizovaný způsob vykreslování checkboxů a RadioListů. Místo dřívějšího

<label>...</label><input>

se nyní vykreslují v praktičtějším tvaru

<label><input>...</label>

pročež si myslím, že odpadne většina důvodů, proč jste tyto prvky potřebovali vykreslovat po částech.

Zároveň také odpadá nutnost kreslit checkboxy trošku jinak než jiné prvky, tj. myslet na to, aby label byl na správném místě. Metoda getLabel() či makro {label} totiž u checkboxů nyní nevrací nic a getControl() či {input} vrací HTML v onom novém tvaru. Pokud ale potřebujete staré chování, přepněte se do zmíněného partial renderingu přidáním dvojtečky: {label checkbox:} a {input checkbox:}.

Podpora pro Bootstrap

V příkladech najdete ukázky, jak nakonfigurovat vykreslování formulářů pro Twitter Bootstrap 2 a Bootstrap 3.

Chytřejší validátory

Validační pravidla Form::INTEGER, NUMERIC a FLOAT rovnou převádí hodnotu na integer resp. float. A dále pravidlo Form::URL, které akceptuje i řetězec ve tvaru např. nette.org, jej automaticky doplní na plnohodnotné https://nette.org.

Přibyla nová validační pravidla Form::BLANK (prvek nesmí být vyplněn) a Form::NOT_EQUAL.

A v argumentech všech validátorů se můžete dynamicky odkazovat na jiné prvky. Takže třeba tady prvek value musí být v rozmezí určeným aktuálními hodnotami prvků min a max:

$form->addText('min');
$form->addText('max');
$form->addText('value')
    ->addRule($form::RANGE, 'from %d to %d', array($form['min'], $form['max']));

Chybové zprávy

Makro {control form} nyní vypisuje chybové zprávy přímo vedle souvisejících prvků a nad formulářem se objeví jen ty, které žádnému prvku nepřiřadíme (tj. když místo $form['name']->addError() použijeme $form->addError()). Je to mnohem uživatelsky příjemnější a doporučuji, abyste stejným způsobem vykreslovali i formuláře manuálně, třeba takto. Pomůže vám metoda $form->getOwnErrors(), která vrací chybové zprávy přiřazené jen k formuláři.

Píšeme vlastní prvky

Výrazného zjednodušení doznala tvorba vlastních formulářových prvků. Podívejte se na příklad DateInput, což je prvek pro zadávání data. Zobrazovat se bude jako trojice políček den, měsíc, rok a z pohledu API bude přijímat a vracet objekt DateTime.

Interně se datum reprezentuje jako trojice privátních proměnných $day, $month, $year, které metoda getValue() převede na požadovaný objekt DateTime (tedy pokud půjde o platné datum) a setValue() naopak vstup do této trojice rozloží. Přičemž by měla kontrolovat validitu vstupu a v případě chyby vyhodit výjimku.

Výjimky naopak nevyhazuje loadHttpData(), která se volá po odeslání formuláře, a hodnoty, které uživatel odeslal, získá metodou getHttpData() a uloží do zmíněné trojice proměnných. Jen pozor, tentokrát mluvím o metodě třídy BaseControl, nikoliv Form. Každopádně i v tomto případě getHttpData() vrací očištěná data.

A nakonec metoda getControl() generuje HTML. Pokud je prvek reprezentován jedním HTML elementem, jeho atribut name určí metoda getHtmlName(). Jenže máme prvky tři, tak za název ještě dolepíme řetězec [day], [month] a [year] (včetně těch hranatých závorek). Stejný postfix pak uvádíme při volání getHttpData() ve zmíněné loadHttpData().

Co když místo obyčejného textového pole budeme chtít vykreslit selectbox? Pak oceníte funkci Nette\Forms\Helpers::createSelectBox(). Prvním parametrem je pole nabízených hodnot, druhým pole HTML atributů elementu <option>. V příkladu uvedené selected? s otazníkem znamená, že atribut se uvede pouze u položky s uvedenou hodnotou. Šlo by také uvést např. 'title:' => array(1 => 'January', 2 => ...) s dvojtečkou, což dává možnost každé položce dát jiný title.

Existuje také obdobná funkce createInputList() pro generování skupin inputů. Té lze jako třetí parametr předat pole HTML atributů pro element label, taktéž podporující otazník a dvojtečku.

Dále autoři nových prvků mohou ocenit dvě nové abstraktní třídy ChoiceControl a MultiChoiceControl.

A co ještě?

Pomocí $control->setOmitted() vyjmete prvek z dat, která vrací $form->getValues(). To se hodí pro různé hesla pro kontrolu, antispamové prvky atd. I všechny prvky, které označíte jako $form->setDisabled(), budou takto vyjmuty.

Vylepšeno bylo togglování, nyní by mělo fungovat přesně podle očekávání. Navíc $form->getToggles() vrátí informaci o viditelnosti všech id.

Metoda setValue() u jednotlivých prvků kontroluje datový typ a dále v případě SelectBoxů a podobně vás nenechá nastavit hodnotu, která v nabízených není.

V HTML atributech data-nette-rules se používá čistý JSON, takže nezapomeňte nasadit aktuální netteForms.js.

A nakonec – u jednotlivých tlačítek můžete omezit seznam prvků, které se mají při odeslání formuláře tímto tlačítkem validovat:

$form->addSubmit('edit')
    ->setValidationScope(array($form['name'], $form['password']));

Komentáře

  1. Eda http://evidencia-dopravcov.eu #1

    avatar

    Perfektní, hlavně loadHttpData :-)
    Formuláře v Nette je radost používat, už si život bez nich skoro ani nedovedu představit. Kdo používá něco jiného, okrádá sám sebe.

    před 3 lety | odpovědět
  2. Felix http://jfx.cz #2

    avatar

    Skvělá práce. Ty Choice a MultiChoice jsou parádní. Palec nahoru!

    před 3 lety | odpovědět
  3. Jakub Kontra http://jakubkontra.cz #3

    avatar

    Super práce, těším se na další články! Přesně jak píše Felix!

    před 3 lety | odpovědět
  4. Tomáš Votruba; http://tomasvotruba.cz #4

    avatar

    Děkuji za práci, kterou nám odpadají všechny workaroundy. Příjemná úleva :)

    před 3 lety | odpovědět
  5. Jiří Špaček #5

    avatar

    Wow, už tak skvělé formuláře povýšeny k dokonalosti (aka. co se předtím muselo řešit doplňkem je nyní nativně :))

    před 3 lety | odpovědět
  6. Ondřej Profant #6

    avatar

    Jaké je nejlepší použití těch rendererů pro Bootsrap v Nette (jako v klasické aplikaci podobné sandboxu)?

    Chápu, že to mohu zkopírovat, ale je nějaký hezký způsob, že bych přidal jednu třídu do app a pak jen přes setRenderer?

    před 3 lety | odpovědět | reagoval [7] David Grudl [8] Pablo
  7. David Grudl http://davidgrudl.com #7

    avatar

    #6 Ondřeji Profante, používám to tak, že nad vytvořeným formulářem zavolám funkci, která to renderování nastaví.

    před 3 lety | odpovědět
  8. Pablo #8

    avatar

    #6 Ondřeji Profante,
    Ale môžeš si z toho urobiť aj samostatné triedy a používať ich tak, ako píšeš:
    $form->setRenderer(new HorizontalFormRenderer);
    Stačí si podediť DefaultRenderer, a správne si ho nakonfigurovať:

    class HorizontalFormRenderer extends DefaultFormRenderer
    {
        public $wrappers = array(
            'form' => array(
                'container' => NULL,
                'errors' => TRUE,
            ),
    
            'error' => array(
                'container' => 'div',
                'item' => 'div class="alert alert-error"',
            )
    ...
    před 3 lety | odpovědět
  9. Petr Poupě #9

    avatar

    Už bylo také vymyšleno nějaké elegantní řešení také pro situaci, kdy mám extra dlouhý formulář, který chci renderovat plně automaticky a najednou přibude položka, kterou musím generovat ručně?

    před 3 lety | odpovědět | reagoval [12] Jakub Kontra
  10. Sebastian #10

    avatar

    Dobrý den Davide,
    Twitter Bootstrap 2 a Bootstrap 3 Vám vedou na 404 na GitHubu.

    před 3 lety | odpovědět | reagoval [11] David Grudl
  11. David Grudl http://davidgrudl.com #11

    avatar

    #10 Sebastiane, opraveno

    před 3 lety | odpovědět
  12. Jakub Kontra http://jakubkontra.cz #12

    avatar

    #9 Petře Poupě, Vymysli, všichni budem rádi ;)

    před 3 lety | odpovědět
  13. vosy http://www.obchodniroboti.cz #13

    avatar

    Ahoj je možné pro checkboxlist použít associované pole?? A případně jestli ano jak zadat defaultní hodnoty. Problém viz https://forum.nette.org/…ciovane-pole

    diiix

    před 3 lety | odpovědět
  14. Miroslav Koula #14

    avatar

    Ahoj,

    jde pomocí nového propojení formulářů pomocí n: syntaxe nějak propojit i věci co máme nějakém Containeru?

    před 2 lety | odpovědět | reagoval [15] David Grudl
  15. David Grudl http://davidgrudl.com #15

    avatar

    #14 Miroslave Koulo, buď pomocí makra formContainer nebo zápisem n:name=container-input

    před 2 lety | odpovědět
  16. Martin #16

    avatar

    Dobrý den Davide,

    drobý dotaz ohledně chybových hlášek. Ne vždy si můžeme dovolit vykreslovat prvky foruláře foreach cyklem. Jakým způsobem lze přistoupit k chybovým hláškám prvku, pokud jej vykreslujeme manuálně? Např:

    <div class="form-group">
            {label username}{/label}
            {input username class => 'form-control'}
            <span class="text-warning" n:ifcontent>{???}</span>
    </div>

    Díky za odpověď.
    Martin

    před 2 lety | odpovědět | reagoval [17] Martin
  17. Martin #17

    avatar

    #16 Martine, Tak už jsem objevil ve zdrojácích makro inputError, které to umí :)

    před 2 lety | odpovědět
  18. MB #18

    avatar

    Zdravím,
    chcel by som sa spýtať ako sa dajú definovať pravidlá pre inputy, ktoré sú nadefinované len v metóde getControl (). Napr. Ten vlastný input na dátum, keby mal mať pravidlá na každom jednom inpute, tak ako na to?

    Ďakujem za odpoveď.

    před rokem | odpovědět
  19. Daniel Hošek http://www.danhosek.cz #19

    avatar

    Ahoj, víte někdo, jak nastavit přímo formuláři id? Díky.

    před měsícem | odpovědět

Zanechat komentář

Text komentáře
Kontakt

(kvůli gravataru)



*kurzíva* **tučné** "odkaz":http://example.com /--php phpkod(); \--