Langsung ke konten
KamusNgoding
Pemula Laravel 3 menit baca

Blade Template Engine: Membuat Tampilan Dinamis di Laravel

#laravel #blade #template-engine #layout #komponen #pemula
📚

Baca dulu sebelum ini:

Pendahuluan

Di artikel sebelumnya tentang Routing dan Controller, kita sudah bisa membuat Controller yang memproses request. Tapi hasil outputnya masih teks biasa. Sekarang saatnya membuat tampilan yang nyata dan dinamis!

Blade adalah template engine bawaan Laravel yang menggabungkan kekuatan PHP dengan sintaks yang jauh lebih bersih. Bayangkan HTML yang bisa menampilkan variabel, punya kondisi if, loop foreach, dan bisa diwariskan seperti class OOP.

Di akhir artikel ini, kamu akan bisa:

  • Menampilkan data dari Controller ke View
  • Menggunakan direktif Blade: @if, @foreach, @for
  • Membuat layout yang bisa digunakan ulang
  • Membuat Blade Component yang reusable
  • Memahami perbedaan {{ }} dan {!! !!}

Cara Kerja Blade

File Blade disimpan di folder resources/views/ dengan ekstensi .blade.php. Ketika diakses, Blade dikompilasi menjadi PHP murni dan di-cache, sehingga tidak ada overhead performa.

Membuat View Pertama

# Buat file view secara manual
# resources/views/tasks/index.blade.php
// Di Controller, kirim data ke view menggunakan compact() atau array
class TaskController extends Controller
{
    public function index()
    {
        $tasks = [
            ['id' => 1, 'title' => 'Belajar Laravel', 'done' => false],
            ['id' => 2, 'title' => 'Membuat CRUD App', 'done' => true],
        ];

        // Cara 1: compact() — nama variabel menjadi key
        return view('tasks.index', compact('tasks'));

        // Cara 2: array eksplisit
        return view('tasks.index', ['tasks' => $tasks]);

        // Cara 3: with()
        return view('tasks.index')->with('tasks', $tasks);
    }
}
{{-- resources/views/tasks/index.blade.php --}}
<!DOCTYPE html>
<html lang="id">
<head>
    <title>Daftar Task</title>
</head>
<body>
    <h1>Task Manager</h1>
    <ul>
        @foreach ($tasks as $task)
            <li>{{ $task['title'] }}</li>
        @endforeach
    </ul>
</body>
</html>

Menampilkan Data: {{ }} vs {!! !!}

Ini adalah perbedaan yang sangat penting untuk keamanan:

{{-- {{ }} — AMAN: melakukan HTML escaping (mencegah XSS) --}}
{{ $user->name }}
{{-- Jika $user->name = '<script>alert("hack")</script>'
     Maka output: &lt;script&gt;alert("hack")&lt;/script&gt; (aman) --}}

{{-- {!! !!} — TIDAK AMAN: menampilkan HTML mentah --}}
{!! $post->html_content !!}
{{-- Gunakan HANYA jika kamu yakin kontennya aman (HTML dari editor WYSIWYG) --}}

Aturan emas: Selalu gunakan {{ }} untuk data dari pengguna. Gunakan {!! !!} hanya untuk konten HTML yang kamu kontrol sendiri.

Menampilkan Data dengan Default

{{-- Jika $title null, tampilkan "Tanpa Judul" --}}
{{ $title ?? 'Tanpa Judul' }}

{{-- Menggunakan helper @isset --}}
@isset($user)
    <p>Halo, {{ $user->name }}</p>
@endisset

Direktif Kontrol Alur

@if, @elseif, @else

@if ($user->isAdmin())
    <span class="badge">Admin</span>
@elseif ($user->isPremium())
    <span class="badge">Premium</span>
@else
    <span class="badge">Regular</span>
@endif

{{-- Versi pendek untuk kondisi negatif --}}
@unless ($user->isLoggedIn())
    <a href="/login">Login</a>
@endunless

@foreach dan @forelse

{{-- Perulangan biasa --}}
@foreach ($tasks as $task)
    <div class="task">
        <h3>{{ $task->title }}</h3>
        <p>{{ $task->description }}</p>
    </div>
@endforeach

{{-- @forelse: otomatis handle kondisi kosong --}}
@forelse ($tasks as $task)
    <div class="task">{{ $task->title }}</div>
@empty
    <p>Belum ada task. <a href="{{ route('tasks.create') }}">Buat task pertama!</a></p>
@endforelse

Loop Variable $loop

Blade menyediakan variabel $loop di dalam @foreach yang sangat berguna:

@foreach ($tasks as $task)
    <tr class="{{ $loop->even ? 'bg-gray-100' : '' }}">
        <td>{{ $loop->iteration }}</td>  {{-- Nomor urut (1, 2, 3...) --}}
        <td>{{ $task->title }}</td>
        @if ($loop->first)
            <td>⭐ Pertama</td>
        @elseif ($loop->last)
            <td>🏁 Terakhir</td>
        @else
            <td>-</td>
        @endif
    </tr>
@endforeach

@for dan @while

@for ($i = 1; $i <= 5; $i++)
    <span>{{ $i }}</span>
@endfor

@while ($condition)
    {{-- Jarang digunakan, hati-hati infinite loop --}}
@endwhile

@switch

@switch($task->priority)
    @case('high')
        <span style="color: red">Tinggi</span>
        @break
    @case('medium')
        <span style="color: orange">Sedang</span>
        @break
    @default
        <span style="color: green">Rendah</span>
@endswitch

Layout dengan Template Inheritance

Salah satu fitur terkuat Blade adalah template inheritance — kamu bisa membuat satu layout dasar yang digunakan oleh semua halaman.

Langkah 1: Buat Layout Utama

{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'Task Manager')</title>
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
    {{-- Navigasi --}}
    <nav>
        <a href="{{ route('tasks.index') }}">Task Manager</a>
        @auth
            <span>Halo, {{ auth()->user()->name }}!</span>
            <form action="/logout" method="POST" style="display:inline">
                @csrf
                <button>Logout</button>
            </form>
        @else
            <a href="/login">Login</a>
        @endauth
    </nav>

    {{-- Flash Message --}}
    @if (session('success'))
        <div class="alert alert-success">{{ session('success') }}</div>
    @endif

    {{-- Konten halaman (didefinisikan di child view) --}}
    <main>
        @yield('content')
    </main>

    {{-- Sidebar opsional --}}
    @section('sidebar')
        <aside>Default Sidebar</aside>
    @show

    <footer>
        <p>© 2026 Task Manager</p>
    </footer>

    @yield('scripts')
</body>
</html>

Langkah 2: Gunakan Layout di Child View

{{-- resources/views/tasks/index.blade.php --}}

{{-- 1. Extends layout utama --}}
@extends('layouts.app')

{{-- 2. Isi section 'title' --}}
@section('title', 'Daftar Task')

{{-- 3. Isi section 'content' --}}
@section('content')
    <h1>Daftar Semua Task</h1>

    <a href="{{ route('tasks.create') }}">+ Tambah Task</a>

    @forelse ($tasks as $task)
        <div class="card">
            <h3>{{ $task->title }}</h3>
            <p>{{ $task->description }}</p>
            <a href="{{ route('tasks.show', $task->id) }}">Lihat Detail</a>
        </div>
    @empty
        <p>Belum ada task.</p>
    @endforelse
@endsection

{{-- 4. Override section sidebar (opsional) --}}
@section('sidebar')
    <aside>
        <h4>Filter</h4>
        <a href="?status=pending">Pending</a>
        <a href="?status=done">Selesai</a>
    </aside>
@endsection

{{-- 5. Tambah JavaScript khusus halaman ini --}}
@section('scripts')
    <script>
        console.log('Task index page loaded');
    </script>
@endsection

Blade Components

Blade Components adalah cara modern (sejak Laravel 7) untuk membuat komponen UI yang reusable — mirip dengan komponen React atau Vue.

Membuat Component

# Buat component menggunakan Artisan
php artisan make:component Alert
# Membuat: app/View/Components/Alert.php
# Membuat: resources/views/components/alert.blade.php
// app/View/Components/Alert.php
class Alert extends Component
{
    public function __construct(
        public string $type = 'info',  // 'success', 'warning', 'error', 'info'
        public string $message = '',
    ) {}

    public function render()
    {
        return view('components.alert');
    }
}
{{-- resources/views/components/alert.blade.php --}}
<div class="alert alert-{{ $type }}">
    {{ $message }}
    {{-- $slot untuk konten tambahan --}}
    {{ $slot }}
</div>

Menggunakan Component

{{-- Cara 1: Dengan props --}}
<x-alert type="success" message="Task berhasil disimpan!" />

{{-- Cara 2: Dengan slot (untuk konten lebih kompleks) --}}
<x-alert type="warning">
    Data akan <strong>dihapus permanen</strong>. Yakin?
</x-alert>

{{-- Component tombol yang reusable --}}
<x-button variant="primary" href="{{ route('tasks.create') }}">
    + Tambah Task
</x-button>

@include — Menyertakan Sub-View

Untuk bagian halaman yang sering diulang:

{{-- resources/views/partials/task-card.blade.php --}}
<div class="card">
    <h3>{{ $task->title }}</h3>
    <p>{{ Str::limit($task->description, 100) }}</p>
    <span class="badge {{ $task->done ? 'done' : 'pending' }}">
        {{ $task->done ? 'Selesai' : 'Pending' }}
    </span>
</div>
{{-- Gunakan di halaman lain --}}
@foreach ($tasks as $task)
    @include('partials.task-card', ['task' => $task])
@endforeach

{{-- @include dengan kondisi --}}
@includeWhen($user->isAdmin(), 'partials.admin-controls')
@includeUnless($user->isBanned(), 'partials.comment-form')

Helper URL dan Asset

{{-- URL ke halaman --}}
<a href="{{ url('/tasks') }}">Tasks</a>
<a href="{{ route('tasks.index') }}">Tasks (named route)</a>

{{-- URL ke file di folder public/ --}}
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
<img src="{{ asset('images/logo.png') }}" alt="Logo">
<script src="{{ asset('js/app.js') }}"></script>

{{-- URL ke file upload (dari storage/) --}}
<img src="{{ Storage::url($user->avatar) }}" alt="Avatar">

Stacks — Inject Script dari Child View

{{-- Di layout utama, definisikan stack --}}
@stack('scripts')

{{-- Di child view, push script ke stack --}}
@push('scripts')
    <script src="{{ asset('js/task-form.js') }}"></script>
@endpush

Contoh Praktis: Halaman Lengkap Task Manager

Berikut contoh tampilan lengkap yang mengintegrasikan semua yang sudah kita pelajari:

{{-- resources/views/tasks/index.blade.php --}}
@extends('layouts.app')

@section('title', 'Daftar Task - Task Manager')

@section('content')
<div class="container">
    <div class="header">
        <h1>📋 Semua Task</h1>
        <a href="{{ route('tasks.create') }}" class="btn btn-primary">
            + Tambah Task
        </a>
    </div>

    @if (session('success'))
        <x-alert type="success" :message="session('success')" />
    @endif

    <div class="task-grid">
        @forelse ($tasks as $task)
            <div class="task-card {{ $task->done ? 'done' : '' }}">
                <div class="task-number">{{ $loop->iteration }}</div>
                <h3>{{ $task->title }}</h3>
                <p>{{ Str::limit($task->description, 80) }}</p>

                @if ($task->due_date)
                    <small>📅 {{ $task->due_date->format('d M Y') }}</small>
                @endif

                <div class="task-actions">
                    <a href="{{ route('tasks.show', $task) }}">Lihat</a>
                    <a href="{{ route('tasks.edit', $task) }}">Edit</a>
                    <form action="{{ route('tasks.destroy', $task) }}" method="POST">
                        @csrf
                        @method('DELETE')
                        <button type="submit" onclick="return confirm('Yakin hapus?')">
                            Hapus
                        </button>
                    </form>
                </div>
            </div>
        @empty
            <div class="empty-state">
                <p>🎉 Tidak ada task! <a href="{{ route('tasks.create') }}">Buat yang pertama</a></p>
            </div>
        @endforelse
    </div>

    {{-- Pagination --}}
    {{ $tasks->links() }}
</div>
@endsection

Troubleshooting: Error yang Sering Muncul

Undefined variable $tasks

Penyebab: Controller mengirim nama variabel yang berbeda, atau lupa mengirim data ke view.

Solusi:

// Di Controller, pastikan nama variabel sama dengan yang digunakan di Blade
return view('tasks.index', compact('tasks')); // ✅ $tasks tersedia di view

// Jangan lupa kirim data!
return view('tasks.index'); // ❌ $tasks tidak tersedia

@yield Tidak Menampilkan Konten

Penyebab: Lupa menambahkan @extends di child view, atau nama section tidak cocok.

Solusi:

{{-- Pastikan ada @extends di BARIS PERTAMA child view --}}
@extends('layouts.app')  {{-- ✅ Harus ada! --}}

{{-- Pastikan nama @section sama dengan @yield di layout --}}
{{-- Di layout: @yield('content') --}}
@section('content')  {{-- ✅ Nama harus sama persis --}}
    Konten di sini
@endsection

Pertanyaan yang Sering Diajukan

Apa bedanya Blade vs React/Vue untuk frontend?

Blade berjalan di server-side (PHP di server menghasilkan HTML lengkap yang dikirim ke browser). React/Vue berjalan di client-side (JavaScript di browser yang merender UI). Blade lebih sederhana dan cocok untuk web app tradisional. React/Vue cocok untuk aplikasi SPA yang sangat interaktif. Banyak developer Indonesia menggunakan keduanya: Laravel sebagai API backend + React/Vue sebagai frontend. Baca lebih lanjut tentang cara mengintegrasikan keduanya (termasuk CORS).

Kapan menggunakan @include vs Blade Component?

Gunakan @include untuk partial view sederhana yang tidak butuh logic (footer, header, dsb). Gunakan Blade Component untuk elemen UI yang reusable dan punya logic sendiri (tombol, kartu, modal, alert). Component lebih OOP dan testable.

Bagaimana cara debug variabel di Blade?

{{-- Dump dan die — tampilkan isi variabel lalu hentikan eksekusi --}}
{{ dd($task) }}

{{-- Dump tanpa hentikan eksekusi --}}
{{ dump($tasks) }}

{{-- Tampilkan semua variabel yang tersedia di view ini --}}
{{ dd(get_defined_vars()) }}

Kesimpulan

Blade adalah template engine yang akan kamu gunakan di setiap proyek Laravel. Kita sudah mempelajari:

  • Menampilkan data dengan {{ }} yang aman dari XSS
  • Direktif kontrol alur: @if, @foreach, @forelse, @switch
  • Template inheritance: @extends, @yield, @section untuk layout reusable
  • Blade Components: <x-component> untuk UI yang modular
  • @include untuk partial view

Di artikel berikutnya, kita masuk ke topik yang paling ditunggu: Eloquent ORM — cara Laravel berbicara dengan database menggunakan PHP yang ekspresif tanpa SQL manual.

Blade sudah kamu kuasai — tampilan aplikasimu sekarang bisa jauh lebih hidup! ✨

Artikel Terkait