Tag Template Baru HTML

Menstandarkan template sisi klien

Pengantar

Konsep pembuatan template bukanlah hal baru dalam pengembangan web. Faktanya, bahasa/mesin template sisi server seperti Django (Python), ERB/Haml (Ruby), dan Smarty (PHP) sudah ada sejak lama. Namun, dalam beberapa tahun terakhir, kami telah melihat munculnya ledakan framework MVC. Semuanya sedikit berbeda, tetapi sebagian besar memiliki mekanisme umum untuk merender lapisan presentasi (alias tampilan da): template.

Mari kita hadapi. Template itu luar biasa. Silakan, tanyakan. Bahkan definisinya membuat Anda merasa hangat dan nyaman:

"...tidak perlu dibuat ulang setiap kali..." Tidak tahu tentang Anda, tapi saya suka menghindari pekerjaan ekstra. Lalu, mengapa platform web tidak memiliki dukungan native untuk sesuatu yang jelas-jelas penting bagi developer?

Spesifikasi Template HTML WhatWG adalah jawabannya. Class 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 instancenya nanti saat runtime. Mengutip Rafael Weinstein:

Ini adalah tempat untuk menempatkan kumpulan besar HTML yang tidak ingin Anda ganggu oleh browser sama sekali…apa pun alasannya.

Rafael Weinstein (penulis spesifikasi)

Deteksi Fitur

Untuk mendeteksi <template>, buat elemen DOM dan pastikan 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 dalam markup Anda. File ini berisi "konten template"; pada dasarnya adalah potongan 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.

  1. Kontennya secara efektif tidak aktif hingga diaktifkan. Pada dasarnya, markup Anda adalah DOM tersembunyi dan tidak dirender.

  2. Konten apa pun dalam template tidak akan memiliki efek samping. Skrip tidak berjalan, gambar tidak dimuat, audio tidak diputar,…hingga template digunakan.

  3. Konten dianggap tidak ada dalam dokumen. Menggunakan document.getElementById() atau querySelector() di halaman utama tidak akan menampilkan node turunan template.

  4. 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 dilarang 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 menggunakan document.importNode(). Properti .content adalah DocumentFragment hanya baca yang berisi inti 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 dicetak, kontennya akan "ditayangkan". Dalam contoh khusus ini, konten di-clone, permintaan gambar dibuat, dan markup akhir dirender.

Demo

Contoh: Skrip inert

Contoh ini menunjukkan inersia 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 bahwa makin kompleks Shadow DOM Anda, semakin banyak penyambungan string yang dilakukan. Hal ini tidak dapat diskalakan, semuanya menjadi kacau dengan cepat, dan bayi mulai menangis. Pendekatan ini juga merupakan awal mula terjadinya XSS. <template> dapat membantu.

Cara yang lebih baik adalah menggunakan 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 beberapa masalah yang saya temui saat menggunakan <template> di dunia nyata:

  • Jika Anda menggunakan modpagespeed, berhati-hatilah dengan bug ini. Template yang menentukan <style scoped> inline, banyak dipindahkan ke head dengan aturan penulisan ulang CSS PageSpeed.
  • Tidak ada cara untuk "pra-merender" template, yang berarti Anda tidak dapat memuat aset secara otomatis, memproses JS, mendownload CSS awal, dll. Hal ini berlaku untuk server dan klien. Satu-satunya waktu template dirender adalah saat template 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.

Jalan menuju standar

Jangan lupa asal 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 masalah umum yang saya temui. Saya menyertakannya dalam artikel ini sebagai perbandingan.

Metode 1: DOM di luar layar

Salah satu pendekatan yang telah digunakan orang selama ini 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. Ringkasan teknik ini:

  • Menggunakan DOM - browser mengetahui DOM. Produk ini sangat bagus. Kita dapat dengan mudah menggandakannya.
  • Tidak ada yang dirender - menambahkan hidden akan mencegah blok ditampilkan.
  • Tidak inert - meskipun konten kami disembunyikan, permintaan jaringan masih dibuat untuk gambar.
  • Gaya visual dan tema yang menjengkelkan - halaman penyematan harus mengawali semua aturan CSS-nya dengan #mytemplate untuk mencakup gaya hingga ke template. Hal ini rapuh dan tidak ada jaminan bahwa kami 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 overload

Teknik lainnya adalah membebani <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 lainnya, termasuk beberapa pendatang 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> adalah display:none secara default.
  • Tidak aktif - browser tidak mengurai konten skrip sebagai JS karena jenisnya disetel ke sesuatu selain "text/javascript".
  • Masalah keamanan - mendorong penggunaan .innerHTML. Mengurai string run-time dari data yang disediakan pengguna dapat dengan mudah menyebabkan kerentanan XSS.

Kesimpulan

Ingat saat jQuery membuat penggunaan DOM menjadi sangat sederhana? Hasilnya adalah querySelector()/querySelectorAll() ditambahkan ke platform. Jelas menguntungkan, bukan? Library yang mempopulerkan pengambilan DOM dengan pemilih dan standar CSS kemudian mengadopsinya. Hal ini tidak selalu berhasil, tetapi saya suka jika berhasil.

Saya rasa <template> adalah kasus yang serupa. Hal ini menstandarkan cara kita membuat template sisi klien, tetapi yang lebih penting, hal ini menghilangkan kebutuhan untuk hack 2008. Membuat seluruh proses penulisan web menjadi lebih wajar, lebih mudah dikelola, dan lebih lengkap selalu merupakan hal yang baik.

Referensi lainnya