Závislé selectboxy: snadná úloha v čistém Nette a JS

David Grudl
Nette Core | 8218
+
+24
-

Závislé selectboxy jsou velmi snadná úloha na řešení v čistém Nette a JS.

Nejprve si připravím nějaký datový model, který mi bude vracet položky pro hlavní (main) select box a pro podřízený (dependent):

class Model
{
	public function getMainItems(): array
	{
		return ...
	}

	public function getDependentItems($main): array
	{
		return ...
	}
}

Dále nějaký EndpointPresenter, prostě API, abych ty položky mohl tahat i na straně prohlížeče jako JSON:

class EndpointPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private Model $model,
	) {}

	public function actionMyData($id): void
	{
		$items = $this->model->getDependentItems($id);
		$this->sendJson($items);
	}
}

No a teď udělám formulář se dvěma selectboxy, které provážu. Položky do podřízeného dávám až v onAnchor (příp. onValidate), tedy ve chvíli, kdy formulář už zná hodnoty odeslané uživatelem, což v době vytváření formuláře ještě neví.

class DemoPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private Model $model,
	) {}

	protected function createComponentForm(): Form
	{
		$form = new Form;
		$main = $form->addSelect('main', 'hlavní:', $this->model->getMainItems())
			->setPrompt('----');
		$dependent = $form->addSelect('dep', 'podřízený:');
		$form->onAnchor[] = fn() =>
			$dependent->setItems($main->getValue() ? $this->model->getDependentItems($main->getValue()) : []);

		// <-- SEM JESTE NECO DOPLNIM
		return $form;
	}
}

Tohle by už mělo samo o sobě fungovat (i bez JS), tak, že uživatel vybere první položku, odešle formulář, vybere druhou a znovu odešle.

Doplním JS a AJAX. A tím nejčistějším způsobem, tj. do formuláře pouze přidám data- atributy, ve kterých si pošlu do HTML (a potažmo JS) informaci, které selectboxy jsou provázané (hlavní select bude mít v atributu data-dependent název podřízeného) a z jakého URL získám položky (zapíšu do data-url), abych mohl následně provázání vyřešit i v JavaScriptu:

$main
	->setHtmlAttribute('data-url', $this->link('Endpoint:myData', '#')) // # is placeholder
	->setHtmlAttribute('data-dependent', $dependent->getHtmlName());
return $form;

A zbývá napsat obslužný JS. Dnes se to dá snadno udělat i bez jQuery, v čistém JS. Následující kód je univerzální, není vázaný na konkrétní dva selectboxy, ale prováže jakékoliv selectboxy na stránce, stačí jim jen přidat ony dva data- attributy.

// najdeme vsechny hlavní selectboxy co maji podrizeny selectbox
document.querySelectorAll('select[data-dependent]').forEach(function (main) {
	// a když uživatel změní vybranou položku…
	main.addEventListener('change', function () {
		let dependent = main.form[main.dataset.dependent]; // podrizeny <select>
		let url = main.dataset.url; // URL pro našeptávání
		// ...udelame pozadavek na Endpoint presenter a posleme 'id'
		fetch(url.replace(encodeURIComponent('#'), encodeURIComponent(main.value)))
			.then(response => response.json())
			// a nahrajeme do podrizeneho nove data
			.then(data => updateSelectbox(dependent, data));
	});
});

// vloží nové <options> do <select>
function updateSelectbox(select, items)
{
	select.innerHTML = ''; // odstranime vse
	for (var id in items) { // vložime nové
		var el = document.createElement('option');
		el.setAttribute('value', id);
		el.innerText = items[id];
		select.appendChild(el);
	}
}

A je to :)

kralik
Člen | 230
+
0
-

Super, už to zkouším.

Jak by toto bylo možné rozšířit o další 2 scénáře použití?
Pomohl by někdo?

  1. Jeden hlavní select box + dva další selectboxy na něm závislé
  • tedy změní se hodnota v hlavním selectboxu a dojde k načtení položek do dvou dalších selectboxů
  1. 3 select boxy
  • 1. hlavní – vybere se hodnota a načtou se data do 2. selectboxu
  • 2. vybere se hodnota a dle ní se načtou data do 3. select boxu
  • 3. vybere se hodnota

Děkuji :-)

Editoval kralik (12. 1. 2022 7:18)

Polki
Člen | 553
+
0
-

A co varianta s JS, kdy je v selectech málo hodnot a nechci zatěžovat server ajaxem?

David Grudl
Nette Core | 8218
+
+2
-

Tak to zkuste doplnit.

kralik
Člen | 230
+
0
-

Prosím můžete někdo poradit jak upravit kód pro použití na scénáře A a B(viz. výše)?

Bohužel já nejsem schopen tuto úpravu vymyslet.

Moc díky

Milo
Nette Core | 1283
+
0
-

Dá se to udělat mnoha způsoby. Například:

add A – do data-dependent neuložíš jednu hodnotu, ale pole hodnot, tedy htmlname každého závislého selectboxu. Následně v actionMyData() pošleš data jako dvouúrovňové pole. A nakonec v JS v change eventu to zase zpracuješ jako pole, tedy o jeden foreach více.

add B – stačí nastavit data atributy i u závislých selectboxů.

chemix
Nette Core | 1310
+
+7
-

Co to dat na blog jako clanek @DavidGrudl ? prijde mi ze to je ten typ kucharek co by tam byl take fajn … ve foru to zapadne mezi dotazy jak udelat zavisle selectboxy …

David Grudl
Nette Core | 8218
+
+1
-

Tohle bylo v podstatě napsané z hlavy, nemám to odzkoušené :) Takže bych ještě počkal.

kralik
Člen | 230
+
0
-

Milo napsal(a):

Dá se to udělat mnoha způsoby. Například:

add A – do data-dependent neuložíš jednu hodnotu, ale pole hodnot, tedy htmlname každého závislého selectboxu. Následně v actionMyData() pošleš data jako dvouúrovňové pole. A nakonec v JS v change eventu to zase zpracuješ jako pole, tedy o jeden foreach více.

add B – stačí nastavit data atributy i u závislých selectboxů.

mohl bys ukázat přímo v příkladech pro scénáře A i B?
Díky

kralik
Člen | 230
+
-4
-

Opravdu nikdo nepomůže s kódem pro scénáře A i B?

Díky

Kamil Valenta
Člen | 815
+
+2
-

Proč to neuděláš jak psal @Milo ?
To není pomoc?
Příklad != vypracování implementace pro Tebe.

Ukaž jak jsi to použil a na čem jsi narazil.

David Grudl
Nette Core | 8218
+
+3
-

Pokud bych měl více nezávislých na jednom hlavním, tak by návrh změnil, a místo aby hlavní odkazoval na jeden podřízený, tak by podřízení odkazovali na jeden hlavní.

PHP:

// místo nastavování atributů na $main by se použil $dependent:
$dependent
	->setHtmlAttribute('data-url', $this->link('Endpoint:myData', '#'))
	->setHtmlAttribute('data-depends', $main->getHtmlName()); // a odkaz na main

a JS:


// najdeme všechny podřízené selectboxy
document.querySelectorAll('select[data-depends]').forEach(function (dependent) {
	let main = dependent.form[dependent.dataset.depends]; // hlavní <select>

	// a když uživatel změní vybranou položku…
	main.addEventListener('change', function () {
		let url = dependent.dataset.url; // URL pro našeptávání
		// ...uděláme požadavek na Endpoint presenter a pošleme klíč
		fetch(url.replace(encodeURIComponent('#'), encodeURIComponent(main.value)))
			.then(response => response.json())
			// a nahrajeme do podřízeného nová data
			.then(data => updateSelectbox(dependent, data));
	});
});

// vloží nové <options> do <select>
function updateSelectbox(select, items)
{
	select.innerHTML = ''; // odstraníme vše
	for (var id in items) { // vložime nové
		var el = document.createElement('option');
		el.setAttribute('value', id);
		el.innerText = items[id];
		select.appendChild(el);
	}
}
David Grudl
Nette Core | 8218
+
+3
-

Pokud by byla kaskáda tří selectboxů, tak prostě přidám třetí prvek a doplním analogicky data atributy a $onAnchor. V případě způsobu provázání dle přechozího komentáře s data-depends by to bylo:

		$s1 = $form->addSelect('s1', null, $this->model->getMainItems())
			->setPrompt('----');

		$s2 = $form->addSelect('s2')
			->setHtmlAttribute('data-depends', $s1->getHtmlName())
			->setHtmlAttribute('data-url', $this->link('Endpoint:myData', '#'));

		$form->onAnchor[] = fn() =>
			$s2->setItems($s1->getValue() ? $this->model->getDependentItems($s1->getValue()) : []);

		$s3 = $form->addSelect('s3')
			->setHtmlAttribute('data-depends', $s2->getHtmlName())
			->setHtmlAttribute('data-url', $this->link('Endpoint:myData', '#')); # jiny odkaz

		$form->onAnchor[] = fn() =>
			$s3->setItems($s2->getValue() ? $this->model->getDependentItems($s2->getValue()) : []); # jina metoda

Samozřejmě je potřeba do modelu a endpointu doplnit dalsi metody generujici data pro ten třetí prvek.

Petr Parolek
Člen | 455
+
0
-

@DavidGrudl trochu OT – bude kod funkční na PHP < 8.0? Jedná se mi o syntaxy fn

David Grudl
Nette Core | 8218
+
+1
-

Nepamatuju si od které verze existuje fn. Kde fn není, použije se obdoba s function. Základy programování v PHP tu snad neřešíme.

David Grudl
Nette Core | 8218
+
+2
-

A nakonec, pokud je málo hodnot a nepotřebuju AJAX, tak není potřeba vytvářet EndpointPresenter, všechny položky si vyexportuju do $subItems a vložím do data atributu data-items:

$subItems= $this->model->getMainItems();
array_walk($subItems, fn(&$v, $k) => $v = $this->model->getDependentItems($k));

$dependent = $form->addSelect('dep', 'podřízený:')
	->setHtmlAttribute('data-items', $subItems)
	->setHtmlAttribute('data-depends', $main->getHtmlName());

a pak je načtu přímo:

	// a když uživatel změní vybranou položku…
	main.addEventListener('change', function () {
		// pokud existuje data-items, vložíme položky přímo
		if (dependent.dataset.items) {
			let items = JSON.parse(dependent.dataset.items);
			updateSelectbox(dependent, items[main.value])
			return;
		}

		// ...jinak uděláme požadavek na Endpoint presenter a pošleme klíč
		...
	});

(Zase vycházím z kodu, kde se vazba dělá přes data-depends)

kralik
Člen | 230
+
0
-

Ahoj,
zkouším scénář A, jeden hlavní select box a dva selectboxy na něm závislé.

Presenter

protected function createComponentFormAddDoc(): UI\Form {
$form = new UI\Form();
...
$main = $form->addSelect('zavod', 'Závod', $zavod)
                ->setHtmlAttribute('class','form-control')
                ->setDefaultValue($this->mf)
                ->addRule($form::FILLED,'ZÁVOD musí být vybrán');

$dependent = $form->addSelect('oddeleni', 'Oddělení',$odd)
                        ->setHtmlAttribute('class','form-control')
                        ->setDefaultValue('')
                        ->addRule($form::FILLED,'ODDĚLENÍ musí být vybráno');

$dWf =  $form->addSelect('wf', 'Workflow', $wf[$this->mf])
                        ->setHtmlAttribute('class','form-control')
                        ->setDefaultValue('')
                        ->addRule($form::FILLED,'WORKFLOW musí být vybráno');

$form->onAnchor[] = fn () =>
            $dependent->setItems($this->mainModel->getDepOddeleni($main->getValue() ?? 'default'));
...
$dependent->setHtmlAttribute('data-url', $this->link('myData','#'))
	        ->setHtmlAttribute('data-depends',$main->getHtmlName());

addobj.latte

// najdeme vsechny hlavní selectboxy co maji podrizeny selectbox
    document.querySelectorAll('select[data-depends]').forEach(function (dependent) {
        // a když uživatel změní vybranou položku…
        let placeholder = encodeURIComponent('#');
	    let main = dependent.form[dependent.dataset.main]; // hlavní <select>

        // a když uživatel změní vybranou položku…
        main.addEventListener('change', function () {l}
            let url = dependent.dataset.url; // URL pro našeptávání

            // ...udelame pozadavek na Endpoint presenter a posleme 'sid'
            fetch(url.replace(placeholder, encodeURIComponent(main.value)))
                .then(response => response.json())
                // a nahrajeme do podrizeneho nove data
                .then( data => {

                    updateSelectbox(dependent, data);
                });

        {r});
    });

    // vloží nové <options> do <select>
    function updateSelectbox(select, items)
    {
        select.innerHTML = ''; // odstranime vse
        for (var id in items) { // vložime nové
            var el = document.createElement('option');
            el.setAttribute('value', id);
            el.innerText = items[id];
            select.appendChild(el);
        }
    }
  • v konzoli dostávám chybu „main is undefined“
  • nevím jak do toho zakomponovat i druhý selectbox ($dWf), který je závislý na $main

Předem moc díky za pomoc

dsar
Backer | 53
+
0
-

In my opinion having also an example with snippets would promote the Nette-way of doing AJAX stuff (especially for those ones that don't like JavaScript, like me) and furthermore simplify things for newbies (like above).

Even without snippets, since the form works even without JavaScript (a good thing), the code could be reduced by submitting the form and updating the whole form's body

David Grudl
Nette Core | 8218
+
0
-

@kralik v JS byla chyba, místo dependent.dataset.main má by dependent.dataset.depends

David Grudl
Nette Core | 8218
+
0
-

A máš tam $form->onSuccess[] = ...?

kralik
Člen | 230
+
0
-

David Grudl napsal(a):

A máš tam $form->onSuccess[] = ...?

ano, mám toto.

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

Ještě jsem to zkoušel a musel jsem do formuláře přidat fn i pro druhý selectbox.

..
 $form->onAnchor[] = fn () =>
            $dWf->setItems($this->mainModel->getDepWf($main->getValue() ?? 'default'));

Již se zdá, že to běhá v pořádku.
Jdu ještě vyzkoušet scénář B (se třemi vzájemně provázanými selectboxy).

Pak jsem kdyžtak dám celé řešení.

Díky

Editoval kralik (19. 1. 2022 12:55)

kralik
Člen | 230
+
0
-

Super, funguje to krásně.
Níže shrnuji celé řešení.

Jeden hlavní selectbox a dva závislí

  • celkem 3 selectboxy, změnou hodnoty 1. selectboxu dojde k načtení hodnot do dalších dvou selectboxů

PRESENTER

public function actionOptionOddeleni($sid): void{

        if($sid){
          $data = $this->mainModel->getDepOddeleni($sid);

		      $this->sendJson($data);
        }


	}

    public function actionOptionWf($sid): void{

        if($sid){
          $data = $this->mainModel->getDepWf($sid);

		      $this->sendJson($data);
        }


	}

protected function createComponentFormAddDoc(): UI\Form {
...
$main = $form->addSelect('zavod', 'Závod', $zavod)
                ->setHtmlAttribute('class','form-control')
                ->setDefaultValue($this->mf)
                ->addRule($form::FILLED,'ZÁVOD musí být vybrán');
$dependent = $form->addSelect('oddeleni', 'Oddělení',$odd)
                        ->setHtmlAttribute('class','form-control')
                        ->setDefaultValue('')
                        ->addRule($form::FILLED,'ODDĚLENÍ musí být vybráno');

$form->onAnchor[] = fn () =>
       $dependent->setItems($this->mainModel->getDepOddeleni($main->getValue() ?? 'default'));

$dWf =  $form->addSelect('wf', 'Workflow', $wf[$this->mf])
              ->setHtmlAttribute('class','form-control')
              ->setDefaultValue('')
              ->addRule($form::FILLED,'WORKFLOW musí být vybráno');
$form->onAnchor[] = fn () =>
       $dWf->setItems($this->mainModel->getDepWf($main->getValue() ?? 'default'));
...

$dependent->setHtmlAttribute('data-url', $this->link('optionOddeleni','#'))
	        ->setHtmlAttribute('data-depends',$main->getHtmlName());

$dWf->setHtmlAttribute('data-url', $this->link('optionWf','#'))
	        ->setHtmlAttribute('data-depends',$main->getHtmlName());
}

LATTE

<script>
    // najdeme vsechny hlavní selectboxy co maji podrizeny selectbox
    document.querySelectorAll('select[data-depends]').forEach(function (dependent) {
        // a když uživatel změní vybranou položku…
        let placeholder = encodeURIComponent('#');
	    let main = dependent.form[dependent.dataset.depends]; // hlavní <select>

        // a když uživatel změní vybranou položku…
        main.addEventListener('change', function () {l}
            let url = dependent.dataset.url; // URL pro našeptávání

            // ...udelame pozadavek na Endpoint presenter a posleme 'sid'
            fetch(url.replace(placeholder, encodeURIComponent(main.value)))
                .then(response => response.json())
                // a nahrajeme do podrizeneho nove data
                .then( data => {
                    updateSelectbox(dependent, data);
                });
        {r});
    });

    // vloží nové <options> do <select>
    function updateSelectbox(select, items)
    {
        select.innerHTML = ''; // odstranime vse
        for (var id in items) { // vložime nové
            var el = document.createElement('option');
            el.setAttribute('value', id);
            el.innerText = items[id];
            select.appendChild(el);
        }
    }

</script>

Tři závislé selectbox

  • celkem 3 selectboxy, změnou hodnoty 1. selectboxu dojde k načtení hodnot do 2. selectboxů
  • změnou hodnoty ve 2. selectboxu dojde k načtení hodnot do 3. selectboxů

PRESENTER

public function actionOptionLinky($sid): void{

        if($sid){
            $data = $this->mainModel->getDSlinka($sid);

		        $this->sendJson($data);
        }


	}

    public function actionOptionPracoviste($lin): void{

        if($lin){
            $data = $this->mainModel->getDSpracoviste($lin);

		        $this->sendJson($data);
        }

	}

protected function createComponentFormAddDoc(): UI\Form {
...
$main = $form->addSelect('sektor', 'Sektor/Oddělení:', $sek)
               ->setPrompt('Vyberte')
               ->setHtmlAttribute('class','form-control')
               ->addRule($form::FILLED,'Sektor/Oddělení musí být vybrán');

$linka = $form->addSelect('linka', 'Linka')
                ->setHtmlAttribute('class','form-control')
                ->setPrompt('Nejdříve vyberte sektor/oddělení')
                ->setHtmlAttribute('data-depends',$main->getHtmlName())
                ->setHtmlAttribute('data-url', $this->link('optionLinky','#'));

$form->onAnchor[] = fn () =>
               $linka->setItems($main->getValue() ? $this->mainModel->getDSlinka($main->getValue()) : []);

$pracoviste = $form->addSelect('pracoviste', 'Pracoviště')
                ->setHtmlAttribute('class','form-control')
                ->setPrompt('Nejdříve vyberte sektor/oddělení')
                ->setHtmlAttribute('data-depends',$linka->getHtmlName())
                ->setHtmlAttribute('data-url', $this->link('optionPracoviste','#'));

$form->onAnchor[] = fn () =>
                $pracoviste->setItems($linka->getValue() ? $this->mainModel->getDSpracoviste($linka->getValue()) : []);


}

LATTE

<script>
     // najdeme vsechny hlavní selectboxy co maji podrizeny selectbox
    document.querySelectorAll('select[data-depends]').forEach(function (dependent) {
        // a když uživatel změní vybranou položku…
        let placeholder = encodeURIComponent('#');
	    let main = dependent.form[dependent.dataset.depends]; // hlavní <select>

        // a když uživatel změní vybranou položku…
        main.addEventListener('change', function () {l}
            let url = dependent.dataset.url; // URL pro našeptávání

            // ...udelame pozadavek na Endpoint presenter a posleme 'sid'
            fetch(url.replace(placeholder, encodeURIComponent(main.value)))
                .then(response => response.json())
                // a nahrajeme do podrizeneho nove data
                .then( data => {
                    updateSelectbox(dependent, data);
                });
        {r});
    });

    // vloží nové <options> do <select>
    function updateSelectbox(select, items)
    {
        select.innerHTML = ''; // odstranime vse
        for (var id in items) { // vložime nové
            var el = document.createElement('option');
            el.setAttribute('value', id);
            el.innerText = items[id];
            select.appendChild(el);
        }
    }
</script>

Děkuji všem za pomoc

kralik
Člen | 230
+
0
-

Ahoj,
prosím o radu.

Řeším následující scénář závislých selectboxů:

Ve form mám 4 select boxy ZÁVOD, ODDĚLENÍ, WORKFLOW, PODPIS

Výběrem hodnoty v sb ZÁVOD se načtou položky do ODDĚLENÍ a WORKFLOW
Výběrem hodoty v sb WORKFLOW se načtou položky do PODPIS

Všechno závislé doplňování funguje.

Ale problém nastane když dojde k submitu formuláře.

Ten vrátí chybu „Please select a valid option“ na sb PODPIS.

Bohužel netuším proč.
Ve formuláři tato hodnota není požadována.

Formulář v presenteru

$form = new UI\Form($this, $name);

        $form->setHtmlAttribute('class','ajax');

        $main = $form->addSelect('zavod', 'Závod', $zavod)
                ->setHtmlAttribute('class','form-control')
                ->setDefaultValue($mf)
                ->addRule($form::FILLED,'ZÁVOD musí být vybrán');

        $dependent = $form->addSelect('oddeleni', 'Oddělení',$odd)
                        ->setHtmlAttribute('class','form-control')
                        ->setDefaultValue($modd)
                        ->addRule($form::FILLED,'ODDĚLENÍ musí být vybráno');

        $form->onAnchor[] = fn () =>
            $dependent->setItems($this->mainModel->getDepOddeleni($main->getValue() ?? 0));

        $dWf =  $form->addSelect('wf', 'Workflow', $wf[$mf])
                        ->setHtmlAttribute('class','form-control')
                        ->setDefaultValue(0);

        $form->onAnchor[] = fn () =>
            $dWf->setItems($this->mainModel->getDepWf($main->getValue() ?? 0));

        $dPodpis = $form->addSelect('wfpodpis', 'Podpis',$podpis)
            ->setHtmlAttribute('class','form-control');


        $form->onAnchor[] = fn () =>
            $dPodpis->setItems($this->mainModel->getDepPodpis($dWf->getValue() ?? 0));

        ...

        $form->addSubmit('ok', 'Uložit')
                ->setHtmlAttribute('class','btn btn-primary');

        $dependent->setHtmlAttribute('data-url', $this->link('optionOddeleni','#'))
	        ->setHtmlAttribute('data-depends',$main->getHtmlName());

        $dWf->setHtmlAttribute('data-url', $this->link('optionWf','#'))
	        ->setHtmlAttribute('data-depends',$main->getHtmlName());

        $dPodpis->setHtmlAttribute('data-url', $this->link('optionPodpis','#'))
	        ->setHtmlAttribute('data-depends',$dWf->getHtmlName());

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

        return $form;

Latte

<script>
    //NOTE: Závislé selectboxy
        // najdeme vsechny hlavní selectboxy co maji podrizeny selectbox
        document.querySelectorAll('select[data-depends]').forEach(function (dependent) {
            // a když uživatel změní vybranou položku…
            let placeholder = encodeURIComponent('#');
            let main = dependent.form[dependent.dataset.depends]; // hlavní <select>

            // a když uživatel změní vybranou položku…
            main.addEventListener('change', function () {l}
                let url = dependent.dataset.url; // URL pro našeptávání

                // ...udelame pozadavek na Endpoint presenter a posleme 'sid'
                fetch(url.replace(placeholder, encodeURIComponent(main.value)))
                    .then(response => response.json())
                    // a nahrajeme do podrizeneho nove data
                    .then( data => {
                        updateSelectbox(dependent, data);
                    });
            {r});
        });

        // vloží nové <options> do <select>
        function updateSelectbox(select, items){
            select.innerHTML = ''; // odstranime vse
            for (var id in items) { // vložime nové
                var el = document.createElement('option');
                el.setAttribute('value', id);
                el.innerText = items[id];
                select.appendChild(el);
            }
        }
    //Konec
</script>

Předem díky


kralik
Člen | 230
+
0
-

Ahoj,
prosím o pomoc, viz. výše.

díky

Pepino
Člen | 256
+
0
-

@kralik zkus tomu selectu nastavit

->setRequired(false)

případně ještě

->checkDefaultValue(false)

Editoval Pepino (29. 6. 2022 13:51)

kralik
Člen | 230
+
0
-

Pepino napsal(a):

@kralik zkus tomu selectu nastavit

->setRequired(false)

případně ještě

->checkDefaultValue(false)

toto bohužel nepomohlo.
když dám do formuláře

$podpis[1] = 'Někdo';

Zruším tento řádek aby se mi nenačetly závislé hodnoty.

$dPodpis->setHtmlAttribute('data-url', $this->link('optionPodpis','#'))
	        ->setHtmlAttribute('data-depends',$dWf->getHtmlName());

Následně vyberu v selectboxu podpis „Někdo“, tak se formulář submitne a data se předají.

Problém nastane pokud se mají položky selectboxu načíst v závislosti na vybrané položce „wf“
Při zkoumání prvku jsou tam data Ajaxem správně načtená, do toho selectboxu „podpis“.

Bohužel netuším kde je problém.

dehtak
Člen | 113
+
-4
-

a jak ste vyresily problem s timhle ?
Nette\Forms\Controls\SelectBox

	public function getValue()
	{
		return array_key_exists($this->value, $this->items)
			? $this->value
			: null;
	}

Takto zavisly seletbox vraci vzdy null
Vim udelat novou tridu seletbox a do ni napsat novou metodu getValue.
A samozrejme se musi upravit i metoda SetValue.
Ale to by ste do toho vaseho manualu "":https://blog.nette.org/…-javascriptu na webu meli pridat !!! Protoze jinak je to nefunkcni !!

class ZavislySelectBox extends \Nette\Forms\Controls\SelectBox {

 	public function setValue($value){
		if (empty($this->items) && $value !== null) {
            $this->items[$value] = $value;
		}

		$this->value = $value === null ? null : key([(string) $value => null]);
		return $this;
    }

    public function getValue(){
		return $this->value;
	}
}

Asi tak nejak zhruba

PS : Misto toho minusovani by ste mohli napsat co tam mam blbe, vsak mam pravdu ze bez novy tridy to nebude fachat

Editoval dehtak (12. 7. 2022 14:36)

kralik
Člen | 230
+
0
-

Ahoj,
lze vyřešit „dvojí main závislé selectboxy“?
Narazím když bych chtěl dva nezávislé na sobě, ale závislé selectboxy, vysvětlím.

1. Závislý main SelectBox
Závod na jehož výběru závisí selectBox Workflow.
→ výběrem Závodu se doplní položky Workflow = toto funguje dobře.

2. Závislý main SelectBox
Dodavatel na jehož výběru závisí selectBox Provozovna
→ výběrem 1. selectBoxu Závod se v Dodavateli ani v Provozovně nic nestane
→ až s výběrem Dodavatel se doplní položky Provozovna
Bohužel toto nemohu rozchodit.

Zkouším postup https://blog.nette.org/…-javascriptu ten funguje dobře na 1. Závislý SelectBox.

Ale druhý závislý SelectBoxu je bez reakce, ani se nevyvolá Ajax, při změne položky Dodavatel.

Myslím, že problém je v javascriptu, ale to netuším jak vyřešit.

javascript

document.querySelectorAll('select[data-depends]').forEach(function (dependent) {
            // a když uživatel změní vybranou položku…
            let placeholder = encodeURIComponent('#');
            let main = dependent.form[dependent.dataset.depends]; // hlavní <select>

            // a když uživatel změní vybranou položku…
            main.addEventListener('change', function () {l}
                let url = dependent.dataset.url; // URL pro našeptávání

                // ...udelame pozadavek na Endpoint presenter a posleme 'sid'
                fetch(url.replace(placeholder, encodeURIComponent(main.value)))
                    .then(response => response.json())
                    // a nahrajeme do podrizeneho nove data
                    .then( data => {
                        updateSelectbox(dependent, data);
                    });
            {r});
        });

Můžete mi poradit?
Díky

Allconius
Člen | 317
+
0
-

Ahoj, zkoušel jsem také podle postupu zde
Všechno mi funguje, sedí mi i počet položek, ale místo názvů mám všude [object Object]

Json je ve formátu:

{"id":131,"name":"AAAAAAA"},{"id":21,"name":"Adaptační příprava"}

takže předpokládám že problém je jen v tom JS update:

                        function updateSelectbox(select, items)
                        {
                            select.innerHTML = ''; // odstraníme vše
                            for (let id in items) { // vložíme nové
                                let el = document.createElement('option');
                                el.setAttribute('value', id);
                                el.innerText = items[id];
                                select.appendChild(el);
                            }
                        }

jak mu předat ten název z „name“ ?

Allconius
Člen | 317
+
0
-

Tak takto je to funkční pro mě (pokud mám všude id a name) :


                        function updateSelectbox(select, items)
                        {
                            select.innerHTML = ''; // odstraníme vše
                            let el = document.createElement('option');
                            el.setAttribute('value', 0);
                            el.innerText = 'Žádná';
                            select.appendChild(el);
                            for (i in items)
                            {
                                let el = document.createElement('option');
                                el.setAttribute('value', items[i]["id"]);
                                el.innerText = items[i]["name"];
                                select.appendChild(el);
                            }
                        }


vladimir.biro
Člen | 163
+
0
-

Hello.
Mam malej problem. Zavisle selectboxy mi funguji v poradku, ale na druhej selectbox (ten zavislej) mam napojene ->toggle() a toto uz nefunguje. Kdyz si v zavislem selectboxu vyberu tu moznost, ktera zobrazuje dane #id, tak se zobrazi, to je fajn, ale po zmene halvniho selectboxu, se zavislej selectbox zmeni v dusledku nactenych novych hodnot, ale dane #id mi zustane zobrazene, i kdyz dana addCondition() podminka uz neplati. Musim pak prepnout zavislej selectbox rucne (a pak se pripadne vratit) a az pak #id zmizi.

Resil tohle nekdo pls?

Dekuju za jakekoli rady.

Pepino
Člen | 256
+
0
-

@vladimirbiro
Mělo by myslím stačit po změně hlavního selectboxu zavolat Nette.toggleForm https://github.com/…etteForms.js#L563
Nebo možná nastavit na tom hlavím selectboxu taky toggle.

Higr
Člen | 16
+
0
-

Vytvořil jsem si formuláře podle zadání a vše funguje správně. Nicméně pokud formulář použiju pro editování záznamu, dostanu error Value ‚1‘ is out of allowed set [] in field… na

$form->setDefaults();

dělám neco špatně nebo je uvedený tutorial nepoužitelný pro editování záznamu?
koukal jsem že se to řešilo tady ale asi neúspěšně https://forum.nette.org/…-setdefaults

Editoval Higr (21. 6. 9:59)

Šaman
Člen | 2658
+
+4
-

Higr napsal(a):

Vytvořil jsem si formuláře podle zadání a vše funguje správně. Nicméně pokud formulář použiju pro editování záznamu, dostanu error Value ‚1‘ is out of allowed set [] in field… na

$form->setDefaults();

dělám neco špatně nebo je uvedený tutorial nepoužitelný pro editování záznamu?
koukal jsem že se to řešilo tady ale asi neúspěšně https://forum.nette.org/…-setdefaults

Od boku: Když nastavuješ defaultní hodnoty, musíš vybrat z těch, které formulář umožňuje vybrat. Tedy z nastavených dat těch inputů. Takže v actionEdit musíš nastavit $dependent->setItems() podle defaultní hodnoty nadřizeného selectu.
Protože v prázdném formuláři ten závislý select nemá žádné možnosti a ty se mu nastaví pomocí ajaxu až po změně hlavního selectu. Ale ty mu chceš při editaci nastavit hodnoty ihned, bez toho ajaxového načtení.

Higr
Člen | 16
+
0
-

Šaman napsal(a):

Higr napsal(a):

Vytvořil jsem si formuláře podle zadání a vše funguje správně. Nicméně pokud formulář použiju pro editování záznamu, dostanu error Value ‚1‘ is out of allowed set [] in field… na

$form->setDefaults();

dělám neco špatně nebo je uvedený tutorial nepoužitelný pro editování záznamu?
koukal jsem že se to řešilo tady ale asi neúspěšně https://forum.nette.org/…-setdefaults

Od boku: Když nastavuješ defaultní hodnoty, musíš vybrat z těch, které formulář umožňuje vybrat. Tedy z nastavených dat těch inputů. Takže v actionEdit musíš nastavit $dependent->setItems() podle defaultní hodnoty nadřizeného selectu.
Protože v prázdném formuláři ten závislý select nemá žádné možnosti a ty se mu nastaví pomocí ajaxu až po změně hlavního selectu. Ale ty mu chceš při editaci nastavit hodnoty ihned, bez toho ajaxového načtení.

Děkuji, takhle mi to funguje. Hodilo by se to dopsat do toho tutorialu. Viděl jsem několik lidí, kteří hledali řešení pro ten samý problém.

Marek Znojil
Člen | 84
+
0
-

U prvků, které dědí ‎Nette\Forms\Controls\ChoiceControl‎ můžeš metodou checkDefaultValue(false) vypnout kontrolu hodnot v seznamu, viz poslední odstavec https://doc.nette.org/…rms/controls#….