Bağımlı seçim kutuları Nette ve saf JS'de zarif bir şekilde
Birinde bir değer seçtikten sonra diğerinde seçeneklerin dinamik olarak güncellendiği zincirleme seçim kutuları nasıl oluşturulur? Bu, Nette ve saf JavaScript'te kolay bir iştir. 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şturalım.
İlk olarak, her iki seçim kutusu için girdileri döndürecek bir veri modeli hazırlayacağız. Muhtemelen bunları veritabanından alacaktır. Tam uygulama önemli değildir, bu nedenle arayüzün nasıl görüneceğine dair ipucu verelim:
class World
{
public function getCountries(): array
{
return ...
}
public function getCities($country): array
{
return ...
}
}
Toplam şehir sayısı gerçekten çok fazla olduğu için bunları AJAX
kullanarak alacağız. Bu amaçla, her ülkedeki şehirleri JSON olarak
döndürecek bir API olan bir EndpointPresenter
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);
}
}
Az sayıda şehir varsa (örneğin başka bir gezegende 😉) veya model çok
fazla olmayan verileri temsil ediyorsa, hepsini JavaScript'e dizi olarak
aktarabilir ve AJAX isteklerinden tasarruf edebiliriz. Bu durumda
EndpointPresenter
adresine gerek kalmayacaktır.
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', 'Country:', $this->world->getCountries())
->setPrompt('----');
$city = $form->addSelect('city', 'City:');
// <-- buraya başka bir şey 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 çalışacaktır. Bu, kullanıcının önce bir ülke seçmesi, formu göndermesi, ardından bir şehir menüsü görünmesi, bunlardan birini seçmesi ve formu tekrar göndermesi ile yapılır.
Ancak, JavaScript kullanarak şehirleri dinamik olarak yüklemekle
ilgileniyoruz. Buna yaklaşmanın en temiz yolu, HTML'ye (ve dolayısıyla
JS'ye) hangi seçim kutularının bağlantılı olduğu ve verilerin nereden
alınacağı hakkında bilgi gönderdiğimiz data-
niteliklerini
kullanmaktır.
Her alt seçim kutusu için, üst öğenin adını içeren bir
data-depends
özniteliği ve ardından AJAX kullanarak öğeleri
alacağımız URL'yi içeren bir data-url
veya tüm seçenekleri
doğrudan listelediğimiz bir data-items
özniteliği iletiriz.
AJAX varyantı ile başlayalım. Üst öğenin adını country
ve bir referansı Endpoint:cities
adresine iletiyoruz.
#
karakterini yer tutucu olarak kullanıyoruz ve JavaScript bunun
yerine kullanıcının seçtiği anahtarı koyacaktır.
$city = $form->addSelect('city', 'City:')
->setHtmlAttribute('data-depends', $country->getHtmlName())
->setHtmlAttribute('data-url', $this->link('Endpoint:cities', '#'));
Peki ya AJAX'sız varyant? Tüm ülkelerin ve tüm şehirlerinin bir dizisini
hazırlıyoruz ve bunu data-items
özelliğine aktarıyoruz:
$items = [];
foreach ($this->world->getCountries() as $id => $name) {
$items[$id] = $this->world->getCities($id);
}
$city = $form->addSelect('city', 'City:')
->setHtmlAttribute('data-depends', $country->getHtmlName())
->setHtmlAttribute('data-items', $items);
JavaScript iş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ğlayacaktır, sadece belirtilen data-
niteliklerini ayarlayın.
Kod saf vanilya 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ı bulma
document.querySelectorAll('select[data-depends]').forEach((childSelect) => {
let parentSelect = childSelect.form[childSelect.dataset.depends]; // parent <select>
let url = childSelect.dataset.url; // öznitelik data-url
let items = JSON.parse(childSelect.dataset.items || 'null'); // öznitelik data-items
// kullanıcı üst seçimdeki seçili öğeyi değiştirdiğinde...
parentSelect.addEventListener('change', () => {
// data-items özniteliği mevcutsa...
if (items) {
// yeni öğeleri doğrudan alt seçim kutusuna yükleyin
updateSelectbox(childSelect, items[parentSelect.value]);
}
// data-url özniteliği mevcutsa...
if (url) {
// yer tutucu yerine seçilen öğe ile uç noktaya AJAX isteği yaparız
fetch(url.replace(encodeURIComponent('#'), encodeURIComponent(parentSelect.value)))
.then((response) => response.json())
// ve alt seçim kutusuna yeni öğeler yükleyin
.then((data) => updateSelectbox(childSelect, data));
}
});
});
// replaces <options> in <select>
function updateSelectbox(select, items)
{
select.innerHTML = ''; // remove all
for (let id in items) { // yeni ekle
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 bağımlı öğeden oluşan bir kaskad oluşturabilirsiniz. Örneğin, seçilen şehre bağlı bir sokak seçimi ekliyoruz:
$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()) : []);
Ayrıca, birden fazla seçim kutusu tek bir ortak kutuya bağlı olabilir.
Sadece data-
niteliklerini benzer şekilde ayarlayın ve öğeleri
setItems()
ile doldurun.
Evrensel olarak çalışan JavaScript kodunda herhangi bir değişiklik yapmaya kesinlikle gerek yoktur.
Güvenlik
Bu örneklerde bile, Nette formlarının sahip olduğu tüm güvenlik mekanizmaları hala korunmaktadır. Özellikle, her seçim kutusu seçilen seçeneğin sunulan seçeneklerden biri olduğunu kontrol eder ve böylece bir saldırgan farklı bir değeri taklit edemez.
Çö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