[2008-09-04] Vlastní vykreslování formulářů
- David Grudl
- Nette Core | 8228
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()
neboecho $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 | 8228
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 | 8228
Několik příkladů vykreslování formulářů. Zdrojové kódy najdete v distribuci.
- jm
- Člen | 10
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 | 8228
Předpokládá to poslední verzi, je možné, že ve starší revizi to nebude fungovat.
- Jakub Šulák
- Člen | 222
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
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(' ');
} else {
return $head->setHtml((string) $control->getLabel() . $this->getValue('label suffix'));
}
}
}
?>
- Panda
- Člen | 569
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
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
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.
- PetrP
- Člen | 587
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
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
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)
- Ondřej Mirtes
- Člen | 1536
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)
- Jan Endel
- Člen | 1016
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 :(.
- 2bfree
- Člen | 248
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)