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!

Jak zabezpečit WordPress: Praktický průvodce

WordPress pohání přes 40 % všech webů na světě. To z něj dělá nejrozšířenější CMS a zároveň nejčastější terč automatizovaných útoků. Boti nepotřebují cílit přímo na vás: systematicky procházejí miliony domén a hledají otevřené dveře. Stačí zapomenutý plugin bez aktualizace, výchozí prefix databáze nebo heslo z uniklé databáze. Tento článek není seznam pluginů. Je to průvodce od základů přes hardening konfigurace až po serverové zabezpečení s konkrétními kroky, které můžete udělat ještě dnes.

Product Engineer: supermani, nebo falešná efektivita?

Stále více firem propouští produktové týmy a sází na jednu roli, která to zvládne celé sama. Product Engineer je člověk, který vymyslí produkt, implementuje ho a vyhodnotí výsledky. S ekosystémem AI agentů místo kolegů. Efektivita? Na první pohled určitě. Ale je rozdíl mezi tím dodávat víc a rychleji a skutečně být efektivní. Tenhle rozdíl firmy zatím moc neřeší.

EU AI Act: co musí vývojářské týmy vědět do 2. srpna 2026

Druhého srpna začnou v EU platit povinnosti pro poskytovatele i provozovatele high-risk AI systémů: posouzení shody, technická dokumentace a quality management na straně providerů, uchovávání logů a dohled nad provozem na straně deployerů. Samostatně vstupují v platnost transparentní pravidla pro chatboty, generativní AI a deepfaky, a ta se týkají všech, nejen high-risk systémů. Kdo nasazuje AI v recruitmentu, credit scoringu nebo HR hodnocení, je v zóně. Čekání na odklad přes Digital Omnibus je sázka na legislativní proces, který ještě neskončil. A kdo si myslí, že se ho to netýká, protože „jen používá ChatGPT" v use casu z Annexu III, pravděpodobně špatně přečetl nařízení.