JavaScript İle Temel DOM İşlemleri

DOM (Belge Nesne Modeli) ile ilgili detaylar değindiğim yazının ardından, konuyu JavaScript ile DOM işlemleri ile sürdürmek istiyorum. Benzer bir konuya jQuery bağlamında jQuery Traversing / DOM Insertion, Around başlığı altında değinmiştim. Ancak, örnekler *wrap metodları ile sınırlı kalmıştı. Bu yazıdaki örnekleri de temel teşkil etmesi açısından mümkün olduğu kadar farklılaştırmaya çalışacağım.

DOM (Belge Nesne Modeli)

DOM (Belge Nesne Modeli) Nedir? yazısında da bahsi geçtiği üzere; DOM, HTML, XHTML, XML gibi belgelerin script dilleriyle iletişim kurabilmesini sağlamak için geliştirilmiş bir arabirimdir ve bir W3C standardıdır. W3C tarafından; DOM, programların ve komut dosyalarının bir belgenin içeriğine, yapısına ve stiline dinamik olarak erişmesini ve güncellemesini sağlayan bir platform ve dilden bağımsız bir arayüz şeklinde ifade edilmektedir1 2.

Bir HTML dosyasında yer alan bütün elemanlar DOM nesnesi (object) olarak nitelendirilir. JavaScript temelinde bir global nesneden (window) bahsetmiştim. Özetlemek gerekirse, window tarayıcı penceresini ifade eden bir global nesnedir ve tarayıcı penceresindeki tüm nesneleri kapsar. document nesnesi ise DOM yapısını içerir3 4 5. window.document ile document aynı tanımlardır6. document içerisinde yer alan bir iframe eleman olarak tanımlanabilir, ancak içeriği ayrı bir window nesnesi, iframe içerisinde render edilen HTML elemanları ise bu iframe'e ait document (window.document) ile ilişkilenir. Bu konu ile ilgili olarak Web API window.postMessage İle Pencereler Arası İletişim başlıklı yazıma göz atabilirsiniz.

Document Nesnesi

Doküman İçeriği

Tekrar DOM ve dolayısıyla document nesnesine dönelim. Aşağıdaki yapı bize temel bir HTML dokümanını verecektir. Eklenecek diğer elemanlar (link, div, vb.) DOM ağacını daha da detaylandırabiliriz. Bu DOM yapısı içerisindeki her elemana konumu, sırası, tanımı (tag), class ve id gibi seçiciler sayesinde erişebilir ve JavaScript kullanarak manipüle edebiliriz.

<html>
  <head>
  ...
  </head>
  <body>
  ...
  </body>
</html>

Modern bir internet tarayıcısının console alanına document yazdığınızda DOM ile ilişkili tüm detaylara ulaşabilirsiniz.

Document Nesnesi

Listelenen bu bilgiler arasında, ilk node (düğüm) firstChild ile erişilebilir7 8. firstElementChild ve documentElement html etiketine ulaşabilmemizi sağlar. Elbette DOM yapsına göre içerikler farklılık arz edebilir. Aynı şekilde, son node da erişilebilir durumdadır ve lastChild, lastElementChild ile bu işlem gerçekleştirilebilir. İlgili node'ların her birinin html etiketiyle ilişkili olduğunu görebilirsiniz. Bunun nedeni html elemanın bir DOM içerisinde kullanılan en üst kapsayıcı node olmasıdır. Bu nedenle root element olarak nitelendirilmektedir.

DOM Tree

html sonrasında, bir alt seviyede head, body elemanları yer alır. Bu nedenle, body, head, documentElement, title tek bir nesneyi döndürürken, diğer elemanlar dizi (array) döndürebilir. Bu duruma örnek olarak aşağıdaki yapılar incelenebilir.

Az önce de bahsi geçtiği üzere; document.head, head etiketini, document.body ise body etiketini getirir. document.title ile sayfa başlığına ulaşabiliriz. document.documentElement bize root element olan HTML ile birlikte tüm dokümana ulaşma imkanı verir.

Doküman içerisinde bulunan tüm bağlandılara document.links (<a href="...">...</a>) ile ulaşabiliriz. Eğer gömülü elemanlar (<object>) var ise document.embeds, formlar document.forms, görseller document.images ve script dosyaları document.scripts ile listelenebilir. Bunların yanı sıra fontlar, eklentiler, stil dosyaları ve çerezler gibi daha pek çok bilgiye ulaşmak mümkün4.

// <head></head>
document.head
// <body></body>
document.body

Bir örnek bir HTML dosyası oluşturalım. Bu HTML dosyasını yazının kalan bölümünde de kullanacağım. Tarayıcının console alanına document yazdığınızda bu HTML dosyası içeriğine dair bilgiler listelenecektir.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Hello World!</title>
  </head>
  <body id="page" class="single-page">
    <div id="main" class="container">
      <div class="row">
        <div class="col">
          <h1>Lorem Ipsum</h1>
        </div>
        <div class="col">
          <p><strong id="status">Dolor Sit Amet</strong></p>
        </div>
        <div class="col">
          <ul class="list">
            <li class="even">List item <span>(1)</span></li>
            <li class="odd">List item <span>(2)</span></li>
            <li class="even">List item <span>(3)</span></li>
            <li class="odd">List item <span>(4)</span></li>
          </ul>
        </div>
        <div class="col">
          <div id="even">
            <strong>Even</strong>
            <ul>
              <li>List item 5</li>
            </ul>
          </div>
          <div id="odd">
            <strong>Odd</strong>
            <ul>
              <li>List item 6</li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

JavaScript ile ilgili elementlere getElementsByClassName(), getElementsByTagName(), getElementById() başta olmak üzere pek çok metod vasıtasıyla erişebilmekteyiz. Dikkat çekmek istediği bir konu ilgili metod tanımındaki getElements* ve getElement* ayrımı. Çoğul kullanımda bir dizi dönüş yapacaktır. ID ise class ve tag'lerden farklı olarak benzersiz bir tanımlayıcıdır9.

const el = document.getElementById('status');
console.log(el.innerHTML); // Dolor Sit Amet

const el = document.getElementsByTagName('strong');
[...el].forEach(e => console.log(e.innerHTML));  // el.status.innerHTML

Element ilişkili metodların yanı sıra, DOM içerisinde sorgu (query) temelli seçimler de yapabilmekteyiz. Sorgu bir tag olabileceği gibi, class, id veya bir öznitelik (attribute) değer(ler)i ile ilgili de gerçekleştirilebilir.

document
  .querySelector('p');
document
  .body
  .querySelector("style[type='text/css'], style:not([type])");

document
  .querySelectorAll('[data-name*="funnel-chart-percent"]');
document
  .querySelectorAll('iframe[data-src]');

querySelector() belirtilen seçici grubuyla eşleşen ilk elemanı döndürürken10, querySelectorAll() belirtilen seçici grubuyla eşleşen öğelerin bir statik listesini (NodeList) döndürür11.

Tekrar örneğimize dönelim ve col class'ına sahip ilk elemanı seçelim.

document  
  .getElementsByClassName('col')[0];

Şimdi bu örneği biraz daha detaylandıralım ve col class'ına sahip 3. eleman içerisindeki 3. li etiket içeriğine ulaşalım.

document
  .getElementsByClassName('col')[2]
  .getElementsByTagName('li')[2]
  .innerHTML;
// ya da
document
  .querySelector('.col>ul>li:nth-child(3)')
  .innerHTML;

Yukarıdaki örnek HTML içerisinde, liste elemanları even ve odd class'larına sahipler. Yeni örneğimizde, even ve odd olarak 2 section oluşturup liste elemanlarının değerlerini ilgili section içerisine alalım12 13.

[...document.querySelectorAll('.list li')]
  .forEach(e => document
    .getElementById(e.className)
    .querySelector('ul')
    .innerHTML += e.outerHTML);

Element İşlemleri

Artık, doküman içerisinde sırasına, diğer node ile ilişkisine, class, id veya tag tanımına ve hata özniteliklerine göre DOM içi elemanlara nasıl ulaşabileceğimizi biliyoruz. O halde, şimdi ulaştığımız bu elemanlarla ilgili detaylara nasıl ulaşabileceğimize bakalım14.

Yukarıda da bahsi geçtiği üzere; innerHML ve outerHTML birer Element propety. innerHML, öğenin içerdiği HTML veya XML biçimlendirmesini string olarak verir veya ayarlarken15, outerHTML ilgili Element'in kendisini de dahil ederek alt elemanlarla beraber string olarak verir veya ayarlar16.

document.getElementById('status').innerHTML; // "Dolor Sit Amet"
document.getElementById('status').outerHTML; // "<strong id=\"status\">Dolor Sit Amet</strong>"

innerHML ve outerHTML dışında, attributes, classList, className, id ve tagName gibi tanımları kullanarak seçme ve getAttribute(), hasAttribute(), getAttributeNames(), hasAttributes(), matches() gibi metodlarla da listeleme ve düzenleme işlemleri gerçekleştirilebilmekte9.

document.getElementsByTagName("strong")[0].attributes // [id="status"]
document.getElementsByTagName("body")[0].attributes //  [id="page", class="single-page"]

for (let name of document.querySelector('ul').getAttributeNames()) {
  let
    value = document
      .querySelector('li')
      .getAttribute(name);

  console.log(`name: ${name}, value: ${value}`); // name: class, value: even

  value
    .setAttribute("class", "odd");
}

Yeni elementler oluşturmak ve/veya var olan elementleri silmek istediğimizde ise createElement(), removeChild(), appendChild(), replaceChild() metodlarından document düzeyinde faydalanabilmekteyiz4 17 18.

let
  newItem = document
    .createElement('li'),
  updatedItem = document
    .getElementById('even')
    .getElementsByTagName('ul')[0];

newItem
  .textContent = 'List item 7';
updatedItem
  .appendChild(newItem)
  .setAttribute('class', 'new');

Son olarak, farklı iç içe node'ların sahip olduğu değerleri alabileceğimiz bir örnek ile yazısı sonlandıralım. Yukarıdaki HTML içeriği içerisinde yer alan listeleri tıklayarak console alanından iletilen değerleri takip edebilirsiniz.

[...document.querySelectorAll('ul')].forEach(item => {
  item.addEventListener('click', el => {
    let value = el.target.firstChild.nodeValue.trim().replace(/[()]/g,'');
    (value === '') ? console.log('Empty area!') : console.log(value);
  });
});

Yukarıdaki örnek kod parçacığı even id'li liste içerisine yeni bir item eklemekte ve bu item için new class'ı atamakta. DOM manipülasyonu ile ilgili başlangıç yazısının devamı olarak ilgili bir sonraki yazıda bir to-do list uygulaması hazırlayacağım19 14 20. Ardından, ilgili metodları kullanarak21 22 23 veri listeleme ve görselleştirme konularını detaylandıracağım.