Document Object Model

Document Object Model (DOM) merupakan sebuah ketentuan yang dikembangkan oleh W3C untuk berinteraksi dengan objek-objek yang ada di dalam HTML, XML, maupun XHTML. DOM bersifat cross-platform dan language-independent, yang artinya DOM dapat digunakan dengan bahasa pemrograman apapun, dalam sistem operasi manapun. Tentunya pengembang bahasa pemrograman atau sistem operasi harus mengimplementasikan antarmuka DOM terlebih dahulu sebelum dapat kita gunakan pada aplikasi kita.

Standar DOM dikembangkan untuk berinteraksi dengan elemen-elemen dokumen HTML dan XML, mulai dari pembuatan elemen baru sampai dengan manipulasi dan penghapusan elemen. Pada bagian ini kita akan membahas konsep-konsep dasar untuk berinteraksi dengan DOM tanpa masuk ke pembahasan masing-masing elemen dalam DOM. Fokus pembahasan akan ditujukan ke pemanfaatan DOM dengan efektif. Pembahasan mengenai masing-masing elemen DOM tidak akan efektif dilakukan melalui buku, karena perkembangan DOM yang sangat cepat (isi buku kemungkinan besar tidak akan aktual lagi ketika buku selesai ditulis). Referensi dari masing-masing elemen DOM sendiri dapat dibaca pada dokumen online seperti Mozilla Deveoper Network (https://developer.mozilla.org/).

Struktur Elemen DOM

Sebuah dokumen HTML direpresentasikan oleh DOM dalam bentuk sebuah struktur hirarkis pohon. Misalkan sebuah dokumen HTML sederhana seperti berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<html>
    <head>
        <title>Dokumen HTML Sederhana</title>
        <script type="text/javascript" src="scripts/scripts.js"></script>
    </head>
    <body>
        <h1>Judul Halaman</h1>
        <p>Sebuah paragraf</p>
    </body>
</html>

Pada contoh kode di atas, kita dapat melihat bagaimana di dalam tag html terdapat banyak elemen-elemen lain, dan elemen lain dapat mengandung elemen lain lagi di dalamya. Elemen html, yang menjadi penampung seluruh elemen lain dianggap sebagai akar (root) dari pohon, dan setiap elemen di dalamnya sebagai leaf. Sederhananya, kode HTML di atas dapat direpresentasikan sebagai pohon sesuai dengan gambar berikut:

HTML direpresentasikan Sebagai Pohon

HTML direpresentasikan Sebagai Pohon

Struktur pohon seperti pada gambar di atas merupakan cara DOM melihat dokumen HTML. Perhatikan juga bahwa terdapat dua jenis elemen pada pohon: node (simpul) yang ditampilkan dalam bentuk kotak putih, serta teks yang ditampilkan dalam bentuk tulisan. Setiap elemen HTML adalah merupakan node, dan dapat membungkus elemen HTML lainnya. Elemen teks, di sisi lain, tidak dapat memiliki elemen lain di dalamnya.

Manipulasi DOM

Sebagai bahasa yang dibuat untuk membuat dokumen HTML menjadi interaktif, Javascript memiliki kaitan yang erat dengan DOM. DOM menyediakan antarmuka untuk manipulasi dokumen, sementara Javascript menjadi bahasa yang melakukan eksekusi terhadap antarmuka yang disediakan. Pada bagian ini kita akan melihat bagaimana melakukan manipulasi terhadap elemen DOM yang ada dalam dokumen HTML, mulai dari akses, modifikasi, sampai menghapus elemen DOM.

Akses Elemen DOM

Sebelum mulai melakukan manipulasi terhadap elemen-elemen DOM, tentunya kita harus terlebih dahulu mampu memilih mana elemen yang akan kita ubah propertinya. Terdapat beberapa fungsi yang dapat kita gunakan untuk mengakses elemen DOM, yaitu:

  1. document.getElementById, digunakan untuk mengakses elemen DOM berdasarkan nilai properti id pada elemen. Hanya akan mengembalikan satu elemen DOM karena idealnya nilai properti id bersifat unik dalam satu dokumen. Jika terdapat lebih dari satu elemen dengan id yang sama, maka elemen yang pertama ditemukan akan dikembalikan.
  2. document.getElementsByClassName, seperti namanya digunakan untuk memanggil seluruh elemen DOM dengan nilai properti class yang diberikan. Mengembalikan objek mirip array, yaitu HTMLCollection dari seluruh elemen DOM yang cocok.
  3. document.getElementsByName, mengambil elemen berdasarkan nilai properti name. Mengembalikan HTMLCollection seperti getElementsByClassName.
  4. document.getElementsByTagName, memilih elemen-elemen dengan tag HTML tertentu. Sama seperti semua fungsi yang mengembalikan banyak elemen DOM, memberikan nilai berupa HTMLCollection.
  5. document.getElementsByTagNameNS, sama seperti getElementsByTagName, hanya saja fungsi ini melakukan penyaringan tambahan berdasarkan namespace pada dokumen XHTML. Fungsi ini diimplementasikan berbeda-beda oleh tiap browser pada saat penulisan (Febuari 2014) dan disarankan untuk jangan digunakan.
  6. document.querySelector, mencari elemen DOM pertama yang sesuai dengan aturan selector CSS yang diberikan ke fungsi.
  7. document.querySelectorAll, sama seperti querySelector, tetapi mengembalikan semua elemen yang memenuhi aturan (bukan hanya elemen pertama). Fungsi ini mengembalikan NodeList, bukan HTMLCollection.

Setelah membaca deskripsi singkat dari ketujuh fungsi di atas, tentu saja terdapat beberapa pertanyaan dari pembaca. Apa itu NodeList dan HTMLCollection? Bagaimana objek DOM direpresentasikan? Cara menggunakan fungsinya seperti apa? Mari kita jawab semua pertanyaan-pertanyaan ini dengan langsung bermain-main dengan semua fungsi yang ada!

Misalkan kita memiliki dokumen HTML seperti berikut (seluruh contoh kode dari titik ini akan menggunakan kode HTML di bawah):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE HTML>
    <html lang="id">
    <head>
        <meta charset="UTF-8">
        <title>Akses Elemen DOM</title>
    </head>
    <body>
        <div id="container">
            <h1 class="heading">Judul Pertama</h1>
            <p>Paragraf Pertama</p>
            <p>Paragraf Kedua</p>
            <p>Paragraf Ketiga</p>
            <h2 class="heading">Judul Kedua</h2>

            <form action="post" id="comments">
                <input type="text"
                   name="comment-title"
                   id="comment-title"><br>

                <textarea name="comment-box"
                      id="comment-box"
                      class="box"></textarea><br>

                <input type="reset" class="button">
                <input type="submit" class="button submit">
            </form>
        </div>

        <script type="text/javascript" src="script.js"></script>
    </body>
    </html>

Kita dapat mengakses salah satu elemen dalam dokumen, contohnya elemen input untuk judul komentar (id: “comment-title”) menggunakan fungsi getElementById:

1
document.getElementById("comment-title")

Pemanggilan fungsi di atas akan memberikan kita elemen HTML yang benar, dibungkus dalam objek Element. Objek Element digunakan untuk merepresentasikan sebuah elemen DOM dalam dokumen HTML, dan memiliki beberapa method dan properti standar. Method dan properti standar ini tidak akan kita bahas semuanya langsung, tetapi sedikit demi sedikit, sesuai dengan kebutuhan. Jika ingin melihat daftar lengkap dari properti dan method, silahkan kunjungi dokumentasi yang relevan pada MDN (Mozilla Developer Network).

Karena getElementById (dan fungsi-fungsi sejenis lain) mengembalikan objek, maka kita dapat menyimpan hasil eksekusi fungsi ke dalam variabel, layaknya objek-objek lain:

1
2
3
4
var ct = document.getElementById("comment-title");

ct.getAttribute("type"); // mengembalikan "text"
ct.getAttribute("name"); // mengembalikan "comment-title"

Pada contoh di atas, kita memanggil method milik Element, yaitu getAttribute yang berguna untuk mengambil nilai dari atribut yang dimiliki elemen. Sebagai objek khusus untuk reperesentasi DOM, Element juga ditampilkan secara khusus pada developer tools yang ada dalam browser:

Tampilan Element dalam Chrome Developer Tools

Tampilan Element dalam Chrome Developer Tools

Dari gambar di atas, dapat dilihat bagaimana variabel test yang berisi string ditampilkan hanya sebagai string, sementara ct ditampilkan dalam bentuk tag HTML. Kita dapat memanfaatkan tampilan ini ketika debugging untuk memastikan bahwa elemen yang kita ambil sudah benar.

Perhatikan juga bagaimana kita memanggil fungsi getAttribute terhadap ct, bukan document. Objek document pada DOM merepresentasikan keseluruhan dokumen HTML kita, sementara ct menandai elemen spesifik. Karena kita ingin mengambil atribut hanya pada elemen spesifik, maka kita memanggil getAttribute hanya pada ct. Pastikan anda mengerti perbedaan kedua objek ini agar dapat mengerti bagian-bagian berikutnya dengan mudah.

Selanjutnya, untuk mengambil elemen DOM berdasarkan nama class, kita dapat menggunakan fungsi getElementsByClassName:

1
2
3
4
var headings = document.getElementsByClassName("heading");

headings[0]; // elemen h1 pada dokumen
headings[1]; // elemen h2 pada dokumen

Fungsi getElementsByClassName akan mengembalikan HTMLCollection, yang sifatnya sekilas mirip dengan sebuah array. Perbedaan utama adalah bahwa HTMLCollection memiliki tambahan dua buah method, yaitu item dan namedItem. Seperti yang dapat ditebak, fungsi kedua method tersebut adalah untuk mengambil elemen yang tepat, dari indeks (item) maupun nama (namedItem).

1
2
3
4
var buttons = document.getElementsByClassName("button");

buttons.item(0);                // sama dengan button[0]
button.namedItem("btn-submit"); // button submit (cek atribut "name")

Sama seperti Element, HTMLCollection juga ditampilkan secara khusus pada developer tools dalam browser.

Tampilan HTMLCollection dalam Chrome Developer Tools

Tampilan HTMLCollection dalam Chrome Developer Tools

Fungsi-fungsi selanjutnya, yaitu getElementsByName dan getElementsByTagName memiliki cara penggunaan yang sama dengan getElementsByClassName:

1
2
3
4
5
6
7
8
var paragraphs = document.getElementsByTagName("p");

paragraphs.length;  // mengembalikan 3
paragraphs.item(0); // mengembalikan paragraf pertama

var ct = document.getElementsByName("comment-title");

ct[0]; // mengembalikan kotak judul komentar

Selain semua fungsi di atas, kita juga dapat menggunakan aturan selector CSS untuk mengambil elemen DOM, melalui fungsi querySelector dan querySelectorAll. querySelector mengambil hanya satu elemen pertama yang memenuhi syarat selector CSS, sementara querySelectorAll mengambil semua elemen yang memenuhi aturan.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// mengambil semua elemen dengan class "heading"
// dan tag p
var els = document.querySelectorAll(".heading, p");

els.length;  // mengembalikan 5
els.item(0); // elemen h1.heading

// mengembalikan hanya h1.heading saja, karena
// elemen tersebut merupakan elemen pertama yang
// memenuhi syarat selector CSS yang dikirimkan
document.querySelector(".heading");

Ingat juga bahwa querySelectorAll mengembalikan NodeList, bukan HTMLCollection. Perbedaan utama antara kedua objek ini adalah bahwa NodeList dapat menampung objek selain dari Element, dan NodeList tidak memiliki method namedItem. Terdapat beberapa perbedaan lain secara internal, tetapi tidak akan kita bahas lebih dalam karena tidak terlalu banyak perbedaan yang tampak pada penggunaan umum.

Membuat Elemen DOM

Seperti yang dijelaskan di bagian awal, DOM memiliki dua jenis elemen, yaitu elemen node dan teks. Karenanya, pembuatan elemen baru untuk DOM juga dilakukan dengan dua cara, yaitu document.createElement untuk pembuatan node, dan document.createTextNode untuk pembuatan teks.

1
2
var paragraf = document.createElement("p");
var teks = document.createTextNode("Teks yang dibuat secara dinamis");

Sampai titik ini, paragraf masih merupakan sebuah elemen paragraf yang tidak memiliki isi apa-apa. Kita dapat memasukkan teks ke dalam paragraf dengan menggunakan method appendChild:

1
paragraf.appendChild(teks);

Sekarang kita telah memiliki sebuah elemen p berisi teks “Teks yang dibuat secara dinamis”. Setelah memiliki elemen baru, tentunya kita harus memasukkan elemen tersebut ke dalam dokumen HTML sebelum elemen baru dapat dilihat oleh pengguna. Untuk memasukkannya, kita masih dapat menggunakan appendChild, pada bagian dokumen yang kita inginkan. Misalnya kita ingin memasukkan teks di dalam div dengan id container:

1
2
var container = document.getElementById("container");
container.appendChild(paragraf);

Setelah menambahkan elemen paragraf seperti pada kode di atas, kita dapat melihat bagaimana sebuah teks paragraf baru bertambah pada akhir div:

Halaman Sebelum dan Setelah Penambahan Elemen

Halaman Sebelum dan Setelah Penambahan Elemen

Selain menambahkan elemen pada akhir seperti ini, kita juga dapat menambahkan elemen di depan sebuah elemen lainnya melalui fungsi insertBefore. Fungsi insertBefore menerima dua parameter: parameter pertama berupa elemen yang ingin ditambahkan, sementara parameter kedua adalah tempat di mana parameter pertama diletakkan. Agar tidak bingung mari kita langsung lihat contohnya. Misalkan paragraf ingin diletakkan sebelum “Judul Pertama” sehingga hasil yang diinginkan adalah sebagai berikut:

Paragraf Dimasukkan Sebelum "Judul Pertama"

Paragraf Dimasukkan Sebelum “Judul Pertama”

Maka kita dapat menggunakan insertBefore seperti ini:

1
2
3
var judulPertama = document.querySelector("h1.heading");

container.insertBefore(paragraf, judulPertama);

Cara penambahan elemen baru ke dalam DOM memang cukup sederhana. Cara penambahan DOM dengan fungsi appendChild maupun insertBefore secara otomatis akan membuat browser menyusun ulang elemen-elemen yang ada dalam dokumen HTML, untuk memastikan dokumen ditampilkan dengan benar. Proses penyusunan ulang ini kita kenal dengan istilah reflow.

Setiap kali kita memanggil appendChild ataupun insertBefore, browser akan melakukan reflow. Pada penambahan satu atau dua elemen hal ini tidak terlalu berpengaruh, tetapi jika kita menambahkan banyak elemen, performa dari browser akan menjadi lambat. Pergeseran banyak elemen berkali-kali juga tentunya akan membuat pengguna tidak nyaman menggunakan halaman web kita.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// SANGAT lambat, dan memaksa browser
// melakukan banyak reflow.
// Jangan lakukan ini!
var link;

for (var i = 0; i < 10; i ++) {
    link = document.createElement('a');
    link.innerHTML = 'test';
    container.appendChild(link);
}

Solusi dari permasalahan ini yaitu dengan menggunakan DocumentFragment. DocumentFragment merupakan sebuah objek yang menampung sepotong dokumen. Objek ini dibuat dengan tujuan agar kita dapat membangun elemen DOM kompleks yang memiliki banyak komponen. Setelah membangun elemen dengan banyak komponen ke dalam DocumentFragment kita kemudian dapat menambahkan semua elemen tersebut ke dalam dokumen HTML sekaligus. Dengan cara ini browser hanya akan melakukan satu kali reflow, sehingga tidak ada penalti performa.

DocumentFragment dapat dibuat melalui fungsi createDocumentFragment seperti berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var link, fragment = document.createDocumentFragment();

for (var i = 0; i < 10; i ++) {
    link = document.createElement('a');
    link.innerHTML = 'test';
    fragment.appendChild(link);
}

// reflow hanya terjadi di sini
element.appendChild(fragment);

Perhatikan bagaimana createDocumentFragment dipanggil melalui objek document. Hal ini dikarenakan DocumentFragment yang dibuat oleh createDocumentFragment masih kosong, sehingga bisa dikatakan fungsi ini memiliki aturan main yang sama dengan createElement, tetapi menghasilkan objek yang berbeda.

Menghapus Elemen DOM

Penghapusan elemen DOM dilakukan dengan sangat sederhana, hanya melalui satu fungsi saja: removeChild. Prosesnya sederhana: pilih elemen yang akan dihapus, kemudian panggil removeChild pada penampung elemen yang akan dihapus tersebut:

1
2
3
var judulKedua = document.querySelector("h2.heading");

container.removeChild(judulKedua);

Berbeda dengan penambahan elemen, tidak terdapat kasus khusus untuk penghapusan banyak elemen DOM. Pada prakteknya, kita juga sangat jarang membuang banyak elemen DOM sekaligus.

Modifikasi Atribut DOM

Inti dari pengembangan dokumen HTML yang interaktif adalah perubahan konten maupun atribut dari DOM. Misalnya jika ingin membuat sebuah elemen text area bertambah besar ketika pengguna menekan Enter, kita dapat menambahkan nilai atribut row pada text area. Kebanyakan interaksi dan dinamisme dokumen HTML dilakukan dengan cara seperti ini. Pertanyaannya tentunya adalah: bagaimana kita mengubah atribut-atribut yang ada pada DOM?

Pada bagian sebelumnya kita telah melihat fungsi getAttribute yang mengambil nilai atribut DOM. Untuk mengganti atau menambahkan nilai atribut DOM, kita dapat menggunakan fungsi kakaknya, yaitu setAttribute. Fungsi setAttribute menerima dua parameter, yaitu atribut yang ingin ditambahkan sebagai parameter pertama, dan isi dari atribut tersebut sebagai parameter kedua. Langsung saja, berikut contoh penggunaan fungsi ini:

1
2
3
4
5
6
7
var textbox = document.getElementById("comment-title");

textbox.setAttribute("size", 40);

// textbox menjadi:
// <input type="text" name="comment-title" id="comment-title" size="40">
textbox;

Kita juga dapat menggunakan setAttribute untuk atribut yang sudah ada. Jika digunakan pada atribut yang sudah ada setAttribute akan memperbaharui nilai atribut pada parameter pertama:

1
2
3
4
5
textbox.setAttribute("size", 30);

// textbox menjadi:
// <input type="text" name="comment-title" id="comment-title" size="30">
textbox;

Selain dapat menambahkan atribut baru, DOM juga memungkinkan kita untuk menghapus atribut yang telah ada. Fungsi untuk melakukan penghapusan atribut yaitu removeAttribute. Fungsi removeAttribute hanya menerima satu parameter, yaitu atribut yang ingin dihapus. Berikut adalah cara penggunaan fungsi ini:

1
2
3
4
5
textbox.removeAttribute("size");

// textbox menjadi:
// <input type="text" name="comment-title" id="comment-title">
textbox;

Penambahan, perubahan, dan penghapusan atribut merupakan cara yang sederhana dan efektif untuk menambahkan sedikit efek dinamis pada dokumen HTML. Tetapi tentunya metode ini tidak memberikan kita terlalu banyak kebebasan untuk membangun dokumen HTML yang dinamis. Ingat kembali, tag HTML beserta atributnya dirancang hanya sebagai kode untuk merepresentasikan makna semantik dokumen. Tampilan dari dokumen idealnya kita perindah melalui CSS. Karenanya, jika ingin mengembangkan dokumen interaktif yang juga cantik (dengan berbagai animasi dan perubahan tampilan) kita perlu mengubah juga CSS yang mempengaruhi DOM. Kita akan melihat cara untuk melakukan hal ini pada bagian berikutnya.

Mengganti Tampilan Elemen DOM

Jika pada bagian sebelumnya kita telah melihat bagaimana mengganti atribut elemen DOM, maka pada bagian ini kita akan melihat cara menggantikan atribut CSS pada elemen DOM. Salah satu cara yang paling sederhana untuk mengganti properti CSS adalah dengan memanfaatkan atribut style pada sebuah elemen. Karena atribut style akan menerapkan properti CSS sesuai dengan nilai yang diberikan, kita dapat langsung menggantikan nilainya melalui setAttribute:

1
2
var container = document.getElementById("container");
container.setAttribute("style", "background: #D33");

Cara penggantian kode CSS seperti ini, meskipun mudah dilakukan dan memberikan hasil yang benar, sangat tidak disarakan untuk kita gunakan. Bayangkan jika kita ingin menambahkan satu lagi properti CSS setelah menambahkan properti dengan cara di atas. Mau tidak mau, kita harus mengambil nilai atribut style kembali, dan kemudian mengubah isi nilai tersebut, dan mengirimkannya kembali melalui setAttribute seperti ini:

1
2
3
4
var cssLama = cont.getAttribute("style");
var cssBaru = cssLama + "; color: white;";

cont.setAttribute("style", cssBaru);

Dan sekarang bayangkan lagi jika kita ingin mengganti properti CSS background menggunakan setAttribute. Modifikasi nilai CSS seperti ini tentunya tidak optimal, dan akan sangat menyulitkan kita di masa depan. Untungnya, DOM menyediakan cara yang lebih mudah untuk menggantikan nilai CSS yang melekat pada sebuah elemen, yaitu melalui properti style.

Properti style dapat dipanggil pada semua elemen DOM yang juga merupakan elemen HTML. style termasuk sebagai salah satu properti dari antarmuka (interface) HTMLElement. Ketika kita menggunakan style, browser akan mengimplementasikan nilai CSS yang diberikan secara inline, yang artinya hasil akhir yang didapatkan akan sama dengan mengubah atribut style secara langsung.

Berikut adalah contoh penggunaan atribut style untuk mengubah properti CSS dari sebuah elemen:

1
2
3
4
5
6
var container = document.getElementById("container");
container.style.background = "#D33";
container.style.color = "white";

// jika ingin mengubah nilai background lagi
container.style.background = "#D37";

Dan karena sifat dinamis dari Javascript, kita dapat menyimpan properti style dari sebuah elemen ke dalam variabel, dan kemudian mengubah nilainya di sana:

1
2
3
var CSSContainer = container.style;

CSSContainer.padding = "1em";

Pengambilan nilai dari CSS yang ada juga dapat dilakukan dengan mudah:

1
var warnaTeks = CSSContainer.color;

Salah satu kekurangan dari pengambilan nilai properti CSS melalui style seperti ini adalah bahwa kita hanya dapat mengambil nilai properti CSS satu per satu. Selain itu, kita juga tidak dapat mengambil nilai properti CSS yang diterapkan pada pseudo-element seperti :hover.

Untuk menutupi kedua kekurangan tersebut, kita dapat menggunakan fungsi window.getComputedStyle. Fungsi ini akan mengembalikan seluruh properti CSS yang ada pada sebuah elemen sekaligus. window.getComputedStyle menerima dua parameter, yaitu elemen yang ingin diambil kode CSS-nya sebagai parameter pertama, dan parameter kedua yang berisi pseudo-element yang akan diterapkan pada elemen di parameter pertama. Parameter kedua tidak harus diisikan, dan jika tidak diisikan akan memberikan seluruh CSS pada elemen di parameter pertama saja.

Fungsi window.getComputedStyle akan mengembalikan sebuah objek bertipe CSSStyleDeclaration, yang menampung seluruh properti CSS yang diterapkan pada sebuah elemen, beserta dengan nilai dari masing-masing propertinya. Objek CSSStyleDeclaration sendiri memiliki banyak method dan properti, yang detilnya dapat dibaca pada dokumentasi yang relevan. Method yang akan kita gunakan pada contoh nanti hanya satu, yaitu getPropertyValue, yang sesuai namanya mengambil nilai dari properti CSS yang relevan.

Langsung saja, berikut contoh penggunaan dari window.getComputedStyle:

1
2
3
4
5
var container   = document.getElementById("container");
var propertiCSS = window.getComputedStyle(container, null);

// Mengembalikan rgb(255, 255, 255)
var warnaTeks = propertiCSS.getPropertyValue("color");

Pada contoh kode di atas, propertiCSS merupakan objek CSSStyleDeclaration yang menampung seluruh properti CSS yang ada, dan diterapkan pada container. Dengan mengambil seluruh properti CSS yang ada sekaligus, kita dapat melakukan perhitungan kompleks terhadap nilai-nilai properti yang ada tanpa perlu terlalu banyak kode berulang. Misalnya kita dapat menghitung dan menyamakan jarak margin per elemen yang ada dalam halaman tanpa perlu memanggil style berkali-kali.

Mengganti Tampilan DOM dengan Kelas CSS

Sejauh ini kita telah melihat bagaimana kita dapat mengakses dan mengganti properti CSS pada elemen DOM dengan menggunakan style maupun getComputedStyle. Walaupun sangat berguna, kedua metode awal ini memiliki satu kekurangan yang cukup buruk, yaitu bahwa cara seperti ini mencampur adukkan bagian logis kode kita (javascript) dengan bagian penampilan (CSS).

Ingat kembali bahwa tujuan dari pemisahan HTML-CSS-Javascript adalah untuk mendapatkan kerapihan kode dengan membuat masing-masing komponen memiliki tujuan khusus. Pergantian tampilan secara langsung melalui javascript seperti ini akan mengakibatkan kode kita tercampur aduk antara kode logis dan penampilan. Belum lagi perubahan nilai pada style yang akan selalu menghasilkan reflow. Kode seperti ini:

1
2
3
element.style.fontWeight = 'bold';
element.style.textDecoration = 'none';
element.style.color = '#000';

Akan memaksa browser menjalankan tiga kali *reflow*! Oke, sekarang kita telah melihat masalahnya. Bagaimana dengan solusi dari masalah ini?

Salah satu solusi terbaik pada kasus ini adalah dengan melakukan perubahan terhadap atribut class pada elemen secara langsung. Ide utamanya adalah bahwa kita menyimpan seluruh kode tampilan dalam sebuah kelas CSS, dan jika ingin mengubah tampilan elemen DOM kita dapat mengubah kelas CSS-nya sesuai dengan penampilan yang kita inginkan.

Bayangkan kombinasi HTML dan CSS berikut (kita tidak memisahkan kode CSS ke dalam file sendiri di sini untuk menyederhanakan contoh):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Class List CSS</title>

    <style type="text/css">
        .teksWah {
            color: #D55;
            font-size: 2em;
            font-style: italic;
        }

        .teksWew {
            color: #55D;
            font-size: 1.5em;
        }
    </style>
</head>
<body>
    <h1 id="teks">Sebuah teks sederhana</h1>
</body>
</html>

Kita dapat melihat bahwa terdapat dua buah kelas CSS pada kode di atas, yaitu teksWah dan teksWew. Masing-masing dari kelas ini memiliki properti CSS yang berbeda-beda, dan kita akan mencoba mengubah tampilan pada h1#text dengan menambahkan dan mengurangi kelas CSS.

Pertama-tama, pastinya kita harus mengambil elemen yang relevan terlebih dahulu:

1
var teks = document.getElementById("teks");

Selanjutnya, kita dapat mengakses seluruh class yang ada pada suatu elemen dengan menggunakan properti classList. Karena elemen kita belum memiliki kelas, maka kita akan menyimpan classList ke dalam sebuah variabel terlebih dahulu.

1
var cssTeks = teks.classList;

Kita kemudian dapat menambahkan atau menghapus class CSS dari teks dengan menggunakan method add dan remove, yang cukup jelas kegunaannya dari namanya.

1
2
3
4
5
6
7
// teks menjadi memiliki class teksWew
// tampilan otomatis berubah
cssTeks.add("teksWew");

// kelas teksWew dihilangkan dari teks
// tampilan kembali ke awal
cssTeks.remove("teksWew");

Jika ingin membalikkan keaktifan class (jika tidak ada jadikan ada; jika ada jadikan tidak ada) kita dapat menggunakan method toggle:

1
2
cssTeks.toggle("teksWah"); // kelas teksWah ditambahkan (karena belum ada)
cssTeks.toggle("teksWah"); // kelas teksWah dihilangkan (karena sudah ada)

Terakhir, kita juga dapat mengecek keberadaan sebuah class dalam elemen DOM dengan menggunakan method contains milik classList:

1
2
3
4
5
cssTeks.contains("teksWew"); // mengembalikan false

cssTeks.add("teksWew");

cssTeks.contains("teksWew"); // mengembalikan true

Dengan memanfaatkan fitur-fitur yang disediakan oleh classList ini, kita dapat mengubah tampilan dari sebuah elemen DOM dengan mudah dan rapi, serta hanya menjalankan satu kali reflow. Idealnya kita harus selalu mencoba untuk menggunakan cara ini dibandingkan dengan style maupun getComputedStyle.

Sampai di sini kita telah melihat bagaimana kita dapat melakukan perubahan terhadap tampilan elemen DOM melalui kode program. Tentunya hal ini tidak terlalu berguna, karena yang sebuah dokumen HTML interaktif disebut “interaktif” adalah karena adanya intervensi pengguna. Mungkin kita ingin teks berubah warna ketika pengguna menekan tombol tertentu. Atau mungkin gambar membesar ketika di-klik. Untuk mencapai efek seperti itu kita harus menggunakan teknik perubahan tampilan yang baru saja kita pelajari berbarengan dengan event DOM.

Kita akan melakukan pembahasan mengenai event DOM pada bab berikutnya. Sebelum masuk ke event DOM, kita akan melihat penelusuran DOM terlelbih dahulu, karena tidak terlalu berguna jika kita hanya mampu mengganti tampilan satu dari elemen DOM saja. Lebih baik jika kita dapat menelusuri seluruh elemen DOM, dan mengganti tampilannya ketika diperlukan.

Penelusuran Elemen DOM

Ketika membuat dokumen HTML dinamis, seringkali kita perlu menambahkan atau menghapus elemen-elemen DOM yang tidak kita ketahui dengan pasti. Misalnya, bisa saja kita ingin menghapus semua baris dari sebuah tabel yang memiliki nilai tertentu pada kolom pertamanya. Untuk melakukan hal-hal seperti ini kita perlu menelusuri elemen DOM yang ada, selangkah demi selangkah. Pada contoh penghapusan baris tabel tadi misalnya, kita dapat mengambil tabel (misalnya berdasarkan id) dan kemudian menelusuri isi dari tabel tersebut.

Langsung saja, andai kata kita memiliki tabel seperti berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<table id="tabel-contoh">
    <thead>
        <tr>
            <th>Judul 1</th>
            <th>Judul 2</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Isi 1</td>
            <td>Isi 2</td>
        </tr>
        <tr>
            <td>Isi 3</td>
            <td>Isi 4</td>
        </tr>
    </tbody>
    <tfoot>
        <tr>
            <td>Total 1</td>
            <td>Total 2</td>
        </tr>
    </tfoot>
</table>

Untuk melakukan penelusuran elemen-elemen dalam tabel di atas, pertama-tama tentunya kita harus mengambil tabelnya terlebih dahulu:

1
var tabel = document.getElementById("tabel-contoh");

Setelah mendapatkan elemen DOM dari tabel, kita kemudian dapat mengambil seluruh elemen DOM yang berada setingkat di bawah tabel dengan memanggil properti childNodes:

1
2
3
4
// mengembalikan
// [#text, <thead>​…​</thead>​, #text,
//  <tbody>​…​</tbody>​, #text, <tfoot>​…​</tfoot>​, #text]
tabel.childNodes;

Perhatikan bagaimana selain mendapatkan thead, tbody, dan tfoot kita juga diberikan tiga buah #text. Objek #text ini merupakan konten teks yang juga dianggap bagian dari DOM, dan berisi spasi dan baris baru yang kita berikan untuk merapikan kode HTML. Jadi misalnya jika kita menuliskan HTML tanpa spasi (sering dijumpai pada HTML hasil kompilasi):

1
2
3
4
<table id="tabel-contoh"><thead><tr><th>Judul 1</th><th>Judul 2</th>
</tr></thead><tbody><tr><td>Isi 1</td><td>Isi 2</td></tr><tr><td>Isi
3</td><td>Isi 4</td></tr></tbody><tfoot><tr><td>Total 1</td><td>Tota
l 2</td></tr></tfoot></table>

maka kita tidak akan melihat #text ketika memanggil childNodes.

1
2
3
// mengembalikan
// [<thead>​…​</thead>​, <tbody>​…​</tbody>​, <tfoot>​…​</tfoot>]
tabel.childNodes;

Karena childNodes mengembalikan sebuah array, maka kita dapat mengambil “anak” dari tabel yang kita inginkan seperti kita mengambil elemen array:

1
2
var anakTabel  = table.childNodes;
var badanTabel = anakTabel[1]; // badanTabel berisi <tbody>

Kita juga dapat mengakses thead dan tfoot melalui tbody dengan memanggil properti nextSibling dan previousSibling:

1
2
var kepalaTabel = badanTabel.previousSibling;
var kakiTabel   = badanTabel.nextSibling;

Penggunaan istilah “anak” (child) dan “saudara” (sibling) ini berasal dari penampilan tabel yang mirip dengan pohon silsilah keluarga:

Penampilan Pohon

Penampilan Pohon

Penampilan pohon yang mirip seperti silsilah ini memungkinkan kita melihat cabang dari pohon sebagai individu di dalam keluarga. Misalnya jika kita melihat silsilah dari tbody pada gambar sebelumnya, maka kita dapat melihat kenapa kepalaTabel adalah saudara dari badanTabel:

Pohon Tabel dengan Keterangan (Pusat: tbody)

Pohon Tabel dengan Keterangan (Pusat: tbody)

Dari gambar di atas, dapat dilihat juga bahwa secara otomatis, keseluruhan tabel menjadi “orang tua” (parent) dari badanTabel. Berdasarkan nama properti sebelumnya, kita dapat menebak bahwa untuk mengakses seluruh tabel dari badanTabel kita dapat memanggil properti parentNode:

1
2
// mengembalikan <table id="contoh-tabel">..</table>
badanTabel.parentNode;

Dengan pengetahuan yang kita miliki sekarang, kita dapat mengimplementasikan sebuah fungsi yang menelusuri seluruh elemen DOM secara rekursif seperti berikut:

1
2
3
4
5
6
7
8
var TelusuriDOM = function telusuri(node) {
    node = node.firstChild;

    while (node) {
        telusuri(node);
        node = node.nextSibling;
    }
};

Fungsi di atas akan melakukan penelusuran ke anak dari node yang aktif sekarang, dan kemudian ke seluruh anak dari anak tersebut, serta anak dari saudara-saudaranya. Tetapi penelusuran saja tentunya tidak terlalu berguna. Algoritma yang digunakan pada fungsi ini adalah algoritma yang paling sederhana untuk penelusuran pohon secara rekursif.

Mari kita tambahkan fitur untuk memanggil sebuah fungsi (yang diberikan pengguna) untuk dijalankan pada setiap node:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var TelusuriDOM = function telusuri(node, fungsi_aksi) {
    fungsi_aksi(node);

    node = node.firstChild;

    while (node) {
        telusuri(node);
        node = node.nextSibling;
    }
};

Atau mungkin lebih lagi, yaitu jika kita hanya ingin menjalankan fungsi_aksi pada saat tertentu saja, di mana syarat eksekusinya ditentukan oleh pengguna (melalui fungsi lagi tentunya):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var TelusuriDOM = function telusuri(node, fungsi_cek, fungsi_aksi) {
    if (fungsi_cek(node)) {
        fungsi_aksi(node);
    }

    node = node.firstChild;

    while (node) {
        telusuri(node, fungsi_cek, fungsi_aksi);
        node = node.nextSibling;
    }
};

Kita kemudian dapat memberikan fungsi_cek dan fungsi_aksi kepada TelusuriDOM. Contohnya, jika kita ingin menghapus baris-baris dalam tabel yang memiliki kolom berisi “Isi 4”. fungsi_cek sudah pasti harus diisikan dengan pengecekan nilai “Isi 4” dalam setiap node, sementara fungsi_aksi akan melakukan penghapusan baris. Berikut detil implementasinya:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Merupakan fungsi_cek
// Memastikan bahwa node merupakan sebuah <td> (melalui nodeName)
// dan isi dari node adalah "Isi 4"
var CekIsiKolom = function (node) {
    return node.innerHTML === "Isi 4" && node.nodeName === "TD";
};

// Merupakan fungsi_aksi
// Karena fungsi ini hanya dijalankan ketika CekIsiKolom benar,
// maka kita dapat mengasumsikan node selalu adalah <td>.
var HapusBaris = function (node) {
    var baris = node.parentNode;  // orang tua <td> selalu adalah <tr>
    var badan = baris.parentNode; // (logikanya) idem

    badan.removeChild(baris);
};

TelusuriDOM(tabel, CekIsiKolom, HapusBaris);
comments powered by Disqus
Kembali ke bertzzie.com