HEEx adalah bahasa template Phoenix untuk menyusun HTML dengan interpolasi
nilai yang aman dan struktur yang dapat dikomposisi. Topik ini membahas sintaks
HEEx yang digunakan sehari-hari, komponen fungsi dengan atribut, serta struktur
layout yang disederhanakan pada Phoenix 1.8. Setelah membaca, template
*.html.heex pada proyek dapat dibaca dan ditulis, termasuk menelusuri
hubungan antara root layout dan app layout.
Interpolasi dan atribut dinamis#
HEEx ditulis dalam sigil ~H pada modul Elixir atau dalam berkas
*.html.heex yang disematkan oleh modul view. Nilai Elixir disisipkan dengan
kurung kurawal, misalnya {@judul}, sedangkan ekspresi Elixir lengkap
menggunakan tanda <%= %>. Keduanya menyandikan nilai secara otomatis, sehingga
string yang berisi karakter HTML tidak dapat menyusup sebagai markah. Sifat
ini menjaga template tetap aman dari serangan injeksi tanpa usaha tambahan.
Atribut HTML juga menerima nilai dinamis dengan kurung kurawal, misalnya
<a href={~p"/catatan/#{c.id}"}>. Sigil ~p memverifikasi jalur tersebut
terhadap rute yang terdaftar pada waktu kompilasi, sehingga salah ketik
rute terdeteksi sebelum aplikasi berjalan. Kontrol perulangan dan percabangan
pun berbentuk atribut, yaitu :for untuk perulangan dan :if untuk
percabangan, sebagaimana ditunjukkan pada skrip berikut.
Delegasi ke komponen lain juga berlangsung dengan sintaks yang sama.
Komponen dipanggil seperti tag HTML dengan awalan titik, misalnya
<.sapa nama="Ali" />, dan atributnya divalidasi terhadap deklarasi attr
komponen tersebut. Validasi ini menjangkau tipe atribut, sehingga atribut
yang dideklarasikan :string tidak menerima daftar maupun bilangan. Efeknya,
kesalahan yang paling sering terjadi pada template, yaitu nama nilai yang
berubah tanpa semua pemanggilnya ikut diubah, terdeteksi saat kompilasi.
Merender HEEx ke string#
Skrip berikut mendefinisikan dua komponen HEEx, lalu merendernya menjadi
string HTML. Komponen sapa/1 memiliki atribut wajib nama, sedangkan
daftar/1 menggunakan :for dan :if sekaligus.
Mix.install([{:phoenix_live_view, "~> 1.1"}, {:jason, "~> 1.4"}])
Logger.configure(level: :warning)
defmodule SapaanHTML do
use Phoenix.Component
attr :nama, :string, required: true
def sapa(assigns) do
~H"""
<h1>Selamat belajar, {@nama}</h1>
"""
end
def daftar(assigns) do
~H"""
<ul>
<li :for={c <- @catatan}>{c.judul} <em :if={c.draf}>(draf)</em></li>
</ul>
"""
end
end
Code.ensure_loaded(Phoenix.LiveViewTest)
IO.puts(Phoenix.LiveViewTest.rendered_to_string(SapaanHTML.sapa(%{nama: "Ali"})))
assigns = %{catatan: [%{judul: "Puisi hujan", draf: true}, %{judul: "Rumput beton", draf: false}]}
IO.puts(Phoenix.LiveViewTest.rendered_to_string(SapaanHTML.daftar(assigns)))Jalankan skripnya dengan elixir sapaan.exs. Keluarannya berupa string HTML
persis hasil template, termasuk perulangan yang melebarkan menjadi dua butir
<li> dan percabangan yang hanya menampilkan tanda draf untuk catatan pertama.
<h1>Selamat belajar, Ali</h1>
<ul>
<li>Puisi hujan <em>(draf)</em></li><li>Rumput beton </li>
</ul>Makro attr di atas fungsi komponen mendeklarasikan atribut beserta tipenya dan
keharusannya. Pemanggilan yang tidak menyertakan atribut wajib gagal pada waktu kompilasi, dan
validasi ini berlaku juga pada template .heex yang disematkan oleh modul view.
Fungsi Phoenix.LiveViewTest.rendered_to_string/1 digunakan di sini agar hasil
HEEx dapat diperiksa sebagai string biasa dalam skrip mandiri. Pemanggilan
Code.ensure_loaded/1 sebelum fungsi tersebut menjamin modul penguji sudah
dimuat pada skrip tanpa proyek Mix, dan bantuan yang sama digunakan oleh rangkaian
tes LiveView untuk memeriksa hasil render.
Root layout dan app layout pada Phoenix 1.8#
Phoenix 1.8 menyederhanakan layout menjadi dua bagian. Root layout berada di
berkas components/layouts/root.html.heex dan memuat kerangka dokumen,
yaitu <head> dengan judul, CSS, dan JavaScript. Berikut potongan aslinya
dari proyek hasil generator Phoenix 1.8:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content={get_csrf_token()} />
<.live_title default="NamaProyek" suffix=" · Phoenix Framework">
{assigns[:page_title]}
</.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} />
</head>
<body>
{@inner_content}
</body>
</html>Penyederhanaan terjadi pada app layout. Pada Phoenix 1.8, app layout
bukan berkas .heex terpisah lagi, melainkan komponen fungsi bernama app/1
di dalam modul Layouts. Setiap template halaman membuka komponen tersebut secara eksplisit,
sebagaimana tampak pada potongan new.html.heex hasil phx.gen.auth:
# tidak-dijalankan (fragmen templat, bukan skrip utuh)
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<.header>
<p>Log in</p>
</.header>
<!-- isi halaman -->
</Layouts.app>
"""Komponen app/1 menerima slot inner_block dan merendernya dengan
{render_slot(@inner_block)}, lalu mengelilingi isi dengan menu dan area
utama. Pesan flash dirender oleh <.flash_group flash={@flash} /> yang juga
berada di dalam app/1. Pola pembungkusan eksplisit ini membuat template
halaman menetapkan sendiri layout yang digunakannya. Root layout tetap
menjadi kerangka terluar yang disetel oleh pipeline :browser melalui
plug :put_root_layout.
Pemisahan dua lapisan layout ini memiliki konsekuensi praktis pada perilaku
respons HTTP. Karena root layout disetel oleh plug pada pipeline, rute API
yang tidak melewati :browser tidak membawa kerangka dokumen sama sekali,
sehingga respons JSON tidak pernah terbungkus <html>. Sementara itu,
halaman yang membutuhkan kerangka berbeda, misalnya halaman cetak tanpa menu,
cukup membungkus dirinya dengan komponen lain tanpa mengubah pipeline.
Kesalahan umum#
Kesalahan pertama adalah mencampur sintaks interpolasi. Kurung kurawal hanya
berlaku di dalam sigil ~H dan berkas .heex, sehingga template .eex lama
yang berisi {@nilai} menampilkan kurung kurawal apa adanya. Pastikan berkas
memiliki akhiran .html.heex sebelum menggunakan sintaks HEEx.
Kesalahan kedua adalah mengakses assign yang belum dikirim. Setiap template
hanya melihat nilai yang dikirim pemanggilnya, misalnya render(conn, :new, form: form) membuat @form tersedia, sedangkan @current_scope hanya ada
apabila pipeline mengisinya. Mengakses assign yang tidak ada melempar error
KeyError, dan perbaikannya adalah mengirim nilai tersebut atau mendeklarasikan
nilai bawaan pada makro attr.
Kesalahan ketiga adalah memindahkan isi halaman ke root layout. Root layout
memuat kerangka dokumen dan berlaku untuk semua respons HTML, termasuk halaman
error, sehingga menu aplikasi yang ditaruh di sana juga muncul pada halaman
error. Menu dan bagian yang terikat aplikasi ditempatkan pada komponen
app/1, dan isi halaman diteruskan melalui slot inner_block.
Ringkasan#
- HEEx menyandikan interpolasi secara otomatis, baik melalui
{nilai}maupun<%= ekspresi %>, dengan:fordan:ifsebagai kontrol template. - Komponen fungsi mendeklarasikan atributnya dengan makro
attrdan divalidasi pada waktu kompilasi. - Layout Phoenix 1.8 terdiri atas
root.html.heexsebagai kerangka dokumen dan komponenLayouts.appsebagai pembungkus isi halaman melalui slot. - Template halaman membungkus dirinya dengan
<Layouts.app ...>dan pesan flash dirender oleh<.flash_group>di dalamnya.
Referensi#
- Templates — Phoenix Framework — Panduan template Phoenix beserta HEEx.
- Phoenix.Component — Phoenix LiveView
— Dokumentasi komponen HEEx,
attr, dan atribut:forserta:if. - Layouts — Phoenix Framework — Panduan layout, root layout, dan app layout.