Ilustrasi checklist dengan kursor mengklik, simbol event dan to-do list

Ini dia garis finis seri JavaScript Basic. Lima artikel terakhir kamu mengumpulkan kepingan puzzle: variabel, keputusan, perulangan, function, array dan object, lalu DOM. Hari ini semua kepingan itu dirangkai jadi satu gambar utuh: aplikasi to-do list yang beneran bisa dipakai.

Keping terakhir yang belum kita bahas adalah event: cara JavaScript "mendengarkan" apa yang dilakukan pengguna. Tombol diklik, teks diketik, form disubmit, semua itu adalah event. Tanpa event, aplikasimu cuma bisa jalan sekali saat halaman dimuat lalu diam. Dengan event, aplikasimu hidup dan merespons.

Di akhir artikel ini kamu tidak cuma paham event, tapi juga punya satu aplikasi lengkap: tambah tugas, tandai selesai, hapus tugas, dan datanya tidak hilang walau browser ditutup. Ini proyek pertama yang layak kamu tunjukkan ke temanmu.

Event: Telinganya Program

Analogi paling pas: event itu seperti bel rumah. Kamu tidak berdiri di depan pintu seharian menunggu tamu. Kamu pasang bel, lalu beraktivitas seperti biasa. Saat bel berbunyi, kamu merespons. Program pun begitu: ia tidak mengecek tombol terus-menerus, ia "memasang telinga" lalu menunggu.

Cara memasang telinga di JavaScript adalah addEventListener:

let tombol = document.querySelector("#tombol-sapa");
tombol.addEventListener("click", function() {
  console.log("Tombol diklik!");
});

Tiga bagiannya: elemen yang dipasangi telinga (tombol), nama event yang didengarkan ("click"), dan function yang dijalankan saat event terjadi (disebut callback). Function-nya tidak dijalankan sekarang, ia cuma "didaftarkan" dan akan dipanggil nanti setiap kali tombol diklik.

Event yang wajib kamu kenal di awal: click (diklik), input (teks di kolom berubah), keydown (tombol keyboard ditekan), dan submit (form dikirim). Polanya selalu sama, yang beda cuma namanya.

Berhenti dan tebak dulu! Berapa kali "klik!" tercetak kalau tombolnya diklik 3 kali?

let i = 0;
tombol.addEventListener("click", function() {
  i = i + 1;
  console.log("klik! ke-" + i);
});
Jawaban: 3 kali, dengan isi "klik! ke-1", "klik! ke-2", "klik! ke-3". Variabel i hidup di luar function callback, jadi nilainya bertahan antar klik. Setiap klik menjalankan function yang sama lagi dari awal, tapi i-nya melanjutkan dari nilai terakhir. Ini pola dasar semua penghitung, skor game, dan keranjang belanja: variabel di luar, event mengubahnya.

Form dan preventDefault: Menjinakkan Perilaku Bawaan Browser

Untuk to-do list kita butuh kolom input dan tombol tambah. Cara rapi menampungnya adalah form:

<form id="form-todo">
  <input type="text" id="input-todo" placeholder="Tulis tugas...">
  <button type="submit">Tambah</button>
</form>
<ul id="daftar-todo"></ul>

Tapi form punya perilaku bawaan yang menyebalkan: saat disubmit, browser me-reload halaman. Itu perilaku dari zaman web kuno, saat form memang dipakai untuk mengirim data ke server. Untuk aplikasi modern kita, reload berarti semua data di memori hilang. Solusinya satu baris sakti:

let form = document.querySelector("#form-todo");
form.addEventListener("submit", function(event) {
  event.preventDefault(); // jangan reload!
  let teks = document.querySelector("#input-todo").value;
  console.log("Tugas baru: " + teks);
});

Dua hal baru di sini. Pertama, function callback menerima parameter event: paket informasi tentang kejadian yang baru terjadi. Kedua, event.preventDefault() artinya "batalkan perilaku bawaan browser untuk event ini". Tanpa baris ini, aplikasimu akan reload setiap kali tombol Tambah diklik dan semua tugas lenyap. Ini bug yang dialami hampir setiap pemula exactly satu kali, lalu tidak pernah lupa lagi.

Ketiga, .value: cara mengambil teks yang diketik pengguna di dalam input. Beda dengan textContent yang untuk membaca isi elemen biasa.

Dua Persepsi yang Sering Salah

Pertama: "onclick di HTML sama bagusnya dengan addEventListener." Untuk latihan kecil memang mirip, tapi addEventListener menang telak di kode nyata: satu elemen bisa dipasangi banyak listener sekaligus (onclick cuma satu, yang baru menimpa yang lama), dan kode JavaScript-mu tidak tercecer di file HTML. Ini soal kerapian jangka panjang.

Kedua: "data aplikasi harus selalu disimpan ke server." Untuk aplikasi latihan seperti ini, browser punya localStorage: gudang kecil di browser pengguna untuk menyimpan data sebagai teks, yang bertahan walau tab ditutup. Server itu urusan nanti (seri backend). Jangan menunda bikin aplikasi nyata hanya karena belum bisa backend.

Proyek: Aplikasi To Do List Lengkap

Saatnya merangkai semuanya. Arsitekturnya sederhana dan ini pola yang akan kamu pakai di semua aplikasi CRUD nanti: data disimpan di array, tampilan digambar ulang dari array setiap ada perubahan.

let todos = []; // data utama: array of string

function gambarUlang() {
  let daftar = document.querySelector("#daftar-todo");
  daftar.innerHTML = ""; // hapus tampilan lama
  for (let i = 0; i < todos.length; i++) {
    let li = document.createElement("li");
    li.textContent = todos[i];
    // tombol hapus per item
    let btnHapus = document.createElement("button");
    btnHapus.textContent = "Hapus";
    btnHapus.addEventListener("click", function() {
      todos.splice(i, 1); // buang 1 item di posisi i
      simpan();
      gambarUlang();
    });
    li.appendChild(btnHapus);
    daftar.appendChild(li);
  }
}

function simpan() {
  localStorage.setItem("todos", JSON.stringify(todos));
}

let form = document.querySelector("#form-todo");
form.addEventListener("submit", function(event) {
  event.preventDefault();
  let input = document.querySelector("#input-todo");
  if (input.value.trim() === "") return; // tolak tugas kosong
  todos.push(input.value);
  input.value = ""; // kosongkan kolom
  simpan();
  gambarUlang();
});

// saat halaman dibuka: muat data lama kalau ada
let tersimpan = localStorage.getItem("todos");
if (tersimpan) {
  todos = JSON.parse(tersimpan);
}
gambarUlang();

Mari bedah alur berpikirnya, karena ini lebih penting daripada kode-nya:

  • Satu sumber kebenaran. Array todos adalah "data asli". Tampilan HTML cuma cermin-nya. Semua perubahan (tambah, hapus) mengubah array dulu, lalu cermin digambar ulang. Jangan pernah mengubah tampilan tanpa mengubah data, nanti keduanya tidak sinkron dan bug muncul.
  • localStorage cuma bisa menyimpan teks. Makanya array diubah jadi teks dengan JSON.stringify saat menyimpan, dan diubah balik jadi array dengan JSON.parse saat memuat. Dua function ini pasangan yang tidak terpisahkan.
  • splice(i, 1) artinya "di posisi i, buang 1 item". Ini method array serbaguna untuk menghapus (atau menyisipkan) di posisi tertentu.

Berhenti dan tebak dulu! Kenapa di dalam gambarUlang baris pertamanya selalu daftar.innerHTML = ""? Apa yang terjadi kalau baris itu dihapus?

function gambarUlang() {
  let daftar = document.querySelector("#daftar-todo");
  daftar.innerHTML = "";
  // ...gambar ulang dari array todos
}
Jawaban: daftar akan menampilkan item ganda. Tanpa dikosongkan dulu, setiap pemanggilan gambarUlang akan menumpuk item baru di atas item lama. Tambah 1 tugas, daftar menampilkan semua tugas dua kali. Pola "kosongkan dulu, gambar ulang dari data" memastikan cermin selalu sama persis dengan data asli. Ingat: tampilan itu cermin, bukan data.

Kuis cepat: Kenapa event.preventDefault() wajib ada di listener submit form to-do list ini?

Latihan: Ubah, Jangan Cuma Dibaca

Kali ini latihannya adalah menambah fitur ke aplikasi yang sudah jadi, seperti programmer sungguhan:

  1. Tambah status selesai. Ubah array todos dari array of string jadi array of object: { teks: "...", selesai: false }. Tambah checkbox atau klik pada item untuk menandai selesai (tampilkan dengan coretan via style.textDecoration). Sesuaikan gambarUlang, simpan, dan submit handler-nya.
  2. Tambah tombol "Hapus yang selesai". Satu tombol di bawah daftar yang membuang semua item dengan selesai: true sekaligus (petunjuk: filter, atau loop + array baru). Jangan lupa simpan() dan gambarUlang() setelahnya.
  3. Tambah validasi duplikat. Tolak tugas yang teksnya sudah ada di daftar (petunjuk: cek dengan loop atau includes sebelum push). Kasih tahu pengguna lewat alert atau teks peringatan.

Kalau tiga-tiganya berhasil, selamat: kamu baru saja melakukan pekerjaan programmer profesional, yaitu mengembangkan fitur di atas kode yang sudah ada.

Habis Ini ke Mana?

Seri JavaScript Basic resmi tamat. Kamu sekarang bisa menyimpan data, mengambil keputusan, mengulang pekerjaan, membungkus kode jadi function, mengelola banyak data, mengendalikan halaman web, merespons pengguna, dan menyimpan data permanen di browser. Itu fondasi yang solid.

Tapi ada satu skill yang belum kita sentuh dan hukumnya wajib bagi programmer: Git. Selama ini kodemu cuma hidup di laptopmu. Bagaimana kalau laptop rusak? Bagaimana kalau kamu mau pamer kode ke GitHub? Bagaimana kalau kerja tim? Seri berikutnya, Git dan GitHub, dimulai dari artikel Apa Itu Git dan Kenapa Programmer Wajib Bisa. Sampai jumpa di dapur berikutnya.