Bir görünümün DOM içeriğini değiştirdikten sonra tıklama işleyicilerinin çalışmaya devam etmesi için, güncelleme boyunca varlığını koruyan bir üst öğeye tek bir dinleyici ekleyin. Bu kılavuz, Mac’te Safari kullanarak küçük ve sentetik bir karo seçiciyle bu düzenin nasıl uygulanacağını ve denetleneceğini gösterir.
Görev belirli: JavaScript görünümünün içeriğini yeniden oluştururken düğmelerini duyarlı, seçimini ise korunaklı tutmak. Temel JavaScript bilgisine ve güncel bir Safari tarayıcısına ihtiyacınız var. Örnek, standart tarayıcı API’lerini kullanır ve herhangi bir çerçeve ya da paket kurulumu gerektirmez.
DOM değiştirildikten sonra tıklamalar neden çalışmaz?
addEventListener() ile kaydedilen bir dinleyici, kaydedildiği olay hedefine bağlıdır. Bir kapsayıcının innerHTML özelliğine değer atamak, sağlanan işaretlemeyi ayrıştırır ve alt öğelerini değiştirir. Bu nedenle, kimliği ve etiketi aynı olsa bile yeni oluşturulan bir düğme, önceki düğmeye eklenmiş bir dinleyiciyi devralmaz. MDN’nin innerHTML başvurusuna ve DOM Standardı’nın olay dinleyicisi modeline bakın.
Kapsayıcının içeriğini değiştirmekle kapsayıcının kendisini değiştirmek arasında önemli bir fark vardır. Kodunuz kapsayıcıyı koruyup yalnızca alt öğelerini yeniden oluşturuyorsa, kapsayıcıdaki dinleyici yerinde kalabilir. Dışarıdaki bir işleme kapsayıcıyı da değiştiriyorsa, varlığını koruyan bir üst öğe seçmeniz veya yeni kapsayıcı başlatılırken dinleyici eklemeniz gerekir.
Normal kabarcıklanan tıklama olaylarında olay yetkilendirme, bir üst öğenin alt öğelerinden kaynaklanan etkileşimleri işlemesini sağlar. Değiştirilen her düğmeye bağlama yapmak yerine, kararlı üst öğeye bağlanın ve olay geldiğinde ilgili düğmeyi belirleyin. MDN’nin olay kabarcıklanması kılavuzu bu düzeni açıklar.
Dinleyici nerede bulunmalı?
İşleme işleminizin değiştirdiği tam DOM sınırını belirleyerek başlayın. Ardından bu sınırın dışında kalan en dar ve uygun üst öğeyi seçin. Aşağıdaki karar yardımcısı, yaygın güncelleme düzenlerinde belgelenmiş değiştirme ve dinleyici davranışlarını uygular. MDN: replaceChildren(), DOM Standardı: EventTarget.
| Güncellemeniz | Dinleyicinin yeri | Yaşam döngüsü gereksinimi |
|---|---|---|
| Mevcut bir görünüm kökünün alt öğelerini yeniden oluşturma | Tıklamaları bu kökten devralma yoluyla işleme | Kök oluşturulduğunda bir kez kaydetme |
| Görünüm kökünü de değiştirme | Varlığını koruyan bir dış kapsayıcıdan devralma yoluyla işleme veya yeni köke bağlama | Önceki görünümün kaydını temizleme |
| Tek tek işleyici gerektiren düğmeler oluşturma | Oluşturma sırasında her yeni düğmeye bağlama | Yeni denetimleri kullanılabilir kılmadan önce bağlamayı tamamlama |
| Tüm belgeyi yeniden yükleme | Dinleyicileri yeni belgede başlatma | Sayfanın normal başlatma işlemini yeniden çalıştırma |
Aşağıdaki örnekte yapı; başlık, durum metni, sıralı karo seçenekleri kümesi ve yenileme denetimi içeren bir görünüm kökü olarak kalır. Her işleme bu alt öğeleri aynı sırayla yeniden oluşturur. Yalnızca görünüm kökü, dinleyici ve JavaScript durumu kalıcıdır.
Safari’de değiştirmeye dayanıklı eksiksiz bir görünüm oluşturma
Önce geçici kullanacağınız bir tarayıcı sekmesi hazırlayın. Mac’inizde Safari’de Settings… bölümünü açın, Advanced seçeneğini belirleyin ve Show features for web developers seçeneğini etkinleştirin. Develop → Show Web Inspector yoluyla Web Inspector’ı açın veya Option-Command-I tuşlarına basın. Bunlar, Web Inspector’ı etkinleştirme hakkındaki WebKit kılavuzunda belgelenen adımlardır.
about:blank adresine gidin, Web Inspector’ı açın ve Console sekmesini seçin. Aşağıdaki programın tamamını istemine yapıştırıp çalıştırın. WebKit, Console isteminin incelenen sayfanın seçili yürütme bağlamında JavaScript çalıştırdığını belgeler. Gösterimin uygulamanızdan ayrı kalması için boş sekmeyi kullanın.
Bu, eksiksiz bir tarayıcı programıdır: kendi DOM’unu oluşturur, örnek verilerini tanımlar, görünümü sayfaya ekler ve bir temizleme yöntemi sunar. Görünümün alt öğelerini replaceChildren() kullanarak değiştirir; bu yöntem düğüm nesnelerini kabul eder ve mevcut alt öğe kümesini değiştirir. MDN’nin yöntem başvurusu bu işlemi açıklar.
(() => {
window.domClickDemo?.destroy();
const root = document.createElement("section");
root.id = "dom-click-demo";
const controller = new AbortController();
const items = [
{ id: "a", label: "Tile A" },
{ id: "b", label: "Tile B" },
{ id: "c", label: "Tile C" }
];
const state = { selected: "None", refreshes: 0 };
function makeButton(action, label, id) {
const button = document.createElement("button");
button.type = "button";
button.dataset.action = action;
if (id !== undefined) button.dataset.id = id;
const caption = document.createElement("span");
caption.textContent = label;
button.append(caption);
return button;
}
function render() {
const heading = document.createElement("h2");
heading.textContent = "Tile picker";
const status = document.createElement("p");
status.textContent =
`Selected: ${state.selected}; refreshes: ${state.refreshes}`;
const list = document.createElement("ul");
for (const item of items) {
const row = document.createElement("li");
row.append(makeButton("select", item.label, item.id));
list.append(row);
}
const refresh = makeButton("refresh", "Replace view content");
root.replaceChildren(heading, status, list, refresh);
}
function handleClick(event) {
if (!(event.target instanceof Element)) return;
const button = event.target.closest("button[data-action]");
if (!button || !root.contains(button) || button.disabled) return;
if (button.dataset.action === "select") {
const item = items.find(item => item.id === button.dataset.id);
if (!item) return;
state.selected = item.label;
} else if (button.dataset.action === "refresh") {
state.refreshes += 1;
} else {
return;
}
render();
}
root.addEventListener("click", handleClick, {
signal: controller.signal
});
render();
document.body.append(root);
window.domClickDemo = {
root,
destroy() {
controller.abort();
root.remove();
}
};
})();
Dinleyici render() dışında kaydedilir. Her seçim ve yenileme yeni düğmeler oluşturur, ancak bu düğmelerin tümü ilk kökün altında kalır. Durum nesnesi de oluşturulan alt ağaç dışında bulunur; bu nedenle durum paragrafını değiştirmek, seçili karoyu veya yenileme sayısını sıfırlamaz.
İç içe başlık metni bilerek kullanılmıştır. Bir düğmenin metnine tıklamak, hedef olarak alt öğesini belirleyebilir. closest("button[data-action]"), eşleşen denetimi bulmak için hedefi ve üst öğelerini arar; böylece yalnızca düğmenin kendisi olay hedefi olduğunda çalışan bir işleyiciyi önler. MDN’nin closest() başvurusu, aramayı ve eşleşme olmadığında dönen null değerini açıklar.
Kapsayıcılık denetimi, eşleşen denetimi bu görünümün sınırları içinde tutar. DOM Standardı, contains() öğesini, verilen düğümün düğümün kendisi veya alt öğelerinden biri olup olmadığına göre tanımlar. DOM Standardı: Node.contains().
Başlatma, oluşturma ve kaldırma işlemlerini ayrı tutun
Bu örneğin üç açık sorumluluğu vardır. Başlatma kökü oluşturur ve dinleyicisini kaydeder. Oluşturma, kökün alt öğelerini duruma göre günceller. Kaldırma, dinleyiciyi ve kökü siler. Bu ayrım, uygulamanın yaşam döngüsü sözleşmesidir; uygulamanıza uyarlarken eşdeğer sorumlulukları koruyun.
Dinleyici bir AbortSignal alır ve destroy() denetleyicisini iptal eder. İptal, ilişkili kaydı kaldırır; bu durum MDN’nin addEventListener() başvurusunda belgelenmiştir. Programın tamamını yeniden çalıştırmak önceki gösterimi kaldırır ve birden fazla demo görünümünün birikmesini önler.
Her oluşturma işleminin içine yeni bir anonim dinleyici koymayın. Tekrarlanan anonim işlev ifadeleri farklı işlev nesneleri oluşturur; bu nedenle kayıtları varlığını koruyan bir kökte birikebilir. Aynı geri çağırımı, olay türünü ve yakalama ayarını yeniden kullanmak yinelenen bir kaydı önler; ancak bağlamayı başlatma aşamasında tutmak sahipliği daha açık kılar. MDN: addEventListener(), DOM Standardı: dinleyici ekleme.
Tekrarlanan değiştirme, seçim ve temizlemeyi denetleyin
Örneği sınamak için şu adımları izleyin. Beklenen sonuçlar, tamamlanmış bir Safari testinin iddiası değil, programdaki açık durum güncellemelerinin sonucudur.
Tile Böğesine tıklayın. DurumSelected: Tile B; refreshes: 0olmalıdır.Replace view contentöğesine üç kez tıklayın. DurumSelected: Tile B; refreshes: 3olmalıdır.Tile Cmetnine tıklayın. DurumSelected: Tile C; refreshes: 3olarak değişmelidir.- Başlığa veya durum paragrafına tıklayın. İki değer de değişmemelidir.
- Console’da
window.domClickDemo.destroy()ifadesini çalıştırın. Gösterim kaybolmalıdır. - Programın tamamını yeniden çalıştırın.
Selected: None; refreshes: 0durumunda tek bir yeni görünüm belirmelidir.
İlk üç denetim değiştirme, durumun korunması ve iç içe tıklama hedeflerini kapsar. Dördüncüsü eylem filtrelemesini denetler. Son iki adım, kaldırma ve başlatma yolunu denetler. Birlikte, yalnızca başlangıç düğmelerinin çalıştığını doğrulamak yerine gerçek yaşam döngüsünü sınarlar.
Programın JavaScript sözdizimi Node ile denetlendi; ayrı bir deterministik EventTarget denetimi de geri çağırımın yinelenen kaydını ve iptal sinyaliyle kaldırılmasını doğruladı. Bu denetimde tarayıcı DOM oluşturma ve Safari etkileşimi çalıştırılmadı; bunları Mac’inizde doğrulamak için yukarıdaki adımları izleyin.
Uygulamanızda işleyici hâlâ çalışmazsa ne yapmalı?
Davranışın farklılaşmaya başladığı ilk noktayı araştırın. Uygulamanızın değiştirme sınırını, olay yolunu ve durum güncellemelerini denetlerken örneğin kapsamını küçük tutun.
| Belirti | Önce neyi denetlemeli | Uygun düzeltme |
|---|---|---|
| Tıklamalar dış görünüm yenilenene kadar çalışıyor | Dinleyicinin kökü de değiştirildi mi? | Yeni kökü başlatma sırasında bağlayın veya varlığını koruyan bir üst öğe seçin |
| Tıklama geliyor ama eylem çalışmıyor | Yeni denetim seçici ve eylem değeriyle hâlâ eşleşiyor mu? | Denetimin seçici ve eylem sözleşmesini koruyun |
| Düğme metnine tıklama farklı davranıyor | İşleyici yalnızca tam hedefi mi denetliyor? | Denetimi closest() ile belirleyin |
| Tek tıklama durumu birkaç kez güncelliyor | Birden fazla kayıt veya hem doğrudan hem devralmalı işleyiciler etkin mi? | Eylemin sorumluluğunu tek bir başlatma yoluna verin |
| İşleyici çalışıyor ama görüntü sıfırlanıyor | Durum oluşturma sırasında mı yeniden oluşturuluyor? | Gerekli durumu değiştirilen alt ağacın dışında tutun |
Yayılımı da inceleyin. stopPropagation() çağıran bir alt öğe işleyicisi, kabarcıklanan olayın devralmalı dinleyicinize ulaşmasını engelleyebilir. MDN hem yayılımı durdurmayı hem de olay yetkilendirmeyi açıklar. Dinleyici evrelerini değiştirmeden önce yayılımın neden durdurulduğunu gözden geçirin; çünkü evreleri değiştirmek yürütme sırasını da değiştirir.
Doğrudan düğme dinleyicileri kullanmanız gerekiyorsa, değiştirme düğmelerini oluşturduğunuz her seferde bunları ekleyin. Tek seferlik bir sorgu ve bağlama geçişi, sonraki oluşturmaların yarattığı düğümleri başlatamaz: kayıtlar olay hedeflerine bağlıdır. Aynı şekilde, yetkilendirme de işleyicinin oluşturucunuzun attığı bir kökte kalmasını sağlayamaz. Bu sınırlar, DOM Standardı’nın dinleyici modelinden kaynaklanır.
Bu görev için kalıcı düzen; kararlı bir görünüm kökü, tek bir yetkilendirilmiş dinleyici, oluşturulan alt öğelerin dışında tutulan durum ve açık bir temizleme yoludur. Her güncellemede hangi düğümlerin kaldığını belirleyin, dinleyiciyi buna göre yerleştirin ve değişikliği daha büyük görünüme entegre etmeden önce art arda birkaç değiştirme işlemini doğrulayın.
Kaynaklar
- Web Inspector’ı etkinleştirme — WebKit.
- Web Inspector arayüzü — WebKit.
- Element: innerHTML özelliği — MDN Web Docs.
- Olay kabarcıklanması — MDN Web Docs.
- Element: replaceChildren() yöntemi — MDN Web Docs.
- Element: closest() yöntemi — MDN Web Docs.
- EventTarget: addEventListener() yöntemi — MDN Web Docs.
- DOM Standardı — WHATWG.