Views dan Templates di NexaUI Framework

Pengenalan

Views dalam arsitektur MVC bertanggung jawab untuk menampilkan data kepada pengguna. NexaUI menggunakan sistem template yang powerful namun sederhana untuk memisahkan logika presentasi dari logika bisnis, memungkinkan pengembangan antarmuka yang fleksibel dan mudah dipelihara.

Struktur Template

Template di NexaUI disimpan dalam direktori templates/ dan diorganisir berdasarkan area fungsional atau jenis device:

templates/
├── 404.html            # Error page
├── dashboard/          # Admin dashboard templates
│   ├── index.html
│   ├── users.html
│   └── settings.html
├── index.html          # Main layout
├── mobile/             # Mobile-specific templates
│   ├── index.html
│   └── article.html
├── tablet/             # Tablet-specific templates
│   ├── index.html
│   └── article.html
└── theme/              # Theme components
    ├── header.html
    ├── footer.html
    ├── menu.html
    └── sidebar.html

Template Dasar

Template NexaUI menggunakan sintaks sederhana dengan placeholder dalam kurung kurawal {variable}. Berikut adalah contoh template dasar:

<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{title}</title>
    <link rel="stylesheet" href="http://www.tatiye.net/assets/css/style.css">
</head>
<body>
    <div class="container">
        <h1>{title}</h1>
        <p>{content}</p>
        
        <!-- Conditional rendering -->
        <!-- IF logged_in -->
            <p>Welcome, {user_name}!</p>
        <!-- ELSE -->
            <p>Please <a href="http://www.tatiye.net/login">login</a> to continue.</p>
        <!-- END IF -->
    </div>
</body>
</html>

Template Engine

NexaUI menggunakan template engine yang mendukung berbagai fitur:

Variabel dan Ekspresi

Variabel ditampilkan menggunakan sintaks {variable}:

<h1>{title}</h1>
<p>{description}</p>

<!-- Nested properties -->
<p>Author: {article.author.name}</p>

<!-- With default value -->
<p>{comment|default:"No comments yet"}</p>

<!-- With filters -->
<p>Posted on: {created_at|date:"Y-m-d H:i"}</p>
<p>{content|truncate:100}</p>

Kondisional

Blok kondisional memungkinkan rendering bersyarat:

<!-- Simple condition -->
<!-- IF logged_in -->
    <p>Welcome back!</p>
<!-- ELSE -->
    <p>Please login.</p>
<!-- END IF -->

<!-- Multiple conditions -->
<!-- IF role == "admin" -->
    <a href="http://www.tatiye.net/admin">Admin Panel</a>
<!-- ELSEIF role == "editor" -->
    <a href="http://www.tatiye.net/editor">Editor Dashboard</a>
<!-- ELSE -->
    <a href="http://www.tatiye.net/profile">Your Profile</a>
<!-- END IF -->

<!-- Comparison operators -->
<!-- IF items_count > 0 -->
    <p>You have {items_count} items in your cart.</p>
<!-- ELSE -->
    <p>Your cart is empty.</p>
<!-- END IF -->

Loop

Loop digunakan untuk mengiterasi array atau koleksi:

<!-- Simple loop -->
<ul>
    <!-- LOOP users -->
        <li>{name} ({email})</li>
    <!-- END LOOP -->
</ul>

<!-- Loop with index -->
<table>
    <!-- LOOP products -->
        <tr class="odd">
            <td>{_index + 1}</td>
            <td>{name}</td>
            <td>{price|currency}</td>
        </tr>
    <!-- END LOOP -->
</table>

<!-- Empty check -->
<!-- LOOP comments -->
    <div class="comment">
        <h4>{author}</h4>
        <p>{text}</p>
    </div>
<!-- EMPTY -->
    <p>No comments yet.</p>
<!-- END LOOP -->

Include

Include digunakan untuk menyertakan template lain:

<!-- Include header template -->
{include/theme/header}

<!-- Main content -->
<div class="content">
    <h1>{title}</h1>
    <p>{content}</p>
</div>

<!-- Include footer template -->
{include/theme/footer}

<!-- Include with variables -->
{include/theme/sidebar sidebar_title="Navigation" active_item="home"}

Layout

NexaUI mendukung sistem layout untuk menghindari duplikasi kode. Layout adalah template yang berisi struktur umum halaman dengan placeholder untuk konten spesifik.

Mendefinisikan Layout

Layout biasanya disimpan di templates/layouts/:

<!-- templates/layouts/default.html -->
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{title} - NexaUI</title>
    <link rel="stylesheet" href="http://www.tatiye.net/assets/css/style.css">
    
    <!-- BLOCK head -->
    <!-- END BLOCK -->
</head>
<body>
    <header>
        {include/theme/header}
    </header>
    
    <main>
        <!-- BLOCK content -->
        <p>Default content</p>
        <!-- END BLOCK -->
    </main>
    
    <footer>
        {include/theme/footer}
    </footer>
    
    <script src="http://www.tatiye.net/assets/js/desktop.combined.min.js"></script>
    
    <!-- BLOCK scripts -->
    <!-- END BLOCK -->
</body>
</html>

Menggunakan Layout

Template dapat menggunakan layout dan mengisi blok konten:

<!-- templates/articles/show.html -->
{extends/layouts/default}

<!-- BLOCK head -->
<meta property="og:title" content="{article.title}">
<meta property="og:description" content="{article.excerpt}">
<!-- END BLOCK -->

<!-- BLOCK content -->
<article>
    <h1>{article.title}</h1>
    <div class="meta">
        By {article.author.name} on {article.published_at|date:"F j, Y"}
    </div>
    <div class="content">
        {article.content}
    </div>
</article>

<section class="comments">
    <h2>Comments ({article.comments|count})</h2>
    
    <!-- LOOP article.comments -->
        <div class="comment">
            <h4>{author}</h4>
            <p>{text}</p>
        </div>
    <!-- EMPTY -->
        <p>No comments yet.</p>
    <!-- END LOOP -->
</section>
<!-- END BLOCK -->

<!-- BLOCK scripts -->
<script src="http://www.tatiye.net/assets/js/comments.js"></script>
<!-- END BLOCK -->

Rendering View dari Controller

Di controller, gunakan method render() untuk menampilkan template dengan data:

public function show(int $id): void
{
    $article = $this->articleRepository->find($id);
    
    if (!$article) {
        $this->notFound();
        return;
    }
    
    $comments = $this->commentRepository->findByArticle($id);
    
    $data = [
        'title' => $article->getTitle(),
        'article' => $article,
        'comments' => $comments
    ];
    
    $this->render('articles/show', $data);
}

Anda juga dapat menentukan layout yang berbeda:

// Render dengan layout tertentu
$this->render('articles/show', $data, 'layouts/sidebar');

// Render tanpa layout
$this->render('articles/show', $data, null);

Helper

NexaUI menyediakan beberapa helper untuk digunakan dalam template:

URL Helper

<!-- Generate URL -->
<a href="http://www.tatiye.net/articles">All Articles</a>
<a href="http://www.tatiye.net/articles/123">View Article</a>

<!-- With parameters -->
<a href="{link/articles?category=tech&sort=newest}">Tech Articles</a>

<!-- Asset URL -->
<img src="http://www.tatiye.net/assets/images/logo.png" alt="Logo">
<link rel="stylesheet" href="http://www.tatiye.net/assets/css/style.css">

Form Helper

<!-- CSRF token -->
<form method="post" action="http://www.tatiye.net/articles/store">
    {csrf_field}
    
    <!-- Input with old value -->
    <input type="text" name="title" value="{old.title}" placeholder="Title">
    
    <!-- Error display -->
    <!-- IF errors.title -->
        <span class="error">{errors.title}</span>
    <!-- END IF -->
    
    <button type="submit">Save</button>
</form>

Filter

<!-- Date formatting -->
<p>Published: {article.published_at|date:"F j, Y"}</p>

<!-- Text formatting -->
<p>{article.excerpt|truncate:100}</p>
<p>{article.content|nl2br}</p>

<!-- Number formatting -->
<p>Price: {product.price|currency}</p>
<p>Rating: {product.rating|number_format:1}</p>

<!-- HTML escaping -->
<p>{user_input|escape}</p>

<!-- Custom filter -->
<p>{user.role|translate}</p>

Responsive Design

NexaUI mendukung template yang berbeda untuk device yang berbeda:

// Di controller
public function index(): void
{
    $articles = $this->articleRepository->findLatest(10);
    $data = ['articles' => $articles];
    
    // Render template berdasarkan device
    if ($this->isMobile()) {
        $this->render('mobile/articles/index', $data);
    } elseif ($this->isTablet()) {
        $this->render('tablet/articles/index', $data);
    } else {
        $this->render('articles/index', $data);
    }
}

Atau Anda dapat menggunakan deteksi otomatis:

// Otomatis memilih template berdasarkan device
$this->renderResponsive('articles/index', $data);

Partial dan Komponen

Untuk kode yang digunakan kembali, Anda dapat membuat partial atau komponen:

Partial

<!-- templates/partials/article_card.html -->
<div class="article-card">
    <h3><a href="{link/articles/{id}}">{title}</a></h3>
    <div class="meta">By {author.name} on {published_at|date:"F j, Y"}</div>
    <p>{excerpt|truncate:150}</p>
    <a href="{link/articles/{id}}" class="read-more">Read more</a>
</div>

<!-- Using the partial -->
<div class="articles">
    <!-- LOOP articles -->
        {include/partials/article_card}
    <!-- END LOOP -->
</div>

Komponen

NexaUI juga mendukung komponen yang lebih dinamis:

<!-- Using a component -->
{component/NexaPagination total=100 current=5 per_page=10}

<!-- Using a component with custom template -->
{component/NexaAvatar user=current_user size="large" template="theme/custom_avatar"}

Komponen didefinisikan sebagai class PHP:

// system/components/NexaPagination.php
namespace App\System\Components;

class NexaPagination
{
    public function render(array $params): string
    {
        $total = $params['total'] ?? 0;
        $current = $params['current'] ?? 1;
        $perPage = $params['per_page'] ?? 10;
        
        // Calculate pagination
        $totalPages = ceil($total / $perPage);
        
        // Generate HTML
        $html = '';
        
        return $html;
    }
}

Praktik Terbaik

  • Separation of Concerns - Jaga template tetap fokus pada presentasi, hindari logika bisnis kompleks di dalamnya.
  • DRY (Don't Repeat Yourself) - Gunakan layout, partial, dan komponen untuk menghindari duplikasi kode.
  • Naming Convention - Gunakan konvensi penamaan yang konsisten untuk template, seperti resource/action.html (contoh: articles/show.html).
  • Escape Output - Selalu escape output untuk mencegah XSS, kecuali jika memang diperlukan HTML mentah.
  • Responsive Design - Pertimbangkan berbagai device dan ukuran layar dalam desain template Anda.
  • Minimize Logic - Jaga logika di template tetap minimal dan sederhana, lakukan pemrosesan kompleks di controller atau service.