Předávání persistentních parametrů ve formuláři

m.brecher
Generous Backer | 863
+
+1
-

Zkoušel jsem předávání persistentních parametrů v presenteru a komponentě, tam funguje OK

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
    /** @persistent */
    public string $test = '';
.....
}

Ale uvnitř formuláře se mě persistentní parametry rozchodit nepodařilo – zkoušel jsem to ve vlastní třídě poděděné z Nette\Application\UI\Form

class BaseForm extends Nette\Application\UI\Form
{
    /** @persistent */
    public string $test = '';
.....
}

Skoro to vypadá, že ve formulářích persistentní parametry Nette nepodporuje. Neporadil by někdo, zda to přeci jenom jde a zda nedělám někde nějakou chybu? Díky ;)

Kamil Valenta
Člen | 815
+
0
-

Persistentní parametr nepaří do Form ad-hoc, ale to Control FormFactory…
Takže komponenta zpracuje persistentní parametr a vrátí Form, který už ho využije…

Polki
Člen | 553
+
0
-

A prosím neextenduj form. Není to potřeba, pokud si nepřidáváš například nějaké traity. Na 99,999% si vystačíš s FormFactory třídou

m.brecher
Generous Backer | 863
+
0
-

Kamil Valenta napsal(a):

Persistentní parametr nepaří do Form ad-hoc, ale to Control FormFactory…
Takže komponenta zpracuje persistentní parametr a vrátí Form, který už ho využije…

@Kamil Valenta

Můžeš prosím Tě poslat nějaký příklad PHP kódu k použití persistentního paametru ve formuláři díky té Control FormFactory? Neumím si přdstavit jak na to, to by ta FormFactory měla dědit z třídy Nette Control?

Kamil Valenta
Člen | 815
+
0
-

Je to velmi podobné jako tady:
https://forum.nette.org/…any-datagrid?p=38

Form nebo Grid je v podstatě kompoenta, kterou obalíš do své komponenty, ve které si přenášíš persistentní parametry.

Takže máš:

class MyControl extends BaseControl {
	 /** @var $id @persistent */
    public $id = null;

	protected function createComponentTestForm() {
		// tady si zavoláš create() z FormFactory...
    }
}
Kamil Valenta
Člen | 815
+
0
-

Polki napsal(a):

A prosím neextenduj form. Není to potřeba, pokud si nepřidáváš například nějaké traity. Na 99,999% si vystačíš s FormFactory třídou

To se mi zdá jako odvážné tvrzení, odhadovat kdo si s čím vystačí. Těžko bych vzpomínal, kdy jsem si naopak vystačil. Mám hromadu rozšíření, ano, asi by je šlo napsat i jinak než extends, ale naopak se ptám, proč by to extendovat neměl. Chápu, že se dědičnost nemá nadužívat, ale zase bát se jí jako čert kříže mně připadá zbytečné…

Polki
Člen | 553
+
+1
-

Kamil Valenta napsal(a):

Polki napsal(a):

A prosím neextenduj form. Není to potřeba, pokud si nepřidáváš například nějaké traity. Na 99,999% si vystačíš s FormFactory třídou

To se mi zdá jako odvážné tvrzení, odhadovat kdo si s čím vystačí. Těžko bych vzpomínal, kdy jsem si naopak vystačil. Mám hromadu rozšíření, ano, asi by je šlo napsat i jinak než extends, ale naopak se ptám, proč by to extendovat neměl. Chápu, že se dědičnost nemá nadužívat, ale zase bát se jí jako čert kříže mně připadá zbytečné…

Nejde o to bát se dědičnosti. Dědičnost je super, když se umí použít. Dokonce je i rychlejší tvorba instance 1 třídy, která je nějakým 5 potomkem, než tvorba 5 instancí, které jsou spolu propojeny.
Jen člověk musí vědět, jak tu dědičnost použít.

A zrovna Nette formuláře jsou postaveny tak, aby se dědičnost mohla využívat co nejméně. Pokud potřebuju přidat nějakou metodu rozšiřující původní Nette\Application\UI\Form například chci aby uměly mé formuláře ->addZip(), tak pak vytvořím traitu, extendnu formulář, dám tam traitu, a používám můj extended form…

Ale to je tak všechno. Už jsem viděl frajery, kteří se snažili měnit výchozí vykreslování formulářů, předávat nějaké speciální parametry, vykonávat vnitřně obsluhu formuláře v integrovaných metodách onSuccess, předdefinovat jim v konstruktoru výchozí inputy a validační pravidla atd.

No a včera jak jsme viděl toto vlákno, tak k tomu přibylo, aby formuláře uměly přijmat perzistentní parametry. Kam se jako poděly principy SOLID?
Formulář by se měl starat jen o to, aby byl formulářem. Ne o to, jak se bude na jako stránce vykreslovat (k tomu je view), ne o to, na které stránce s jakými parametry se zobrazí (od toho jsou controlery), ne o to, jak se zpracuje (od toho jsou modely), ne od toho jaká pole by měl obsahovat a jaké parametry přijmat (od toho josu továrny) a jak je validovat (zase model), případně jak se překreslovat (zase view). A když tohle všechno vyhodíme, tak nám zůstane jen klasická třída Form, případně naše rozšířená třída o naše metody na přidání nových typů inputů, která jak jsem psal v 99.999% nepotřebuje upravit díky dobrému návrhu kódu. Pokud potřebujeme přepsat pro nějaký konkrétní případ náš formulář na více místech, tak od toho slouží továrna. Vytvořit si nějakou FormFactory, která bude obsahovat například modifikace jako $form->getElementPrototype()->setAttribute('novalidate', 'novalidate'); Je lepší řešení, než extendovat form jak pominutý a toto nastavovat natvrdo v konstruktoru a to hlavně z toho důvodu, že máš 1 továrnu, která tvoří form a další továrny už jen rozšiřují tuto základní továrnu a ideálně kompozicí. Tím se dostaneš k tomu, že máš kód lépe navržen a dobře ho testuješ, protože konstrukci new Form() máš jen na jednom místě a ostatní továrny volají tuto basic továrnu. No a když potřebuješ globálně pro všechny formy přidat nějakou fičuru, tak ji přidáš do té základní továrny. Z hlediska testování pak máš taky výhodu, protože se ta basic FormFactory jednouše mockne a vracet bude jiný nějaký zase mockovaný form a nemusíš vymýšlet kouzla aby si svůj divně extendovaný form byl schopný nějak šikovně testovat apod..

Osobně jsem dělal na něco přes 500 projektů a extends u formů jsem použil 1× právě na definici vlastních inputů jako jsou addMobile, addZip, addPin apod..

m.brecher
Generous Backer | 863
+
+1
-

@KamilValenta

Před dvěma lety jsem nakonec persistenci parametrů ve formuláři v POST vzdal. Nyní na druhý pokus jsem učinil tento závěr:

Ve formulářích NELZE žádným způsobem udělat parametr s rozsahem persistence jenom na POST formuláře. Prostě to nejde.

a) formulář v presenteru v POST přenese jenom presenter + action + některé (automaticky ne všechny) parametry a sice ty které jsou buďto persistentní v presenteru, nebo jsou uvedeny v metodě příslušné akce. Jinak ne. Jenže persistentní parametry mají rozsah na všechny akce presenteru, což se tady nehodí. Teoreticky je zde ještě jedna možnost nějak využít toho, že parametr uvedený v metodě akce se do POST formuláře přenese. To jsem nezkoušel, jenže já stejně chci formuláře mít v Control.

b) formulář v komponentě poděděné z Control – tam je situace horší než v presenteru. Persistentní parametr komponenty se přenáší do odkazů GET se signálem komponenty, ale do POST formuláře nikoliv. A protože v komponentě nemáme metody akcí, tak zde není už žádná jiná možnost.

Zkusil jsem input hidden a tudy cesta vede, komplikuje to sice data formuláře, není to čistý návrh, ale jsem vděčný, že to alespoň nějak jde.

Tak kdyby zase někdo někdy potřeboval přenášet parametry přes POST formuláře tak nějak takhle vypadá schůdná cesta:

class MyForm extends Control
{
    public int $num = 1;

    public function render(): void
    {
        .......
    }

    public function createComponentForm(): Form
    {
        $form = new Form;
        $form->addSubmit('submit', 'Odeslat');
        $form->addHidden('__num');
        $form->onSuccess[] = $this->handleSuccess(...);
        return  $form;
    }

    public function handleSuccess(Form $form, ArrayHash $values): void
    {
        $this->num = (int)$values->__num;   // bacha na nešikovné přetypování ?int v php !!
        $this->num ++;
        $form['__num']->setValue($this->num);  // nepoužívat setDefaultValue() !!
    }
}

Editoval m.brecher (26. 1. 2023 7:16)

m.brecher
Generous Backer | 863
+
0
-

Ahoj,

opravuji svoje tvrzení před rokem:

formulář v komponentě poděděné z Control – tam je situace horší než v presenteru. Persistentní parametr komponenty se přenáší do odkazů GET se signálem komponenty, ale do POST formuláře nikoliv.

Znovu jsem vyzkoušel persistentní parametr definovaný v komponentě použít ve vnořeném formuláři:

class TestForm extends Nette\Application\UI\Control
{
    #[Persistent]
    public int $num = 0;

    public function createComponentForm()
    {
        $form = new Nette\Application\UI\Form;
        for($i = 1; $i <= $this->num; $i++){
            $form->addText('text_'.$i, 'Text '.$i);
        }
        $form->addSubmit('send', 'Odeslat');

        $form->onSuccess[] = fn() => $this->num++;
        return $form;
    }

    public function render()
    {
        $this['form']->render();
    }
}

a funguje to – persistentní parametr komponenty se přenese při submitech formuláře, vyzkoušeno v nette/forms 3.1.11

Higr
Člen | 16
+
0
-

m.brecher napsal(a):

Ahoj,

opravuji svoje tvrzení před rokem:

formulář v komponentě poděděné z Control – tam je situace horší než v presenteru. Persistentní parametr komponenty se přenáší do odkazů GET se signálem komponenty, ale do POST formuláře nikoliv.

Znovu jsem vyzkoušel persistentní parametr definovaný v komponentě použít ve vnořeném formuláři:

class TestForm extends Nette\Application\UI\Control
{
    #[Persistent]
    public int $num = 0;

    public function createComponentForm()
    {
        $form = new Nette\Application\UI\Form;
        for($i = 1; $i <= $this->num; $i++){
            $form->addText('text_'.$i, 'Text '.$i);
        }
        $form->addSubmit('send', 'Odeslat');

        $form->onSuccess[] = fn() => $this->num++;
        return $form;
    }

    public function render()
    {
        $this['form']->render();
    }
}

a funguje to – persistentní parametr komponenty se přenese při submitech formuláře, vyzkoušeno v nette/forms 3.1.11

Ahoj, zkoušel jsem to podle tvého příkladu, mám v presenteru handle kterým setnu $customId, to funguje správně ale jakmile form submitnu tak se $customId resetne zpět na 0, dělám něco špatně?

class FormFactory extends \Nette\Application\UI\Control
{

	#[Persistent]
	public int $customId = 0;

	public function createCustomForm(callable $onSuccess): CustomForm
	{
		bdump($this->customId); // 0
		$form = new CustomForm($this->customId); // extends Nette UI Form
		$form->init();

		$form->onSuccess[] = function() use ($onSuccess): void
		{
			$onSuccess();
		};

		return $form;
	}
}
m.brecher
Generous Backer | 863
+
0
-

@Higr

Ahoj, zkoušel jsem to podle tvého příkladu, mám v presenteru handle kterým setnu $customId, to funguje správně ale jakmile form submitnu tak se $customId resetne zpět na 0, dělám něco špatně?

Pošli handle z presenteru + celou třídu CustomForm, takhle bez kódu je těžko říct, kde je chyba.

Taco
Člen | 50
+
0
-

Nette\Application\UI\Form nepodporuje persistentní parametry, protože neimplementuje rozhraní Nette\Application\UI\StatePersistent.

@Higr aby ti fungovali persistentní parametry i v komponentě, nestačí jen uvést, které parametry to mají být. Musíš to říct i presenteru.

m.brecher
Generous Backer | 863
+
0
-

@Taco

@Higr aby ti fungovali persistentní parametry i v komponentě, nestačí jen uvést, které parametry to mají být. Musíš to říct i presenteru.

Tak to není, podle dokumentace se persistentní parametry definují v třídě komponenty, zatímco atributem #[Persistent] u třídy presenteru se označí persistentní komponenty. Persistentní parametr a persistentní komponenta není totéž.