Gölge DOM 101

Dominic Cooney
Dominic Cooney

Giriş

Web Bileşenleri, aşağıdakileri sağlayan son teknoloji standartlar grubudur:

  1. Widget oluşturmayı mümkün kılmak
  2. …güvenilir bir şekilde yeniden kullanılabilir.
  3. …ve bileşenin sonraki sürümünde dahili uygulama ayrıntıları değişirse sayfaları bozmaz.

Bu, ne zaman HTML/JavaScript ne zaman da Web Bileşenleri kullanacağınıza karar vermeniz gerektiği anlamına mı geliyor? Hayır HTML ve JavaScript, etkileşimli görsel öğeler oluşturabilir. Widget'lar etkileşimli görsel öğelerdir. Widget geliştirirken HTML ve JavaScript becerilerinizden yararlanmanız faydalı olacaktır. Web Bileşenleri standartları, bunu yapmanıza yardımcı olacak şekilde tasarlanmıştır.

Ancak HTML ve JavaScript'den oluşturulan widget'ların kullanımını zorlaştıran temel bir sorun vardır: Bir widget'ın içindeki DOM ağacı, sayfanın geri kalanından kapsüllenmez. Bu kapsülleme eksikliği, doküman stil sayfanızın widget'ın içindeki bölümlere yanlışlıkla uygulanabileceği, JavaScript'inizin widget'ın içindeki bölümleri yanlışlıkla değiştirebileceği, kimliklerinizin widget'ın içindeki kimliklerle çakışma ihtimalinin olduğu vb. anlamına gelir.

Web bileşenleri üç bölümden oluşur:

  1. Şablonlar
  2. Gölge DOM
  3. Özel Öğeler

Gölge DOM, DOM ağacı kapsülleme sorununu ele alır. Web bileşenlerinin dört bölümü birlikte çalışacak şekilde tasarlanmıştır ancak Web bileşenlerinin hangi bölümlerinin kullanılacağını seçebilirsiniz. Bu eğiticide, Gölge DOM'un nasıl kullanılacağı gösterilmektedir.

Merhaba, Shadow World

Gölge DOM ile öğeler, kendileriyle ilişkilendirilmiş yeni bir tür düğüm alabilir. Bu yeni düğüm türüne gölge kök denir. Kendisiyle ilişkili bir gölge kökü olan öğelere gölge ana makine denir. Gölge ana makinenin içeriği oluşturulmaz, bunun yerine gölge kökün içeriği oluşturulur.

Örneğin, aşağıdaki gibi bir işaretlemeniz varsa:

<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
</script>

yerine

<button id="ex1a">Hello, world!</button>
<script>
function remove(selector) {
  Array.prototype.forEach.call(
      document.querySelectorAll(selector),
      function (node) { node.parentNode.removeChild(node); });
}

if (!HTMLElement.prototype.createShadowRoot) {
  remove('#ex1a');
  document.write('<img src="SS1.png" alt="Screenshot of a button with \'Hello, world!\' on it.">');
}
</script>

Sayfanız aşağıdaki gibi görünüyor

<button id="ex1b">Hello, world!</button>
<script>
(function () {
  if (!HTMLElement.prototype.createShadowRoot) {
    remove('#ex1b');
    document.write('<img src="SS2.png" alt="Screenshot of a button with \'Hello, shadow world!\' in Japanese on it.">');
    return;
  }
  var host = document.querySelector('#ex1b');
  var root = host.createShadowRoot();
  root.textContent = 'こんにちは、影の世界!';
})();
</script>

Bununla da kalmaz, sayfadaki JavaScript düğmenin textContent değerini sorarsa "こんにちは、影の世界!" yerine "Hello, world!" değerini alır. Bunun nedeni, gölge kökün altındaki DOM alt ağacının kapsüllenmiş olmasıdır.

İçeriği sunudan ayırma

Şimdi içeriği sunumdan ayırmak için Gölge DOM'u kullanmaya bakacağız. Şu ad etiketine sahip olduğumuzu varsayalım:

<style>
.ex2a.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.ex2a .boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.ex2a .name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="ex2a outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

İşaretleme aşağıda verilmiştir. Bugün şunu yazacaksınız. Gölge DOM kullanılmaz:

<style>
.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

DOM ağacında kapsülleme olmadığı için ad etiketinin tüm yapısı belgeye açıktır. Sayfadaki diğer öğeler stil veya komut dosyası için yanlışlıkla aynı sınıf adlarını kullanıyorsa başımız ağrıyacaktır.

Bu durumdan kaçınabiliriz.

1. Adım: Sunu Ayrıntılarını Gizleme

Anlamsal olarak muhtemelen yalnızca şunu önemsiyoruz:

  • Bu bir isim etiketi.
  • Adı "Bob".

Öncelikle, istediğimiz gerçek semantiklere daha yakın bir işaretleme yazarız:

<div id="nameTag">Bob</div>

Ardından, sunu için kullanılan tüm stilleri ve div'leri bir <template> öğesine yerleştiririz:

<div id="nameTag">Bob</div>
<template id="nameTagTemplate">
<span class="unchanged"><style>
.outer {
  border: 2px solid brown;

  … same as above …

</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div></span>
</template>

Bu noktada yalnızca "Bob" oluşturulur. Sunu amaçlı DOM öğelerini bir <template> öğesinin içine taşıdığımız için bu öğeler oluşturulmaz ancak JavaScript'den erişilebilir. Gölge kökü doldurmak için bunu şu anda yapıyoruz:

<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);

Gölge kök oluşturduğumuza göre ad etiketi tekrar oluşturulur. Ad etiketini sağ tıklayıp öğeyi incelerseniz bunun semantik işaretleme olduğunu görürsünüz:

<div id="nameTag">Bob</div>

Bu, Gölge DOM'u kullanarak ad etiketinin sunum ayrıntılarını dokümandan gizlediğimizi gösterir. Gösterim ayrıntıları, Gölge DOM'da kapsüllenir.

2. Adım: İçeriği sunudan ayırın

Ad etiketimiz artık sunum ayrıntılarını sayfadan gizliyor ancak aslında sunumu içerikten ayırmıyor. Bunun nedeni, içerik ("Barış" adı) sayfada olsa da oluşturulan adın gölge köke kopyaladığımız ad olmasıdır. Ad etiketindeki adı değiştirmek istersek bunu iki yerde yapmamız gerekir ve bu iki yer senkronize olmayabilir.

HTML öğeleri, birleşiktir. Örneğin, bir tablonun içine bir düğme yerleştirebilirsiniz. Burada kompozisyona ihtiyacımız var: Ad etiketi, kırmızı arka plan, "Merhaba" metni ve ad etiketindeki içeriğin bir kompozisyonu olmalıdır.

Bileşen yazarı olarak, <content> adlı yeni bir öğe kullanarak bilegenizle birlikte kompozisyonun nasıl çalıştığını siz tanımlarsınız. Bu işlem, widget'ın sunumunda bir ekleme noktası oluşturur ve ekleme noktası, bu noktada gösterilecek içeriği gölge ana makineden seçer.

Gölge DOM'daki işaretlemeyi şu şekilde değiştirirseniz:

<span class="unchanged"><template id="nameTagTemplate">
<style>
  …
</style></span>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    <content></content>
  </div>
</div>
<span class="unchanged"></template></span>

Ad etiketi oluşturulduğunda, gölge ana makinenin içeriği <content> öğesinin göründüğü noktaya yansıtılır.

Artık ad yalnızca dokümanda bulunduğundan dokümanın yapısı daha basittir. Sayfanızda kullanıcının adını güncellemeniz gerekirse şunları yazmanız yeterlidir:

document.querySelector('#nameTag').textContent = 'Shellie';

Hepsi bu kadar. Ad etiketinin içeriğini <content> ile yansıttığımız için ad etiketinin oluşturulması tarayıcı tarafından otomatik olarak güncellenir.

<div id="ex2b">

Artık içerik ve sunumu ayırdık. İçerik dokümanda, sunu ise Gölge DOM'dadır. Bir öğe oluşturulacağı zaman tarayıcılar tarafından otomatik olarak senkronize edilirler.

3. adım: Kâr elde edin

İçeriği ve sunumu ayırarak içeriği işleyen kodu basitleştirebiliriz. Ad etiketi örneğinde, bu kodun birkaç yerine yalnızca bir <div> içeren basit bir yapıyla ilgilenmesi gerekir.

Artık sunumuzu değiştirdiğimizde kodda herhangi bir değişiklik yapmamız gerekmiyor.

Örneğin, ad etiketimizi yerelleştirmek istediğimizi varsayalım. Ad etiketi hâlâ bir ad etiketi olduğundan dokümandaki anlamsal içerik değişmez:

<div id="nameTag">Bob</div>

Gölge kök kurulum kodu aynı kalır. Gölge köke nelerin yerleştirildiği değişir:

<template id="nameTagTemplate">
<style>
.outer {
  border: 2px solid pink;
  border-radius: 1em;
  background: url(sakura.jpg);
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
  font-family: sans-serif;
  font-weight: bold;
}
.name {
  font-size: 45pt;
  font-weight: normal;
  margin-top: 0.8em;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="name">
    <content></content>
  </div>
  と申します。
</div>
</template>

Ad güncelleme kodunuz, basit ve tutarlı olan bileşenin yapısına bağlı olabileceğinden bu, günümüzde web'de mevcut olan duruma kıyasla büyük bir gelişmedir. Ad güncelleme kodunuzun oluşturma için kullanılan yapıyı bilmesi gerekmez. Oluşturulan içeriği göz önünde bulundurursak ad, İngilizce olarak ikinci sırada görünür ("Merhaba! Adım ") ancak önce Japonca olarak ("と申します") gösterilir. Gösterilen adı güncelleme açısından bu ayrım anlam açısından anlamsızdır. Bu nedenle, ad güncelleme kodunun bu ayrıntıyı bilmesi gerekmez.

Ek Kredi: Gelişmiş Projeksiyon

Yukarıdaki örnekte, <content> öğesi gölge ana makinedeki tüm içerikleri seçmektedir. select özelliğini kullanarak bir içerik öğesinin neyi yansıtacağını kontrol edebilirsiniz. Birden fazla içerik öğesi de kullanabilirsiniz.

Örneğin, şunu içeren bir dokümanınız varsa:

<div id="nameTag">
  <div class="first">Bob</div>
  <div>B. Love</div>
  <div class="email">bob@</div>
</div>

ve belirli içerikleri seçmek için CSS seçicileri kullanan bir gölge kökü:

<div style="background: purple; padding: 1em;">
  <div style="color: red;">
    <content **select=".first"**></content>
  </div>
  <div style="color: yellow;">
    <content **select="div"**></content>
  </div>
  <div style="color: blue;">
    <content **select=".email">**</content>
  </div>
</div>

<div class="email"> öğesi hem <content select="div"> hem de <content select=".email"> öğeleriyle eşleşir. Bora'nın e-posta adresi kaç kez ve hangi renklerde gösteriliyor?

Cevap, Bora'nın e-posta adresinin bir kez ve sarı renkte görünmesidir.

Bunun nedeni, Gölge DOM'da bilgisayar korsanlığı yapanların bildiği gibi, ekranda gerçekten oluşturulan öğelerin ağacını oluşturmanın büyük bir parti gibi olmasıdır. İçerik öğesi, dokümandaki içeriğin sahne arkası Gölge DOM oluşturma partisine girmesine olanak tanıyan davettir. Bu davetiyeler sırayla gönderilir. Kimin davetiye alacağı, davetiyenin kime gönderildiğine (yani select özelliğine) bağlıdır. Davet edilen içerikler her zaman daveti kabul eder (kim kabul etmez ki?) ve çalışmaya başlar. Bu adrese tekrar davetiye gönderilirse evde kimse olmadığı için davetiye partinize gelmez.

Yukarıdaki örnekte <div class="email"> hem div seçiciyi hem de .email seçiciyi eşleştiriyor ancak div seçiciyi içeren içerik öğesi belgede daha önce geldiği için <div class="email"> sarı partiye gider ve mavi partiye kimse katılamaz. (Bu neden olabilir. Ancak dert paylaştıkça azalır.)

Bir öğe hiçbir partiye davet edilmezse hiç oluşturulmaz. İlk örnekteki "Merhaba dünya" metninde de bu işlem uygulanmıştır. Bu, tamamen farklı bir oluşturma işlemi gerçekleştirmek istediğinizde yararlıdır: Anlamsal modeli belgeye yazın. Bu model, sayfadaki komut dosyalarının erişebileceği bir modeldir ancak oluşturma amacıyla gizlenir ve JavaScript kullanılarak Shadow DOM'da gerçekten farklı bir oluşturma modeline bağlanır.

Örneğin, HTML'de kullanışlı bir tarih seçici vardır. <input type="date"> yazarsanız şık bir pop-up takvim görürsünüz. Ancak kullanıcının tatlı ada tatili (Red Vines'den yapılmış hamaklarla) için bir tarih aralığı seçmesine izin vermek istiyorsanız ne olur? Belgenizi şu şekilde oluşturursunuz:

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

Ancak, tarih aralığını vurgulayan şık bir takvim oluşturmak için tablo kullanan Shadow DOM oluşturun. Kullanıcı takvimdeki günleri tıkladığında bileşen, startDate ve endDate girişlerindeki durumu günceller. Kullanıcı formu gönderdiğinde bu giriş öğelerindeki değerler gönderilir.

Oluşturma işlemi uygulanmayacaksa etiketleri neden dokümana ekledim? Bunun nedeni, kullanıcı formu Shadow DOM'u desteklemeyen bir tarayıcıda görüntülese bile formun kullanılabilir durumda kalması ancak görünümünün daha kötü olmasıdır. Kullanıcı şuna benzer bir şey görür:

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

Gölge DOM 101'i Geçtiniz

Bunlar Gölge DOM'un temel bilgileridir. Gölge DOM 101 dersini başarıyla tamamladınız. Gölge DOM ile daha fazlasını yapabilirsiniz. Örneğin, bir gölge ana makinesinde birden fazla gölge kullanabilir, kapsayıcı için iç içe gölgeler kullanabilir veya Model Odaklı Görünümler (MDV) ve Gölge DOM'u kullanarak sayfanızı tasarlayabilirsiniz. Web bileşenleri, gölge DOM'dan daha fazlasıdır.

Bu konuları daha sonraki yayınlarda açıklayacağız.