Formulář s dynamickými selecty a setDefaults
- toka
- Člen | 253
Pokud mám připravený formulář dle postupu zde a chci formuláři např. v editaci nastavit výchozí hodnoty pro závislý select, např. v metodě actionEdit(), dostávám chybovou hlášku, že chci nastavit hodnotu, která neexistuje, protože povolené je prázdné pole.
Pokud to provedu přímo v createComponentForm, tak to funguje správně, ale nepřijde mi to jako správné řešení. Jak toto řešíte? Díky.
Nefunguje
class DemoPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private World $world,
) {}
protected function createComponentForm(): Form
{
$form = new Form;
$country = $form->addSelect('country', 'Stát:', $this->world->getCountries())
->setPrompt('----');
$city = $form->addSelect('city', 'Město:');
$items = [];
foreach ($this->world->getCountries() as $id => $name) {
$items[$id] = $this->world->getCities($id);
}
$city = $form->addSelect('city', 'Město:')
->setHtmlAttribute('data-depends', $country->getHtmlName())
->setHtmlAttribute('data-items', $items);
$form->onAnchor[] = fn() =>
$city->setItems($country->getValue()
? $this->world->getCities($country->getValue())
: []);
// $form->onSuccess[] = ...
return $form;
}
public function actionEdit(int $id)
{
// Data jsem získal někde s databáze
$this['form']->setDefaults('...'); // Value '1' is out of allowed set [] in field 'city'
}
}
Funguje
class DemoPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private World $world,
) {}
protected function createComponentForm(): Form
{
$form = new Form;
$country = $form->addSelect('country', 'Stát:', $this->world->getCountries())
->setPrompt('----');
$city = $form->addSelect('city', 'Město:');
$items = [];
foreach ($this->world->getCountries() as $id => $name) {
$items[$id] = $this->world->getCities($id);
}
$city = $form->addSelect('city', 'Město:')
->setHtmlAttribute('data-depends', $country->getHtmlName())
->setHtmlAttribute('data-items', $items);
$form->onAnchor[] = fn() =>
$city->setItems($country->getValue()
? $this->world->getCities($country->getValue())
: []);
// $form->onSuccess[] = ...
// Data jsem získal někde s databáze
$form->->setDefaults('...'); // Funguje
return $form;
}
}
- m.brecher
- Generous Backer | 863
@toka
Pokud to provedu přímo v createComponentForm, tak to funguje správně, ale nepřijde mi to jako správné řešení.
Naopak, je běžnou praxí, že modelová třída řídí formulář. Co s tím má presenter společného? Je to záležitost formuláře a modelu.
Nicméně, sosat data do formuláře v akci presenteru lze a asi bude stačit odložit tuto akci na pozdější fázi životního cyklu formuláře:
public function actionEdit(int $id)
{
// Data jsem získal někde s databáze
$this['form']->onRender[] = fn() => $this['form']->setDefaults($data); // calback formou arrow funkce
}
Nebo totéž přímo v createComponent:
public functioncreateComponentForm(): Form
{
$form = new Form;
// .....
$form->onRender[] = fn() => $form->setDefaults($data); // calback formou arrow funkce
}
- toka
- Člen | 253
Děkuji za reakci.
Toto právě vůbec nefunguje, s tím onRender[]
to je jako bez
něj, jako když tam bude pouze
$this['form']->setDefaults($data)
.
A mít to zase v tom function createComponentForm()
zase
vyžaduje nějakým způsobem do té funkce dostat parametry navíc, kdy mi
přijde logické to dělat v tom action
. Pokud ten formulář
nemá dynamické obsahy v selectech, tak je zcela funkční dát to
actionEdit()
to setDefaults()
tak, jak to mám
uvedeno výše.
Jde tedy o to, že ten select neobsahuje v tu dobu žádná data a je to prázdné pole.
public function actionEdit(int $id)
{
// Data jsem získal někde s databáze
// Nefunguje
$this['form']->onRender[] = fn() => $this['form']->setDefaults($data);
}
- toka
- Člen | 253
Chci vlastně dosáhnout toho samého co je v ukázce.
Tam v actionEdit
není nic jiného, než co chci já –
$form->setDefaults($record)
.
class RecordPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Facade $facade,
) {
}
public function actionAdd(): void
{
$form = $this->getComponent('recordForm');
$form->onSuccess[] = [$this, 'addingFormSucceeded'];
}
public function actionEdit(int $id): void
{
$record = $this->facade->get($id);
if (
!$record // oveření existence záznamu
|| !$this->facade->isEditAllowed(/*...*/) // kontrola oprávnění
) {
$this->error(); // chyba 404
}
$form = $this->getComponent('recordForm');
$form->setDefaults($record); // nastavení výchozích hodnot
$form->onSuccess[] = [$this, 'editingFormSucceeded'];
}
protected function createComponentRecordForm(): Form
{
// ověříme, že akce je 'add' nebo 'edit'
if (!in_array($this->getAction(), ['add', 'edit'])) {
$this->error();
}
$form = new Form;
// ... přidáme políčka formuláře ...
return $form;
}
public function addingFormSucceeded(Form $form, array $data): void
{
$this->facade->add($data); // přidání záznamu do databáze
$this->flashMessage('Successfully added');
$this->redirect('...');
}
public function editingFormSucceeded(Form $form, array $data): void
{
$id = (int) $this->getParameter('id');
$this->facade->update($id, $data); // aktualizace záznamu
$this->flashMessage('Successfully updated');
$this->redirect('...');
}
}
- toka
- Člen | 253
Ano, pokud je to setDefaults()
v tom
createComponentForm()
, tak to funguje dle očekávání –
správně. Aktuálně to tak používám, ale nepřijde mi to ideální
řešení – nelíbí se mi.
Rád bych to řešil v action
.
Skutečně se správně doplní ta hodnota toho druhého selectu, který je závislý na prvním.
Editoval toka (29. 11. 2023 9:20)
- Martk
- Člen | 661
Tohle mi totiž nefunguje, ani nemůže, protože nastavím city na
CZ_CITY2, ale onAnchor se ještě nezavolal, takže je z toho chyba:
Value 'CZ_CITY_2' is out of allowed set [] in field 'city'
,
protože items je prázdné pole.
protected function createComponentTest()
{
$countries = ['CZ', 'SK', 'PL'];
$cities = [
'CZ' => ['CZ_CITY_1', 'CZ_CITY_2', 'CZ_CITY_3'],
'SK' => ['SK_CITY_1', 'SK_CITY_2', 'SK_CITY_3'],
'PL' => ['PL_CITY_1', 'PL_CITY_2', 'PL_CITY_3'],
];
$form = new Form();
$country = $form->addSelect('country', 'Stát:', array_combine($countries, $countries))
->setPrompt('----');
$city = $form->addSelect('city', 'Město:')
->setHtmlAttribute('data-depends', $country->getHtmlName())
->setHtmlAttribute('data-items', $cities);
$form->onAnchor[] = fn() =>
$city->setItems($country->getValue()
? array_combine($cities[$country->getValue()], $cities[$country->getValue()])
: []);
$form->setDefaults([
'country' => 'CZ',
'city' => 'CZ_CITY_2',
]);
return $form;
}
Proto se divím, že ti to funguje. Zkouším to na v3.1.11.
Fungovat by mohlo něco takového:
protected function createComponentTest()
{
$countries = ['CZ', 'SK', 'PL'];
$cities = [
'CZ' => ['CZ_CITY_1', 'CZ_CITY_2', 'CZ_CITY_3'],
'SK' => ['SK_CITY_1', 'SK_CITY_2', 'SK_CITY_3'],
'PL' => ['PL_CITY_1', 'PL_CITY_2', 'PL_CITY_3'],
];
$form = new Form();
$country = $form->addSelect('country', 'Stát:', array_combine($countries, $countries))
->setPrompt('----');
$form['city'] = (new DependentSelectBox('Město:', $country, fn(?string $value) =>
$value
? array_combine($cities[$value], $cities[$value])
: []))
->setHtmlAttribute('data-depends', $country->getHtmlName())
->setHtmlAttribute('data-items', $cities);
$form->setDefaults([
'country' => 'CZ',
'city' => 'CZ_CITY_2',
]);
return $form;
}
<?php declare(strict_types = 1);
namespace App;
use Nette\Forms\Control;
use Nette\Forms\Controls\SelectBox;
final class DependentSelectBox extends SelectBox
{
/** @var callable */
private $getItems;
public function __construct($label, private Control $dependency, callable $getItems)
{
parent::__construct($label);
$this->getItems = $getItems;
}
public function loadHttpData(): void
{
$this->loadItems();
parent::loadHttpData();
}
public function setValue(mixed $value): void
{
$this->loadItems();
parent::setValue($value);
}
private function loadItems(): void
{
if (isset($this->getItems)) { // BaseControl calls $this->setValue(null), skip it
$this->setItems(($this->getItems)($this->dependency->getValue()));
}
}
}