Pernahkah Anda mengalami masalah dalam menemukan induk dari simpul DOM di JavaScript, tetapi tidak yakin berapa banyak level yang harus Anda lewati untuk mencapainya?
<div data-id="123"> <button>Click me</button> </div>Itu cukup mudah, bukan?
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_Dalam hal ini, Node. parentNode API sudah cukup. Apa yang dilakukannya adalah mengembalikan simpul induk dari elemen yang diberikan. Dalam contoh di atas, var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_9adalah tombol yang diklik;
Tapi bagaimana jika struktur HTML bersarang lebih dalam dari itu?
<div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>_Pekerjaan kami menjadi jauh lebih sulit dengan menambahkan beberapa elemen HTML lagi. Tentu, kita bisa melakukan sesuatu seperti <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>_0, tapi ayolah… itu tidak elegan, dapat digunakan kembali, atau dapat diskalakan
Cara lama. Menggunakan <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>_1-loop
Salah satu solusinya adalah dengan menggunakan <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>1 loop yang berjalan hingga simpul induk ditemukan
function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }_Menggunakan contoh HTML yang sama dari atas lagi, akan terlihat seperti ini
var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"_Solusi ini jauh dari sempurna. Bayangkan jika Anda ingin menggunakan ID atau kelas atau jenis pemilih lainnya, alih-alih nama tag. Setidaknya itu memungkinkan sejumlah variabel simpul anak antara induk dan sumber kami
Ada juga jQuery
Kembali pada hari itu, jika Anda tidak ingin berurusan dengan menulis jenis fungsi yang kami lakukan di atas untuk setiap aplikasi (dan mari kita menjadi nyata, siapa yang mau?), maka perpustakaan seperti jQuery berguna (dan masih . Ini menawarkan metode <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>_3 untuk hal itu
$("button").closest("[data-id='123']")Cara baru. Menggunakan <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>_4
Meskipun jQuery masih merupakan pendekatan yang valid (hei, sebagian dari kita terikat padanya), menambahkannya ke proyek hanya untuk metode yang satu ini adalah berlebihan, terutama jika Anda dapat melakukan hal yang sama dengan JavaScript asli
Dan di situlah <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>_5 beraksi
var button = document.querySelector("button"); console.log(button.closest("div")); // prints the HTMLDivElementDi sana kita pergi. Begitulah mudahnya, dan tanpa pustaka atau kode tambahan apa pun
<div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>_4 memungkinkan kita melintasi DOM sampai kita mendapatkan elemen yang cocok dengan pemilih yang diberikan. Kehebatannya adalah kami dapat melewati pemilih mana pun yang juga akan kami berikan kepada <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>7 atau <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>8. Itu bisa berupa ID, kelas, atribut data, tag, atau apa pun
element.closest("#my-id"); // yep element.closest(".some-class"); // yep element.closest("[data-id]:not(article)") // hell yeahJika <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>_5 menemukan simpul induk berdasarkan pemilih yang diberikan, ia mengembalikannya dengan cara yang sama seperti function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }0. Jika tidak, jika tidak menemukan induk, ia mengembalikan function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }1 sebagai gantinya, membuatnya mudah digunakan dengan kondisi function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }2
var button = document.querySelector("button"); console.log(button.closest(".i-am-in-the-dom")); // prints HTMLElement console.log(button.closest(".i-am-not-here")); // prints null if (button.closest(".i-am-in-the-dom")) { console.log("Hello there!"); } else { console.log(":("); }Siap untuk beberapa contoh kehidupan nyata?
Gunakan Kasus 1. Dropdown
CodePen Embed Fallback
Demo pertama kami adalah implementasi dasar (dan jauh dari sempurna) dari menu tarik-turun yang terbuka setelah mengeklik salah satu item menu tingkat atas. Perhatikan bagaimana menu tetap terbuka bahkan saat mengklik di mana saja di dalam dropdown atau memilih teks?
<div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>5 API adalah yang mendeteksi klik luar itu. Dropdown itu sendiri adalah elemen function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }_4 dengan kelas function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }5, jadi mengklik di mana saja di luar menu akan menutupnya. Itu karena nilai untuk function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }_6 akan menjadi function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }1 karena tidak ada simpul induk dengan kelas ini
function handleClick(evt) { // ... // if a click happens somewhere outside the dropdown, close it. if (!evt.target.closest(".menu-dropdown")) { menu.classList.add("is-hidden"); navigation.classList.remove("is-expanded"); } }Di dalam fungsi callback function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }8, kondisi memutuskan apa yang harus dilakukan. tutup drop down. Jika tempat lain di dalam unordered list diklik, <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>5 akan menemukan dan mengembalikannya, menyebabkan dropdown tetap terbuka
Gunakan Kasus 2. Tabel
CodePen Embed Fallback
Contoh kedua ini merender tabel yang menampilkan informasi pengguna, katakanlah sebagai komponen di dasbor. Setiap pengguna memiliki ID, tetapi alih-alih menampilkannya, kami menyimpannya sebagai atribut data untuk setiap elemen var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"0
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_0Kolom terakhir berisi dua tombol untuk mengedit dan menghapus pengguna dari tabel. Tombol pertama memiliki atribut var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"1 dari var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"2, dan tombol kedua adalah var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"3. Saat kami mengklik salah satunya, kami ingin memicu beberapa tindakan (seperti mengirim permintaan ke server), tetapi untuk itu, ID pengguna diperlukan
Pendengar peristiwa klik dilampirkan ke objek jendela global, jadi setiap kali pengguna mengklik di suatu tempat di halaman, fungsi panggilan balik function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }8 dipanggil
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_1Jika klik terjadi di tempat lain selain salah satu tombol ini, tidak ada atribut var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"1, maka tidak ada yang terjadi. Namun, saat mengklik salah satu tombol, tindakan akan ditentukan (omong-omong, itu disebut delegasi acara), dan sebagai langkah berikutnya, ID pengguna akan diambil dengan memanggil var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"6
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_2Fungsi ini mengharapkan node DOM sebagai satu-satunya parameter dan, saat dipanggil, menggunakan <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>5 untuk menemukan baris tabel yang berisi tombol yang ditekan. Ini kemudian mengembalikan nilai var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"_8, yang sekarang dapat digunakan untuk mengirim permintaan ke server
Gunakan Kasus 3. Tabel di Bereaksi
Mari kita tetap menggunakan contoh tabel dan lihat bagaimana kita menanganinya pada proyek React. Berikut kode untuk komponen yang mengembalikan tabel
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_3Saya menemukan bahwa kasus penggunaan ini sering muncul — cukup umum untuk memetakan kumpulan data dan menampilkannya dalam daftar atau tabel, lalu mengizinkan pengguna untuk melakukan sesuatu dengannya. Banyak orang menggunakan fungsi panah sebaris, seperti itu
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_4Meskipun ini juga cara yang valid untuk menyelesaikan masalah, saya lebih suka menggunakan teknik var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"8. Salah satu kelemahan dari fungsi panah inline adalah bahwa setiap kali React merender ulang daftar, ia perlu membuat fungsi panggilan balik lagi, yang mengakibatkan kemungkinan masalah kinerja saat menangani data dalam jumlah besar.
Dalam fungsi panggilan balik, kami hanya berurusan dengan acara tersebut dengan mengekstraksi target (tombol) dan mendapatkan elemen induk var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"0 yang berisi nilai var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"8
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_5Gunakan Kasus 4. Modal
CodePen Embed Fallback
Contoh terakhir ini adalah komponen lain yang saya yakin pernah Anda temui di beberapa titik. sebuah modal. Modal sering kali menantang untuk diimplementasikan karena harus menyediakan banyak fitur sekaligus dapat diakses dan (idealnya) berpenampilan menarik
Kami ingin fokus pada cara menutup modal. Dalam contoh ini, itu mungkin dengan menekan $("button").closest("[data-id='123']")2 pada keyboard, mengklik tombol di modal, atau mengklik di mana saja di luar modal
Dalam JavaScript kami, kami ingin mendengarkan klik di suatu tempat di modal
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_6Modal disembunyikan secara default melalui kelas utilitas ________11______3. Hanya ketika pengguna mengklik tombol merah besar, modal terbuka dengan menghapus kelas ini. Dan begitu modal terbuka, mengklik di mana saja di dalamnya — kecuali tombol tutup — tidak akan menutupnya secara tidak sengaja. Fungsi panggilan balik pendengar acara bertanggung jawab untuk itu
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_7var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });_9 adalah simpul DOM yang diklik yang, dalam contoh ini, adalah seluruh tampilan latar belakang modal, $("button").closest("[data-id='123']")5. Node DOM ini tidak berada dalam $("button").closest("[data-id='123']")6, karenanya <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>4 dapat meluapkan semua yang diinginkan dan tidak akan menemukannya. Kondisi memeriksanya dan memicu fungsi ________11______8
Mengklik di suatu tempat di dalam nodal, ucapkan judulnya, akan menjadikan $("button").closest("[data-id='123']")6 sebagai simpul induk. Dalam hal ini, kondisinya tidak benar, meninggalkan modal dalam keadaan terbuka
Oh, dan tentang dukungan browser…
Seperti halnya API JavaScript "baru" yang keren, dukungan browser adalah sesuatu yang perlu dipertimbangkan. Kabar baiknya adalah bahwa <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>_5 bukanlah hal baru dan didukung di semua browser utama selama beberapa waktu, dengan cakupan dukungan sebesar 94%. Saya akan mengatakan ini memenuhi syarat sebagai aman untuk digunakan dalam lingkungan produksi
Satu-satunya browser yang tidak menawarkan dukungan apa pun adalah Internet Explorer (semua versi). Jika Anda harus mendukung IE, Anda mungkin lebih baik menggunakan pendekatan jQuery
Seperti yang Anda lihat, ada beberapa kasus penggunaan yang cukup solid untuk <div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- .. --> </article> </div>5. Pustaka apa, seperti jQuery, yang dibuat relatif mudah bagi kami di masa lalu sekarang dapat digunakan secara native dengan JavaScript vanilla
Berkat dukungan browser yang bagus dan API yang mudah digunakan, saya sangat bergantung pada metode kecil ini di banyak aplikasi dan belum pernah kecewa.