Latte / Formulář – manuální vykreslení pouze jednoho prvku formuláře

emololftw
Člen | 81
+
0
-

Ahoj komunito,

projekt obsahuje opravdu velký formulář generovaný skrze nette/forms
Je možnost manuálně vykreslit pouze jeden jediný prvek formuláře? Chci se vyhnout kvůli jednomu prvku renderování celého formuláře.
Něco ve stylu:

{form orderForm}

	{define frm-orderDate-pair}
		....
	{/define}

	{control $form}

{/form}
Kamil Valenta
Člen | 758
+
0
-

Ne, ale můžeš si pomocí printForm zobrazit, co by automatický render vygeneroval, zkopírovat a upravit ten jeden input…

m.brecher
Generous Backer | 736
+
0
-

@KamilValenta

Ahoj, vykreslování formulářů je v Nette docela problém. Defaultní renderer má nepříjemné nedodělky z hlediska frontendu a na složitější věci použít nejde. Generovat kód pro každý formulář a provést následné úpravy by bylo použitelné, kdyby nenásledovaly další a další změny ve struktuře databáze, což u inovativních projektů je velmi běžné.

Jako Nette nováček zkouším tohle nějak vyřešit a snažím se napsat si nějakou šikovnou latte šablonu, která by umožnila:

  • automatické vykreslování jakýchkoliv formulářů
  • možnost kterou by uvítal @emololftw – definovat pro konkrétní prvek vlastní html/latte šablonu

ale zatím jsem ve fázi pokusů.

Jak u Vás ve firmě tohle máte vyřešené (komponenty pomocí trait jste vymysleli hezky – viz naše minulá diskuze)?

David Grudl
Nette Core | 8111
+
+6
-

Já v tom žádný problém nevidím. Proč bych měl s úpravou databáze měnit kód? Prostě si vyrobím jednoduchou šablonu, která vygeneruje jakýkoliv formulář a uložím ji do basic-form.latte.

<form n:name=$form>
	<ul class=error n:ifcontent>
		<li n:foreach="$form->ownErrors as $error">{$error}</li>
	</ul>

	<table>
		<tr n:foreach="$form->controls as $input"
			n:if="!$input->getOption(rendered) && $input->getOption(type) !== hidden"
			n:class="$input->required ? required">

			<th>{label $input /}</th>
			<td>{input $input} <span class=error n:ifcontent>{$input->error}</span></td>
		</tr>
	</table>
</form>

Pak stačí ji zavolat a předat název nebo instanci formuláře do proměnné form:

{include basic-form.latte, form: myFormName}

Chci vykreslit nějaký prvek jinak? Upravím šablonu basic-form.latte tak, že si v ní připravím bloky, které pak můžu přepsat, viz dědičnost šablon. Bloky můžou mít klidně dynamický název, můžu do nich zakomponovat jméno vykreslovaného prvku. Takže třeba:

...
	<th n:inner-block="label-{$input->name}">{label $input /}</th>
	<td n:inner-block="input-{$input->name}">{input $input} <span class=error n:ifcontent>{$input->error}</span></td>
...

Pro prvek např. orderDate tak vzniknou bloky label-orderDate a input-orderDate, které můžu snadno přepsat použitím značky embed:

{embed basic-form.latte, form: myFormName}
	{block input-orderDate}
		nový obsah pred inputem
		{include parent}
		nový obsah za inputem
	{/block}
{/embed}

Nelíbí se mi uvádět všude cestu k souboru basic-form.latte a název parametru form:? Tak z toho udělám blok a název parametru zadefinuji:

{define basic-form $form}
	<form n:name=$form>
		<ul class=error n:ifcontent>
			<li n:foreach="$form->ownErrors as $error">{$error}</li>
		</ul>

		<table>
			<tr n:foreach="$form->controls as $input"
				n:if="!$input->getOption(rendered) && $input->getOption(type) !== hidden"
				n:class="$input->required ? required">

				<th n:inner-block="label-{$input->name}">{label $input /}</th>
				<td n:inner-block="input-{$input->name}">{input $input} <span class=error n:ifcontent>{$input->error}</span></td>
			</tr>
		</table>
	</form>
{/define}

Tento blok načtu pomocí značky import a stačí to udělat pouze v @layout.latte:

{import basic-form.latte}

A pak bude stačit psát:

{include basic-form myFormName}

nebo

{embed basic-form myFormName}
	...
{/embed}

Jestli vás napadá, jak by to mohlo jít lépe nebo jednodušeji, klidně dejte vědět, mě ne.

m.brecher
Generous Backer | 736
+
0
-

@DavidGrudl dík, něco takhle jednoduchého jsem měl na mysli. Vyzkouším.

emololftw
Člen | 81
+
0
-

@DavidGrudl Geniální! Děkuji za nakopnutí

Kamil Valenta
Člen | 758
+
0
-

m.brecher napsal(a):
Jak u Vás ve firmě tohle máte vyřešené (komponenty pomocí trait jste vymysleli hezky – viz naše minulá diskuze) ??

Popravdě nijak. Samotný form (coby komponenta) se generuje automaticky ze struktury databáze, tohle ušetří mnoho práce. Ale šablony, ty už píšeme ručně. Sázíme formuláře do gridu a nejde moc automaticky říct, kolik col-N bude který input mít. Navíc do toho vstupují různé buttony u labelů, nápovědy, náhledy u uploadů. Stejně bychom nějakou obecnou šablonu přepisovali pro každý form. Je naprosté minimum formulářů, které by vystačily s automatickým renderem. A kvůli nim se neoplatí udržovat nějakou mašinérii. Takže u nás zatím vede manuální render.

P.S. na traitách jsme nic nevymysleli :) jen je používáme.

m.brecher
Generous Backer | 736
+
0
-

@KamilValenta

Díky za zajímavé informace,

Samotný form (coby komponenta) se generuje automaticky ze struktury databáze

Tohle je zajímavý postup, to by mě blíže zajímalo. Neposlal by Jsi sem nějaký příklad kódu jak to konkrétně děláte? Co validační pravidla, co defaultní hodnoty prvků, co chybové hlášky – tohle všechno generujete automaticky, aniž by jste museli ručně psát $form pro každý prvek?

Takže u nás zatím vede manuální render.

OK, je to hromada ruční práce, ale vede to k cíli.

Kamil Valenta
Člen | 758
+
0
-

m.brecher napsal(a):

Samotný form (coby komponenta) se generuje automaticky ze struktury databáze

Tohle je zajímavý postup, to by mě blíže zajímalo. Neposlal by Jsi sem nějaký příklad kódu jak to konkrétně děláte ??

$form = new Form;

$form->table('article')
    ->without('image')
    ->generate();

$form->addUpload('image', 'Obrázek');

$form->onSuccess[] = [$this, 'formSucceeded'];
return $form;

Můžeš si takhle z té automatiky nějaké sloupce vyhodit a doplnit ručně, protože třeba ‚image‘ je VARCHAR a vygeneroval by se jak input=text.
Jinak je to celkem přímočaré, umí to text (i s modifikací date a time), textArea, checkbox (ten se generuje z tinyINT). Pokud to narazí v db na FK, udělá se číselník v selectu.

Co validační pravidla,

Drtivá většina je zadána již omezením v db, takže se vygenerují taky. A pokud chceš připsat nějaké vlastní (děje se velmi zřídka), vždy můžeš přes $form[‚name‘]->addRule()
SetNullable() a setRequired() také poznáš už z db.

co defaultní hodnoty prvků,

Pokud formuláři nepředáš nějaké id, vezme defaultní hodnoty z db.
Pokud předáš, načte danou větu z tabulky.

co chybové hlášky

Ty tak jako tak tečou přes translator, takže se nagenerují samy z názvu tabulky a sloupce a zbytek řeší překlad

Takže u nás zatím vede manuální render.

OK, je to hromada ruční práce, ale vede to k cíli.

Sám přemýšlím k tomu znásilnit třeba komentáře v tabulce, ale jak jsem psal, nejde jen o sazbu, jde o různé prvky kolem inputů a stejně by se do toho furt muselo sahat. Oproti tomu napsat šablonu pro form je otázka chvilky – třebaže rutinní práce.

Editoval Kamil Valenta (1. 1. 2023 9:35)

mystik
Člen | 289
+
0
-

Je ten kod co form generuje nekde k nahlednuti? Uz delsi dobu uvazuju o necem podobnem a celkem by me zajimalo jak to mate resene.

Bulldog
Člen | 110
+
0
-

@KamilValenta Taky používáme na pár projektech automatické generování formulářů.

Místo datového typu z databáze ale máme v tabulce u sloupečků poznámku, kterou využíváme na přesnější definici co vlastně za prvek má být použito a popřípadě i dodatečná validační pravidla.

Viděl jsem to už i způsoby, že pro formuláře byla separátní tabulka, kde každý řádek byl definice formuláře pro každou tabulku, kde to chtěli lidi využít, ale to mi zase přišlo zbytečné.

m.brecher
Generous Backer | 736
+
0
-

@KamilValenta díky, ta třída Form by mě také zajímala blíže, také o něčem takovém uvažuji

checkbox (ten se generuje z tinyINT)

Také jsem pro boolean v mySql používal tinyint, ale nedávno jsem přešel na bit(1) který má může mít jenom dvě hodnoty – 0 nebo 1.

@Bulldog

Místo datového typu z databáze ale máme v tabulce u sloupečků poznámku, kterou využíváme na přesnější definici co vlastně za prvek má být použito a popřípadě i dodatečná validační pravidla.

To by mě zajímalo, jak to máte vymyšlené Vy, nemáš někde nějakou ukázku kódu?

Kamil Valenta
Člen | 758
+
0
-

m.brecher napsal(a):
Také jsem pro boolean v mySql používal tinyint, ale nedávno jsem přešel na bit(1) který má může mít jenom dvě hodnoty – 0 nebo 1.

Kvůli kompatibilitě s některými nástroji používáme TINYINT(1). A tuším i některými exporty, kde BIT zlobil. Ale úplně nesleduji, zda ty důvody už všechny nevzal čas. Nedělal jsem ani test, zda je na tom výkonově BIT pro 1 bool nějak lépe.

@mystik @mbrecher Ta třída nikde veřejně není, popravdě obsahuje i dost interních věcí kolem, bylo by hezké to někdy učesat, ale není to v prioritách. Můžeme se ale domluvit bokem a obecné části si ukázat.

Bulldog
Člen | 110
+
0
-

@mbrecher Bohužel nemám. Jde o určité know-how a teda nic specifického k tomu říct nemůžu :/

m.brecher
Generous Backer | 736
+
0
-

@KamilValenta o výkon u bit(1) ani nejde, je to jediná logicky správná volba. Ty jsi bit(1) zkoušel a narazil Jsi na potíže ? Které nástroje to byly?

Kamil Valenta
Člen | 758
+
+1
-

Je to opět trochu akademická debata a moc ji nechci rozjíždět (o to víc ve vlákně o manuálním vykreslování formů).
Oni se ani ti vývojáři MySQL nevydali tou „jedinou logicky správnou volbou“.
https://dev.mysql.com/…a-types.html

BIT alespoň v minulosti nebyl bezpečnou volbou pro přenositelnost mezi OS, mezi verzemi MySQL ani RDBMS. Nesleduji, zda má MySQL vyřešené všechny bugy kolem LEFT JOINů přes BIT sloupec.
Spíš je otázka, co to praktického přinese, krom těch rizik. Stejně se oboje uloží na 1 byte.

Na nástroje už asi nevzpomenu, je to dost let zpět. Možná Workbench, možná MySQL Administrator. Nevím, roky už používáme Heidi.