Menangkap dan pengelembungan mengizinkan kita untuk mengimplementasikan salah satu pola penanganan peristiwa paling kuat yang disebut dengan delegasi peristiwa (event delegation).
Ide utama yaitu jika kita memiliki banyak elemen yang akan di tangani dengan cara yang sama, maka sebaiknya daripada memberikan sebuah penangan pada setiap elemen tersebut – kita buat sebuah penangan (handler) pada elemen atas yang melingkupi semua elemen tersebut.
Pada penangan kita mendapatkan event.target
untuk melihat dimanakah kejadian itu terjadi, dan akan menangani kejadian itu.
Mari lihat sebuah contoh – Ba-Gua diagram mencerminkan filosofi Cina kuno.
Ini dia:
HTMLnya seperti ini:
<table>
<tr>
<th colspan="3"><em>Bagua</em> Bagan: Arah, Elemen, Warna, Arti</th>
</tr>
<tr>
<td class="nw"><strong>Barat Laut</strong><br>Logam<br>Perak<br>Orang Tua</td>
<td class="n">...</td>
<td class="ne">...</td>
</tr>
<tr>...2 buah teks seperti di atas...</tr>
<tr>...2 buah teks seperti di atas...</tr>
</table>
Tabel memiliki 9 sel, tapi bisa saja memiliki 99 atau 9999 sell, tidaklah penting.
Tugas kita adalah untuk memberikan highlight ke sel <td>
yang di klik.
Daripada mengatur sebuah penangan onclick
pada setiap <td>
(yang bisa sangat banyak) – kita akan mengatur sebuah penangan “penangkap-semua” pada elemen <table>
.
Penangan itu akan menggunakan event.target
untuk mendapatkan elemen yang diklik dan menghighlightnya.
Kodenya:
let selectedTd;
table.onclick = function(event) {
let target = event.target; // dimanakah klik terjadi?
if (target.tagName != 'TD') return; // bukan di TD? kita tidak peduli
highlight(target); // highlight elemen itu
};
function highlight(td) {
if (selectedTd) { // hapus elemen lain yang sudah di highlight
selectedTd.classList.remove('highlight');
}
selectedTd = td;
selectedTd.classList.add('highlight'); // menghighlight elemen yang baru
}
Kode seperti itu, tidak peduli berapa banyak sel yang ada pada table tersebut. Kita bisa menambahkan/menghapuskan td
secara dinamis pada waktu kapanpun dan proses menghighlight akan tetap berfungsi.
Tapi, tetap ada kekurangannya.
Klik mungkin tidak terjadi pada <td>
, tapi pada elemen didalamnya.
Pada kasus kita jika dilihat pada HTML, kita memiliki sebuah elemen bersarang pada <td>
, seperti <strong>
:
<td>
<strong>Barat Laut</strong>
...
</td>
Biasanya, jika klik terjadi pada <strong>
maka elemen itu akan menjadi nilai dari event.target
.
Pada penangan (handler) table.onclick
kita sebaiknya mengambil event.target
dan mencari tahu apakah klik terjadi didalam <td>
atau tidak.
Ini kode yang sudah diperbaiki:
table.onclick = function(event) {
let td = event.target.closest('td'); // (1)
if (!td) return; // (2)
if (!table.contains(td)) return; // (3)
highlight(td); // (4)
};
Penjelasan:
- Metode
elem.closest(selector)
akan mengembalikan elemen atas terdekat yang sama dengan pemilih (selector). Pada kasus kita yang dicari adalah<td>
pada bagian atas dari elemen sumber. - Jika
event.target
tidak didalam<td>
, maka kita akan langsung mengembalikan, karena tidak ada yang bisa dilakukan. - Jika pada kasus elemen bersarang didalam tabel,
event.target
bisa saja merupakan elemen<td>
, tapi berada diluar tabel yang kita atur. Jadi kita memeriksa jika tabel itu adalah tabel yang kita butuh<td>
. - Dan, jika benar, maka beri highlight pada elemen itu.
Hasilnya, kita memiliki kode yang cepat, efisien dalam memberikan highlight, yang tidak peduli terhadap jumlah dari elemen <td>
pada sebuah tabel.
Contoh Delegasi: tindakan dalam markup
Ada kegunaan lain untuk delegasi acara.
Bayangkan, kita mau membuat sebuah menu dengan tombol “Simpan”, “Muat”, “Cari” dan seterusnya. Dan ada sebuah objek dengan metode simpan
, muat
, cari
… Bagaimana cara untuk menyamakan mereka?
Ide pertama yaitu dengan mengatur penangan (handler) berbeda pada setiap tombol, Tapi ada solusi yang lebih elegan. Kita bisa menambahkan sebuah penangan (handler) untuk seseluruhan menu dan menambahkan atribut data-action
untuk tombol yang bisa memanggil/memiliki sebuah metode:
<button data-action="save">Klik untuk simpan</button>
Penangan (handler) membaca atribut dan mengeksekusi metode yang sama dengan atribut. Coba lihat contohnya:
<div id="menu">
<button data-action="save">Simpan</button>
<button data-action="load">Muat</button>
<button data-action="search">Cari</button>
</div>
<script>
class Menu {
constructor(elem) {
this._elem = elem;
elem.onclick = this.onClick.bind(this); // (*)
}
save() {
alert('Menyimpan');
}
load() {
alert('Memuat');
}
search() {
alert('Mencari');
}
onClick(event) {
let action = event.target.dataset.action;
if (action) {
this[action]();
}
};
}
new Menu(menu);
</script>
Harap dicatat bahwa this.onClick
terikat pada this
di (*)
. Itu penting, karena jika tidak this
didalamnya akan menyimpan referensi ke DOM elemen (elem
), buka ke objek Menu
, dan this[action]
tidak akan seperti yang kita inginkan.
Jadi,apakah keuntuk yang diberikan delegasi kepada kita disini?
- Kita teidak perlu lagi menulis kode untuk mengatur penangan (handler) untuk setiap tombol. Kita hanya perlu membuat sebuah metode dan menaruh markup didalamnya.
- Struktur HTML menjadi fleksible, dan kita bisa menambah/menghapus tombol kapanpun kita mau.
Kita juga bisa menggunakan class .action-save
, action-load
, tapi sebuah atribut data-action
lebih baik secara semantik. Dan kita bisa gunakan itu pada aturan CSS juga.
Perilaku pola
Kita juga bisa menggunakan delegasi peristiwa untuk menambahkan ‘perilaku’ kepada elemen secara deklarasi, dengan atribut khusus dan class.
Pola memiliki 2 bagian:
- Kita tambahkan sebuah atribut khusus ke sebuah elemen yang menjelaskan perilakunya.
- Penangan dokumen secara umum untuk melacak peristiwa, dan jika sebuah peristiwa terjadi pada elemen yang memiliki atribut khusus – jalankan sebuah proses.
Perilaku: Menghitung
Contohnya, disini atribut data-counter
menambahkan sebuah perilaku: “Menambah nilai pada klik” ke tombol:
Penghitung: <input type="button" value="1" data-counter>
Penghitung lainnya: <input type="button" value="2" data-counter>
<script>
document.addEventListener('click', function(event) {
if (event.target.dataset.counter != undefined) { // Jika ada atributnya...
event.target.value++;
}
});
</script>
Jika kita mengklik sebuah tombol – nilainya akan bertambah. Bukan tombol, tapi pendekatan secara umum penting pada kasus ini.
Bisa ada banyak atribut dengan data-counter
sebanyak yang kita mau. Kta bisa menambah atribut baru ke HTML kapanpun kita mau. Menggunakan delegasi peristiwa kita “memperpanjang” HTML, menambahkan sebuah atribut baru untuk menjelaskan sebuah perilaku baru.
addEventListener
Pada saat kita mengatur sebuah penangan peristiwa (event handler) ke objek dokumen
, sebaiknya selalu gunakan addEvenListener
, dan bukan document.on<event>
, karena yang kedua akan mengakibatkan konflik: penangan baru akan menimpah penangan yang lama.
Untuk projek asli, adalah normal untuk memiliki banyak penangan (handler) yang di atur ke document
pada bagian code yang berbeda.
Perilaku: Pengalih
Satu lagi contoh dari perilaku. Sebuah klik pada elemen dengan atribut data-toggle-id
akan menampilkan/menyembunyikan elemen dengan id
yang sama:
<button data-toggle-id="subscribe-mail">
Tampilkan formulir berlangganan
</button>
<form id="subscribe-mail" hidden>
Email kamu: <input type="email">
</form>
<script>
document.addEventListener('click', function(event) {
let id = event.target.dataset.toggleId;
if (!id) return;
let elem = document.getElementById(id);
elem.hidden = !elem.hidden;
});
</script>
Catat lagi apa yang kita lakukan. Sekarang, untuk menambahkan fungsi beralih pada elemen – tidak memerlukan pengetahuan tentang JavaScript, hanya perlu menggunakan atribut data-toggle-id
.
Hal ini akan sangat menyederhanakan proses – tidak perlu menulis JavaScript untuk setiap elemen. Hanya perlu menggunakan perilaku. Penangan (handler) tingkat dokumen akan membuat proses ini berfungsi pada setiap elemen yang ada di dalam halaman tersebut.
Kita juga bisa menggabungkan beberapa perilaku pada sebuah elemen.
“Perilaku” pola bisa menjadi alternatif terhadap fargmen kecil JavaScript.
Ringkisan
Delegasi peristiwa sangatlah keren! Itu salah satu pola yang paling berguna untuk peristiwa DOM.
Itu sering digunakan untuk menambahkan penangan untuk elemen yang mirip, tapi bukan hanya untuk itu.
Algoritmanya:
- Taruh sebuah penangan (handler) pada elemen atas.
- Di penangan (handler) – periksa sumber elemen dengan menggunakan
event.target
. - Jika peristiwa terjadi didalam elemen yang kita inginkan, maka tangani peristiwa itu.
Keuntungan:
- Menyederhanakan proses inisialisasi dan menghemat memori: tidak perlu membuat banyak penangan (handler).
- Sedikit Kode: saat menambahkan dan menghapus elemen, kita tidak perlu tambah/hapus penangan (handler).
- Modifikasi DOM: Kita bisa secara banyak menambahkan/menghapuskan elemen dengan
innerHTML
dan sejenisnya.
Delegasi tentu juga memiliki batasannya:
- Pertama, peristiwa harus bisa mengelembung. Bebebrapa peristiwa tidak mengelembung. Juga, penangan (handler) pada level bawah tidak boleh menggunakan
event.stopPropagation()
. - Kedua, delegasi mungkin menambahkan muatan pada CPU, karena penangan (handler) pada level atas akan bereaksi pada peristiwa yang terjadi didalam elemen itu, tidak peduli jika peristiwa itu yang kita inginkan atau tidak. Tapi biasanya proses muatannya tidak besar dan bisa diabaikan, jadi kita tidak perlu memperhitungkannya.
komentar
<code>
, untuk beberapa baris – bungkus dengan tag<pre>
, untuk lebih dari 10 baris – gunakan sandbox (plnkr, jsbin, < a href='http://codepen.io'>codepen…)