Dethe Elza ( [email protected] ), Arsitek Teknis Senior, Blast Radius
Document Object Model (DOM) adalah salah satu alat yang paling umum digunakan untuk memanipulasi data XML dan HTML, namun potensinya jarang dimanfaatkan sepenuhnya. Dengan memanfaatkan DOM dan membuatnya lebih mudah digunakan, Anda mendapatkan alat yang ampuh untuk aplikasi XML, termasuk aplikasi Web dinamis.
Edisi ini menampilkan kolumnis tamu, teman dan kolega Dethe Elza. Dethe memiliki pengalaman luas dalam mengembangkan aplikasi Web menggunakan XML, dan saya ingin mengucapkan terima kasih kepadanya karena telah membantu saya memperkenalkan pemrograman XML menggunakan DOM dan ECMAScript. Pantau terus kolom ini untuk kolom Dethe lainnya.
- David Mertz
DOM adalah salah satu API standar untuk memproses XML dan HTML. Ini sering dikritik karena memakan banyak memori, lambat, dan bertele-tele. Namun, ini adalah pilihan terbaik untuk banyak aplikasi, dan tentu saja lebih sederhana dibandingkan SAX, API utama XML lainnya. DOM secara bertahap muncul di alat-alat seperti browser web, browser SVG, OpenOffice, dan sebagainya.
DOM bagus karena merupakan standar dan diterapkan secara luas serta dimasukkan ke dalam standar lain. Sebagai standar, penanganan datanya bersifat agnostik bahasa pemrograman (yang mungkin merupakan kelebihan atau bukan, tetapi setidaknya hal ini membuat cara kami menangani data menjadi konsisten). DOM sekarang tidak hanya dibangun ke dalam browser Web, namun juga merupakan bagian dari banyak spesifikasi berbasis XML. Sekarang ini adalah bagian dari gudang senjata Anda, dan mungkin Anda masih menggunakannya sesekali, saya rasa inilah saatnya untuk memanfaatkan sepenuhnya apa yang dibawanya.
Setelah bekerja dengan DOM selama beberapa waktu, Anda akan melihat pola berkembang -- hal-hal yang ingin Anda lakukan berulang kali. Pintasan membantu Anda bekerja dengan DOM yang panjang dan membuat kode yang cukup jelas dan elegan. Berikut kumpulan tips dan trik yang sering saya gunakan, beserta beberapa contoh JavaScript.
Trik pertama
untuk insertAfter dan prependChild
adalah "tidak ada trik".DOM memiliki dua metode untuk menambahkan node anak ke node kontainer (biasanya Elemen, atau Dokumen atau Fragmen Dokumen): appendChild(node) dan insertBefore(node, referenceNode). Sepertinya ada sesuatu yang hilang. Bagaimana jika saya ingin menyisipkan atau menambahkan node anak setelah node referensi (menjadikan node baru sebagai yang pertama dalam daftar)? Selama bertahun-tahun, solusi saya adalah menulis fungsi berikut:
Listing 1. Metode yang salah untuk menyisipkan dan menambahkan by
fungsi insertAfter(induk, simpul, simpul referensi) {
if(referenceNode.nextSibling) {
parent.insertBefore(node, referenceNode.nextSibling);
} kalau tidak {
parent.appendChild(simpul);
}
}
function prependChild(induk, simpul) {
if (orang tua.Anak Pertama) {
parent.insertBefore(node, parent.firstChild);
} kalau tidak {
parent.appendChild(simpul);
}
}
Faktanya, seperti Listing 1, fungsi insertBefore() telah didefinisikan untuk kembali ke appendChild() ketika node referensi kosong. Jadi daripada menggunakan metode di atas, Anda dapat menggunakan metode di Listing 2, atau lewati metode tersebut dan cukup gunakan fungsi bawaan:
Listing 2. Cara yang benar untuk menyisipkan dan menambahkan dari sebelumnya
fungsi insertAfter(induk, simpul, simpul referensi) {
parent.insertBefore(node, referenceNode.nextSibling);
}
function prependChild(induk, simpul) {
parent.insertBefore(node, parent.firstChild);
}
Jika Anda baru mengenal pemrograman DOM, penting untuk diketahui bahwa, meskipun Anda dapat memiliki beberapa pointer yang menunjuk ke sebuah node, node tersebut hanya dapat berada di satu lokasi di pohon DOM. Jadi jika ingin dimasukkan ke dalam pohon, tidak perlu mengeluarkannya dari pohon terlebih dahulu karena akan terhapus secara otomatis. Mekanisme ini berguna saat menyusun ulang node hanya dengan memasukkannya ke posisi barunya.
Menurut mekanisme ini, jika Anda ingin menukar posisi dua node yang berdekatan (disebut node1 dan node2), Anda dapat menggunakan salah satu solusi berikut:
node1.parentNode.insertBefore(node2, node1);
atau
node1.parentNode.insertBefore(node1.nextSibling, node1);
Apa lagi yang bisa Anda lakukan dengan DOM?
DOM banyak digunakan di halaman web. Jika Anda mengunjungi situs bookmarklet (lihat Sumberdaya), Anda akan menemukan banyak skrip pendek kreatif yang dapat mengatur ulang halaman, mengekstrak tautan, menyembunyikan gambar atau iklan Flash, dan banyak lagi.
Namun, karena Internet Explorer tidak mendefinisikan konstanta antarmuka Node yang dapat digunakan untuk mengidentifikasi tipe node, Anda harus memastikan bahwa jika Anda menghilangkan konstanta antarmuka, Anda terlebih dahulu menentukan konstanta antarmuka dalam skrip DOM untuk Web.
Listing 3. Memastikan node telah ditentukan
if (!jendela['Node']) {
window.Node = Objek baru();
Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3;
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
Node.NOTATION_NODE = 12;
}
Listing 4 menunjukkan cara mengekstrak semua node teks yang terdapat dalam sebuah node:
Listing 4. Teks internal
fungsi innerText(simpul) {
// apakah ini node teks atau CDATA?
if (node.nodeType == 3 || node.nodeType == 4) {
kembalikan node.data;
}
var saya;
var returnValue = [];
untuk (i = 0; i < node.childNodes.length; i++) {
returnValue.push(innerText(node.childNodes[i]));
}
return returnValue.join('');
}
Pintasan
Orang sering mengeluh bahwa DOM terlalu bertele-tele dan fungsi sederhana memerlukan banyak kode. Misalnya, jika Anda ingin membuat elemen
Listing 5. "Jalan panjang" untuk membuat
Jika Anda sering membuat node dengan cara ini, mengetikkan semua kode ini akan membuat Anda cepat lelah. Harus ada solusi yang lebih baik - dan memang ada! Berikut adalah utilitas yang membantu Anda membuat elemen, mengatur properti dan gaya elemen, dan menambahkan node anak teks. Kecuali parameter nama, semua parameter lainnya bersifat opsional.
Listing 6. Pintasan fungsi elem()
elemen fungsi(nama, attr, gaya, teks) {
var e = document.createElement(nama);
jika (attr) {
untuk (masukkan attrs) {
if (kunci == 'kelas') {
e.className = attrs[kunci];
} else if (kunci == 'id') {
e.id = attrs[kunci];
} kalau tidak {
e.setAttribute(kunci, attrs[kunci]);
}
}
}
jika (gaya) {
untuk (kunci dalam gaya) {
e.gaya[kunci] = gaya[kunci];
}
}
jika (teks) {
e.appendChild(document.createTextNode(teks));
}
kembali e;
}
Dengan menggunakan pintasan ini, Anda dapat membuat elemen
Listing 7. Cara mudah membuat
Utilitas ini dapat menghemat banyak waktu ketika Anda ingin dengan cepat membuat objek DHTML kompleks dalam jumlah besar. Pola di sini berarti jika Anda memiliki struktur DOM tertentu yang perlu sering dibuat, gunakan utilitas untuk membuatnya. Hal ini tidak hanya mengurangi jumlah kode yang Anda tulis, tetapi juga mengurangi pemotongan dan penempelan kode yang berulang-ulang (penyebab kesalahan) dan mempermudah berpikir jernih saat membaca kode.
Apa selanjutnya?
DOM sering kali kesulitan memberi tahu Anda node berikutnya dalam urutan dokumen. Berikut adalah beberapa utilitas untuk membantu Anda bergerak maju dan mundur antar node:
Listing 8. nextNode dan prevNode
// mengembalikan node berikutnya dalam urutan dokumen
fungsi simpul berikutnya(simpul) {
jika (!simpul) mengembalikan nol;
if (node.firstChild){
kembalikan node.firstChild;
} kalau tidak {
kembali nextWide(simpul);
}
}
// fungsi pembantu untuk nextNode()
fungsi nextWide(simpul) {
jika (!simpul) mengembalikan nol;
if (node.saudara berikutnya) {
kembalikan node.nextSibling;
} kalau tidak {
return nextWide(node.parentNode);
}
}
// mengembalikan node sebelumnya dalam urutan dokumen
fungsi prevNode(simpul) {
jika (!simpul) mengembalikan nol;
if (node.previousSibling) {
kembalikan previousDeep(node.previousSibling);
}
return node.parentNode;
}
// fungsi pembantu untuk prevNode()
fungsi sebelumnyaDeep(simpul) {
jika (!simpul) mengembalikan nol;
while (node.childNodes.length) {
simpul = simpul.Anak terakhir;
}
simpul kembali;
}
Gunakan DOM dengan mudah
Terkadang Anda mungkin ingin melakukan iterasi melalui DOM, memanggil fungsi pada setiap node atau mengembalikan nilai dari setiap node. Faktanya, karena ide-ide ini sangat umum, DOM Level 2 sudah menyertakan ekstensi yang disebut DOM Traversal dan Range (yang mendefinisikan objek dan API untuk mengulangi semua node di DOM), yang digunakan untuk menerapkan Fungsi dan memilih rentang di DOM . Karena fungsi ini belum ditentukan di Internet Explorer (setidaknya belum), Anda dapat menggunakan nextNode() untuk melakukan hal serupa.
Di sini, idenya adalah membuat beberapa alat sederhana dan umum, lalu merakitnya dengan cara berbeda untuk mencapai efek yang diinginkan. Jika Anda terbiasa dengan pemrograman fungsional, ini akan terasa familier. Pustaka Beyond JS (lihat Sumberdaya) mengedepankan gagasan ini.
Listing 9. Utilitas DOM fungsional
// mengembalikan Array dari semua node, dimulai dari startNode dan
// melanjutkan sisa pohon DOM
fungsi listNodes(startNode) {
var daftar = Array baru();
var simpul = startNode;
sementara(simpul) {
daftar.push(simpul);
simpul = simpul berikutnya(simpul);
}
daftar pengembalian;
}
// Sama seperti listNodes(), namun bekerja mundur dari startNode.
// Perhatikan bahwa ini tidak sama dengan menjalankan listNodes() dan
// membalik daftar.
fungsi listNodesReversed(startNode) {
var daftar = Array baru();
var simpul = startNode;
sementara(simpul) {
daftar.push(simpul);
simpul = sebelumnyaNode(simpul);
}
daftar pengembalian;
}
// terapkan fungsi ke setiap node di nodeList, kembalikan daftar hasil baru
peta fungsi(daftar, fungsi) {
var result_list = Array baru();
for (var i = 0; i < daftar.panjang; i++) {
result_list.push(func(daftar[i]));
}
kembalikan hasil_daftar;
}
// terapkan pengujian pada setiap node, kembalikan daftar node baru yang mana
// tes(simpul) menghasilkan nilai benar
filter fungsi(daftar, pengujian) {
var result_list = Array baru();
for (var i = 0; i < daftar.panjang; i++) {
if (tes(daftar[i])) hasil_daftar.push(daftar[i]);
}
kembalikan hasil_daftar;
}
Listing 9 berisi empat alat dasar. Fungsi listNodes() dan listNodesReversed() dapat diperluas ke panjang opsional, mirip dengan metode array's slice(). Saya biarkan ini sebagai latihan untuk Anda. Hal lain yang perlu diperhatikan adalah fungsi map() dan filter() sepenuhnya umum dan dapat digunakan untuk bekerja dengan daftar apa pun (bukan hanya daftar node). Sekarang saya tunjukkan beberapa cara menggabungkannya.
Listing 10. Menggunakan utilitas fungsional
// Daftar semua nama elemen dalam urutan dokumen
fungsi isElement(simpul) {
return node.nodeType == Node.ELEMENT_NODE;
}
fungsi nama simpul(simpul) {
kembalikan node.nodeName;
}
var elementNames = peta(filter(listNodes(dokumen),isElement), nodeName);
// Semua teks dari dokumen (abaikan CDATA)
fungsi isText(simpul) {
return node.nodeType == Node.TEXT_NODE;
}
fungsi nilai simpul(simpul) {
kembalikan node.nodeValue;
}
var allText = peta(filter(listNodes(dokumen), isText), nodeValue);
Anda dapat menggunakan utilitas ini untuk mengekstrak ID, mengubah gaya, menemukan node tertentu dan menghapusnya, dan banyak lagi. Setelah DOM Traversal dan Range API diimplementasikan secara luas, Anda dapat menggunakannya untuk memodifikasi pohon DOM tanpa terlebih dahulu membuat daftarnya. Tidak hanya kuat, tetapi cara kerjanya juga mirip dengan yang saya soroti di atas.
Zona bahaya DOM
Perhatikan bahwa API DOM inti tidak memungkinkan Anda mengurai data XML menjadi DOM, atau membuat serial DOM menjadi XML. Fungsi-fungsi ini didefinisikan dalam ekstensi DOM Level 3 "Muat dan Simpan", namun belum sepenuhnya diterapkan, jadi jangan memikirkannya sekarang. Setiap platform (browser atau aplikasi DOM profesional lainnya) memiliki metodenya sendiri untuk mengkonversi antara DOM dan XML, namun konversi lintas platform berada di luar cakupan artikel ini.
DOM bukanlah alat yang sangat aman - terutama ketika menggunakan DOM API untuk membuat pohon yang tidak dapat diserialkan sebagai XML. Jangan pernah mencampur API non-namespace DOM1 dan API yang mendukung namespace DOM2 (misalnya, createElement dan createElementNS) dalam program yang sama. Jika Anda menggunakan namespace, cobalah untuk mendeklarasikan semua namespace pada posisi elemen root dan jangan menimpa awalan namespace, jika tidak maka segalanya akan menjadi sangat membingungkan. Secara umum, selama Anda mengikuti rutinitas, Anda tidak akan memicu situasi kritis yang dapat membuat Anda mendapat masalah.
Jika Anda telah menggunakan innerText dan innerHTML Internet Explorer untuk penguraian, Anda dapat mencoba menggunakan fungsi elem(). Dengan membangun utilitas serupa, Anda mendapatkan lebih banyak kemudahan dan mewarisi manfaat kode lintas platform. Mencampur kedua metode ini sangat buruk.
Beberapa karakter Unicode tidak disertakan dalam XML. Implementasi DOM memungkinkan Anda untuk menambahkannya, tetapi konsekuensinya adalah tidak dapat diserialkan. Karakter ini mencakup sebagian besar karakter kontrol dan karakter individual dalam pasangan pengganti Unicode. Ini hanya terjadi jika Anda mencoba memasukkan data biner ke dalam dokumen, tetapi itu adalah situasi yang sulit.
Kesimpulan
Saya telah membahas banyak hal yang dapat dilakukan DOM, namun masih banyak lagi yang dapat dilakukan oleh DOM (dan JavaScript). Pelajari dan jelajahi contoh-contoh ini untuk melihat bagaimana contoh-contoh tersebut dapat digunakan untuk memecahkan masalah yang mungkin memerlukan skrip klien, templat, atau API khusus.
DOM memiliki keterbatasan dan kekurangan, tetapi juga memiliki banyak kelebihan: DOM dibangun di banyak aplikasi; cara kerjanya sama baik Anda menggunakan teknologi Java, Python, atau JavaScript; sangat mudah menggunakan SAX; yang sederhana dan kuat untuk digunakan. Semakin banyak aplikasi yang mulai mendukung DOM, termasuk aplikasi berbasis Mozilla, OpenOffice, dan XMetaL Blast Radius. Semakin banyak spesifikasi yang memerlukan DOM dan memperluasnya (misalnya SVG), sehingga DOM selalu ada di sekitar Anda. Anda sebaiknya menggunakan alat yang banyak digunakan ini.