Tujuan Belajar
Setelah pelajaran ini, kamu akan bisa:
- Menggabungkan semua konsep JavaScript (variabel, kondisional, loop, function, DOM, event)
- Membangun aplikasi web interaktif yang sesungguhnya
- Merasakan kepuasan membuat sesuatu yang benar-benar berguna!
Materi Singkat
Selamat! Kamu telah menyelesaikan seluruh materi HTML, CSS, dan JavaScript dasar. Ini adalah pelajaran terakhir, dan kita akan membangun proyek mini yang menggabungkan semuanya.
Yang Akan Kita Bangun
Sebuah aplikasi "To-Do List Interaktif" yang memiliki:
- Tambah tugas baru
- Tandai tugas selesai/belum
- Hapus tugas
- Counter jumlah tugas tersisa
- Simpan data ke localStorage (data tidak hilang saat refresh!)
Konsep yang Dipakai
- DOM manipulation — membuat dan mengubah elemen
- Event listener — merespons klik dan submit
- Array — menyimpan daftar tugas
- Object — struktur data satu tugas
- Function — memisahkan logika
- localStorage — menyimpan data di browser
- Template literal — membuat HTML dinamis
Contoh Kode
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>To-Do List</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Arial, sans-serif;
background: #f3f4f6;
min-height: 100vh;
display: flex;
justify-content: center;
padding: 40px 16px;
}
.app {
background: white;
border-radius: 12px;
padding: 32px;
width: 100%;
max-width: 480px;
box-shadow: 0 4px 24px rgba(0,0,0,0.08);
height: fit-content;
}
h1 {
font-size: 1.5rem;
color: #1a1a1a;
margin-bottom: 4px;
}
.subtitle {
color: #6b7280;
font-size: 0.875rem;
margin-bottom: 24px;
}
.form-tambah {
display: flex;
gap: 8px;
margin-bottom: 24px;
}
.form-tambah input {
flex: 1;
padding: 10px 14px;
border: 1px solid #e5e7eb;
border-radius: 6px;
font-size: 1rem;
outline: none;
transition: border-color 0.2s;
}
.form-tambah input:focus {
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37,99,235,0.1);
}
.btn-tambah {
padding: 10px 18px;
background: #2563eb;
color: white;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: background 0.2s;
}
.btn-tambah:hover { background: #1d4ed8; }
.stats {
font-size: 0.875rem;
color: #6b7280;
margin-bottom: 12px;
}
.stats strong { color: #2563eb; }
.daftar-tugas {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
}
.item-tugas {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
border: 1px solid #e5e7eb;
border-radius: 8px;
transition: border-color 0.2s, background 0.2s;
}
.item-tugas:hover { border-color: #93c5fd; }
.item-tugas.selesai {
background: #f0fdf4;
border-color: #bbf7d0;
}
.btn-check {
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid #d1d5db;
background: none;
cursor: pointer;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
font-size: 0.75rem;
color: white;
}
.item-tugas.selesai .btn-check {
background: #16a34a;
border-color: #16a34a;
}
.teks-tugas {
flex: 1;
font-size: 0.9375rem;
color: #1a1a1a;
transition: all 0.2s;
}
.item-tugas.selesai .teks-tugas {
text-decoration: line-through;
color: #9ca3af;
}
.btn-hapus {
background: none;
border: none;
color: #d1d5db;
cursor: pointer;
font-size: 1.2rem;
line-height: 1;
padding: 2px 6px;
border-radius: 4px;
transition: color 0.2s, background 0.2s;
}
.btn-hapus:hover {
color: #dc2626;
background: #fef2f2;
}
.kosong {
text-align: center;
color: #9ca3af;
padding: 32px 0;
font-size: 0.875rem;
}
.error-msg {
color: #dc2626;
font-size: 0.8rem;
margin-top: 4px;
height: 16px;
}
</style>
</head>
<body>
<div class="app">
<h1>To-Do List</h1>
<p class="subtitle">Catat tugasmu, selesaikan satu per satu.</p>
<form class="form-tambah" id="form-tambah">
<input
type="text"
id="input-tugas"
placeholder="Tugas baru..."
autocomplete="off"
/>
<button type="submit" class="btn-tambah">+ Tambah</button>
</form>
<p class="error-msg" id="error-msg"></p>
<p class="stats" id="stats">0 tugas tersisa</p>
<ul class="daftar-tugas" id="daftar"></ul>
</div>
<script>
// ── State ──────────────────────────────────────
const STORAGE_KEY = "belajarjs:todo:v1";
function ambilTugas() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
return raw ? JSON.parse(raw) : [];
} catch {
return [];
}
}
function simpanTugas(tugas) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tugas));
} catch {
// silently fail
}
}
let tugas = ambilTugas();
// ── Render ─────────────────────────────────────
function render() {
const daftar = document.querySelector("#daftar");
const stats = document.querySelector("#stats");
const tersisa = tugas.filter(t => !t.selesai).length;
stats.innerHTML = tersisa === 0 && tugas.length > 0
? `<strong>Semua selesai!</strong> 🎉`
: `<strong>${tersisa}</strong> tugas tersisa dari ${tugas.length} total`;
if (tugas.length === 0) {
daftar.innerHTML = '<li class="kosong">Belum ada tugas. Tambahkan yang pertama!</li>';
return;
}
daftar.innerHTML = tugas.map((t, i) => `
<li class="item-tugas ${t.selesai ? "selesai" : ""}" data-index="${i}">
<button class="btn-check" data-action="toggle" data-index="${i}"
aria-label="${t.selesai ? "Batalkan" : "Selesaikan"} tugas">
${t.selesai ? "✓" : ""}
</button>
<span class="teks-tugas">${t.teks}</span>
<button class="btn-hapus" data-action="hapus" data-index="${i}"
aria-label="Hapus tugas">×</button>
</li>
`).join("");
}
// ── Event delegation ───────────────────────────
document.querySelector("#daftar").addEventListener("click", (e) => {
const btn = e.target.closest("[data-action]");
if (!btn) return;
const index = parseInt(btn.dataset.index);
const action = btn.dataset.action;
if (action === "toggle") {
tugas[index].selesai = !tugas[index].selesai;
} else if (action === "hapus") {
tugas.splice(index, 1);
}
simpanTugas(tugas);
render();
});
// ── Tambah tugas ───────────────────────────────
document.querySelector("#form-tambah").addEventListener("submit", (e) => {
e.preventDefault();
const input = document.querySelector("#input-tugas");
const errorMsg = document.querySelector("#error-msg");
const teks = input.value.trim();
if (!teks) {
errorMsg.textContent = "Nama tugas tidak boleh kosong!";
return;
}
errorMsg.textContent = "";
tugas.push({ teks, selesai: false });
simpanTugas(tugas);
render();
input.value = "";
input.focus();
});
// ── Init ───────────────────────────────────────
render();
</script>
</body>
</html>Tugas: Kembangkan Sendiri!
Salin kode di atas ke file todo.html dan tambahkan minimal 2 fitur baru dari pilihan berikut:
Pilihan fitur tambahan:
- [ ] Filter — tombol "Semua", "Aktif", "Selesai" untuk memfilter tampilan daftar
- [ ] Hapus semua yang selesai — tombol untuk menghapus semua tugas yang sudah ditandai selesai
- [ ] Edit tugas — klik teks tugas untuk mengeditnya langsung (gunakan
contenteditable) - [ ] Prioritas — dropdown saat tambah tugas: Rendah, Sedang, Tinggi. Tampilkan dengan warna berbeda
- [ ] Deadline — input date saat tambah tugas, tampilkan tanggal di setiap item
- [ ] Drag to reorder — ubah urutan tugas dengan drag-and-drop (tantangan!)