31 Desember 2021

_Browser default actions_

Banyak peristiwa yang otomatis menjalankan sebuah aksi oleh browser.

Contohnya:

  • Sebuah klik pada link ??? akan mengarahkan ke URL tersebut.
  • Sebuah klik pada tombol formulir ??? akan melakukan proses pengiriman data formulir ke server.
  • Menekan tombol mouse pada sebuah teks dan mengerakannya ??? akan memilih teks tersebut.

Jika ke menanggani sebuah peristiwa di JavaScript, kita mungkin tidak mau aksi bawaan dari browser terjadi, dan kita mau membuat sebuah aksi baru.

Mencegah aksi browser

Ada 2 cara untuk memberitahukan browser bahwa kita tidak mau peristiwa itu terjadi:

  • Cara utama dengan menggunakan objek event. Ada sebuah metode dengan nama event.preventDefault().
  • Jika penangan (handler) di atur menggunakan on<event> (bukan addEventListener), maka mengembalikan false juga bisa befungsi.

Pada HTML dibawah, sebuah klik pada link tidak akan memindahkan halaman, browser tidak melakukan apapun:

<a href="/" onclick="return false">Klik disini</a>
atau
<a href="/" onclick="event.preventDefault()">disini</a>

Pada contoh selanjutnya kita akan menggunakan teknik untuk menu yang dibuat oleh JavaScript.

Returning false dari sebuah penangan adalah sebuah pengecualian

Nilai yang di kembalikan oleh penangan (handler) bisanya dibiar.

Hanya pada return false terdapat pengecualian jika tejadi pada sebuah penangan (handler) yang diatur menggunakan on<event>.

Pada semua kasus yang lain, nilai return akan dibiarkan. Khusunya, tidak masuk akal untuk mengembalikan true.

Contoh: menu

Bayangkan menu situs, seperti ini:

<ul id="menu" class="menu">
  <li><a href="/html">HTML</a></li>
  <li><a href="/javascript">JavaScript</a></li>
  <li><a href="/css">CSS</a></li>
</ul>

Dan begini tampilannya jika ditambahkan CSS:

Item menu diimplementasikan sebagai HTML-link <a>, bukan tombol <button>. Ada beberapa alasan untuk melakukannya, misalnya:

  • Banyak orang suka menggunakan ???klik kanan??? ??? ???buka di jendela baru???. Jika kita menggunakan <button> atau <span>, itu tidak akan berhasil.
  • Mesin pencari mengikuti tautan <a href="..."> saat mengindeks.

Jadi kami menggunakan <a> di markup. Tetapi biasanya kami bermaksud menangani klik dalam JavaScript. Jadi kita harus mencegah tindakan bawaan browser.

Seperti ini:

menu.onclick = function(event) {
  if (event.target.nodeName != 'A') return;

  let href = event.target.getAttribute('href');
  alert( href ); // ...bisa memuat dari server, membuat UI dll

  return false; // mencegah bawaan browser (tidak pergi ke URL)
};

Jika kita menghilangkan return false, maka setelah kode kita dijalankan, browser akan melakukan ???tindakan bawaan??? ??? menavigasi ke URL di href. Dan kami tidak membutuhkannya di sini, karena kami menangani klik sendiri.

Dan juga, menggunakan delegasi peristiwa di sini membuat menu kami sangat fleksibel. Kita dapat menambahkan daftar bersarang dan menatanya menggunakan CSS ke ???slide down???.

Peristiwa lanjutan

Peristiwa tertentu mengalir satu ke yang lain. Jika kita mencegah peristiwa pertama, tidak akan ada yang kedua.

Contohnya, mousedown pada sebuah <input> mengarah ke fokus di dalamnya, dan peristiwa focus. Jika kita mencegah peristiwa mousedown, tidak ada fokus.

Coba klik pada <input>dibawah ??? peristiwa focus terjadi. Tapi jika kita klik pada yang kedua, tidak ada fokus.

<input value="Ada fokus" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Klik saya">

Itu karena tindakan browser dibatalkan pada mousedown. Pemfokusan masih dimungkinkan jika kita menggunakan cara lain untuk memasukkan input. Misalnya, tombol Tab untuk beralih dari input pertama ke input kedua. Tapi tidak dengan klik mouse lagi.

Pilihan penangan ???passive???

Pilihan passive: true opsional dari addEventListener memberi sinyal kepada browser bahwa penangan tidak akan memanggil preventDefault().

Mengapa itu mungkin diperlukan?

Ada beberapa peristiwa seperti touchmove pada perangkat seluler (ketika pengguna menggerakkan jari mereka melintasi layar), yang menyebabkan pengguliran secara bawaan, tetapi pengguliran tersebut dapat dicegah menggunakan preventDefault() di pengendali.

Jadi, ketika browser mendeteksi peristiwa seperti itu, browser harus terlebih dahulu memproses semua penangan (handler), dan kemudian jika preventDefault tidak dipanggil di mana pun, browser dapat melanjutkan dengan menggulir. Itu dapat menyebabkan penundaan dan ???kegelisahan??? yang tidak perlu di UI.

Pilihan passive: true memberi tahu browser bahwa penangan (handler) tidak akan membatalkan pengguliran. Kemudian browser segera menggulir memberikan pengalaman lancar maksimal, dan peristiwa ditangani dengan cara.

Untuk beberapa browser (Firefox, Chrome), passive atur menjadi true secara bawaan untuk peristiwa touchstart dan touchmove.

event.defaultPrevented

Properti event.defaultPrevented adalah true jika tindakan default dicegah, dan false jika sebaliknya.

Ada kasus penggunaan yang menarik untuk itu.

Anda ingat di bab Menggelembung (_bubbling_) dan menangkap (_capturing_) kita berbicara tentang event.stopPropagation() dan mengapa menghentikan pengelembungan itu buruk?

Terkadang kita dapat menggunakan event.defaultPrevented sebagai gantinya, untuk memberi sinyal pada penangan peristiwa lain bahwa peristiwa tersebut ditangani.

Mari kita lihat contoh praktisnya.

Secara bawaan, browser pada peristiwa contextmenu (klik kanan mouse) menampilkan menu konteks dengan pilihan standar. Kita bisa mencegahnya dan menunjukkannya sendiri, seperti ini:

<button>Klik kanan menampilkan menu konteks browser</button>

<button oncontextmenu="alert('Buat menu baru'); return false">
  Klik kanan menampilkan menu konteks browser
</button>

Sekarang, selain menu konteks itu, kami ingin menerapkan menu konteks seluruh dokumen.

Setelah klik kanan, menu konteks terdekat akan muncul.

<p>Klik kanan menampilkan menu konteks browser</p>
<button id="elem">Klik kanan menampilkan menu konteks browser</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Tombol konteks menu");
  };

  document.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Dokumen konteks menu");
  };
</script>

Masalahnya adalah ketika kita mengklik elem, kita mendapatkan dua menu: level tombol dan (peristiwa muncul) menu level dokumen.

Bagaimana memperbaikinya? Salah satu solusinya adalah dengan berpikir seperti: ???Ketika kita menangani klik kanan pada pengendali tombol, mari hentikan gelembungnya??? dan gunakan event.stopPropagation():

<p>Klik kanan menampilkan menu konteks browser</p>
<button id="elem">Klik kanan menampilkan menu konteks browser (diperbaiki dengan event.stopPropagation)</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    event.stopPropagation();
    alert("Tombol konteks menu");
  };

  document.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Dokumen konteks menu");
  };
</script>

Sekarang menu tingkat tombol berfungsi sebagaimana dimaksud. Tapi bayarannya tinggi. Kami selamanya menolak akses ke informasi dari klik kanan untuk kode yang lain, termasuk penghitung yang mengumpulkan statistik dan sebagainya. Hal ini sangat tidak bijaksana.

Solusi alternatif adalah dengan memeriksa penangan (handler) document jika tindakan bawaan dicegah? Jika demikian, maka peristiwa itu ditangani, dan kita tidak perlu bereaksi.

<p>Klik kanan menampilkan menu konteks browser (Menambah pemeriksa untuk event.defaultPrevented)</p>
<button id="elem">Klik kanan menampilkan menu konteks browser</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Tombol konteks menu");
  };

  document.oncontextmenu = function(event) {
    if (event.defaultPrevented) return;

    event.preventDefault();
    alert("Dokumen konteks menu");
  };
</script>

Sekarang semuanya berfungsi dengan benar. Jika kita memiliki elemen bersarang, dan masing-masing elemen memiliki menu konteksnya sendiri, itu juga akan berfungsi. Pastikan untuk memeriksa event.defaultPrevented di setiap penangan (handler) contextmenu.

event.stopPropagation() dan event.preventDefault()

Seperti yang bisa kita lihat dengan jelas, event.stopPropagation() dan event.preventDefault() (juga dikenal dengan return false) adalah dua hal yang berbeda. Dan mereka tidak terkait satu dengan yang lain.

Arsitektur menu konteks bersarang

Ada juga cara alternatif untuk mengimplementasikan menu konteks bersarang. Salah satunya adalah memiliki satu objek global dengan penangan (handler) untuk document.oncontextmenu, dan juga metode yang memungkinkan kita untuk menyimpan penangan (handler) lain di dalamnya.

Objek akan menangkap klik kanan apa pun, melihat melalui penangan (handler) yang tersimpan dan menjalankan yang sesuai.

Tapi kemudian setiap bagian kode yang menginginkan menu konteks harus tahu tentang objek itu dan menggunakan bantuannya alih-alih penangan (handler) contextmenu sendiri.

Rincian

Ada banyak aksi bawaan browser:

  • mousedown ??? memulai pemilihan (gerakkan mouse untuk memilih).
  • click pada <input type="checkbox"> ??? centang/hapus centang pada input.
  • submit ??? mengklik <input type="submit"> atau menekan Enter di dalam bidang formulir menyebabkan peristiwa ini terjadi, dan browser mengirimkan formulir setelahnya.
  • keydown ??? menekan tombol dapat menyebabkan penambahan karakter ke dalam bidang, atau tindakan lainnya.
  • contextmenu ??? peristiwa terjadi dengan klik kanan, tindakannya adalah menampilkan menu konteks browser.
  • ???ada lagi???

Semua tindakan bawaan dapat dicegah jika kita ingin menangani peristiwa (event) secara eksklusif dengan JavaScript.

Untuk mencegah peristiwa (event) bawaan ??? gunakan event.preventDefault() atau return false. Metode kedua hanya berfungsi untuk penangan (handler) yang ditetapkan dengan on<event>.

Pilihan passive: true dari addEventListener memberi tahu browser bahwa tindakan tersebut tidak akan dicegah. Itu berguna untuk beberapa aktivitas seluler, seperti touchstart dan touchmove, untuk memberi tahu browser bahwa browser tidak boleh menunggu semua penangan (handler) selesai sebelum menggulir.

Jika tindakan bawaan dicegah, nilai event.defaultPrevented menjadi true, jika tidak maka false.

Tetap semantik, jangan menyalahgunakan elemen

Secara teknis, dengan mencegah tindakan bawaan dan menambahkan JavaScript, kita dapat menyesuaikan perilaku elemen apa pun. Misalnya, kita dapat membuat tautan <a> berfungsi seperti tombol, dan tombol <button> berfungsi sebagai tautan (mengalihkan ke URL lain atau lebih).

Tapi secara umum kita harus menjaga makna semantik dari elemen HTML. Misalnya, <a> harus melakukan navigasi, bukan tombol.

Selain menjadi ???hal yang baik???, itu membuat HTML Anda lebih baik dalam hal aksesibilitas.

Juga jika kita mempertimbangkan contoh dengan <a>, maka harap dicatat: browser memungkinkan kita untuk membuka link tersebut di jendela baru (dengan mengklik kanan link tersebut dan cara lain). Dan penggunan menyukai hal tersebut. Tetapi jika kita membuat tombol berperilaku sebagai tautan dengan menggunakan JavaScript dan bahkan terlihat seperti tautan menggunakan CSS, maka fitur browser khusus <a> tetap tidak akan berfungsi pada tombol itu.

Tugas

pentingnya: 3

Kenapa pada kode dibawah return false tidak berfungsi sama sekali?

<script>
  function handler() {
    alert( "..." );
    return false;
  }
</script>

<a href="https://w3.org" onclick="handler()">browser akan membuka w3.org</a>

browser akan mengikuti URL yang di klik, tapi kita tidak mau hal itu terjadi.

Bagaimana cara memperbaikinya?

Pada saat browser membaca atribut on* seperti onclick, browser akan membuat sebuah penangan (handler) dari kontennya.

Untuk onclick"handler()" fungsinya akan menjadi:

function(event) {
  handler() // konten dari onclick
}

Sekarang kita bisa melihat bahwa nilai yang dikembalikan oleh handler() tidak digunakan dan tidak mempengaruhi hasilnya.

Cara memperbaikinnya mudah:

<script>
  function handler() {
    alert("...");
    return false;
  }
</script>

<a href="https://w3.org" onclick="return handler()">w3.org</a>

Kita juga bisa menggunakan event.preventDefault(), seperti ini:

<script>
  function handler(event) {
    alert("...");
    event.preventDefault();
  }
</script>

<a href="https://w3.org" onclick="handler(event)">w3.org</a>
pentingnya: 5

Buat semua link yang ada didalam elemen dengan id="contents" akan menanyakan kepada pengguna jika mereka mau meninggalkan website. Dan jika mereka tidak mau maka halaman tidak akan berpindah.

Seperti ini:

Rincian:

  • HTML didalam elemen bisa di muat dan di buat kembali secara dinamis secara acak, jadi kita tidak bisa menemukan semua link dan memberikan penangan (handler). Gunakan delegasi peristiwa.
  • Kontent bisa saja merupakan tag bersarang. Di dalam link juga, seperti <a href=".."><i>...</i></a>.

Buka sandbox untuk tugas tersebut.

Itu merupakan salah satu cara yang bagus dalam pemanfaatan pola delegasi peristiwa.

Pada kehidupan nyata kita bisa mengirim permintaan ???logging??? ke server yang menyimpan informasi tentang dari mana pengujung meninggalkan website daripada menanyakannya secara langsung. Atau kita bisa memuat konten dan menunjukannya tepat pada halaman (jika diizinkan).

Yang kita butuhkan hanyalah menangkap contents.onclick dan menggunakan confim untuk menanyakan pengguna. Sebuah ide bagus adalah dengan menggunakan link.getAttribute('href') dari pada menggunakan link.href untuk URL. Lihat solusinya untuk rincian.

Buka solusi di kotak pasir.

pentingnya: 5

Create an image gallery where the main image changes by the click on a thumbnail.

Like this:

P.S. Use event delegation.

Buka sandbox untuk tugas tersebut.

The solution is to assign the handler to the container and track clicks. If a click is on the <a> link, then change src of #largeImg to the href of the thumbnail.

Buka solusi di kotak pasir.

Peta tutorial