20 Desember 2021

Delegasi Peristiwa

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:

  1. 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.
  2. Jika event.target tidak didalam <td>, maka kita akan langsung mengembalikan, karena tidak ada yang bisa dilakukan.
  3. 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>.
  4. 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:

  1. Kita tambahkan sebuah atribut khusus ke sebuah elemen yang menjelaskan perilakunya.
  2. 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.

Untuk penangan tingkat dokumen – selalu gunakan 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:

  1. Taruh sebuah penangan (handler) pada elemen atas.
  2. Di penangan (handler) – periksa sumber elemen dengan menggunakan event.target.
  3. 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.

Tugas

pentingnya: 5

Ada sebuah daftar dengan tombol [x]. Buat tombol itu berfungsi.

Seperti ini:

Tambahan: Gunakan 1 event listener pada container, gunakan delegasi peristiwa (event delegation).

Buka sandbox untuk tugas tersebut.

pentingnya: 5

Buat sebuah pohon yang akan menampilkan/menyembunyikan elemen bawahan pada saat di klik:

Syarat:

  • Hanya satu penangan peristiwa (event handler) gunakan delegasi peristiwa.
  • Klik di luar judul node (pada ruang kosong) tidak boleh melakukan apa-apa.

Buka sandbox untuk tugas tersebut.

Solusi terbagi atas 2 bagian.

  1. Bungkus setiap node pada pohon kedalam <span>. Kemudian kita bisa menambahkan :hover dengan CSS-style dan menanggani klik tepat pada teks, karena lebar <span> sama dengan lebar tulisan (lebar tidak sama jika tidak menggunakan <span>);
  2. Atur sebuah penangan (handler) ke tree akar dari node dan tanggani setiap klik pada judul <span>.

Buka solusi di kotak pasir.

pentingnya: 4

Buat sebuah table yang dapat diurutkan: klik pada elemen <th> harus mengurutkan kolom dibawahnya.

Setiap <th> memiliki tipe pada atribut, seperti ini:

<table id="grid">
  <thead>
    <tr>
      <th data-type="number">Umur</th>
      <th data-type="string">Nama</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>5</td>
      <td>John</td>
    </tr>
    <tr>
      <td>10</td>
      <td>Ann</td>
    </tr>
    ...
  </tbody>
</table>

Pada contoh diatas kolom memiliki nomor, dan kolom kedua – string (teks). Fungsi pengurutan harus menanggani pengurutan berdasarkan tipenya.

Hanya tipe "string" dan "number" yang bisa di urutkan.

Contoh yang sudah jadi:

Tambahan: Tabel bisa besar, dengan banyak baris dan kolom.

Buka sandbox untuk tugas tersebut.

pentingnya: 5

Buat Kode JS untuk perilaku tooltip.

Pada saat mouse menghampir sebuah elemen dengan data-tooltip, tooltip harus tampil diatasnya, dan pada saat mouse itu pindah tooltipnya di sembunyikan.

Contoh dari HTML yang beranotasi:

<button data-tooltip="Tooltip lebih panjang dari elemen tombol">Tombol pendek</button>
<button data-tooltip="HTML<br>tooltip">Sebuah tombol lainnya</button>

Harus berfungsi seperti ini:

Pada tugas ini kita beranggapan bawah semua elemen dengan data-tooltip memiliki teks didalamnya, Tidak ada elemen bersarang (belum).

Rincian:

  • Jarak antara elemen dan tooltip harusnya 5px.
  • Jika memungkinkan, tooltip harus ditengah relatif pada elemen yang beranotasi.
  • Tooltip tidak boleh melewati ujung dari jendela (window). Biasanya tooltip harus berada di atas elemen, tapi jika elemen itu berada pada bagian atas halaman, dan tidak ada area untuk tooltip, maka posisi tooltip dibawah elemen.
  • Konten tooltip diberikan dalam atribut ‘data-tooltip’. Ini bisa menjadi HTML asalan.

Kamu akan membutuhkan 2 peristiwa:

  • mouseover akan dijalankan pada saat pointer berada di atas elemen beranotasi.
  • mouseout akan dijalankan pada saat pointer meninggalkan elemen yang beranotasi.

Tolong gunakan delegasi peristiwa: atur 2 buah penangan pada document untuk melacak semua “masukan” dan “keluaran” dari elemen yang memiliki data-tooltip dan untuk menanggani tooltip dari elemen itu.

Setelah perilaku tooltip dibuat, bahkan orang yang tidak familiar dengan JavaScript bisa menambahkan elemen yang beranotasi.

Tambahan: Tooltip hanya bisa ditujukan satu-satu.

Buka sandbox untuk tugas tersebut.

Peta tutorial