Gölge DOM 101

Dominic Cooney
Dominic Cooney

Giriş

Web Bileşenleri, aşağıdakileri yapan son teknoloji ürünü standartlar grubudur:

  1. Widget oluşturmayı mümkün hale getirme
  2. ...güvenilir şekilde yeniden kullanılabiliyor
  3. ...ve bileşenin sonraki sürümü dahili uygulama ayrıntılarını değiştirirse sayfaları bozmaz.

Bu, ne zaman HTML/JavaScript'i ve Web Bileşenlerini ne zaman 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 ve görsel öğelerdir. Bir widget geliştirirken HTML ve JavaScript becerilerinizi kullanmak mantıklıdır. Web Bileşenleri standartları, bunu yapmanıza yardımcı olmak için tasarlanmıştır.

Ancak, HTML ve JavaScript'ten oluşturulan widget'ların kullanımını zorlaştıran temel bir sorun vardır: Bir widget içindeki DOM ağacı, sayfanın geri kalanına dahil edilmez. Kapsülleme olmaması, belge stil sayfanızın yanlışlıkla widget'ın içindeki parçalara uygulanabileceği anlamına gelir; JavaScript'iniz yanlışlıkla widget'ın içindeki bölümleri değiştirebilir; kimlikleriniz widget içindeki kimliklerle çakışabilir ve bu böyle devam edebilir.

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şenleri'nin dört bölümü birlikte çalışacak şekilde tasarlanmıştır ancak Web Bileşenleri'nin hangi bölümlerinin kullanılacağını da belirleyebilirsiniz. Bu eğitim, Gölge DOM'un nasıl kullanılacağını gösterir.

Merhaba Gölge Dünya

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

Örneğin, şuna benzer 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ın görünümü

<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>

Bunun yanı sıra, sayfadaki JavaScript, düğmenin textContent değerini sorduğunda "こんちちちち memnuniyetinin, 影の世界!"!" ifadesinde değil, gölge kökünün altındaki DOM alt ağacı kapsüllendiği için "Merhaba dünya!" şeklinde karşımıza çıkar.

Sunudan İçeriği Ayırtma

Şimdi, içeriği sunudan ayırmak için Gölge DOM'yi kullanacağı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>

Burada işaretlemeyi görebilirsiniz. Bugün yazacaklarınız buydu. Gölge DOM kullanmaz:

<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ığından, ad etiketinin tüm yapısı dokümana gösterilir. Sayfadaki diğer öğeler stil verme veya komut dosyası düzenleme için yanlışlıkla aynı sınıf adlarını kullanırsa çok kötü bir zamanımız olur.

Kötü zaman yaşamaktan kaçınabiliriz.

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

Semantik olarak büyük olasılıkla yalnızca şuna önem veririz:

  • Bu bir ad etiketidir.
  • Adı "Bob".

Öncelikle, istediğimiz gerçek anlama daha yakın bir işaretleme yazıyoruz:

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

Daha sonra, sunum için kullanılan tüm stilleri ve div öğelerini bir <template> öğesine yerleştiriyoruz:

<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 oluşturulan tek şey "Bob" olur. Sunum DOM öğelerini bir <template> öğesi içinde taşıdığımız için oluşturulmaz ancak bu öğelere JavaScript'ten erişilebilir. Şimdi gölge kökünü doldurmak için bunu yapıyoruz:

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

Artık bir gölge kök oluşturduğumuza göre, ad etiketi tekrar oluşturulur. Ad etiketini sağ tıklayıp öğeyi inceleyerek güzel, anlamsal işaretleme olduğunu görürseniz:

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

Bu, Gölge DOM'yi kullanarak ad etiketinin sunu ayrıntılarını belgeden gizlediğimizi gösterir. Sunu ayrıntıları Gölge DOM'a kapsüllenir.

2. Adım: İçeriği Sunudan Ayırma

Ad etiketimiz artık sunu ayrıntılarını sayfadan gizliyor ancak aslında sunuyu içerikten ayırmaz, çünkü sayfada içerik ("Bob" adı) olsa da oluşturulan ad, gölge köküne kopyaladığımız addır. Ad etiketindeki adı değiştirmek istersek bunu iki yerde yapmamız gerekir ve bunlar senkronize olmayabilir.

HTML öğeleri biçimseldir. Örneğin, bir tablonun içine düğme yerleştirebilirsiniz. Burada kompozisyon gereklidir: Ad etiketi kırmızı arka plandan, "Merhaba!" metninden ve ad etiketindeki içerikten oluşmalıdır.

Bileşen yazarı olarak siz, <content> adlı yeni bir öğe kullanarak bestenin widget'ınızla nasıl çalışacağını tanımlarsınız. Bu, widget'ın sunumunda bir ekleme noktası oluşturur ve ekleme noktası, bu noktada sunmak üzere gölge ana makineden içeriği alır.

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

<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.

Adı artık tek bir yerde, yani belgede olduğundan dokümanın yapısı artık daha basit. Sayfanızın, kullanıcının adını güncellemesi gerekirse şunu yazmanız yeterlidir:

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

Hepsi bu kadar. Ad etiketinin içeriğini <content> ile yerine projelendirdiğimiz için ad etiketi oluşturma işlemi tarayıcı tarafından otomatik olarak güncellenir.

<div id="ex2b">

Artık içerik ile sunumu birbirinden ayırmış bulunuyoruz. İçerik belgede, sunu ise Gölge DOM'dedir. Bu dosyalar, bir öğe oluşturma zamanı geldiğinde tarayıcı tarafından otomatik olarak senkronize edilir.

3. Adım: Kâr

İçeriği ve sunuyu ayırarak, içeriğe müdahale eden kodu basitleştirebiliriz. Ad etiketi örneğinde, bu kodun birkaç yerine bir <div> içeren basit bir yapıyla çalışması yeterlidir.

Sunumu değiştirirsek kodda da değişiklik yapmamız gerekmez.

Örneğin, ad etiketimizi yerelleştirmek istediğinizi düşünelim. Bu hâlâ bir ad etiketi olduğu için dokümanın anlamsal içeriği değişmiyor.

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

Gölge kök kurulum kodu aynı kalır. Gölge köke yerleştirilen her şey 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, bileşenin basit ve tutarlı yapısına bağlı olabileceğinden bu, günümüzde web'deki duruma göre büyük bir iyileştirmedir. Ad güncelleme kodunuzun, oluşturma için kullanılan yapıyı bilmesine gerek yoktur. Neyin oluşturulduğunu düşünürsek, ad İngilizce olarak ikinci sırada ("Hi! Benim adım"), ancak bunların ilki Japoncadır ("と申ちすす" öncesi). Bu ayrım, görüntülenen adın güncellenmesi açısından anlamsızdır. Bu nedenle, ad güncelleme kodunun bu ayrıntıdan haberdar olması gerekmez.

Ekstra Kredi: Gelişmiş Projeksiyon

Yukarıdaki örnekte <content> öğesi, gölge ana makineden tüm içeriği alır. select özelliğini kullanarak bir içerik öğesinin neleri yansıttığını kontrol edebilirsiniz. Birden çok 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çeriği seçmek için CSS seçicileri kullanan bir gölge kökü bulunur:

<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. İbrahim'in e-posta adresi kaç kez ve hangi renklerde görünüyor?

Yanıt, İbrahim'in e-posta adresinin bir kez göründüğü ve sarı renkte olduğudur.

Bunun nedeni, Gölge DOM'a saldıran kişilerin bildiği gibi, ekranda gösterilenin 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 katılmasını sağlayan davetiyedir. Bu davetiyeler sırayla teslim edilir. Davetiyeyi kimin alacağı, davetiyenin kime gönderildiğine bağlıdır (yani select özelliği). İçerik, bir kere davet edildikten sonra daveti her zaman kabul eder (kim istemez ki!) ve yoldan çıkar. Bu adrese tekrar bir davetiye gönderilirse evde kimse yok ve evinize de gelmiyor.

Yukarıdaki örnekte <div class="email"> hem div hem de .email seçici ile eşleşir ancak div seçiciye sahip içerik öğesi dokümanda daha önce geldiğinden <div class="email"> sarı tarafa gider ve hiç kimse mavi tarafa gelemez. (Amalar şirketi sevse de asla bilemezsiniz. gerçi, mavi renk bu neden olabilir.)

Bir öğe hiçbir tarafa davet edilmezse hiç oluşturulmaz. İlk örnekte "Merhaba dünya" metni için olan budur. Bu, tamamen farklı bir oluşturma işlemi gerçekleştirmek istediğinizde yararlı olur: Anlamsal modeli dokümana yazın. Bu model, sayfadaki komut dosyaları tarafından erişilebilir ancak bunu oluşturma amacıyla gizler ve JavaScript kullanarak Gölge DOM'da gerçekten farklı bir oluşturma modeline bağlayın.

Örneğin, HTML'nin güzel bir tarih seçicisi vardır. <input type="date"> yazarsanız şık bir pop-up takvim görüntülenir. Peki kullanıcının tatlı ada tatili için (örneğin, Red Vines'dan yapılmış hamaklarla) bir tarih aralığı seçmesine izin vermek isterseniz ne olur? Dokümanınızı ş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ı vb. vurgulayan şık bir takvim oluşturmak için tablo kullanan Gölge 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 ise bu giriş öğelerinden alınan değerler gönderilir.

Oluşturulmayacaksa neden dokümana etiket ekledim? Bunun nedeni, bir kullanıcı formu Gölge DOM'u desteklemeyen bir tarayıcıyla görüntülerse formun yine de kullanılabilir olması ancak o kadar iyi olmamasıdır. Kullanıcı şuna benzer bir sayfa 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çersiniz

Gölge DOM'un temel özellikleri bu şekildedir. Gölge DOM 101'den başarıyla geçersiniz. Gölge DOM ile daha fazlasını yapabilirsiniz. Örneğin, bir gölge ana makinesinde birden fazla gölge veya kapsülleme için iç içe yerleştirilmiş gölgeler kullanabilir ya da Modele Dayalı Görünümler (MDV) ve Gölge DOM kullanarak sayfanızı tasarlayabilirsiniz. Web Bileşenleri, Gölge DOM'den daha fazlasıdır.

Bunları sonraki gönderilerde açıklayacağız.