Menstandarkan template sisi klien
Pengantar
Konsep template bukanlah hal baru dalam pengembangan web. Faktanya, bahasa/mesin template sisi server seperti Django (Python), ERB/Haml (Ruby), dan Smarty (PHP) telah ada sejak lama. Namun dalam beberapa tahun terakhir, kami melihat ledakan framework MVC. Semuanya sedikit berbeda, tetapi sebagian besar memiliki mekanisme umum untuk merender lapisan presentasinya (alias da view): template.
Mari kita hadapi. {i>Template<i} sangat bermanfaat. Silakan bertanya. Bahkan definisinya membuat Anda merasa hangat dan nyaman:
"...tidak harus dibuat ulang setiap saat..." Saya tidak tahu dengan Anda, tapi saya suka menghindari pekerjaan ekstra. Lalu mengapa platform web tidak memiliki dukungan native untuk sesuatu yang sangat penting bagi developer?
Spesifikasi Template HTML WhatWG adalah jawabannya. Contoh ini menentukan
elemen <template>
baru yang menjelaskan pendekatan berbasis DOM standar
untuk template sisi klien. Dengan template, Anda dapat mendeklarasikan fragmen markup yang diuraikan sebagai HTML, tidak digunakan saat pemuatan halaman, tetapi dapat dibuat instance-nya nanti saat runtime. Mengutip Rafael Weinstein:
Ini adalah tempat untuk meletakkan sekumpulan besar HTML yang Anda inginkan agar tidak diganggu oleh browser... karena alasan apa pun.
Rafael Weinstein (penulis spesifikasi)
Deteksi Fitur
Untuk mendeteksi <template>
fitur, 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 mewakili template di markup Anda. Class ini berisi
"konten template"; pada dasarnya potongan inert dari DOM yang dapat di-clone.
Anggap template sebagai bagian dari scaffolding yang dapat Anda gunakan (dan gunakan kembali) sepanjang
masa pakai aplikasi Anda.
Untuk membuat konten dengan template, deklarasikan beberapa markup dan gabungkan ke 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 secara efektif tidak aktif hingga 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 dari sebuah 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. Perlu diperhatikan bahwa "di mana saja" berarti<template>
dapat digunakan dengan aman di tempat-tempat yang dilarang oleh parser HTML...semua, kecuali turunan model konten. Status 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 paling sederhana untuk melakukannya adalah dengan membuat salinan mendalam dari .content
menggunakan document.importNode()
. Properti .content
adalah DocumentFragment
hanya baca yang berisi nyali 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 template diberi stempel, kontennya "ditayangkan". Dalam contoh khusus ini, konten di-clone, permintaan gambar dibuat, dan markup akhir dirender.
Demo
Contoh: Skrip inert
Contoh ini menunjukkan inertness konten template. <script>
hanya
berjalan saat tombol ditekan, yang akan menstempel 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
Kebanyakan orang melampirkan Shadow DOM ke host dengan menyetel 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. Tidak meluas, keadaan menjadi cepat
berantakan, dan bayi mulai menangis. Pendekatan ini juga merupakan awal lahirnya XSS! <template>
dapat membantu.
Hal yang lebih masuk akal adalah bekerja dengan DOM secara langsung dengan menambahkan konten template ke root bayangan:
<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>
Gotcha
Berikut adalah beberapa kesalahan yang saya temukan saat menggunakan <template>
di luar sana:
- Jika Anda menggunakan modpagespeed, berhati-hatilah
terhadap bug ini. Template
yang menentukan
<style scoped>
inline, banyak yang dipindahkan ke head dengan penulisan ulang aturan CSS PageSpeed. - Tidak ada cara untuk "melakukan pra-rendering" template, artinya Anda tidak dapat melakukan pramuat aset, memproses JS, mendownload CSS awal, dll. Hal itu berlaku baik untuk server maupun klien. Satu-satunya waktu render template adalah saat template ditayangkan.
Hati-hati dengan template bertingkat. Fiturnya tidak berperilaku seperti yang Anda harapkan. Contoh:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>
Mengaktifkan template luar tidak akan mengaktifkan template dalam. Dengan kata lain, template bertingkat mengharuskan turunannya juga diaktifkan secara manual.
Jalan menuju
Jangan lupa dari mana kita berasal. Jalan menuju {i>template<i} HTML berbasis standar sudah lama. Selama bertahun-tahun, kami telah menemukan beberapa trik cerdas untuk membuat {i>template<i} yang bisa digunakan kembali. Di bawah ini adalah dua yang sering saya temui. Saya menyertakannya dalam artikel ini sebagai perbandingan.
Metode 1: DOM di Luar Layar
Salah satu pendekatan yang telah lama digunakan orang adalah membuat DOM "offscreen"
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. Berikut ringkasan teknik ini:
- Menggunakan DOM - browser mengetahui DOM. Kerja yang bagus. Kita dapat menggandakannya dengan mudah.
- Tidak ada yang dirender - menambahkan
hidden
akan mencegah blok ditampilkan. - Tidak inert - meskipun konten kita tersembunyi, permintaan jaringan masih dibuat untuk gambar.
- Gaya visual dan tema yang merepotkan - halaman penyematan harus mengawali semua aturan CSS-nya dengan
#mytemplate
agar dapat menentukan cakupan gaya hingga template tersebut. Hal ini bersifat rapuh dan tidak ada jaminan bahwa kita tidak akan mengalami konflik penamaan di masa mendatang. Misalnya, kita melakukan hosing jika halaman embedding sudah memiliki elemen dengan ID tersebut.
Metode 2: Skrip kelebihan beban
Teknik lainnya adalah membebani <script>
dan memanipulasi kontennya
sebagai string. John Resig mungkin adalah orang pertama yang menampilkannya pada tahun 2008 dengan
utilitas Micro Templating miliknya.
Sekarang sudah ada banyak perubahan lain, termasuk beberapa anak baru yang bergabung seperti handlebars.js.
Contoh:
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
Berikut ringkasan teknik ini:
- Tidak ada yang dirender - browser tidak merender blok ini karena
<script>
bernilaidisplay:none
secara default. - Inert - browser tidak mengurai konten skrip sebagai JS karena jenisnya ditetapkan ke sesuatu selain "text/javascript".
- Masalah keamanan - mendorong penggunaan
.innerHTML
. Penguraian string waktu proses untuk data yang disediakan pengguna dapat dengan mudah menyebabkan kerentanan XSS.
Kesimpulan
Ingat ketika jQuery membuat bekerja dengan DOM menjadi sangat sederhana? Hasilnya adalah querySelector()
/querySelectorAll()
ditambahkan ke platform. Kemenangan yang jelas, bukan? Sebuah library mempopulerkan pengambilan DOM dengan pemilih dan standar CSS kemudian mengadopsinya. Caranya tidak selalu seperti itu, tetapi saya menyukai cara itu.
Menurut saya, <template>
adalah kasus yang serupa. Fitur ini menstandarkan cara kami melakukan template sisi klien, tetapi yang lebih penting, menghilangkan kebutuhan akan peretasan kami pada versi 2008.
Membuat seluruh proses penulisan web lebih masuk akal, lebih mudah dikelola, dan lebih
berfitur lengkap selalu merupakan hal yang baik dalam buku saya.
Referensi tambahan
- Spesifikasi WhatWG
- Pengantar Komponen Web
- <web>components</web> (video) - sebuah presentasi yang sangat komprehensif oleh Anda sendiri.