Simple Page Navigation

Click the buttons below to load different pages without a full page reload.

Uses hx-swap-oob to update both content and navigation buttons in a single response.

About - Simple Page Demo
About

This demo showcases the htmx-bundle for Symfony.

Source Code View on GitHub
<?php

declare(strict_types=1);

namespace App\Controller;

use Mdxpl\HtmxBundle\Request\HtmxRequest;
use Mdxpl\HtmxBundle\Response\HtmxResponse;
use Mdxpl\HtmxBundle\Response\HtmxResponseBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/simple-page/{slug}', name: 'app_simple_page')]
final class SimplePageController extends AbstractController
{
    private const PAGES = [
        'home' => ['name' => 'Home', 'description' => 'Welcome to the htmx-bundle demo! Navigate using the menu above.'],
        'about' => ['name' => 'About', 'description' => 'This demo showcases the htmx-bundle for Symfony.'],
        'contact' => ['name' => 'Contact', 'description' => 'Get in touch with us for more information.'],
    ];

    public function __invoke(HtmxRequest $htmx, string $slug = 'home'): HtmxResponse
    {
        $page = self::PAGES[$slug] ?? throw new NotFoundHttpException('Page not found');

        $viewData = [
            'menu' => self::PAGES,
            'page' => $page,
            'currentSlug' => $slug,
        ];

        $builder = HtmxResponseBuilder::create($htmx->isHtmx);

        if ($htmx->isHtmx) {
            return $builder
                ->success()
                ->viewBlock('simple_page.html.twig', 'pageContent', $viewData)
                ->viewBlock('simple_page.html.twig', 'pageNav', $viewData)
                ->build();
        }

        return $builder
            ->success()
            ->view('simple_page.html.twig', $viewData)
            ->build();
    }
}
{% block pageNav %}
<nav id="page-nav" hx-swap-oob="true">
    <ul>
        {% for slug, item in menu %}
        <li>
            <a href="{{ path('app_simple_page', {'slug': slug}) }}"
               {{ currentSlug == slug ? 'aria-current="page"' : '' }}
               hx-get="{{ path('app_simple_page', {'slug': slug}) }}"
               hx-push-url="true"
               hx-target="#pageContent"
               hx-swap="innerHTML transition:true"
               hx-indicator="#loader">
                {{ item.name }}
            </a>
        </li>
        {% endfor %}
    </ul>
</nav>
{% endblock %}

{% block pageContent %}
<title>{{ page.name }} - Simple Page Demo</title>
<article>
    <header><strong>{{ page.name }}</strong></header>
    <p>{{ page.description }}</p>
</article>
{% endblock %}