HtmxTypeExtension Demo

This demo showcases the htmx option available on all Symfony form fields via HtmxTypeExtension.

Powered by PicoCSS + htmx

1 Live Search / Autocomplete

Type at least 2 characters to search users. Uses hx-trigger with delay and condition.

Try: john jane alice bob @example

htmx options: get, trigger, target, indicator, params, on::before-request
2 Cascading Selects

Select a country to load cities dynamically. Uses hx-on::config-request to modify the URL.

htmx options: get, trigger, target, on::config-request
3 Inline Validation

Fields validate on blur. Server-side validation without full form submit.

htmx options: post, trigger, target, swap
4 Conditional Fields

Select account type to show/hide additional fields. Uses conditional option from ConditionalTypeExtension.

Personal account selected - no additional fields required.
conditional: trigger, endpoint (auto-configures htmx)
5 Form Submit

The entire form submits via htmx using form_start attributes.

Info: On validation error, the page auto-scrolls to the error message via triggerAfterSwap(['scrollTo' => '...']) passed from the controller response.


HtmxOptions Builder

Use fluent builder API with HtmxOptions:

use Mdxpl\HtmxBundle\Form\Htmx\HtmxOptions;
use Mdxpl\HtmxBundle\Form\Htmx\Trigger\Trigger;

$builder->add('search', TextType::class, [
    'htmx' => HtmxOptions::create()
        ->getRoute('app_search')
        ->trigger(Trigger::keyup()->changed()->delay(300))
        ->target('#results')
        ->indicator('#spinner'),
]);

Available Methods

MethodAttribute
get() / getRoute()hx-get
post() / postRoute()hx-post
trigger()hx-trigger
target()hx-target
swap()hx-swap
indicator()hx-indicator
include()hx-include
vals()hx-vals
confirm()hx-confirm
onBeforeRequest()hx-on::before-request
Trigger Builder

Build complex triggers with Trigger class:

use Mdxpl\HtmxBundle\Form\Htmx\Trigger\Trigger;

// keyup changed delay:300ms
Trigger::keyup()->changed()->delay(300)

// blur changed delay:500ms
Trigger::blur()->changed()->delay(500)

// change (for selects)
Trigger::change()

// keyup[target.value.length >= 2]
Trigger::keyup()->condition('target.value.length >= 2')
Route with Placeholders

Use placeholders for dynamic field values:

// Server-side: {name}, {id}, {full_name}
HtmxOptions::create()
    ->postRoute('app_validate', ['field' => '{name}'])
    ->target('#{id}-validation')

// Client-side: {value} (auto-generates JS)
HtmxOptions::create()
    ->getRoute('app_cities', ['country' => '{value}'])
    ->trigger(Trigger::change())
Conditional Fields

Use conditional to show/hide fields:

$builder
    ->add('accountType', ChoiceType::class, [
        'choices' => [
            'Personal' => 'personal',
            'Business' => 'business',
        ],
        'expanded' => true,
    ])
    ->add('business', BusinessFieldsType::class, [
        'conditional' => [
            'trigger' => 'accountType',
            'endpoint' => '/form/business-fields',
        ],
    ]);
Source Code View on GitHub
<?php

declare(strict_types=1);

namespace App\Controller;

use App\Form\BusinessFieldsType;
use Mdxpl\HtmxBundle\Attribute\HtmxOnly;
use Mdxpl\HtmxBundle\Form\Htmx\HtmxOptions;
use Mdxpl\HtmxBundle\Form\Htmx\SwapStyle;
use Mdxpl\HtmxBundle\Form\Htmx\Trigger\Trigger;
use Mdxpl\HtmxBundle\Request\HtmxRequest;
use Mdxpl\HtmxBundle\Response\HtmxResponse;
use Mdxpl\HtmxBundle\Response\HtmxResponseBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\Validator\ValidatorInterface;

#[Route('/advanced-form')]
final class AdvancedFormController extends AbstractController
{
    private const TEMPLATE = 'advanced_form.html.twig';

    private const USERS = [
        ['id' => 1, 'name' => 'John Doe', 'email' => '[email protected]'],
        ['id' => 2, 'name' => 'Jane Smith', 'email' => '[email protected]'],
        ['id' => 3, 'name' => 'Bob Johnson', 'email' => '[email protected]'],
        ['id' => 4, 'name' => 'Alice Brown', 'email' => '[email protected]'],
        ['id' => 5, 'name' => 'Charlie Wilson', 'email' => '[email protected]'],
        ['id' => 6, 'name' => 'Diana Martinez', 'email' => '[email protected]'],
        ['id' => 7, 'name' => 'Edward Lee', 'email' => '[email protected]'],
        ['id' => 8, 'name' => 'Fiona Clark', 'email' => '[email protected]'],
    ];

    private const LOCATIONS = [
        'usa' => [
            'name' => 'United States',
            'cities' => [
                'nyc' => 'New York',
                'la' => 'Los Angeles',
                'chi' => 'Chicago',
                'hou' => 'Houston',
            ],
        ],
        'uk' => [
            'name' => 'United Kingdom',
            'cities' => [
                'lon' => 'London',
                'man' => 'Manchester',
                'bir' => 'Birmingham',
                'edi' => 'Edinburgh',
            ],
        ],
        'de' => [
            'name' => 'Germany',
            'cities' => [
                'ber' => 'Berlin',
                'mun' => 'Munich',
                'ham' => 'Hamburg',
                'fra' => 'Frankfurt',
            ],
        ],
        'pl' => [
            'name' => 'Poland',
            'cities' => [
                'war' => 'Warsaw',
                'kra' => 'Krakow',
                'wro' => 'Wroclaw',
                'gda' => 'Gdansk',
            ],
        ],
    ];

    #[Route('', name: 'app_advanced_form', methods: ['GET', 'POST'])]
    public function index(HtmxRequest $htmx, Request $request): HtmxResponse
    {
        $formData = $request->request->all('form');
        $isBusiness = ($formData['accountType'] ?? 'personal') === 'business';

        $form = $this->createAdvancedForm($isBusiness);
        $form->handleRequest($request);

        $countries = array_combine(
            array_map(static fn ($data) => $data['name'], self::LOCATIONS),
            array_keys(self::LOCATIONS),
        );

        $selectedCountry = (string) $form->get('country')->getData();
        $cities = $selectedCountry !== '' ? (self::LOCATIONS[$selectedCountry]['cities'] ?? []) : [];

        $viewData = [
            'form' => $form->createView(),
            'countries' => $countries,
            'cities' => $cities,
            'isBusiness' => $isBusiness,
            'routePrefix' => 'app_advanced_form',
        ];

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

        if ($form->isSubmitted()) {
            if ($form->isValid()) {
                return $builder
                    ->success()
                    ->viewBlock(self::TEMPLATE, 'submitSuccess', ['data' => $form->getData()])
                    ->build();
            }

            $viewData['form'] = $form->createView();

            return $builder
                ->failure()
                ->viewBlock(self::TEMPLATE, 'formContent', $viewData)
                ->triggerAfterSwap(['scrollTo' => '#form-error'])
                ->build();
        }

        if ($htmx->isHtmx) {
            return $builder
                ->success()
                ->viewBlock(self::TEMPLATE, 'formContent', $viewData)
                ->build();
        }

        return $builder
            ->success()
            ->view(self::TEMPLATE, $viewData)
            ->build();
    }

    #[Route('/search/users', name: 'app_advanced_form_search_users', methods: ['GET'])]
    #[HtmxOnly]
    public function searchUsers(HtmxRequest $htmx, Request $request): HtmxResponse
    {
        $formData = $request->query->all('form');
        $query = strtolower(trim((string) ($formData['user'] ?? '')));

        $results = [];
        if (\strlen($query) >= 2) {
            $results = array_filter(
                self::USERS,
                static fn ($user) => str_contains(strtolower($user['name']), $query)
                    || str_contains(strtolower($user['email']), $query),
            );
            $results = array_values($results);
        }

        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock(self::TEMPLATE, 'searchResults', ['results' => $results])
            ->build();
    }

    #[Route('/cities/{country?}', name: 'app_advanced_form_cities', methods: ['GET'])]
    #[HtmxOnly]
    public function cities(HtmxRequest $htmx, ?string $country = null): HtmxResponse
    {
        $cities = $country !== null ? (self::LOCATIONS[$country]['cities'] ?? []) : [];
        $isEmpty = $cities === [];

        $cityForm = $this->createFormBuilder(options: ['csrf_protection' => false])
            ->add('city', ChoiceType::class, [
                'label' => 'City',
                'placeholder' => $isEmpty ? 'Select a country first...' : 'Select a city...',
                'choices' => $isEmpty ? [] : array_flip($cities),
                'disabled' => $isEmpty,
            ])
            ->getForm();

        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock(self::TEMPLATE, 'citySelect', [
                'cityField' => $cityForm->get('city')->createView(),
            ])
            ->build();
    }

    #[Route('/validate/{field}', name: 'app_advanced_form_validate', methods: ['POST'])]
    #[HtmxOnly]
    public function validateField(
        HtmxRequest $htmx,
        Request $request,
        ValidatorInterface $validator,
        string $field,
    ): HtmxResponse {
        $formData = $request->request->all('form');
        $value = $formData[$field] ?? '';

        $constraints = $this->getFieldConstraints($field);
        $violations = $validator->validate($value, $constraints);

        $errors = [];
        foreach ($violations as $violation) {
            $errors[] = $violation->getMessage();
        }

        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock(self::TEMPLATE, 'fieldValidation', [
                'errors' => $errors,
                'field' => $field,
                'isValid' => $errors === [] && $value !== '',
            ])
            ->build();
    }

    #[Route('/business-fields', name: 'app_advanced_form_business_fields', methods: ['GET'])]
    #[HtmxOnly]
    public function businessFields(HtmxRequest $htmx, Request $request): HtmxResponse
    {
        /** @var array<string, string> $formData */
        $formData = $request->query->all('form');
        $isBusiness = ($formData['accountType'] ?? 'personal') === 'business';

        $form = $this->createFormBuilder(options: ['csrf_protection' => false])
            ->add('business', BusinessFieldsType::class, [
                'is_required' => $isBusiness,
            ])
            ->getForm();

        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock(self::TEMPLATE, 'businessFields', [
                'businessForm' => $form->get('business')->createView(),
                'isBusiness' => $isBusiness,
            ])
            ->build();
    }

    #[Route('/validate/business/{field}', name: 'app_advanced_form_validate_business', methods: ['POST'])]
    #[HtmxOnly]
    public function validateBusinessField(
        HtmxRequest $htmx,
        Request $request,
        ValidatorInterface $validator,
        string $field,
    ): HtmxResponse {
        /** @var array<string, array<string, string>> $formData */
        $formData = $request->request->all('form');
        $businessData = $formData['business'] ?? [];
        $value = $businessData[$field] ?? '';

        $constraints = $this->getBusinessFieldConstraints($field);
        $violations = $validator->validate($value, $constraints);

        $errors = [];
        foreach ($violations as $violation) {
            $errors[] = $violation->getMessage();
        }

        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock(self::TEMPLATE, 'fieldValidation', [
                'errors' => $errors,
                'field' => $field,
                'isValid' => $errors === [] && $value !== '',
            ])
            ->build();
    }

    /**
     * @return FormInterface<array<string, mixed>>
     */
    private function createAdvancedForm(bool $requireBusinessFields = false): FormInterface
    {
        $countries = array_combine(
            array_map(static fn ($data) => $data['name'], self::LOCATIONS),
            array_keys(self::LOCATIONS),
        );

        $builder = $this->createFormBuilder(options: [
                'csrf_protection' => false,
            ])
            ->add('user', TextType::class, [
                'required' => false,
                'label' => 'Search Users',
                'attr' => [
                    'placeholder' => 'Type at least 2 characters...',
                    'autocomplete' => 'off',
                ],
                'constraints' => [
                    new NotBlank(message: 'Please search for a user'),
                    new Choice(
                        choices: array_column(self::USERS, 'name'),
                        message: 'Please select a valid user from the list',
                    ),
                ],
                'htmx' => HtmxOptions::create()
                    ->getRoute('app_advanced_form_search_users')
                    ->trigger(Trigger::keyup()->changed()->delay(300)->condition('target.value.length >= 2'))
                    ->target('#user-results')
                    ->indicator('#search-spinner')
                    ->onBeforeRequest('document.querySelector("#user-results").innerHTML = ""'),
            ])
            ->add('country', ChoiceType::class, [
                'label' => 'Country',
                'choices' => array_merge(['' => ''], $countries),
                'constraints' => [
                    new NotBlank(message: 'Please select a country'),
                ],
                'cascading' => [
                    'target' => 'city',
                    'endpoint' => '/advanced-form/cities/{value}',
                ],
            ]);

        $addCityField = function (FormInterface $form, ?string $countryCode): void {
            $cities = $countryCode !== null ? (self::LOCATIONS[$countryCode]['cities'] ?? []) : [];
            $isEmpty = $cities === [];

            $form->add('city', ChoiceType::class, [
                'label' => 'City',
                'placeholder' => $isEmpty ? 'Select a country first...' : 'Select a city...',
                'choices' => $isEmpty ? [] : array_flip($cities),
                'disabled' => $isEmpty,
                'constraints' => [
                    new NotBlank(message: 'Please select a city'),
                ],
            ]);
        };

        $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($addCityField): void {
            $data = $event->getData();
            $countryCode = \is_array($data) ? ($data['country'] ?? null) : null;
            $addCityField($event->getForm(), $countryCode);
        });

        $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($addCityField): void {
            $data = $event->getData();
            $countryCode = \is_array($data) ? ($data['country'] ?? null) : null;
            $addCityField($event->getForm(), $countryCode);
        });

        $builder
            ->add('email', EmailType::class, [
                'label' => 'Email',
                'required' => false,
                'attr' => ['placeholder' => 'Enter your email...'],
                'constraints' => [
                    new NotBlank(message: 'Email is required'),
                    new Email(message: 'Please enter a valid email address'),
                ],
                'htmx' => HtmxOptions::create()
                    ->postRoute('app_advanced_form_validate', ['field' => '{name}'])
                    ->trigger(Trigger::blur()->changed()->delay(500))
                    ->target('#form_{name}-validation')
                    ->swap(SwapStyle::InnerHTML),
            ])
            ->add('username', TextType::class, [
                'label' => 'Username',
                'required' => false,
                'attr' => ['placeholder' => 'Choose a username...'],
                'constraints' => [
                    new NotBlank(message: 'Username is required'),
                    new Length(min: 3, max: 20, minMessage: 'Username must be at least {{ limit }} characters', maxMessage: 'Username cannot exceed {{ limit }} characters'),
                    new Regex(pattern: '/^[a-zA-Z0-9_]+$/', message: 'Username can only contain letters, numbers and underscores'),
                ],
                'htmx' => HtmxOptions::create()
                    ->postRoute('app_advanced_form_validate', ['field' => '{name}'])
                    ->trigger(Trigger::blur()->changed()->delay(500))
                    ->target('#form_{name}-validation')
                    ->swap(SwapStyle::InnerHTML),
            ])
            ->add('accountType', ChoiceType::class, [
                'label' => 'Account Type',
                'choices' => [
                    'Personal' => 'personal',
                    'Business' => 'business',
                ],
                'expanded' => true,
                'data' => 'personal',
            ])
            ->add('business', BusinessFieldsType::class, [
                'required' => false,
                'is_required' => $requireBusinessFields,
                'conditional' => [
                    'trigger' => 'accountType',
                    'endpoint' => '/advanced-form/business-fields',
                ],
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Submit Form',
            ]);

        return $builder->getForm();
    }

    /**
     * @return array<\Symfony\Component\Validator\Constraint>
     */
    private function getFieldConstraints(string $field): array
    {
        return match ($field) {
            'email' => [
                new NotBlank(message: 'Email is required'),
                new Email(message: 'Please enter a valid email address'),
            ],
            'username' => [
                new NotBlank(message: 'Username is required'),
                new Length(min: 3, max: 20, minMessage: 'Username must be at least {{ limit }} characters', maxMessage: 'Username cannot exceed {{ limit }} characters'),
                new Regex(pattern: '/^[a-zA-Z0-9_]+$/', message: 'Username can only contain letters, numbers and underscores'),
            ],
            default => [],
        };
    }

    /**
     * @return array<\Symfony\Component\Validator\Constraint>
     */
    private function getBusinessFieldConstraints(string $field): array
    {
        return match ($field) {
            'companyName' => [
                new NotBlank(message: 'Company name is required'),
                new Length(min: 2, max: 100, minMessage: 'Company name must be at least {{ limit }} characters'),
            ],
            'taxId' => [
                new NotBlank(message: 'Tax ID is required'),
                new Regex(
                    pattern: '/^[A-Z]{2}[0-9A-Z]{8,12}$/',
                    message: 'Tax ID must be in format: 2 letters followed by 8-12 alphanumeric characters (e.g., PL1234567890)',
                ),
            ],
            default => [],
        };
    }
}
{% block formContent %}
<div class="grid">
    <div>
        {{ form_start(form, {
            'attr': {
                'hx-post': path(routePrefix),
                'hx-target': '#form-wrapper',
                'hx-swap': 'innerHTML',
                'autocomplete': 'off',
                'novalidate': 'novalidate',
            }
        }) }}

        {% if not form.vars.valid and form.vars.submitted %}
        <div id="form-error" class="alert-error">
            <strong>Error:</strong> Please fix the validation errors below.
        </div>
        {% endif %}

        <article>
            <header>
                <strong><kbd>1</kbd> Live Search / Autocomplete</strong>
            </header>
            <p><small>Type at least 2 characters to search users. Uses <code>hx-trigger</code> with delay and condition.</small></p>
            <p><small>Try: <kbd>john</kbd> <kbd>jane</kbd> <kbd>alice</kbd> <kbd>bob</kbd> <kbd>@example</kbd></small></p>

            <div class="field-relative">
                {{ form_row(form.user) }}
                <div id="user-results" class="user-results"></div>
            </div>

            <footer>
                <small><strong>htmx options:</strong> get, trigger, target, indicator, params, on::before-request</small>
            </footer>
        </article>

        <article>
            <header>
                <strong><kbd>2</kbd> Cascading Selects</strong>
            </header>
            <p><small>Select a country to load cities dynamically. Uses <code>hx-on::config-request</code> to modify the URL.</small></p>

            <div class="grid">
                <div>{{ form_row(form.country) }}</div>
                <div>{{ form_row(form.city) }}</div>
            </div>

            <footer>
                <small><strong>htmx options:</strong> get, trigger, target, on::config-request</small>
            </footer>
        </article>

        <article>
            <header>
                <strong><kbd>3</kbd> Inline Validation</strong>
            </header>
            <p><small>Fields validate on blur. Server-side validation without full form submit.</small></p>

            {{ form_row(form.email) }}
            {{ form_row(form.username) }}

            <footer>
                <small><strong>htmx options:</strong> post, trigger, target, swap</small>
            </footer>
        </article>

        <article>
            <header>
                <strong><kbd>4</kbd> Conditional Fields</strong>
            </header>
            <p><small>Select account type to show/hide additional fields. Uses <code>conditional</code> option from <code>ConditionalTypeExtension</code>.</small></p>

            {{ form_row(form.accountType) }}

            <div id="{{ form.business.vars.conditional.wrapper_id|default('business-fields') }}">
                {{ block('businessFields') }}
            </div>

            <footer>
                <small><strong>conditional:</strong> trigger, endpoint (auto-configures htmx)</small>
            </footer>
        </article>

        <article>
            <header>
                <strong><kbd>5</kbd> Form Submit</strong>
            </header>
            <p><small>The entire form submits via htmx using form_start attributes.</small></p>
            <p><small><ins>Info:</ins> On validation error, the page auto-scrolls to the error message via <code>triggerAfterSwap(['scrollTo' => '...'])</code> passed from the controller response.</small></p>

            <hr>

            {{ form_row(form.submit) }}
        </article>

        {{ form_end(form, {'render_rest': false}) }}
    </div>

    <div>
        <article>
            <header><strong>HtmxOptions Builder</strong></header>
            <p><small>Use fluent builder API with <code>HtmxOptions</code>:</small></p>

            <div class="code-example">
                <pre><code>use Mdxpl\HtmxBundle\Form\Htmx\HtmxOptions;
use Mdxpl\HtmxBundle\Form\Htmx\Trigger\Trigger;

$builder->add('search', TextType::class, [
    'htmx' => HtmxOptions::create()
        ->getRoute('app_search')
        ->trigger(Trigger::keyup()->changed()->delay(300))
        ->target('#results')
        ->indicator('#spinner'),
]);</code></pre>
            </div>

            <h4>Available Methods</h4>
            <table>
                <thead>
                    <tr><th>Method</th><th>Attribute</th></tr>
                </thead>
                <tbody>
                    <tr><td>get() / getRoute()</td><td>hx-get</td></tr>
                    <tr><td>post() / postRoute()</td><td>hx-post</td></tr>
                    <tr><td>trigger()</td><td>hx-trigger</td></tr>
                    <tr><td>target()</td><td>hx-target</td></tr>
                    <tr><td>swap()</td><td>hx-swap</td></tr>
                    <tr><td>indicator()</td><td>hx-indicator</td></tr>
                    <tr><td>include()</td><td>hx-include</td></tr>
                    <tr><td>vals()</td><td>hx-vals</td></tr>
                    <tr><td>confirm()</td><td>hx-confirm</td></tr>
                    <tr><td>onBeforeRequest()</td><td>hx-on::before-request</td></tr>
                </tbody>
            </table>
        </article>

        <article>
            <header><strong>Trigger Builder</strong></header>
            <p><small>Build complex triggers with <code>Trigger</code> class:</small></p>

            <div class="code-example">
                <pre><code>use Mdxpl\HtmxBundle\Form\Htmx\Trigger\Trigger;

// keyup changed delay:300ms
Trigger::keyup()->changed()->delay(300)

// blur changed delay:500ms
Trigger::blur()->changed()->delay(500)

// change (for selects)
Trigger::change()

// keyup[target.value.length >= 2]
Trigger::keyup()->condition('target.value.length >= 2')</code></pre>
            </div>
        </article>

        <article>
            <header><strong>Route with Placeholders</strong></header>
            <p><small>Use placeholders for dynamic field values:</small></p>

            <div class="code-example">
                <pre><code>// Server-side: {name}, {id}, {full_name}
HtmxOptions::create()
    ->postRoute('app_validate', ['field' => '{name}'])
    ->target('#{id}-validation')

// Client-side: {value} (auto-generates JS)
HtmxOptions::create()
    ->getRoute('app_cities', ['country' => '{value}'])
    ->trigger(Trigger::change())</code></pre>
            </div>
        </article>

        <article>
            <header><strong>Conditional Fields</strong></header>
            <p><small>Use <code>conditional</code> to show/hide fields:</small></p>

            <div class="code-example">
                <pre><code>$builder
    ->add('accountType', ChoiceType::class, [
        'choices' => [
            'Personal' => 'personal',
            'Business' => 'business',
        ],
        'expanded' => true,
    ])
    ->add('business', BusinessFieldsType::class, [
        'conditional' => [
            'trigger' => 'accountType',
            'endpoint' => '/form/business-fields',
        ],
    ]);</code></pre>
            </div>
        </article>
    </div>
</div>
{% endblock %}

{% block searchResults %}
{% if results is defined and results|length > 0 %}
<ul>
    {% for user in results %}
    <li>
        <a href="#" onclick="document.querySelector('[name=\'form[user]\']').value = '{{ user.name }}'; this.closest('ul').remove(); return false;">
            <strong>{{ user.name }}</strong><br>
            <small>{{ user.email }}</small>
        </a>
    </li>
    {% endfor %}
</ul>
{% elseif results is defined %}
<p><small>No users found</small></p>
{% endif %}
{% endblock %}

{% block citySelect %}
{{ form_row(cityField) }}
{% endblock %}

{% block fieldValidation %}
{% if errors is defined and errors|length > 0 %}
<div class="validation-message validation-error">
    {{ errors|first }}
</div>
{% elseif isValid is defined and isValid %}
<div class="validation-message validation-success">
    Valid
</div>
{% endif %}
{% endblock %}

{% block businessFields %}
{% set showBusiness = isBusiness is defined ? isBusiness : (form is defined and form.accountType is defined and form.accountType.vars.value == 'business') %}
{% set businessForm = businessForm is defined ? businessForm : (form is defined and form.business is defined ? form.business : null) %}

{% if showBusiness and businessForm %}
<div class="business-fields">
    <p><strong>Business Account Fields</strong></p>

    {{ form_row(businessForm.companyName) }}
    {{ form_row(businessForm.taxId) }}
    {{ form_row(businessForm.companyAddress) }}
</div>
{% elseif showBusiness %}
<div class="business-fields">
    <p><strong>Business Account Fields</strong></p>
    <p><small>Loading...</small></p>
</div>
{% else %}
<div class="business-fields">
    <kbd>Personal</kbd> account selected - no additional fields required.
</div>
{% if form is defined and form.business is defined %}
    {% do form.business.setRendered() %}
{% endif %}
{% endif %}
{% endblock %}

{% block submitSuccess %}
<article>
    <header>
        <strong>Form Submitted Successfully!</strong>
    </header>

    <h4>Submitted Data:</h4>
    <div class="code-example">
        <pre>{{ data|json_encode(constant('JSON_PRETTY_PRINT')) }}</pre>
    </div>

    <footer>
        <button class="outline"
                hx-get="{{ path(routePrefix|default('app_advanced_form')) }}"
                hx-target="#form-wrapper"
                hx-swap="innerHTML">
            Submit Another
        </button>
    </footer>
</article>
{% endblock %}