Event adalah salah satu bagian yang sangat penting dari suatu aplikasi web. Setiap element pada sebuah halaman website dapat membangkitkan event. Tetapi apa yang penting dari kemampuan tersebut?
Event biasanya dibangkitkan ketika suatu element telah mengalami perubahan kondisi. Misalnya, kita ingin memperbesar sebuah gambar yang ditekan oleh user.
Bagaimana caranya kita mengetahui bahwa sebuah gambar telah ditekan oleh user? Caranya adalah dengan memanfaatkan event yang dibangkitkan oleh gambar tersebut ketika ia menerima tekanan, yaitu event click
.
Tentu masih banyak lagi event lain yang dapat dibangkitkan oleh element-element tertentu pada sebuah halaman website. Misalnya ada event yang menandakan bahwa suatu element telah mengalami perubahan ukuran. Ada event yang menandakan bahwa suatu element sedang dipindahkan oleh pengguna. Ada juga event yang mendandakan bahwa tetikus yang digunakan oleh pengguna sedang berpindah tempat.
Jadi, apa itu event? Dan bagaimana cara kerjanya?
Kita ambil kasus dimana user telah menekan sebuah tombol. Ketika sebuah tombol ditekan, ada beberapa hal yang terjadi. Memahami kejadian ini dapat membantu kita untuk merespon dengan benar ketika sebuah event dibangkitkan.
Anggap saja kode html kita seperti berikut ini:
<html>
<head>
<meta charset="UTF-8">
<title>Event</title>
</head>
<body>
<div>
<button>Tekan Saya</button>
</div>
</body>
<html>
Yang terjadi ketika tombol <button>Tekan Saya</button>
ditekan dapat dilihat pada gambar di bawah ini:
Ketika tombol di atas ditekan, javascript tidak langsung membangkitkan event click
pada tombol tersebut. Melainkan, javascript membangkitkan event click
secara berurutan berdasarkan struktur DOM. Mulai dari objek DOM paling tinggi dan kemudian menurun hingga sampai pada tombol yang benar-benar ditekan tadi.
Jadi, javascript membangkitkan event click
pertama kali pada window
. Kemudian, event click
dibangkitkan pada document
, setelah itu html
, dan seterusnya menurun hingga element div
yang berada di atas element button
. Dan kejadian ini dinamakan sebagai Capture Phase.
Nah, kita dapat mendengarkan event click
ini ketika ia masih dalam fase capture. DEMO:
JS Bin<script src="http://static.jsbin.com/js/embed.js"></script>
Jadi, kita memanfaatkan metode yang bernama addEventListener
pada suatu element untuk mendengarkan event dari element tersebut:
target.addEventListener(tipeEvent, listener [, useCapture]);
target
: adalah element yang membangkitkan eventtipeEvent
: adalah event yang dibangkitkan (dalam contoh di atas, tipe Event adalah "click")listener
: metode yang akan dijalankan ketika eventtipeEvent
dibangkitkan padatarget
useCapture
: bila bernilaitrue
, makalistener
akan dijalankan pada fase Capture.
Bila kita tidak mengisi nilai untuk useCapture
, maka nilai default-nya adalah false
. Ketika false
, maka listener
akan dijalankan pada fase Bubbling. Fase Bubbling akan kita bahas pada bagian berikutnya.
Berdasarkan contoh di atas, fase capture hanya berjalan hingga element div
yang berada tepat di atas element button
. Ketika event click
dibangkitkan pada element button
, fasenya sudah berubah menjadi Target Phase. Ini mendandakan bahwa event telah berada tepat di-element yang mengalami interaksi (dalam contoh kita element yang ditekan oleh pengguna).
Tetapi, kenyataannya, banyak browser yang menerapkan fase capture hingga element target (dalam contoh kita element button
). Ini adalah sebuah quirk, atau kejanggalan.
Target Phase Demo<script src="http://static.jsbin.com/js/embed.js"></script>
Pasca event click
dibangkitkan pada element button
, javascript memulai fase baru, yaitu Bubbling Phase. Fase ini dimulai dari element di atasnya button
, yaitu div
, hingga ke element paling atas.
Jadi, fase Bubbling adalah kebalikan dari fase Capture. Tetapi bagaimana dengan sintaks untuk mendengarkan event pada fase Bubbling ini?
Secara sintaksis, perbedaan antara fase Bubbling hanyalah pada parameter useCapture
yang bernilai false
.
document.addEventListener("click", function() {
console.log("document: fase bubbling: click");
}, false);
Setiap listener
yang dipasang pada sebuah event, akan memperoleh Event Object
. Event Object
tersebut memiliki properti-properti menarik yang dapat kita manfaatkan untuk merespon sebuah event.
Kita ambil contoh pembangkitan event click
di atas:
var tombol = document.getElementsByTagName("button")[0];
tombol.addEventListener("click", function( e ) {
}, false);
Parameter e
yang diterima oleh listener
di atas memiliki properti sebagai berikut:
type
(String): tipe dari event yang dibangkitkantarget
(Node): DOM Node dimana event berasal (dalam contoh kita di atas adalahbutton
)currentTarget
(Node): DOM Node dimana event saat ini sedang dibangkitkan (silahkan lihat kembali 3 fase di atas)bubbles
(Boolean): apakah saat ini kita berada pada fase bubbling?preventDefault
(Function): kita dapat memanfaatkan properti ini untuk membatalkan respon default dari user ketikatarget
mengalami eventtype
. Misalnya ketika suatuurl
ditekan oleh pengguna, maka respon default-nya adalah membuka link tersebut. Namun kita dapat membatalkan respon tersebut dengan menggunakan propertipreventDefault
ini.stopPropagation
(Function): Memberhentikan fase yang sedang terjadi.stopImmediatePropagation
(Function): Hampir sama sepertistopPropagation
di atas. Perbedaannya akan kita bahas nantinya.cancelable
(Boolean): mendakan apakahpreventDefault
bisa dijalankan atau tidak.defaultPrevented
(Boolean): menandakan apakahpreventDefault
telah dijalankan.isTrusted
(Boolean): menandakan bahwa event dibangkitkan oleh browser, bukan secara manual melalui javascript.eventPhase
(Number): menandakan fase aktif yang sedang terjadi: (0) capture, (1) target, (2) bubbling.timestamp
(Number): waktu yang menandakan kapan event terjadi.
Masih ada lebih banyak lagi properti dari parameter e
. Namun tambahan tersebut tergantung dari jenis event yang sedang dibangkitkan.
Untuk berhenti mendengarkan suatu element, maka kita dapat memanfaatkan metode removeEventListener
:
var tombol = document.getElementsByTagName("button")[0];
function callback() {
console.log("#tombol ditekan");
}
// memasang event handler
tombol.addEventListener("click", callback, false);
// melepas event handler
tombol.removeEventListener("click", callback, false);
Kita dapat memberhentikan fase dari event dengan menggunakan metode stopPropagation
dan stopImmediatePropagation
. Meskipun kegunaannya hampir sama, namun terdapat hal yang membedakannya.
Untuk melihat perbedaan antara keduanya, silahkan pembaca melihat demo ini:
JS Bin<script src="http://static.jsbin.com/js/embed.js"></script>
Jadi, stopPropagation
akan memberhentikan fase yang sedang berjalan. Sehingga dalam contoh di atas, event click
pada div
tidak dijalankan ketika button
di tekan. Hal yang sama juga terjadi ketika menggunakan stopImmediatePropagation
.
Bedanya adalah, stopImmediatePropagation
juga memberhentikan listener
yang dipasang untuk mendengarkan event yang sama. Namun, listener
yang diberhentikan hanyalah listener yang dipasang setelah listener
yang menjalankan stopImmediatePropagation
terpasang.
Jadi, dalam contoh di atas, listener
kedua yang dipasang pada button-dua
tidak dijalankan ketika button-dua
ditekan. Itu karena listener
pertama menjalankan stopImmediatePropagation
.
Sementara itu, listener
kedua pada button-satu
tetapi dijalankan ketika button-satu
ditekan. Meskipun listener
pada button-satu
menjalankan stopPropagation
.
Browser secara default memiliki reaksi tersendiri ketika suatu element membangkitkan event. Misal, ketika element a
ditekan, maka browser akan mencoba membuka link pada element tersebut.
Bagaimana bila kita ingin agar browser tidak menjalankan reaksi defaultnya? Kita gunakan preventDefault
.
JS Bin<script src="http://static.jsbin.com/js/embed.js"></script>
Ada yang perlu kita perhatikan ketika menjalankan sebuah listener
, yaitu konteks dari fungsi listener
tersebut.
Misal, perhatikan demo berikut:
JS Bin<script src="http://static.jsbin.com/js/embed.js"></script>
Masalahnya adalah, ketika metode orang.getName()
dijalankan, scope dari fungsi tersebut bukanlah objek orang
, melainkan element button
. Sehingga, this.name
bernilai undefined
. Karena button
tidak memiliki properti tersebut.
Untuk menangani hal tersebut, kita dapat menggunakan pendekatan berikut:
button.addEventListener("click", function() {
orang.getName();
});
Atau, kita dapat juga menerapkan sebuah metode handleEvent
pada objek orang
di atas. Secara otomatis, nilai dari this
akan dipasang pada objek yang memiliki metode handleEvent
tersebut.
var orang: {
name: "keripix",
handleEvent: function() {
alert(this.name);
}
}
button.addEventListener("click", orang, false);
Kita dapat membangkitkan suatu event pada suatu element menggunakan javascript.
Misal, kita ingin membangkitkan event click
secara manual:
button.dispatchEvent(new Event("click"));
Kita juga bisa membuat sebuah event baru:
JS Bin<script src="http://static.jsbin.com/js/embed.js"></script>
Untuk menambahkan data khusus pada Event Object, kita dapat menambahkannya pada properti detail
.
Ketika jumlah listener
yang terpasang pada element-element yang berbeda semakin banyak, maka kita berhadapan dengan masalah performa pada browser.
Ketika sebuah listener
dipasang pada element-element pada suatu halaman website, maka browser harus menyimpan referensi terhadap listener
tersebut. Yang berarti, bertambahnya memori yang dibutuhkan.
Semakin banyak listener
yang dipasang pada element-element pada suatu halaman website, maka browser-pun butuh waktu untuk melakukan pemasangan event listener
tersebut.
Untuk mengurangi beban, kita dapat menggunakan event delegation. Caranya adalah dengan memanfaatkan fase Bubbling yang akan dilalui oleh event ketika suatu event dibangkitkan. Berikut adalah demo dari penerapan event delegation.
JS Bin<script src="http://static.jsbin.com/js/embed.js"></script>
Bila melihat contoh di atas, maka daripada memasang event listener
untuk 5 element li
, kita dapat memasang event listener
pada element ul
. Javascript akan menjalankan listener
(berdasarkan contoh di atas), ketika pembangkitan event berada pada fase bubbling. Demikikanlah contoh penerapan event delegation.
Daripada kita menulis event listener
seperti contoh berikut ini:
button.addEventListener("click", function( e ) {
var target = e.target;
target.classList.add("highlight");
});
Ada baiknya kita mengubahnya menjadi:
button.addEventListener("click", function( e ) {
var target = e.target;
addHighlighter(target);
});
function addHighlighter( el ) {
el.classList.add("highlight");
}
Alasannya adalah, pertama, kita memisahkan antara penanganan event dengan apa yang seharusnya terjadi ketika event dibangkitkan. Penanganan event hanya mengambil apa element target
. Ia akan mendelegasikan tugas "apa yang harus dilakukan ketika button ditekan" pada metode lain.
Keuntungan dari pemisahan di atas adalah kita dapat memanfaatkan metode addHighlighter
pada tempat lain. Karena ada kemungkinan besar, yang memperoleh manfaat dari keberadaan metode tersebut bukan hanya listener
di atas saja.
Alasan kedua, kita dapat lebih mudah menguji respon terhadap suatu event tanpa harus mensimulasikan pembangkitan event tersebut pada unit test kita. Jadi, unit test kita akan menjadi lebih sederhana.