Menstandarkan pembuatan template sisi klien
Pengantar
Konsep pembuatan template bukanlah hal baru dalam pengembangan web. Bahkan, bahasa/mesin template sisi server seperti Django (Python), ERB/Haml (Ruby), dan Smarty (PHP) sudah ada sejak lama. Namun, dalam beberapa tahun terakhir, kita telah melihat ledakan kemunculan framework MVC. Semuanya sedikit berbeda, tetapi sebagian besar memiliki mekanisme yang sama untuk merender lapisan presentasi (alias tampilan): template.
Mari kita hadapi. Template sangat berguna. Silakan, tanyakan kepada orang lain. Bahkan definisi-nya membuat Anda merasa hangat dan nyaman:
"…tidak perlu dibuat ulang setiap kali…" Saya tidak tahu bagaimana dengan Anda, tetapi saya suka menghindari pekerjaan tambahan. Lalu, mengapa platform web tidak memiliki dukungan native untuk sesuatu yang jelas-jelas penting bagi developer?
Spesifikasi Template HTML WhatWG adalah jawabannya. Elemen ini menentukan
elemen <template> baru yang menjelaskan pendekatan berbasis DOM standar
untuk pembuatan template sisi klien. Template memungkinkan Anda mendeklarasikan fragmen markup yang diuraikan sebagai HTML, tidak digunakan saat pemuatan halaman, tetapi dapat dibuat instance-nya
nanti saat runtime. Mengutip Rafael Weinstein:
Elemen ini adalah tempat untuk menempatkan banyak HTML yang tidak ingin diubah oleh browser sama sekali… karena alasan apa pun.
Rafael Weinstein (penulis spesifikasi)
Deteksi fitur
Untuk mendeteksi fitur <template>, buat elemen DOM dan periksa apakah properti .content ada:
function supportsTemplate() {
return 'content' in document.createElement('template');
}
if (supportsTemplate()) {
// Good to go!
} else {
// Use old templating techniques or libraries.
}
Mendeklarasikan konten template
Elemen <template> HTML merepresentasikan template dalam markup Anda. Elemen ini berisi
"konten template"; pada dasarnya bagian DOM yang tidak aktif dan dapat di-clone.
Anggap template sebagai bagian dari scaffolding yang dapat Anda gunakan (dan gunakan kembali) selama masa aktif aplikasi.
Untuk membuat konten dengan template, deklarasikan beberapa markup dan gabungkan dalam elemen <template>:
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
Pilar
Menggabungkan konten dalam <template> memberi kita beberapa properti penting.
Kontennya tidak akan aktif sampai diaktifkan. Pada dasarnya, markup Anda adalah DOM tersembunyi dan tidak dirender.
Konten apa pun dalam template tidak akan memiliki efek samping. Skrip tidak berjalan, gambar tidak dimuat, audio tidak diputar,…hingga template digunakan.
Konten dianggap tidak ada dalam dokumen. Penggunaan
document.getElementById()atauquerySelector()di halaman utama tidak akan menampilkan node turunan template.Template dapat ditempatkan di mana saja di dalam
<head>,<body>, atau<frameset>dan dapat berisi jenis konten apa pun yang diizinkan dalam elemen tersebut. Perhatikan bahwa "di mana saja" berarti<template>dapat digunakan dengan aman di tempat yang tidak diizinkan oleh parser HTML…semua kecuali turunan model konten. Tag ini juga dapat ditempatkan sebagai turunan dari<table>atau<select>:
<table>
<tr>
<template id="cells-to-repeat">
<td>some content</td>
</template>
</tr>
</table>
Mengaktifkan template
Untuk menggunakan template, Anda harus mengaktifkannya. Jika tidak, kontennya tidak akan pernah dirender.
Cara termudah untuk melakukannya adalah dengan membuat deep copy .content-nya menggunakan document.importNode(). Properti .content adalah DocumentFragment hanya baca yang berisi isi template.
var t = document.querySelector('#mytemplate');
// Populate the src at runtime.
t.content.querySelector('img').src = 'logo.png';
var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
Setelah mencetak template, kontennya akan "ditayangkan". Dalam contoh khusus ini, konten di-clone, permintaan gambar dibuat, dan markup akhir dirender.
Demo
Contoh: Skrip inert
Contoh ini menunjukkan kelembaman konten template. <script> hanya
berjalan saat tombol ditekan, yang akan mencetak template.
<button onclick="useIt()">Use me</button>
<div id="container"></div>
<script>
function useIt() {
var content = document.querySelector('template').content;
// Update something in the template DOM.
var span = content.querySelector('span');
span.textContent = parseInt(span.textContent) + 1;
document.querySelector('#container').appendChild(
document.importNode(content, true)
);
}
</script>
<template>
<div>Template used: <span>0</span></div>
<script>alert('Thanks!')</script>
</template>
Contoh: Membuat Shadow DOM dari template
Sebagian besar orang melampirkan Shadow DOM ke host dengan menetapkan string markup ke .innerHTML:
<div id="host"></div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<span>Host node</span>';
</script>
Masalah dengan pendekatan ini adalah semakin kompleks Shadow DOM Anda,
semakin banyak penyambungan string yang Anda lakukan. Hal ini tidak dapat diskalakan, semuanya akan menjadi kacau dengan cepat, dan bayi akan mulai menangis. Pendekatan ini juga merupakan awal mula munculnya XSS. <template> dapat membantu.
Cara yang lebih masuk akal adalah menggunakan DOM secara langsung dengan menambahkan konten template ke shadow root:
<template>
<style>
:host {
background: #f8f8f8;
padding: 10px;
transition: all 400ms ease-in-out;
box-sizing: border-box;
border-radius: 5px;
width: 450px;
max-width: 100%;
}
:host(:hover) {
background: #ccc;
}
div {
position: relative;
}
header {
padding: 5px;
border-bottom: 1px solid #aaa;
}
h3 {
margin: 0 !important;
}
textarea {
font-family: inherit;
width: 100%;
height: 100px;
box-sizing: border-box;
border: 1px solid #aaa;
}
footer {
position: absolute;
bottom: 10px;
right: 5px;
}
</style>
<div>
<header>
<h3>Add a Comment
</header>
<content select="p"></content>
<textarea></textarea>
<footer>
<button>Post</button>
</footer>
</div>
</template>
<div id="host">
<p>Instructions go here</p>
</div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.appendChild(document.querySelector('template').content);
</script>
Gotchas
Berikut beberapa masalah yang saya temui saat menggunakan <template> di lapangan:
- Jika Anda menggunakan modpagespeed, berhati-hatilah
dengan bug ini. Template
yang menentukan
<style scoped>inline, banyak yang dipindahkan ke bagian head dengan aturan penulisan ulang CSS PageSpeed. - Tidak ada cara untuk "melakukan pra-rendering" template, yang berarti Anda tidak dapat melakukan pramuat aset, memproses JS, mendownload CSS awal, dll. Hal ini berlaku untuk server dan klien. Template hanya dirender saat ditayangkan.
Berhati-hatilah dengan template bertingkat. Perilakunya tidak seperti yang Anda harapkan. Contoh:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>Mengaktifkan template luar tidak akan mengaktifkan template dalam. Artinya, template bertingkat mengharuskan turunannya juga diaktifkan secara manual.
Perjalanan menuju standar
Jangan lupakan asal-usul kita. Perjalanan menuju template HTML berbasis standar telah berlangsung lama. Selama bertahun-tahun, kami telah menemukan beberapa trik yang cukup cerdas untuk membuat template yang dapat digunakan kembali. Berikut adalah dua contoh umum yang pernah saya temui. Kami menyertakannya dalam artikel ini sebagai perbandingan.
Metode 1: DOM di luar layar
Salah satu pendekatan yang telah lama digunakan orang adalah membuat DOM "di luar layar"
dan menyembunyikannya dari tampilan menggunakan atribut hidden atau display:none.
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
Meskipun teknik ini berhasil, ada sejumlah kelemahan. Ringkasan teknik ini:
- Menggunakan DOM - browser mengetahui DOM. Model ini sangat bagus dalam hal itu. Kita dapat meng-clone-nya dengan mudah.
- Tidak ada yang dirender - menambahkan
hiddenakan mencegah blok ditampilkan. - Tidak inert - meskipun konten kita disembunyikan, permintaan jaringan masih dibuat untuk gambar.
- Penataan gaya dan pembuatan tema yang rumit - halaman penyematan harus mengawali semua aturan CSS-nya dengan
#mytemplateuntuk membatasi gaya ke template. Hal ini rentan dan tidak ada jaminan bahwa kita tidak akan mengalami konflik penamaan di masa mendatang. Misalnya, kita akan mengalami masalah jika halaman penyematan sudah memiliki elemen dengan ID tersebut.
Metode 2: Skrip overloading
Teknik lainnya adalah meng-overload <script> dan memanipulasi kontennya sebagai string. John Resig mungkin adalah orang pertama yang menunjukkan hal ini pada tahun 2008 dengan
utilitas Micro Templating-nya.
Sekarang ada banyak template engine lainnya, termasuk beberapa template engine baru seperti handlebars.js.
Contoh:
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
Ringkasan teknik ini:
- Tidak ada yang dirender - browser tidak merender blok ini karena
<script>adalahdisplay:nonesecara default. - Inert - browser tidak mengurai konten skrip sebagai JS karena jenisnya ditetapkan ke selain "text/javascript".
- Masalah keamanan - mendorong penggunaan
.innerHTML. Penguraian string run-time dari data yang disediakan pengguna dapat dengan mudah menyebabkan kerentanan XSS.
Kesimpulan
Ingat saat jQuery membuat penggunaan DOM menjadi sangat mudah? Hasilnya, querySelector()/querySelectorAll()
ditambahkan ke platform. Jelas menang, kan? Library memopulerkan pengambilan DOM
dengan pemilih CSS dan standar yang kemudian mengadopsinya. Tidak selalu berhasil, tetapi saya senang jika berhasil.
Sepertinya <template> adalah kasus yang serupa. Ini menstandarkan cara kita melakukan pembuatan template sisi klien, tetapi yang lebih penting, ini menghilangkan kebutuhan akan peretasan tahun 2008.
Membuat seluruh proses penulisan web menjadi lebih masuk akal, lebih mudah dikelola, dan lebih
lengkap selalu merupakan hal yang baik menurut saya.
Referensi lainnya
- Spesifikasi WhatWG
- Pengantar Komponen Web
- <web>components</web> (video) - presentasi yang sangat komprehensif dari saya.