15 Desember 2021

Menggelembung (_bubbling_) dan menangkap (_capturing_)

Ayo mulai dengan sebuah contoh.

Sebuah penangan (handler) di atur ke <div>, tapi juga dijalankan jika kita klik salah satu tag bawaan seperti <em> or <code>:

<div onclick="alert('Penangan (handler)!')">
  <em>Jika kamu menekan pada <code>EM</code>, penangan pada <code>DIV</code> akan berjalan.</em>
</div>

Bukan kah itu sedikit aneh? kenapa penangan (handler) pada <div> berjalan padahal elemen yang di klik adalah <em>?

Menggelembung (bubbling)

Prinsip menggelembung (bubbling) itu sederhana.

Pada saat sebuah peristiwa terjadi ke sebuah elemen, peristiwa itu akan menjalankan penangan (handler) yang ada pada elemen itu, kemudian pada elemen orang tua (parent), dan seterusnya hingga sampai ke elemen yang paling atas (ancestors).

Bayangkan kita memiliki tiga elemen bersarang FORM > DIV > P dengan penagan (handler) pada setiap elemen:

<style>
  body * {
    margin: 10px;
    border: 1px solid blue;
  }
</style>

<form onclick="alert('form')">FORM
  <div onclick="alert('div')">DIV
    <p onclick="alert('p')">P</p>
  </div>
</form>

Sebuah klik pada bagian dalam <p> akan menjalankan onclick:

  1. Yang ada pada <p>.
  2. Kemudian pada <div>.
  3. Kemudian pada <form>.
  4. Dan seterusnya hingga sampai ke objek document.

Jadi jika kita klik pada <p>, kemudian kita akan melihat 3 buah peringatan (alerts): pdivform.

Proses ini disebut dengan “menggelembung (bubbling)”, karena peristiwa akan “mengelembung (bubble)” dari bagian dalam elemen ke atas melalui elemen orang tua (parents) seperti sebuah gelembung di air.

Hampir semua peristiwa bergelembung.

Kata kunci pada kata tersebut adalah “hampir”.

Contohnya, peristiwa focus tidak bergelembung. Masih ada contoh lain, kita akan membahas mereka. Tapi tetap itu hanya pengecualian, dan bukan aturan baku, hampir semua peristiwa mengelembung.

event.target

Sebuah penangan (handler) pada elemen orang tua bisa selalu mendapat detail tentang dimana kejadian itu terjadi.

elemen bersarang yang mengakibatkan peristiwa (event) di panggil di sebut sebuah target elemen, diakses dengan menggunakan event.target.

Catat perbedaan dari this (=event.currentTarget):

  • event.target – adalah “target” elemen yang menginisialisasi peristiwa (event), dan tidak berubah pada proses pengelembungan.
  • this – adalah elemen tersebut, elemen yang sedang menjalankan penangan (handler).

Contohnya, jika sebuah penangan (handler) form.onclick, kemudian form itu akan “menangkap” semua klik yang terjadi didalam form. Tidak peduli dimana klik itu terjadi, klik itu akan mengelembung ke <form> dan akan menjalankan penangan (handler).

Pada penangan (handler) form.onclick:

  • this (=event.currentTarget) adalah elemen <form>, karena penangan (handler) dijalankan pada <form>.
  • event.target adalah elemen didalam <form> dimana peristiwa klik terjadi.

Contohnya:

Hasil
script.js
example.css
index.html
form.onclick = function(event) {
  event.target.style.backgroundColor = 'yellow';

  // chrome membutuhkan waktu untuk mewarnai warna kuning.
  setTimeout(() => {
    alert("target = " + event.target.tagName + ", this=" + this.tagName);
    event.target.style.backgroundColor = ''
  }, 0);
};
form {
  background-color: green;
  position: relative;
  width: 150px;
  height: 150px;
  text-align: center;
  cursor: pointer;
}

div {
  background-color: blue;
  position: absolute;
  top: 25px;
  left: 25px;
  width: 100px;
  height: 100px;
}

p {
  background-color: red;
  position: absolute;
  top: 25px;
  left: 25px;
  width: 50px;
  height: 50px;
  line-height: 50px;
  margin: 0;
}

body {
  line-height: 25px;
  font-size: 16px;
}
<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="example.css">
</head>

<body>
  Sebuah klik untuk menunjukan kedua <code>event.target</code> dan <code>this</code> untuk dibandingkan:

  <form id="form">FORM
    <div>DIV
      <p>P</p>
    </div>
  </form>

  <script src="script.js"></script>
</body>
</html>

this dan event.target bisa merupakan elemen yang sama – itu terjadi pada saat klik terjadi tepat di elemen <form>.

Menghentikan Menggelembung (bubbling)

Sebuah proses menggelembung berasal dari target elemen akan naik keatas. Biasanya proses itu akan terjadi sampai mencapai elemen <html>, dan kemudian ke objek document, dan ada beberapa peristiwa (event) yang bahkan bisa mencapai jendela (window), sambil menjalankan semua penangan (handler) yang ada di setiap elemen.

Tapi salah satu penangan (handler) dapat menghentikan peristiwa (event) jika penangan (handler) beranggapan bahwa proses tersebut telah berhasil di proses.

Metode untuk melakukan perhentian adalah event.stpoPropagation().

Contohnya, body.onclick tidak akan dijalankan jika kamu mengklik pada <button>:

<body onclick="alert(`Proses menggelembung tidak mencapai penangan ini`)">
  <button onclick="event.stopPropagation()">Klik saya</button>
</body>
event.stopImmediatePropagation()

Jika sebuah elemen memiliki beberapa penangan (handler) untuk satu peristiwa (event), maka bahkan jika salah satu dari penangan menghentikan proses pengelembungan, penagan yang lain akan tetap di jalankan.

Dengan kata lain, event.stopPropagation() menghentikan proses yang keatas, tapi pada elemen yang sama penangan (handler) lain akan tetap di jalankan.

Untuk menghentukan pengelembungan (handler) dan mencegah penangan (handler) lain yang ada pada elemen tersebut untuk dijalankan, harus menggunakan metode event.stopImmediatePropagation(). Setelah itu tidak akan ada penangan (handler) yang dijalankan.

Jangan menghentikan proses mengelembung jika tidak perlu!

Proses mengelembung cukup berguna. Jangan menghentikan proses ini jika tidak ada perlu: tentu saja harus di pikir dengan baik-baik.

Terkadang event.stopPropagation() akan menyebabkan jebakan tersembunyi yang mungkin akan menjadi masalah.

Contoh:

  1. Kita membuat sebuah menu yang bersarang. Pada setiap submenu penangan (handles) klik pada elemen itu dan menjalankan stopPropagation jadi bagian luar menu tidak akan dijalankan.
  2. Kemudian kita memutuskan untuk menangkap klik pada keseluruhan jendela (window), untuk melacak kebiasaan pengguna (dimana biasa pengguna mengklik). Beberapa sistem analisa menggunakan metode ini. Biasanya code yang digunakan document.addEventListener('click'…) untuk menangkap semua klik.
  3. Analisis kita tidak akan bekerja pada area dimana kita telah menghentikan peristiwa klik dengan menggunakan stopPropagation. Dengan kata lain kita telah membuat daerah mati (dead zone).

Biasanya tidak ada keperluan utama yang membuat kita harus menghentikan proses mengelembung. Sebuah fungsi yang kelihatannya membutuhkan penggunaan metode itu bisa di selesaikan dengan menggunakan cara lain. Salah satunya dengan menggunakan peristiwa khusus, kita akan membahasnya nanti. Dan juga kita dapat menulis data kedalam objek event pada sebuah penangan (handler) dan membacanya pada penangan (handler) lainnya, jadi kita dapat meneruskan data tentang proses yang terjadi dibawah ke penangan (handler) elemen atas.

Penangkapan (Capturing)

Ada juga sebuah fase pada proses peristiwa yang disebut dengan Penangkapan (capturing). proses ini jarang digunakan, tapi akan berguna pada saat dibutuhkan.

Standar sebuah Peristiwa DOM terbagi menjadi 3 fase, yaitu:

  1. fase penangkapan (capturing phase) – peristiwa mulai mencari elemen.
  2. fase target (target phase) – peristiwa menemukan elemen.
  3. fase mengelembung (bubbling phase) – peristiwa mulai naik ke atas dari elemen dasar.

Berikut ini sebuah gambar tentang klik yang terjadi pada <td> didalam sebuah tabel, yang diambil dari spesifikasi:

Maka: untuk klik pada <td> peristiwa (event) akan pertama melewati elemen paling atas dan turun ke elemen yang bawaha (fase penangkapan), kemudian pada saat mencapai elemen yang di target akan di jalankan pada elemen tersebut (fase target), dan kemudia peristiwa itu akan naik ke atas (fase mengelembung), sambil memanggil penangan (handler) yang ada.

Sebelumnya kita hanya membahas tentang proses pengelembungan, karena proses penangkapan jarang digunakan, biasanya proses ini tidak terlihat oleh kita.

Penangan (Handlers) yang di tambahkan menggunakan on<event>-properti atau menggunakan atribut HTML atau menggunakan dua argumen addEventListener(event, handler) tidak mengetahui tentang proses penangkapan, mereka hanya menjalankan fase ke 2 dan fase ke 3.

Untuk menangkap sebuah peristiwa pada fase penangkapan, kita perlu mengatur penangan (handler) pilihan capture menjadi true:

elem.addEventListener(..., {capture: true})
// atau, hanya "true" karena merupakan alias dari {capture: true}
elem.addEventListener(..., true)

Hanya ada 2 kemungkinan nilai dari pilihan capture:

  • Jika false (bawaan (default) ), maka penangan (handler) di atur pada fase pengelembungan atau fase ke 3.
  • Jika true, aka penangan (handler) di atur pada fase penangkapan atau fase pertama.

Catatan, sementara secara umum hanya ada 3 fase, dan fase ke dua (“fase target”: peristiwa mencapai elemen yang di target) tidak di tangani secara terpisah: penangan (handler) pada kedua fase penangkapan dan pengelembungan di jalankan pada fase tersebut.

Mari lihat kedua fase penangkapan dan pengelembungan:

<style>
  body * {
    margin: 10px;
    border: 1px solid blue;
  }
</style>

<form>FORM
  <div>DIV
    <p>P</p>
  </div>
</form>

<script>
  for(let elem of document.querySelectorAll('*')) {
    elem.addEventListener("click", e => alert(`Penangkapan: ${elem.tagName}`), true);
    elem.addEventListener("click", e => alert(`Pengelembungan: ${elem.tagName}`));
  }
</script>

Kode mengatur penangan(handler) klik pada setiap elemen yang ada di dalam dokumen untuk melihat elemen mana yang berfungsi.

Jika kamu klik pada <p>, maka rangkaian peristiwa sebagai berikut:

  1. HTMLBODYFORMDIV (fase penangkapan, pendengar pertama),
  2. P (fase target, dijalankan 2 kali, karena kita mengatur 2 pendengar: penangkapan dan pengelembungan),
  3. DIVFORMBODYHTML (fase pengelembungan, pendengar kedua).

Ada sebuah properti event.eventPhase yang akan memberikan kita nomor dari fase yang dimana peristiwa tersebut di tangkap. Tapi properti ini jarang digunakan, karena kita biasanya mendapat info itu dari penangan(handler) itu sendiri.

Untuk menghapus penangan, removeEventListener membutuhkan fase yang sama

Jika kita menggunakan addEventListener(..., true), maka kita harus menggunakan fase yang sama pada removeEventListener(..., true) untuk menghapus penangan secara benar.

Pendengar pada elemen dan fase yang sama akan dijalankan berdasarkan urutan mereka

Jika kita memiliki beberapa penangan pada fase yang sama, dan di atur pada elemen yang sama dengan menggunakan addEventListener, mereka akan berjalan sesuai dengan urutan mereka di buat:

elem.addEventListener("click", e => alert(1)); // akan selalu berjalan duluan
elem.addEventListener("click", e => alert(2));

Ringkasan

Pada saat sebuah peristiwa (event) terjadi – elemen yang paling dalam dimana peristiwa itu terjadi akan di tandai dengan label “target elemen” (event.target).

  • Kemudian peristiwa akan turun kebawah dari akar dokumen ke event.target, memanggil penangan yang di atur dengan addEventListener(...,true) (true kependekan dari {capture: true}).
  • Kemudian penangan akan di panggil pada target elemen itu sendiri.
  • Kemudian peristiwa akan naik ekatas dari event.target ke akar dokumen, memanggil penangan yang di ataur menggunakan on<event>, atribut HTML dan addEventListener tanpa argumen ke tiga atau dengan `false/{capture:false}.

Setiap penangan(handler) memiliki akses ke properti objek event:

  • event.target – elemen paling bawah dimana peristiwa itu terjadi.
  • event.currentTarget (=this) – merupakan elemen yang menangani peristiwa (elemen yang memiliki penangan (handler)).
  • event.eventPhase – fase yang sedang terjadi (penangkapan=1, target=2, pengelembungan=3).

Penangan dapat menghentikan peristiwa dengan memanggil event.stopPropagation(), tapi tidak direkomendasikan, karena kita tidak belum tentu tidak memerlukan peristiwa itu pada elemen di atas, mungkin untuk hal yang berbeda.

Fase penangkapan jarang digunakan, biasanya kita menangani peristiwa yang mengelembung. Dan ada logika dibaliknya.

Pada dunianya, pada saat kecelakaan terjadi, petugas setempat akan bereaksi duluan. Mereka lebih mengetahui daerah dimana kejadian itu terjadi. Kemudian petugas yang bertingkat tinggil jika dibutuhkan.

Hal yang sama juga untuk penanganan peristiwa. Kode yang mengatur penangan (handler) pada elemen tertentu mengetahui dengan maksimum rincian tentang elemen tersebut dan apa yang harus dilakukan. Sebuah penangan (handler) pada <td> mungkin lebih cocok untuk <td>, penangan itu mengetahui segalanya, jadi penangan itu harus dijalankan duluan. Kemudian elemen yang diatasnya mengetahui tentang konteksnya, mungkin lebih sedikit, dan seterusnya sampai pada elemen yang paling atas, yang mengatur tentang konsep secara umum dan dijalankan paling akhir.

Pengelembungan dan Penangkapan menyediakan sebuah fondasi untuk “event delegation” – sebuah pola penanganan peristiwa yang cukup penting yang akan kita pelajari pada bab selanjutnya.

Peta tutorial

komentar

baca ini sebelum berkomentar…
  • Jika Anda memiliki saran apa yang harus ditingkatkan - silakan kunjungi kirimkan Github issue atau pull request sebagai gantinya berkomentar.
  • Jika Anda tidak dapat memahami sesuatu dalam artikel – harap jelaskan.
  • Untuk menyisipkan beberapa kata kode, gunakan tag <code>, untuk beberapa baris – bungkus dengan tag <pre>, untuk lebih dari 10 baris – gunakan sandbox (plnkr, jsbin, < a href='http://codepen.io'>codepen…)