Přejít k navigační liště

Zdroják » JavaScript » Závislé selectboxy elegantně v Nette a čistém JavaScriptu

Závislé selectboxy elegantně v Nette a čistém JavaScriptu

Články JavaScript, PHP

Jak vytvořit provázané selectboxy, kdy po volbě hodnoty v jednom se dynamicky načtou volby do druhého? V Nette a čistém JavaScriptu jde o snadnou úlohu. Ukážeme si řešení, které je čisté, znovupoužitelné a bezpečné.

Nálepky:

Text vyšel původně na webu autora.

Datový model

Jako příklad si vytvoříme formulář obsahující selectboxy pro volbu státu a města.

Nejprve si připravíme datový model, který bude vracet položky pro oba selectboxy. Pravděpodobně je bude získávat z databáze. Přesná implementace není podstatná, proto jen naznačíme, jak bude vypadat rozhraní:

class World
{
	public function getCountries(): array
	{
		return ...
	}

	public function getCities($country): array
	{
		return ...
	}
}
Code language: PHP (php)

Protože je celkový počet měst opravdu velký, budeme je získávat pomocí AJAXu. Pro tento účel si vytvoříme EndpointPresenter, tedy API, které nám bude vracet města v jednotlivých státech jako JSON:

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

	public function actionCities($country): void
	{
		$cities = $this->world->getCities($country);
		$this->sendJson($cities);
	}
}
Code language: PHP (php)

Pokud by měst bylo málo (třeba na jiné planetě 😉), nebo by model reprezentoval data, kterých prostě není mnoho, mohli bychom je předat rovnou všechna jako pole do JavaScriptu a ušetřit AJAXové požadavky. V takém případě by nebyl EndpointPresenter potřeba.

Formulář

A pojďme na samotný formulář. Vytvoříme dva selectboxy a ty provážeme, tj. podřízenému (city) nastavíme položky v závislosti na zvolené hodnotě nadřízeného (country). Důležité je, že tak činíme v obsluze události onAnchor, tedy ve chvíli, kdy formulář už zná hodnoty odeslané uživatelem.

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:');
		// <-- sem pak ještě něco doplníme

		$form->onAnchor[] = fn() =>
			$city->setItems($country->getValue()
				? $this->world->getCities($country->getValue())
				: []);

		// $form->onSuccess[] = ...
		return $form;
	}
}
Code language: PHP (php)

Takto vytvořený formulář bude fungovat i bez JavaScriptu. A to tak, že uživatel nejprve vybere stát, odešle formulář, poté se objeví nabídka měst, jedno z nich vybere a formulář odešle znovu.

Nás ale zajímá dynamické načítání měst pomocí JavaScriptu. Nejčistějším způsobem, jak k tomu přistoupit, je využít data- atributy, ve kterých si pošleme do HTML (a potažmo JS) informaci o tom, které selectboxy jsou provázané a odkud se mají čerpat data.

Každému podřízenému selectboxu předáme atribut data-depends s názvem nadřízeného prvku a dále buď data-url s URL, odkud má získávat položky pomocí AJAXu, nebo data-items, kde všechny varianty rovnou uvedeme.

Začněme s AJAXovou variantou. Předáme jméno nadřazeného prvku country a odkaz na Endpoint:cities. Znak # používáme jako placeholder a JavaScript bude místo něj vkládat uživatelem zvolený klíč.

$city = $form->addSelect('city', 'Město:')
	->setHtmlAttribute('data-depends', $country->getHtmlName())
	->setHtmlAttribute('data-url', $this->link('Endpoint:cities', '#'));
Code language: PHP (php)

A varianta bez AJAXu? Připravíme si pole všech států a všech jejich měst, které předáme do atributu data-items:

$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);
Code language: PHP (php)

A zbývá napsat obslužný JavaScript.

JavaScriptová obsluha

Následující kód je univerzální, není vázaný na konkrétní selectboxy country a city z příkladu, ale prováže jakékoliv selectboxy na stránce, stačí jim jen nastavit zmíněné data- atributy.

Kód je napsaný v čistém vanilla JS, nevyžaduje tedy jQuery nebo jinou knihovnu.

// najdeme na stránce všechny podřízené selectboxy
document.querySelectorAll('select[data-depends]').forEach((childSelect) => {
	let parentSelect = childSelect.form[childSelect.dataset.depends]; // nadřízený <select>
	let url = childSelect.dataset.url; // atribut data-url
	let items = JSON.parse(childSelect.dataset.items || 'null'); // atribut data-items

	// když uživatel změní vybranou položku v nadřízeném selectu...
	parentSelect.addEventListener('change', () => {
		// pokud existuje atribut data-items...
		if (items) {
			// nahrajeme rovnou do podřízeného selectboxu nové položky
			updateSelectbox(childSelect, items[parentSelect.value]);
		}

		// pokud existuje atribut data-url...
		if (url) {
			// uděláme AJAXový požadavek na endpoint s vybranou položkou místo placeholderu
			fetch(url.replace(encodeURIComponent('#'), encodeURIComponent(parentSelect.value)))
				.then((response) => response.json())
				// a nahrajeme do podřízeného selectboxu nové položky
				.then((data) => updateSelectbox(childSelect, data));
		}
	});
});

// přepíše <options> v <select>
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);
	}
}
Code language: JavaScript (javascript)

Více prvků a znovupoužitelnost

Řešení není limitované dvěma selectboxy, lze vytvořit klidně kaskádu tří nebo více na sobě závisejících prvků. Například doplníme volbu ulice, která bude závislá na zvoleném městě:

$street = $form->addSelect('street', 'Ulice:')
	->setHtmlAttribute('data-depends', $city->getHtmlName())
	->setHtmlAttribute('data-url', $this->link('Endpoint:streets', '#'));

$form->onAnchor[] = fn() =>
	$street->setItems($city->getValue() ? $this->world->getStreets($city->getValue()) : []);
Code language: PHP (php)

Také může více selectboxů záviset na jednom společném. Stačí jen analogicky nastavit data- atributy a naplnění položek pomocí setItems().

Přičemž není potřeba dělat žádný zásah do JavaScriptového kódu, který funguje univerzálně.

Bezpečnost

I v těchto ukázkách se stále zachovávají všechny bezpečnostní mechanismy, kterými disponují formuláře v Nette. Zejména že každý selectbox kontroluje, zda vybraná varianta je jednou z nabízených a tedy útočník nemůže podstrčit jinou hodnotu.


Řešení funguje v Nette 2.4 a novějším, ukázky kódu jsou psané pro Nette pro PHP 8. Aby fungovaly ve starších verzích, nahraďte property promotion a fn() za function () use (...) { ... }.

Komentáře

Odebírat
Upozornit na
guest
1 Komentář
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
Zobrazit všechny komentáře
novak.radek

pokud hledám řešení dynamicky doplňovaných selectboxů.
Když se však zamyslím nad praktickým použitím při zadávání adres ve formulářích, bylo by řešení uživatelsky poněkud nepříjemné. Hned u druhého výběru hrozí, že mi shoří kolečko u myši, nedejbůh že bych měl vybírat ještě z ulic.
Tím ale nechci snižovat úroveň článku, naopak, díky za opravdu polopatický popis!

Baro vs. Claude Code: Když paralelní agenti prohrají s jedním sezením (a co s tím udělat)

AI
Komentáře: 0
Více agentů musí být rychlejší než jeden, ne? Miodrag Todorović z JigJoy postavil baro - CLI, které paralelně spouští pět Claude sezení místo jednoho - a postavil ho proti novému příkazu /goal v Claude Code. Čekal jasnou výhru paralelismu. Místo toho prohrál v čase, v tokenech i v kvalitě výsledného kódu. Z analýzy tří konkrétních selhání ale vyšel jeden nečekaný závěr: problém nebyl v koordinaci mezi agenty, ale v rozhodnutích, která padla ještě před tím, než se kdokoli z nich probudil. Oprava trvala 200 řádků kódu - a v odvetě baro porazilo /goal o 4 minuty.

WebGPU už mají všechny hlavní enginy. Hotový standard z něj W3C dělat nechce

Na jaře 2026 už WebGPU není jen záležitost Chromia nebo preview buildů. Chrome, Edge, Safari i Firefox ho dodávají v produkčních verzích, ale ne na stejných platfórmach a ne se stejnými limity. WebGPU navíc podle aktuální charty pracovní skupiny nemíří z Candidate Recommendation do W3C Recommendation. Pro vývojáře je proto důležitější konkrétní podpora, fallbacky a limity paměti než formální status standardu.