1. Apa Itu Custom Elements di JavaScript?
Pernahkah Anda ingin membuat komponen antarmuka (misalnya tombol salin kode <tombol-salin> atau kartu peringatan <kotak-info>) yang dapat dipakai di halaman mana saja—baik di halaman HTML statis, di proyek PHP, maupun di berbagai framework—cukup dengan menulis tag HTML-nya saja?
Standar Custom Elements (bagian dari spesifikasi HTML dan DOM yang berstatus Baseline Widely Available) memungkinkan Anda mendaftarkan tag HTML baru ke peramban dengan mewarisi kelas HTMLElement!
Nama tag elemen kustom wajib menggunakan huruf kecil dan mengandung minimal satu tanda hubung (-)—misalnya <kartu-kodeloka>, <tombol-salin>, atau <grafik-metrik>. Mengapa wajib ada tanda hubung -? Agar peramban dan spesifikasi HTML di masa depan dijamin tidak akan pernah bentrok dengan nama elemen bawaan HTML (karena semua elemen bawaan HTML seperti <dialog>, <search>, <article> selalu terdiri dari satu kata tanpa tanda hubung!).
2. Siklus Hidup (Lifecycle Callbacks) Sebuah Custom Element
Ketika Anda mendefinisikan class KomponenSaya extends HTMLElement, peramban secara otomatis memanggil metode-metode siklus hidup khusus ini:
| Metode Siklus Hidup | Kapan Dipanggil oleh Peramban? | Kegunaan Utama |
|---|---|---|
constructor() | Saat instans elemen pertama kali diciptakan di memori. | Membuat Shadow Root (this.attachShadow({ mode: "open" })) atau menginisialisasi state internal. Jangan mengakses atribut/anak di sini! |
connectedCallback() | Setiap kali elemen dimasukkan ke dalam dokumen DOM aktif. | Tempat utama untuk merender tampilan, membaca atribut awal, dan memasang event listener! |
disconnectedCallback() | Setiap kali elemen dicabut/dihapus dari dokumen DOM. | Tempat wajib untuk membersihkan sumber daya: menghapus timer clearInterval, memutus IntersectionObserver.disconnect(), atau menutup koneksi! |
static observedAttributes & attributeChangedCallback(nama, nilaiLama, nilaiBaru) | Setiap kali salah satu atribut yang terdaftar di dalam observedAttributes ditambahkan, diubah, atau dihapus. | Memperbarui tampilan komponen secara reaktif saat atribut HTML-nya diubah! |
3. Contoh Lengkap: Membangun Komponen <tombol-salin> yang Mandiri
Mari kita bangun sebuah elemen kustom <tombol-salin target="#kode-1"></tombol-salin> yang lengkap dengan isolasi gaya Shadow DOM, penyalinan ke papan klip (navigator.clipboard.writeText), dan pembersihan timer yang aman:
class TombolSalin extends HTMLElement {
// 1. Daftarkan atribut mana saja yang perubahannya ingin kita pantau
static observedAttributes = ["label"];
#shadow;
#timerReset = null;
constructor() {
super();
// 2. Buat Shadow DOM terisolasi agar gaya tombol tidak terpengaruh CSS luar
this.#shadow = this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.#render();
this.#shadow
.querySelector("button")
?.addEventListener("click", () => this.#salinTeks());
}
disconnectedCallback() {
// 3. Bersihkan timer jika elemen dihapus dari DOM sebelum 2 detik selesai
clearTimeout(this.#timerReset);
}
attributeChangedCallback(nama, nilaiLama, nilaiBaru) {
if (nama === "label" && nilaiLama !== nilaiBaru) {
const span = this.#shadow.querySelector(".teks-label");
if (span) span.textContent = nilaiBaru ?? "Salin Kode";
}
}
async #salinTeks() {
const selektorTarget = this.getAttribute("target");
const elemenKode = selektorTarget ? document.querySelector(selektorTarget) : null;
if (!elemenKode) return;
const spanLabel = this.#shadow.querySelector(".teks-label");
try {
await navigator.clipboard.writeText(elemenKode.textContent ?? "");
spanLabel.textContent = "✓ Tersalin!";
clearTimeout(this.#timerReset);
this.#timerReset = setTimeout(() => {
spanLabel.textContent = this.getAttribute("label") ?? "Salin Kode";
}, 2000);
} catch {
spanLabel.textContent = "Gagal menyalin";
}
}
#render() {
const labelAwal = this.getAttribute("label") ?? "Salin Kode";
this.#shadow.innerHTML = `
<style>
button {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.35rem 0.75rem;
border: 1px solid #cbd5e1;
border-radius: 0.375rem;
background: #ffffff;
color: #0f172a;
font: inherit;
font-size: 0.8125rem;
font-weight: 600;
cursor: pointer;
}
button:hover {
background: #f1f5f9;
}
</style>
<button type="button">
<span class="teks-label"></span>
</button>
`;
// Gunakan textContent untuk menyisipkan nilai atribut secara aman dari XSS!
this.#shadow.querySelector(".teks-label").textContent = labelAwal;
}
}
// 4. Daftarkan tag <tombol-salin> ke registri elemen kustom peramban!
if (!customElements.get("tombol-salin")) {
customElements.define("tombol-salin", TombolSalin);
}
Begitu skrip di atas dimuat, Anda dapat langsung menggunakan <tombol-salin> di mana saja di dalam HTML:
<pre id="blok-perintah"><code>docker compose up -d --build</code></pre> <tombol-salin target="#blok-perintah" label="Salin Perintah Docker"></tombol-salin>
Latihan Praktik: Membangun Komponen <lencana-status> Reaktif
Buatlah berkas custom-element.html dan lencana-status.js:
- Buat
class LencanaStatus extends HTMLElementdenganstatic observedAttributes = ["jenis", "teks"]. - Ketika atribut
jenisbernilai"sehat","waspada", atau"kritis", perbarui warna indikator di dalam Shadow DOM secara otomatis melaluiattributeChangedCallback. - Daftarkan dengan
customElements.define("lencana-status", LencanaStatus)dan uji perubahan atributnya melaluisetAttribute("jenis", "kritis").