Formular – addCheckboxList – prazdne values v onSuccess

Creator13
Člen | 18
+
0
-

Ahojte, mam problem so spracovanim checkboxov. V onSuccess dostavam prazdne $values. Ked pozriem $form->getHttpData(), tak sa tam pole s oznacenymi checkboxami nachadza.

	/**
	 * @param array<int, OrderProductEntity> $products
	 * @return Form
	 */
	public function create(array $products): Form
	{
		$form = $this->baseFormFactory->createDefaultForm();

		$checkboxListData = $this->getCheckboxListData($products);

		$form->addCheckboxList('products', 'Produkty', $checkboxListData)
			->setHtmlAttribute('class', 'form-check-input w20 h20 order-create-control-selected-product')
			->addRule($form::MinLength, 'Musíte vybrať aspoň %d položku', 1);

		$form->addSubmit('send', 'Vložiť do objednávky')
			->setHtmlAttribute('class', 'add-to-print-order btn btn-primary float-end button-big-1');

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

		return $form;
	}


	public function onSuccess(Form $form, $values): void
	{
		bdump($values);
		bdump($form->getHttpData());

		if (isset($form->getHttpData()['products'])) {
			$products = $form->getHttpData()['products'];
			$this->session->getSection('order')->products = $products;
		}
	}

Takisto mam problem s touto kontrolou. Funguje mi az ked zadam ako podmienku 2. Ak neoznacim ziadny checkbox, tak mi tuto podmienku neberie do uvahy.

->addRule($form::MinLength, 'Musíte vybrať aspoň %d položku', 1);

m.brecher
Generous Backer | 863
+
0
-

@Creator13

Takisto mam problem s touto kontrolou. Funguje mi az ked zadam ako podmienku 2. Ak neoznacim ziadny checkbox, tak mi tuto podmienku neberie do uvahy.

Podmínky přidané addRule() se validují až když prvek obsahuje nějaká data. Aby Ti validace fungovala i pro prázdný prvek, musíš přidat setRequired():

$form->addCheckboxList('products', 'Produkty', $checkboxListData)
			->setHtmlAttribute('class', 'form-check-input w20 h20 order-create-control-selected-product')
			->setRequired('Musíte vybrať aspoň 1 položku')
			->addRule($form::MinLength, 'Musíte vybrať aspoň %d položku', 1);

V onSuccess dostavam prazdne $values. Ked pozriem $form->getHttpData(), tak sa tam pole s oznacenymi checkboxami nachadza.

Nevalidní data formulář do $values nepustí – může to mít souvislost s chybějícím setRequired() jak jsem uvedl výše, oprav validaci a uvidíš.

Editoval m.brecher (23. 8. 13:11)

Kamil Valenta
Člen | 815
+
+2
-

m.brecher napsal(a):

Nevalidní data formulář do $values nepustí – může to mít souvislost s chybějícím setRequired() jak jsem uvedl výše, oprav validaci a uvidíš.

Pokud je vidí ve $form->getHttpData(), tak je vyplnil a pak setRequired() změnu nepřinese.

Zaměřil bych se na $this->getCheckboxListData(), zda vrací stejnou množinu dat při vykreslení formuláře i při jeho odeslání. Tipuji, ale kontext není uveden, že array $products do create() se předá jen při vykreslení formu, při handlu už ne, getCheckboxListData() vrátí [] a proto všechny zvolené checkboxy budou zahozeny jako nevalidní…

Creator13
Člen | 18
+
+1
-

Podarilo sa mi to vyriesit

Po odoslani formulara mi nadradena komponenta predavala prazdne pole $products

public function create(array $products): Form

Nadradena komponenta, z ktorej form berie $products.
Presunul som z render() do __construct

class CreatePrintOrderStep1Control extends Control
{

	use OrderActionPanelControlTrait;
	use PrintOrderCreateStep1FormControlTrait;

	private ?int $userId = null;

	/**
	 * @var array<int, OrderProductEntity>
	 */
	private array $products = [];

	public function __construct(
		private readonly UserDataProvider                        $userDataProvider,
		private readonly OrderActionPanelControlFactory          $orderActionPanelControlFactory,
		private readonly OrderProductDataProvider                $orderProductDataProvider,
		private readonly PrintTechnologyDataProvider             $printTechnologyDataProvider,
		private readonly ArrayService                            $arrayService,
		private readonly PrintOrderCreateStep1FormControlFactory $printOrderCreateStep1FormControlFactory,
	)
	{
		$this->initialize();
	}

	public function initialize(): void
	{
		$userId = $this->userDataProvider->getUserId();
		$products = $this->orderProductDataProvider->getProductsForCreatingPrintOrder($userId, true);

		//products for form
		$this->products = $products;
	}

	public function render(): void
	{
		$this->getTemplate()->products = $this->products;
		$this->getTemplate()->setFile(__DIR__ . '/templates/createPrintOrderStep1.latte');
		$this->getTemplate()->render();
	}

Je toto riesenie korektne? Ma komponenta ine moznosti pre inicializaciu a kontrolu, napr. ako ma presenter startup, action?

m.brecher
Generous Backer | 863
+
-1
-

@Creator13

Je toto riesenie korektne? Ma komponenta ine moznosti pre inicializaciu a kontrolu, napr. ako ma presenter startup, action?

Úplně čisté řešení to není, protože v konstruktoru by bylo lepší inicializaci nemít, ale bohužel komponentový model Nette nemá vestavěný životní cyklus pro komponenty poděděné z UI\Control, což by se hodilo a využilo třeba zrovna v tomto případě.

O něco čistší než dávat inicializaci komponenty do konstruktoru komponenty je volat inicializaci v metodě createComponent<*>() a to tak, aby fáze vytváření komponenty proběhly takto:

  • vytvoření instance komponenty
  • inicializace komponenty
  • připojení hotové komponenty do komponentového modelu presenteru
Kamil Valenta
Člen | 815
+
+1
-

m.brecher napsal(a):
Úplně čisté řešení to není, protože v konstruktoru by bylo lepší inicializaci nemít

Proč? Konstruktor je k tomu, aby inicializoval objekt se vším, co ke své existenci potřebuje.

O něco čistší než dávat inicializaci komponenty do konstruktoru komponenty je volat inicializaci v metodě createComponent<*>()

Někdy asi ano, pokud komponenta vykresluje obecná data, která pochází z venku (i když pak bych spíš očekával setter).
Pokud ale komponenta vykresluje data dodaná modelem (z jednoho místa), nedává mi smysl opakovat se s každým createComponent(), když to může být v konstruktoru na jednom místě.

m.brecher
Generous Backer | 863
+
-1
-

@KamilValenta

Proč? Konstruktor je k tomu, aby inicializoval objekt se vším, co ke své existenci potřebuje.

S tím bych úplně nesouhlasil, záleží na situaci, často bývá přehlednější mít dvě fáze vytvoření a inicializace objektu – třeba create a setup. Nette UI\Control ale životní cyklus nemá – to je realita, potom umístění inicializace do konstruktoru je dobrá volba – aby se předešlo nevýhodám spojeným s opakovaným psaním stejného kódu v createComponent<*> metodách.

Já používám pro formuláře vlastní abstraktní předek odvozený z UI\Control, kde jsem mimo jiné přidal jednoduchý životní cyklus setup/beforeRender a jsem s tím velmi spokojený.

Editoval m.brecher (5. 9. 14:17)

Martk
Člen | 661
+
-2
-

Dejme tomu, že máš 2 akce a 4 handle. Nyní se při všech 6 akcích provede dotaz do databáze (v 5 případech zbytečně, možná někdy ve všech 6 zbytečně) i když vůbec nesaháš na komponentu. Můžeš taky udělat lazy getter:

private function getProducts() {
	return $this->products ??= $this->orderProductDataProvider->getProductsForCreatingPrintOrder($userId, true);
}

a ušetříš dotazy do databáze. Pokud chceš jít cestou Konstruktor je k tomu, aby inicializoval objekt se vším, co ke své existenci potřebuje. Tak products předávej z presenteru a udělej factory na komponentu, jen on ví, kdy se komponenta opravdu použije.

MajklNajt
Člen | 493
+
0
-

Súhlas s @KamilValenta

@Martk Predsa keď nejakú komponentu chcem vykresliť, tie dáta z databázy potrebujem vždy, takže je jedno či ich dostávam zvonku alebo si ich ťahám v komponente, a ak komponentu nevykresľujem, nevolá sa ani jej konstruktor…

Kamil Valenta
Člen | 815
+
+2
-

Martk napsal(a):

Dejme tomu, že máš 2 akce a 4 handle. Nyní se při všech 6 akcích provede dotaz do databáze (v 5 případech zbytečně, možná někdy ve všech 6 zbytečně) i když vůbec nesaháš na komponentu.

Pokud je $products nepovinné pro fungování komponenty, tak bych to do ní cpal setterem a tahal getterem (zda lazy nebo ne není aktuálně předmětem diskuze). Což jsem ale popsal už prvně.

Pokud je $products nevyhnutelně potřeba pro existenci komponenty, připadá mi legitimní chtít to v konstruktoru a instancování objektu bez toho ani nepustit.

Tak products předávej z presenteru a udělej factory na komponentu

Někdy ano, pokud jsou data závislá ještě na něčem z presenteru. Ale pokud jsou fixně tahána z modelu, není důvod mít to na 10 místech (pokud komponentu používám 10x), to už jsem ale také psal prvně.

jen on ví, kdy se komponenta opravdu použije.

Komponenta to ví zrovna tak.

Editoval Kamil Valenta (6. 9. 8:18)

Martk
Člen | 661
+
0
-

Máte pravdu, když jsem to psal, tak jsem měl asi slabší chvilku a nebral jsem ze záhadných důvodů v potaz factory komponenty.

@MajklNajt Když chceš tahat produkty z jiného zdroje a nebo chceš nějak filtrovat produkty, tak v tomto případě musíš napsat novou komponentu a nemůžeš použít stávající, když trváš na tomto přístupu. Píšu z vlastní zkušenosti, kdy jsem musel nejednou přepisovat komponentu a ano, dělám to i tak, že si tahám data v komponentě, protože jsem líný. Zároveň když ty produkty potřebuješ na 3 různých místech, tak se nevolá 3× zbytečný dotaz do databáze na to samé. To jen jako doplnění.

MajklNajt
Člen | 493
+
+2
-

@Martk to je ale iný prípad, diskusia sa týkala inicializácie komponenty v konstruktore… tvoj use-case samozrejme je logicky správny, a je možné ho zrealizovať tak, že si tú kolekciu produktov vyžiadam v konstruktore… pretože ak je komponenta na nich závislá, konstruktor je jediné správne miesto, kde si ich vypýtať