[2008-09-04] Vlastní vykreslování formulářů

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

Jak jsem avizoval už dříve, vykreslování formulářů se přesunulo do samostatné třídy. Tu spojuje s formulářem jediná metoda IFormRenderer::render($form), tudíž dřívější metody vykreslující určité části komentářů jsou zapovězené (nicméně nějakou dobu ještě budou funkční).

Update je v podstatě velmi prostý:

  • $form->renderForm()$form->render() nebo echo $form
  • $form->renderBegin()$form->render('begin')
  • $form->renderEnd()$form->render('end')
  • $form->renderErrors()$form->render('errors')
  • $form->renderBody()$form->render('body')

Celý proces vykreslování má na starosti objekt implementující Nette\Forms\IFormRenderer. Nastavíme jej přes $form->setRenderer($myRenderer) a získáme přes $form->getRenderer(). Jako výchozí vykreslovač se používá objekt třídy ConventionalRenderer, který generuje stejný výstup jako dřívější verze formulářů a obstarává kompatibilní volání jako $form->render('begin') atd. Nově umožňuje změnit obalovací elementy:

	// předpokládá se, že $renderer je instance ConventionalRenderer
	$renderer = $form->getRenderer();
	// budeme generovat formulář jako definiční seznam
	$renderer->wrappers['controls']['container'] = 'dl';
	$renderer->wrappers['pair']['container'] = NULL;
	$renderer->wrappers['label']['container'] = 'dt';
	$renderer->wrappers['control']['container'] = 'dd';

ConventionalRenderer nenabízí mnoho způsobů, jak ovlivnit vzhled formuláře. Je tu sice stále možnost napsat si speciální renderer pro speciální typy formulářů, ale raději jsem chtěl vymyslet nějaký výborně konfigurovatelný renderer. A především snadno a přehledně konfigurovatelný, aby se člověk nemusel učit další syntaxi, kde složitě napíše to, co by v HTML vyjádřil několika značkami. Nakonec mě napadlo vskutku geniální řešení, které jsem přetavil do třídy StylesheetRenderer.

Jakmile jsem měl hotovo, došlo mi, že jsem „objevil“ XSL transformace :-) Což vedlo k myšlenkám, jestli teda nepoužít rovnou XSLT, nebo jen připodobnit syntax, nebo co s tím… Zatím váhám, takže StylesheetRenderer najdete až v dalších revizích. Každopádně poté bude možné pouhým nastavením jednoho řetězce nakonfigurovat vykreslování dokonale :-)

David Grudl
Nette Core | 8107
+
0
-

Ještě jednu poznámku ke grupám:

Na úrovni formuláře je možné prvky sdružovat do skupin. Těmto skupinám lze také nastavovat libovolné uživatelské informace pomocí $group->setOption(klic, hodnota) (totéž platí pro jednotlivé prvky). Data lze pak využít v rendereru. Samotný ConventionalRenderer některým z nich rozumí. Skupině je možno nastavit:

$group->setOption('container', $el); // Html element, kterým se skupina orámuje (standardně <fieldset>)
$group->setOption('embedNext', TRUE); // další skupinu v řadě se při vykreslování vloží dovnitř této

Nejspíš znáte tento letitý příklad na Nette\Forms. Díky skupinám je možné jeho vykreslování zcela zautomatizovat.

Nejprve připomenu jeho stávající definici:

$form = new Form();
$form->addText('name', 'Your name:', 35);
$form->addText('age', 'Your age:', 5);
$form->addRadioList('gender', 'Your gender:', $sex);
$form->addText('email', 'E-Mail:', 35)->emptyValue = '@';

$form->addCheckbox('send', 'Ship to address');
$form->addText('street', 'Street:', 35);
$form->addText('city', 'City:', 35);
$form->addSelect('country', 'Country:', $countries)->skipFirst();

$form->addPassword('password', 'Choose password:', 20);
$form->addPassword('password2', 'Reenter password:', 20);
$form->addFile('avatar', 'Picture:');
$form->addHidden('userid');
$form->addTextArea('note', 'Comment:', 30, 5);

$form->addSubmit('submit1', 'Send');
// + nějaká validační pravidla

Teď jeho definici rozšířím o použití skupin:

$form = new Form();
$form->addGroup('Personal data');
$form->addText('name', 'Your name:', 35);
$form->addText('age', 'Your age:', 5);
$form->addRadioList('gender', 'Your gender:', $sex);
$form->addText('email', 'E-Mail:', 35)->emptyValue = '@';

$form->addGroup('Shipping address')
	->setOption('embedNext', TRUE); // následující skupina se vykreslí uvnitř této
$form->addCheckbox('send', 'Ship to address');

$form->addGroup()
	->setOption('container', /*Nette\Web\*/Html::el('div')->id('sendBox')); // změníme kontejner na div #sendBox
$form->addText('street', 'Street:', 35);
$form->addText('city', 'City:', 35);
$form->addSelect('country', 'Country:', $countries)->skipFirst();

$form->addGroup('Your account');
$form->addPassword('password', 'Choose password:', 20);
$form->addPassword('password2', 'Reenter password:', 20);
$form->addFile('avatar', 'Picture:');
$form->addHidden('userid');
$form->addTextArea('note', 'Comment:', 30, 5);

$form->addGroup();
$form->addSubmit('submit1', 'Send');

Teď už jej stačí vypsat pomocí:

echo $form;
David Grudl
Nette Core | 8107
+
0
-

Několik příkladů vykreslování formulářů. Zdrojové kódy najdete v distribuci.

jm
Člen | 10
+
0
-

David Grudl napsal(a):

	// předpokládá se, že $renderer je instance ConventionalRenderer
	$renderer = $form->getRenderer();
	// budeme generovat formulář jako definiční seznam
	$renderer->wrappers['controls']['container'] = 'dl';
	$renderer->wrappers['pair']['container'] = NULL;
	$renderer->wrappers['label']['container'] = 'dt';
	$renderer->wrappers['control']['container'] = 'dd';

Tak tohle mi ve verzi 83 nefunguje. V pripade

$form->addGroup()->setOption('container', HTML::el('div')->id('timeInt'));
$form->addText('fromTime', 'From', 15);
$form->addText('toTime', 'To', 15);

dostanu HTML:

<div id="timeInt">
<dd>
<tr>
	<th><label for="frmquery-fromTime">From</label></th>

	<td><input type="text" size="15" class="text" name="fromTime" id="frmquery-fromTime" value="" /></td>
</tr>
<tr>
	<th><label for="frmquery-toTime">To</label></th>

	<td><input type="text" size="15" class="text" name="toTime" id="frmquery-toTime" value="" /></td>
</tr>
</dd>
</div>

Ty radky tabulky by tam nemely byt pokud rozumim dobre. Je to bug nebo delam neco spatne?

Diky,

Honza

David Grudl
Nette Core | 8107
+
0
-

Předpokládá to poslední verzi, je možné, že ve starší revizi to nebude fungovat.

jm
Člen | 10
+
0
-

Jasne, uz mi to funguje v revizi 99. Diky.

novator
Člen | 1
+
0
-

Měl bych dotaz.
Pokud pomocí wrapperu nastavím u obalového elementu atribut class tak pokud je zároveň nastavena podmínka, že musí být vyplněn tak se do atributu přidá " required". Nevíte jak se toho jednoduše zbavit?

Jod
Člen | 701
+
0
-

A nepridajú sa obe? class=„trieda required“? To by sa zišlo fixnúť.

David Grudl
Nette Core | 8107
+
0
-

Odstranit wrappers['pair']['.required']

Jakub Šulák
Člen | 222
+
0
-

Chtěl bych se zeptat, jakým způsobem se dá vytvořit TextBox tak, aby se k němu nevytvářel <label>?

<?php
$form->addText('name',''); // vytvori se label ktery je prazdny
$form->addText('name',NULL); // vyhodi vyjimku
?>

Díky za odpověď

vlki
Člen | 218
+
0
-

Použitím vlastního rendereru. Třeba následovně…

<?php
class MyConventionalRenderer extends ConventionalRenderer
{
	public function renderLabel(IFormControl $control)
	{
		$head = $this->getWrapper('label container');
		if ($control instanceof Checkbox || $control instanceof Button || $control instanceof TextArea) { // pridat tridu controlu
			return $head->setHtml('&nbsp;');
		} else {
			return $head->setHtml((string) $control->getLabel() . $this->getValue('label suffix'));
		}
	}
}
?>
kravčo
Člen | 721
+
0
-

Z hľadiska prístupnosti by bolo rozhodne lepšie label generovať a potom ho pomocou CSSka ukryť. Riešiť by to mohol rovnako vlastný renderer, s tým, že len nastaví labelu nejakú triedu.

Panda
Člen | 569
+
0
-

kravco napsal(a):

Z hľadiska prístupnosti by bolo rozhodne lepšie label generovať a potom ho pomocou CSSka ukryť. Riešiť by to mohol rovnako vlastný renderer, s tým, že len nastaví labelu nejakú triedu.

Vlastní renderer není potřeba:

<?php
$form->addText('name','Label:')->getLabelPrototype()->class('hidden');
?>
Jakub Šulák
Člen | 222
+
0
-

Jde mi o to, když například máte vyhledávací box a nevejde se k němu popisek „Hledání“, takže se dá jako emptyValue přímo do Textboxu (vím není to nejlepší řešení, ale někdy to tak prostě grafika vyžaduje). Řešení přes class není moc dobré protože ve zdrojáku mne bude stále strašit <label class=""></label> (prázdný tag).

Takže musím napsat ten render… Stejně si myslím, že by bylo hezčí řešení upravit Nette tak, aby buď na '', NULL, nebo FALSE, ten label nedělalo.

kravčo
Člen | 721
+
0
-

Jakub Šulák napsal(a):

Jde mi o to, když například máte vyhledávací box a nevejde se k němu popisek „Hledání“, takže se dá jako emptyValue přímo do Textboxu (vím není to nejlepší řešení, ale někdy to tak prostě grafika vyžaduje). Řešení přes class není moc dobré protože ve zdrojáku mne bude stále strašit <label class=""></label> (prázdný tag).

Ak budeš robiť web prístupný, v zdrojáku bude <label for="..." class="hidden">Hledání</label> a CSSko mu vysvetlí, že sa nemá zobraziť…

Takže musím napsat ten render… Stejně si myslím, že by bylo hezčí řešení upravit Nette tak, aby buď na '', NULL, nebo FALSE, ten label nedělalo.

Label by tam mal byť za každých okolností, práve kvôli prístupnosti.

Jakub Šulák
Člen | 222
+
0
-

Jo to máš pravdu, budu řešit přes css.

PetrP
Člen | 587
+
0
-

kravco napsal(a):

Ak budeš robiť web prístupný, v zdrojáku bude <label for="..." class="hidden">Hledání</label> a CSSko mu vysvetlí, že sa nemá zobraziť…

Takže musím napsat ten render… Stejně si myslím, že by bylo hezčí řešení upravit Nette tak, aby buď na '', NULL, nebo FALSE, ten label nedělalo.

Label by tam mal byť za každých okolností, práve kvôli prístupnosti.

Jak se změní přístupnost skrytím elementem? Pro uživatele normálních prohlížečů nijak, protože je skrytej. A pro uživatele čteček textu taky ne, protože čtečky už povětšinou css taky podporujou.

nAS
Člen | 277
+
0
-

PetrP napsal(a):

kravco napsal(a):

Label by tam mal byť za každých okolností, práve kvôli prístupnosti.

Jak se změní přístupnost skrytím elementem? Pro uživatele normálních prohlížečů nijak, protože je skrytej. A pro uživatele čteček textu taky ne, protože čtečky už povětšinou css taky podporujou.

Já myslím, že by to mohlo pomoct vyhledávačům. A případně se dá nastavit

position: absolute; left: -10000px;

a bude to přístupné i pro hlasové čtečky.

wnc
Člen | 7
+
0
-

Neexistuje tam wrapper, který by zarámoval všechny grupy? Vykreslil by se tedy těsně do tagu form. Nějak takhle:

<form>
  <groups-wrapper>
    <group-wrapper>
	...
    </group-wrapper>

    <group-wrapper>
	...
    </group-wrapper>
  </groups-wrapper>
</form>

Hodilo by se mi totiž potom nastavit toto:

<?php
  $renderer->wrappers['groups']['container'] = 'table';
  $renderer->wrappers['controls']['container'] = '';
  $renderer->wrappers['group']['container'] = 'tbody';
?>

Jde to vůbec bez nutnosti napsat si vlastní renderer? Díky

Editoval wnc (6. 5. 2009 15:21)

rokerkony
Člen | 122
+
0
-

ahoj ahoj… po dlouhé době jsem se zase dostal k nette… jak se nyní řeší vlastní rendrování (tedy po částech) když se používají tovarničky a volání widget? děkuji…

už vím omlouvám se :-)

Editoval rokerkony (4. 12. 2009 13:41)

Ondřej Mirtes
Člen | 1536
+
0
-

Továrnička v Presenteru:

protected function createComponentForm() {
	$form = new AppForm;

	//přidání prvků, navěšení submit funkce atd.

	return $form;
}

Začátek, errory a konec jdou vyrenderovat takto:

{control form begin}
{control form end}
{control form errors}

Pro vykreslování jednotlivých prvků formuláře si ho musíš předat v render metodě Presenteru:

$this->template->form = $this['form'];

A v šabloně pak psát:

{$form['name']->label} {$form['name']->control}

Chystá se předělání ConventionalRendereru do podoby asi 20 řádků .phtml šablony, takže pak půjdou snáze dělat úpravy.

Editoval Ondřej Mirtes (4. 12. 2009 17:47)

_Martin_
Generous Backer | 679
+
0
-

Ondřej Mirtes napsal(a):

Pro vykreslování jednotlivých prvků formuláře si ho musíš předat v render metodě Presenteru:

Nemusíš, lze použít:

{$control['form']['name']->label} {$control['form']['name']->control}
Ondřej Mirtes
Člen | 1536
+
0
-

To jo no, ale je s tím víc psaní :)

Jan Endel
Člen | 1016
+
0
-

Jenom bych chtěl zahlásit, že odkaz na příklady z tohoto topicku Příklady na form se nak ztratil v propadlišti dějin :(.

Honza Kuchař
Člen | 1662
+
0
-

Příklady jsou v distribuci ve složce /examples.

2bfree
Člen | 248
+
0
-

Snažím se zajistit to, aby po odeslání formuláře nešlo se vrátit v prohlížeči zpět a znova odeslat. Mám ten pocit, že to dělá:

$editPackageForm->addProtection(‚Vypršel ochranný časový limit, odešlete prosím formulář ještě jednou‘);

A mám ten pocit, že když jsem renderoval formulář standardní cestou, tak to i fungovalo. Pokud ne, omlouvám se a prosím o nakopnutí, jak toto vyřešit a ideálně bez databáze.

Pakliže tak tato ochrana funguje, tak mi přestala fungovat, pokud si formulář nechám vypsat následující šablonou

<?php
    {$editPackageForm->render('begin')}
    <div class="dataGridContentContent">
        <div class="formContent">
            {$editPackageForm->render('errors')}
            <div class="label">{$editPackageForm['packageName']->label}</div>
            <div class="input">{$editPackageForm['packageName']->control}</div>
            <div class="label">{$editPackageForm['packageSystemName']->label}</div>
            <div class="input">{$editPackageForm['packageSystemName']->control}</div>
            <div class="label">{$editPackageForm['packageDescription']->label}</div>
            <div class="textarea">{$editPackageForm['packageDescription']->control}</div>
            {$editPackageForm['oldPackageSystemName']->control}
        </div>
    </div>
    <div class="dataGridContentFooter">
        <div class="buttons floatLeft">
            <a href="{link ManagePackages:show}" class="backToManagePackageButton"><span>Zpět</span></a>
        </div>
        <div class="buttons floatRight">
            <span>{$editPackageForm['prepareNewPackage']->control}</span>
        </div>
        <br class="clear" />
    </div>
{$editPackageForm->render('end')}
?>

Nevíte, kde by mohla být chyba?

Editoval 2bfree (2. 6. 2010 16:01)

Ondřej Mirtes
Člen | 1536
+
0
-

Nemáš tam:

{$editPackageForm->render('end')}
2bfree
Člen | 248
+
0
-

Zapomněl jsem ho připsat do ukázky, ale mám ho tam.