Frontend

Build frontends with jQuery and Handlebars templates.

Overview

Yaiko uses:

  • Handlebars — Server-side templating
  • jQuery — Client-side interactions
  • CSS Variables — Design system

Templates

templates/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="{{ csrf_token }}">
    <title>{{ title }}</title>
    <link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
    <div class="app">
        <header class="header">
            <h1>{{ heading }}</h1>
        </header>
        
        <main class="main">
            {{ content }}
        </main>
    </div>
    
    <div id="toast-container"></div>
    
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script src="/static/js/core.js"></script>
    <script src="/static/js/app.js"></script>
</body>
</html>

Rendering Templates

use yaiko_core::template::TemplateEngine;

let engine = TemplateEngine::new("./templates")?;

async fn home(req: Request) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> {
    let html = engine.render("index.html", &json!({
        "title": "My App",
        "heading": "Welcome!",
        "content": "<p>Hello, World!</p>"
    }))?;
    
    Ok(Response::new().html(&html))
}

jQuery API Client

public/js/core.js provides Yaiko.api:

// GET request
Yaiko.api.get('/api/users')
    .then(function(data) {
        console.log(data.users);
    });

// POST request
Yaiko.api.post('/api/users', {
    name: 'John',
    email: 'john@example.com'
}).then(function(data) {
    Yaiko.ui.toast('User created!', 'success');
});

// PUT request
Yaiko.api.put('/api/users/123', { name: 'Jane' });

// DELETE request
Yaiko.api.delete('/api/users/123');

CSRF tokens are automatically included.

UI Components

Toast Notifications

Yaiko.ui.toast('Operation successful', 'success');
Yaiko.ui.toast('Warning message', 'warning');
Yaiko.ui.toast('Error occurred', 'error');
Yaiko.ui.toast('Info message', 'info');

Loading States

Yaiko.ui.showLoading($('#submit-btn'));
// After operation
Yaiko.ui.hideLoading($('#submit-btn'));

Modals

Yaiko.ui.modal.open('my-modal');
Yaiko.ui.modal.close('my-modal');

CSS Design System

public/css/main.css includes CSS variables:

:root {
    /* Colors */
    --color-primary: #6366f1;
    --color-secondary: #10b981;
    --color-bg: #0f172a;
    --color-text: #f1f5f9;
    --color-text-muted: #94a3b8;
    
    /* Typography */
    --font-sans: "Inter", sans-serif;
    --font-mono: "JetBrains Mono", monospace;
    
    /* Spacing */
    --space-4: 1rem;
    --space-8: 2rem;
    
    /* Border Radius */
    --radius-lg: 0.75rem;
    
    /* Shadows */
    --shadow-glow: 0 0 20px rgba(99, 102, 241, 0.3);
}

Button Styles

<button class="btn btn--primary">Primary</button>
<button class="btn btn--secondary">Secondary</button>

Glassmorphism

<div class="glass">
    Frosted glass effect
</div>

Static Files

Files in public/ are served at /static/:

public/
├── css/
│   └── main.css      → /static/css/main.css
├── js/
│   ├── core.js       → /static/js/core.js
│   └── app.js        → /static/js/app.js
├── images/
│   └── logo.png      → /static/images/logo.png
└── fonts/

Example: Todo App

<div id="todo-app">
    <input type="text" id="new-todo" placeholder="Add todo...">
    <button id="add-btn" class="btn btn--primary">Add</button>
    <ul id="todo-list"></ul>
</div>

<script>
$(document).ready(function() {
    function loadTodos() {
        Yaiko.api.get('/api/todos').then(function(data) {
            $('#todo-list').empty();
            data.todos.forEach(function(todo) {
                $('#todo-list').append(
                    '<li data-id="' + todo.id + '">' + 
                    todo.title + 
                    ' <button class="delete-btn">×</button></li>'
                );
            });
        });
    }
    
    $('#add-btn').on('click', function() {
        var title = $('#new-todo').val();
        Yaiko.api.post('/api/todos', { title: title })
            .then(function() {
                $('#new-todo').val('');
                loadTodos();
                Yaiko.ui.toast('Todo added!', 'success');
            });
    });
    
    $(document).on('click', '.delete-btn', function() {
        var id = $(this).parent().data('id');
        Yaiko.api.delete('/api/todos/' + id)
            .then(function() {
                loadTodos();
            });
    });
    
    loadTodos();
});
</script>