Nette ve Saf JavaScript ile Zarif Bağımlı Seçim Kutuları
Birindeki değeri seçtikten sonra diğerine dinamik olarak seçeneklerin yüklendiği bağlantılı seçim kutuları nasıl oluşturulur? Nette ve saf JavaScript ile bu kolay bir görevdir. Temiz, yeniden kullanılabilir ve güvenli bir çözüm göstereceğiz.

Veri Modeli
Örnek olarak, ülke ve şehir seçimi için seçim kutuları içeren bir form oluşturacağız.
Öncelikle, her iki seçim kutusu için öğeleri döndürecek veri modelini hazırlayalım. Muhtemelen bunları veritabanından alacaktır. Kesin uygulama önemli değil, bu yüzden arayüzün nasıl görüneceğini sadece ima edeceğiz:
class World
{
public function getCountries(): array
{
return ...
}
public function getCities($country): array
{
return ...
}
}
Toplam şehir sayısı gerçekten büyük olduğundan, bunları AJAX
kullanarak alacağız. Bu amaçla, bize ayrı ülkelerdeki şehirleri JSON
olarak döndürecek bir EndpointPresenter
, yani bir API
oluşturacağız:
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);
}
}
Eğer şehir sayısı az olsaydı (belki başka bir gezegende 😉) veya
model basitçe çok fazla olmayan verileri temsil etseydi, hepsini doğrudan bir
dizi olarak JavaScript'e iletebilir ve AJAX isteklerinden tasarruf edebilirdik.
Bu durumda EndpointPresenter
'a gerek kalmazdı.
Form
Ve formun kendisine geçelim. İki seçim kutusu oluşturacağız ve bunları
birbirine bağlayacağız, yani üst öğenin (country
) seçilen
değerine bağlı olarak alt (city
) öğeleri ayarlayacağız.
Önemli olan, bunu onAnchor
olay işleyicisinde, yani formun kullanıcı tarafından gönderilen değerleri
zaten bildiği anda yapmamızdır.
class DemoPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private World $world,
) {}
protected function createComponentForm(): Form
{
$form = new Form;
$country = $form->addSelect('country', 'Ülke:', $this->world->getCountries())
->setPrompt('----');
$city = $form->addSelect('city', 'Şehir:');
// <-- buraya daha sonra bir şeyler ekleyeceğiz
$form->onAnchor[] = fn() =>
$city->setItems($country->getValue()
? $this->world->getCities($country->getValue())
: []);
// $form->onSuccess[] = ...
return $form;
}
}
Bu şekilde oluşturulan form, JavaScript olmadan da çalışacaktır. Şöyle ki, kullanıcı önce ülkeyi seçer, formu gönderir, ardından şehir teklifi görünür, birini seçer ve formu tekrar gönderir.
Ancak bizi ilgilendiren, JavaScript kullanarak şehirlerin dinamik olarak
yüklenmesidir. Buna yaklaşmanın en temiz yolu, hangi seçim kutularının
bağlantılı olduğu ve verilerin nereden alınması gerektiği bilgisini
HTML'ye (ve dolayısıyla JS'ye) göndereceğimiz data-
niteliklerini kullanmaktır.
Her alt seçim kutusuna, üst öğenin adını içeren
data-depends
niteliğini ve ayrıca ya AJAX kullanarak öğeleri
alması gereken URL'yi içeren data-url
'yi ya da tüm varyantları
doğrudan listelediğimiz data-items
'ı ileteceğiz.
AJAX varyantıyla başlayalım. Üst öğenin adını country
ve
Endpoint:cities
'e bir bağlantı ileteceğiz. #
karakterini bir yer tutucu olarak kullanıyoruz ve JavaScript bunun yerine
kullanıcı tarafından seçilen anahtarı ekleyecektir.
$city = $form->addSelect('city', 'Şehir:')
->setHtmlAttribute('data-depends', $country->getHtmlName())
->setHtmlAttribute('data-url', $this->link('Endpoint:cities', '#'));
Peki AJAX'sız varyant? Tüm ülkelerin ve tüm şehirlerinin bir dizisini
hazırlayacağız ve bunu data-items
niteliğine ileteceğiz:
$items = [];
foreach ($this->world->getCountries() as $id => $name) {
$items[$id] = $this->world->getCities($id);
}
$city = $form->addSelect('city', 'Şehir:')
->setHtmlAttribute('data-depends', $country->getHtmlName())
->setHtmlAttribute('data-items', $items);
Ve geriye sadece JavaScript işleyicisini yazmak kalıyor.
JavaScript İşleyicisi
Aşağıdaki kod evrenseldir, örnekteki belirli country
ve
city
seçim kutularına bağlı değildir, ancak sayfadaki herhangi
bir seçim kutusunu bağlar, sadece belirtilen data-
niteliklerini
ayarlamak yeterlidir.
Kod saf vanilla JS ile yazılmıştır, bu nedenle jQuery veya başka bir kütüphane gerektirmez.
// sayfadaki tüm alt seçim kutularını buluruz
document.querySelectorAll('select[data-depends]').forEach((childSelect) => {
let parentSelect = childSelect.form[childSelect.dataset.depends]; // üst <select>
let url = childSelect.dataset.url; // data-url niteliği
let items = JSON.parse(childSelect.dataset.items || 'null'); // data-items niteliği
// kullanıcı üst select'teki seçili öğeyi değiştirdiğinde...
parentSelect.addEventListener('change', () => {
// eğer data-items niteliği varsa...
if (items) {
// doğrudan alt seçim kutusuna yeni öğeleri yükleriz
updateSelectbox(childSelect, items[parentSelect.value]);
}
// eğer data-url niteliği varsa...
if (url) {
// yer tutucu yerine seçili öğe ile endpoint'e AJAX isteği yaparız
fetch(url.replace(encodeURIComponent('#'), encodeURIComponent(parentSelect.value)))
.then((response) => response.json())
// ve alt seçim kutusuna yeni öğeleri yükleriz
.then((data) => updateSelectbox(childSelect, data));
}
});
});
// <select> içindeki <options>'ları yeniden yazar
function updateSelectbox(select, items)
{
select.innerHTML = ''; // her şeyi kaldırırız
for (let id in items) { // yenilerini ekleriz
let el = document.createElement('option');
el.setAttribute('value', id);
el.innerText = items[id];
select.appendChild(el);
}
}
Daha Fazla Öğe ve Yeniden Kullanılabilirlik
Çözüm iki seçim kutusuyla sınırlı değildir, üç veya daha fazla birbirine bağlı öğeden oluşan bir kademe oluşturmak mümkündür. Örneğin, seçilen şehre bağlı olacak sokak seçimini ekleyelim:
$street = $form->addSelect('street', 'Sokak:')
->setHtmlAttribute('data-depends', $city->getHtmlName())
->setHtmlAttribute('data-url', $this->link('Endpoint:streets', '#'));
$form->onAnchor[] = fn() =>
$street->setItems($city->getValue() ? $this->world->getStreets($city->getValue()) : []);
Ayrıca, birden fazla seçim kutusu tek bir ortak olana bağlı olabilir.
Sadece data-
niteliklerini benzer şekilde ayarlamak ve
setItems()
kullanarak öğeleri doldurmak yeterlidir.
Bu arada, evrensel olarak çalışan JavaScript kodunda herhangi bir müdahale yapmaya gerek yoktur.
Güvenlik
Bu örneklerde bile, Nette'deki formların sahip olduğu tüm güvenlik mekanizmaları hala korunmaktadır. Özellikle, her seçim kutusu seçilen varyantın sunulanlardan biri olup olmadığını kontrol eder ve böylece saldırgan başka bir değer gönderemez.
Çözüm Nette 2.4 ve sonrasında çalışır, kod örnekleri PHP
8 için yazılmıştır. Eski sürümlerde çalışması için property
promotion ve fn()
yerine
function () use (...) { ... }
yazın.
Yorum göndermek için lütfen giriş yapın