ConventionalRenderer → 20 řádků kódu

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

Jestli se mi nějaká část frameworku skutečně nelíbí, tak to je ConventionalRenderer. A nejde jen o samotný kód této třídy, ale hlavně o to, že doménu šablon přenáši do presenteru, kde se musí čarovat s objekty Html a různými options, aby se docílilo požadovaného vykreslení (a mnohdy to ani nejde). V presenteru by přitom měla být jen prostá definice formuláře a validačních pravidel, nic co souvisí s vykreslováním.

Samozřejmě je možné formulář kreslit manuálně (a tím se vlastně všechno vyřeší), ale lenost programátora se tomu vzpouzí.

Dnes už je možné téměř celý slavný ConventionalRenderer nahradit cca 20 řádky šablony s CurlyBracketsFilterem:

{control $form begin}

<!-- hidden fields -->
<div n:foreach="$form->getComponents(TRUE, 'Nette\Forms\HiddenField') as $control">{$control->control}</div>

<!-- errors -->
{assign errors => $form->errors}
<ul class="error" n:if="$errors" n:block="#errors">
	<li n:foreach="$errors as $error">{$error}</li>
</ul>

<!-- groups -->
<fieldset n:foreach="$form->groups as $group" n:if="$group->controls && $group->getOption('visual')" n:block="#group">
	<legend n:ifset="$group->options['label']">{$group->options['label']}</legend>
	<p n:ifset="$group->options['description']">{$group->options['description']}</p>

	<table n:block="#controls">
	<tr {attr class('required', $control->getOption('required'))} n:foreach="$group->controls as $control" n:if="!$control->getOption('rendered') && $control->getForm(FALSE) === $form" n:block="#pair">
		<th n:block="#label">{if $control instanceof Button || $control instanceof Checkbox}&nbsp;{else}{!$control->label}{/if}</th>

		<td n:block="#control">{!$control->control}{if $control instanceof Checkbox}{!$control->label}{/if}</td>
	</tr>
	</table>
</fieldset>

<!-- non-group -->
{include #controls, group => $form}

{control $form end}

Uvedený kód funguje, abyste jej mohli vyzkoušet, stačí jej uložit do souboru @form.phtml a místo {control myForm} použít

{include '@form.phtml', form => $presenter['myForm']}

Pro celý web pak může stačit jedna definice šablony. A co když bych někde potřeboval mírně upravenou šablonu, třeba s vykreslováním chybových zpráv přímo pod každým prvkem? Lze použít dědění. Místo souboru @form.phtml vložím @form-special.phtml s tímto obsahem:

{extends '@form.phtml'}

<td n:block="#control">
	{!$control->control}{if $control instanceof Checkbox}{!$control->label}{/if}
	{if $control->errors}{include #errors, errors => $control->errors}{/if}
</td>

Tohle celé berte prosím jako nástin, jako koncepci která má k hotovému řešení ještě hodně daleko.

mejla
Člen | 14
+
0
-

jj tohle je opravdu asi jediné řešení „složitých“ formulářů. Do teď jsem stejně skoro všechny formuláře definoval v šabloně lenost – nelenost :)

Honza Marek
Člen | 1664
+
0
-

Jo.. kdysi jsem se taky o něco podobného pokoušel. Chtěl jsem to udělat jako vlastní renderer, který využívá šablony.

A jestli se mi teda nějaká část Nette nelíbí, tak je to standardní ClientScript, protože je naprosto nepřizpůsobitelný.

David Grudl
Nette Core | 7824
+
0
-

Honza M. napsal(a):

A jestli se mi teda nějaká část Nette nelíbí, tak je to standardní ClientScript, protože je naprosto nepřizpůsobitelný.

Ten je hned na řadě ;)

Honza Kuchař
Člen | 1661
+
0
-

David Grudl napsal(a):

Honza M. napsal(a):

A jestli se mi teda nějaká část Nette nelíbí, tak je to standardní ClientScript, protože je naprosto nepřizpůsobitelný.

Ten je hned na řadě ;)

Hurá! :)

Blizzy
Člen | 149
+
0
-

David Grudl napsal(a):

V presenteru by přitom měla být jen prostá definice formuláře a validačních pravidel, nic co souvisí s vykreslováním.

Např. label a cols/rows definice podle mě také souvisí spíš s view než s presenterem, ale jde to vůbec nějak rozumně oddělit?

David Grudl
Nette Core | 7824
+
0
-

Všechny tři údaje je možné při definici formuláře vynechat a nastavit až v šabloně:

<td>{$form['textarea']->label->setText('Poznamka')}</td>
<td>{$form['textarea']->control->col(10)->row(20)}</td>

ps. možná by se dalo rozšířit getLabel(), tak aby fungovalo i překládání:

<td>{$form['textarea']->getLabel('Poznamka')}</td>
Yrwein
Člen | 45
+
0
-

Jinak ještě co se týče clientscriptu, jen takové menší šťouchnutí ku přemýšlení nad výslednou podobou – bylo by skvělé, kdyby ho šlo nějak snadněji integrovat se skripty ala jQuery form validator .
// Jen příklad, který mě kdysi zaujmul. .)

Editoval Yrwein (21. 8. 2009 10:49)

Honza Kuchař
Člen | 1661
+
0
-

Yrwein napsal(a):

bylo by skvělé, kdyby ho šlo nějak snadněji integrovat se skripty ala jQuery form validator .
// Jen příklad, který mě kdysi zaujmul. .)

Wow, to by bylo super! Ale vidím to spíš na vlastní ClientScript.

Používám něco podobného v to Editaovatelném datagridu:

phx
Člen | 651
+
0
-

Zdravim…

To je nejaky novy zapis?

<ul class="error" n:if="$errors" n:block="#errors">
        <li n:foreach="$errors as $error">{$error}</li>
</ul>

Hledam na foru a tohle vidim jako prvni zminku. Prosim o nasmerovani na spravne misto.

Jinak pokud je to novinka tak se mi zamlouva, protoze mam vecne dilema s tim jak odsazovat. Protoze kdyz budeu odsazovat logicky programatorsky

<div>
	{if $foo}
		<p>BAR</p>
	{/if}
</div>

tak vysledny HTML kod je zvlasne odsazen. A jeste vtipnejsi to je u posilani plaintext mailu nebo <pre> kde zalezi na kazdem bilem znaku.

jasir
Člen | 746
+
0
-

phx:

Tady…

Bohužel se v tom vláknu strhla trošku přestřelka, takže ho David promazal a zamkl… Možná už je čas na odemčení? ;-)

Editoval jasir (26. 8. 2009 10:38)

phx
Člen | 651
+
0
-

LOL. Promaza? Nic tam krome Davida nezbylo.

DIKY.

EDIT:
Ha tam jsem to nasel v RSS:) No celkem cahpu proc to promazal, ale skoro pulka tam byla vecna a uzitecna. I kdyz asi bych to hodil do samostatneho filteru.

I kdyz me osobne to je fuk zda ala XML nebo {}. Jedine co me trapi se problem s bilymi znaky.

Editoval phx (26. 8. 2009 11:05)

David Grudl
Nette Core | 7824
+
0
-

Otevřeno

hanakus
Člen | 22
+
0
-

Nebylo by možné obohatit makro widget o parametr určující šablonu?

Něco takového:

{control MyForm '@form.phtml'}
Patrik Votoček
Člen | 2221
+
0
-

parametry samozdřejmě makro widget ma… v tomto pripade se bude volat:

$presenter['myForm']->render('@form.phtml');

takže pokud nastavíš formu svůj renderer kterej bude předpokládat že parametr je šablona tak to bude fungovat…

Trochu OT: widget MyForm nefunguje!!! první písmeno musí být malé!!! (Davide už chápeš proč jsme to na PS tak řešily???)

David Grudl
Nette Core | 7824
+
0
-

Jenže já si nepamatuju, k čemu jsme nakonec došli ;)

Vitek Jezek
hledá kolegy | 285
+
0
-

tusim, ze prvni pismenko bude case-insensitive ; )

Honza Kuchař
Člen | 1661
+
0
-

přesně tak

David Grudl
Nette Core | 7824
+
0
-

Musím si příště dělat zápisy z porady. Takže jako fakt? První casein, ostatní case? Jaké byly argumenty proti celému casein?

Majkl578
Moderator | 1364
+
0
-

Mě se zamlouvá celé case-sensitive. :)

Honza Marek
Člen | 1664
+
0
-

David Grudl napsal(a):

Musím si příště dělat zápisy z porady. Takže jako fakt? První casein, ostatní case? Jaké byly argumenty proti celému casein?

Myslim, že se ti to prostě nelíbilo, takže jsi argumentoval jakousi zpětnou nekompatibilitou, kdyby někdo používal komponenty s názvy odlišenými jen podle velikostí písmen :)

skrivy
Člen | 51
+
0
-

Ahoj,

v úvodním příspěvku je napsáno, že se jedná o nástin. Rád bych věděl, jestli se to už dostalo do nějaké finální zdokumentované verze či nikoliv? Primárně mě zajímá oddělení definice formuláře a validačních pravidel v presenteru a nastavení zobrazení ve view, jak je tady někde ve fóru psáno. Docela se mi ta myšlenka líbí :).

Díky

Editoval skrivy (30. 11. 2009 13:20)

meris
Člen | 8
+
0
-

Velmi pěkné řešení, mám v něm ale jeden problém a to jak nastavit pomocí tohoto řešení parametry formuláře?
Například mám formulář pro editaci zboží v databázi a chci tento formulář předvyplnit údaji z databáze?

toto řešení

widget $form param, param2

je sice funkční ale bohužel potřebuji formulář přestylovat, takže bych rád použil toto čistší řešení.

Tato řešení mi nefungují

widget $form begin, param, param2
widget $form param, param2, begin

Editoval meris (8. 12. 2009 21:29)

meris
Člen | 8
+
0
-

meris napsal(a):
Jak nastavit pomocí tohoto řešení parametry formuláře?

Tak jsem se nad tím ještě jednou pořádně zamyslel, jisté řešení mě napadlo, ale má své mouchy, především data spojuje na úrovní šablony nikoli na úrovní presenteru či modelu a to mi nepřijde moc šikovné.

Volání šablony pro formulář

{include '@form.phtml', form => $presenter['myform'], default => $formParam}

Úprava šablony, kde se vloží tento kód:

{control $form begin }

{if (isset($default))}
  {foreach $default as $key => $value}
    {? $form[$key]->setDefaultValue($value);}
  {/foreach}
{/if}
skrivy
Člen | 51
+
0
-

Ahoj,

tak jsem to začal používat – ač trochu poupraveně a musím říct, že je to super.

Jenom mě mám trochu nesrovnalosti s chybovýma hláškama … do FormControl.php bych přidal do methody addError toto:

if (self::$updateFormErrors)
$this->getForm()->addError($message);

Principielne to pouzivam tak, ze ke kazdemu policku vypisuju lokalni chybu, ktera se k nemu vztahuje (tzn. spatne vyplneny email, nezadany obsah, atd.) a nad formular vypisuju chyby pri zpracovani (server je nedostupny nebo nastal nejakej jinej problem, kterej se nevztahuje k zadnemu policku).

Co Vy na to? Je to spravne nebo se to resi jinak?

Filip Procházka
Moderator | 4668
+
0
-

Ahoj, chtěl bych poprosit Davida, jestli by někam nehodil novou verzi, se kterou se nám chlubil při preview Nette 2beta a dokonce bych i řekl, že sliboval, že to pak někam hodí :) A i kdyby nesliboval, pár lidí by to určitě ocenilo. Díky :)

nanuqcz
Člen | 822
+
0
-

Právě jsem strávil nějakých pár minut předěláváním na novou verzi Nette, tak to hodím i sem, ať z toho mají užitek i ostatní:

{control $form begin}

<!-- hidden fields -->
<div n:foreach="$form->getComponents(TRUE, 'Nette\Forms\Controls\HiddenField') as $control">{$control->control}</div>

<!-- errors -->
{assign errors => $form->errors}
<ul class="error" n:if="$errors" n:block="#errors">
        <li n:foreach="$errors as $error">{$error}</li>
</ul>

<!-- groups -->
<fieldset n:foreach="$form->groups as $group" n:if="$group->controls && $group->getOption('visual')" n:block="#group">
        <legend n:ifset="$group->options['label']">{$group->options['label']}</legend>
        <p n:ifset="$group->options['description']">{$group->options['description']}</p>

        <table n:block="#controls">
          <tr n:class="$control->getOption('required')? required" n:foreach="$group->controls as $control" n:if="!$control->getOption('rendered')" n:block="#pair">
                <th n:block="#label">{if $control instanceof Button || $control instanceof Checkbox}&nbsp;{else}{!$control->label}{/if}</th>

                <td n:block="#control">{!$control->control}{if $control instanceof Checkbox}{!$control->label}{/if}</td>
          </tr>
        </table>
</fieldset>

<!-- non-group -->
{include #controls, group => $form}

{control $form end}

Bohužel n:if="!$control->getOption('rendered') && $control->getForm(FALSE) === $form" jsem musel z bloku pair vyhodit, protože to házelo chybu, kterou se mi nepodařilo opravit (Undefined variable $form).

I tak se ale zdá, že to funguje OK (zatím jsem to moc netestoval).

Editoval xxxObiWan (27. 8. 2011 16:42)

Jan Tvrdík
Nette guru | 2595
+
0
-

HiddenField má špatný namespace. Oproti tomu, co prezentoval David tam chybí pár podstatných (!) věcí, které umožňovali předefinovat určitý pár pomocí bloků.

nanuqcz
Člen | 822
+
0
-

Já nevím, co prezentoval David :-) Snažil jsem se jen najít nějaké funkční řešení, za jakékoli lepší budu vděčný ;-) HiddenField zítra opravím v mojem předešlém přízpěvku (až se vyspím :-))

EDIT: `HiddenField v předešlém přízpěvku opraven

Editoval xxxObiWan (27. 8. 2011 16:43)

Jan Tvrdík
Nette guru | 2595
+
0
-

HosipLan wrote:

Ahoj, chtěl bych poprosit Davida, jestli by někam nehodil novou verzi, se kterou se nám chlubil při preview Nette 2beta a dokonce bych i řekl, že sliboval, že to pak někam hodí :) A i kdyby nesliboval, pár lidí by to určitě ocenilo. Díky :)

+1

Tharos
Člen | 1029
+
0
-

Jestli to pomůže, tak taky +1. :) Taky by se mi to hodilo…

David Grudl
Nette Core | 7824
+
+1
-

Ta „nová“ verze zas tak nová není, protože vznikla před existencí formulářových maker, které by bylo fajn využít. form.latte: (Příklad jsem zaktualizoval pro Nette 2.1 a vyšší)

<form n:name=$form>

	<ul class="error" n:if="$form->ownErrors" n:block="#errors">
		<li n:foreach="$form->ownErrors as $error">{$error}</li>
	</ul>

	<fieldset n:foreach="$form->groups as $group" n:if="$group->controls" n:block="#group">
		<legend n:ifset="$group->options[label]">{$group->options[label]}</legend>
		<p n:ifset="$group->options[description]">{$group->options[description]}</p>

		<table n:block="#controls">
		{foreach $group->controls as $field}
		<tr n:if="!$field->getOption(rendered)" n:class="$field->required ? required" n:block="$field->name.'-row'">
			<th n:block="#label">{label $field /}</th>

			<td n:block="#control">{input $field} {inputError $field}</td>
		</tr>
		{/foreach}
		</table>
	</fieldset>

	{include #controls, group => $form}
</form>

a příklad použití:

{includeblock 'form.latte', form => signInForm}

{define #username-row}
    <tr>
    	<th colspan=2>radek prvku 'username' vykreslime jinak:<br> {$field->label} {$field->control}</th>
    </tr>
{/define}
nanuqcz
Člen | 822
+
0
-

Mírně jsem updatnul skript od Davida (nově vykresluje u inputů description).

{form $form}
	<div n:foreach="$form->getComponents(TRUE, 'Nette\Forms\Controls\HiddenField') as $field">{$field->control}</div>

	<ul class="error" n:if="$form->errors" n:block="#errors">
		<li n:foreach="$form->errors as $error">{$error}</li>
	</ul>

	<fieldset n:foreach="$form->groups as $group" n:if="$group->controls" n:block="#group">
		<legend n:ifset="$group->options[label]">{$group->options[label]}</legend>
		<p n:ifset="$group->options[description]">{$group->options[description]}</p>

		<table n:block="#controls">
		{foreach $group->controls as $field}
		<tr n:if="!$field->getOption('rendered')" n:class="$field->required ? required" n:block="$field->name.'-row'">
			<th n:block="#label">{if $field instanceof Nette\Forms\Controls\Button || $field instanceof Nette\Forms\Controls\Checkbox}&nbsp;{else}{!$field->label}{/if}</th>

			<td n:block="#control">
				{!$field->control}{if $field instanceof Nette\Forms\Controls\Checkbox}{!$field->label}{/if}
				<small n:if="isset($field->options['description'])">{$field->options['description']}</small>
			</td>

		</tr>
		{/foreach}
		</table>
	</fieldset>

	{include #controls, group => $form}
{/form}

P.S. Pokud ve formulářích používáte addGroup() a budou se vám některé řádky formuláře vykreslovat dvakrát, pak nezapomňte do vlastního bloku řádku přidat podmínku (právě jsem nad tím strávil hodinu casu :-)):

{define #username-row}
    {if !$field->getOption('rendered')}
    radek prvku 'username' vykreslime jinak: {$field->label} {$field->control}
    {/if}
{/define}

Editoval nanuqcz (3. 10. 2012 10:50)

duskohu
Člen | 774
+
0
-

Caute asi blba otazka, ale mohli by ste mi niekto povedat co robi tato cast:

n:block="#group"

v tomto?

<fieldset n:foreach="$form->groups as $group" n:if="$group->controls" n:block="#group">
llook
Člen | 407
+
0
-

duskohu napsal(a):

Caute asi blba otazka, ale mohli by ste mi niekto povedat co robi tato cast:

n:block="#group"

v tomto?

<fieldset n:foreach="$form->groups as $group" n:if="$group->controls" n:block="#group">

Definuje blok, takže si ho v případě potřeby můžeš předefinovat, pokud nechceš použít normální fieldset, ale nějaký jiný markup:

<div n:foreach="$form->groups as $group" n:if="$group->controls" n:define="#group">
        <marquee n:ifset="$group->options[label]">{$group->options[label]}</marquee>
        {include #controls, group => $group}
</div>
{includeblock 'form.latte', form => $presenter[signInForm]}