feat: add htmx server-side plumbing
This commit is contained in:
1 parent
3bfb5afef6
commit
ef8a88ed2a
9 files changed
+450
No files matched your search
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Htmx;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Response that makes htmx perform a full page navigation.
|
||||
*
|
||||
* htmx never follows a normal 3xx itself -- the browser does, transparently, and htmx then swaps
|
||||
* whatever the final destination returned, which is a whole page in the wrong place. So a redirect
|
||||
* has to be handed to htmx as an instruction instead: a 200 carrying HX-Redirect.
|
||||
*
|
||||
* Anything returning this from an event listener must also call
|
||||
* ExceptionEvent::allowCustomResponseCode(), or the kernel overwrites the 200.
|
||||
*/
|
||||
class HxRedirectResponse extends Response
|
||||
{
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct('', Response::HTTP_OK, ['HX-Redirect' => $url]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Htmx;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* htmx helpers for controllers. Expects to be used inside an AbstractController.
|
||||
*
|
||||
* Detecting an htmx request stays a plain `$request->headers->has('HX-Request')` -- no bundle,
|
||||
* no argument resolver, no helper service.
|
||||
*/
|
||||
trait HxTrait
|
||||
{
|
||||
/**
|
||||
* Renders several Twig blocks into one response, for an htmx out-of-band swap.
|
||||
*
|
||||
* This is how one interaction updates more than one region: the response body carries the
|
||||
* primary fragment plus any number of others marked `hx-swap-oob`, and htmx dispatches each
|
||||
* to its own target by id. `htmx_oob_swap` is injected into the context so a block can emit
|
||||
* that attribute only when rendered through this path, and stay a plain fragment otherwise.
|
||||
*
|
||||
* @param string[] $blockNames block names to render, in order
|
||||
* @param array<string, mixed> $context
|
||||
* @param string|null $pushUrl pushed onto browser history via HX-Push-Url
|
||||
*/
|
||||
protected function htmxOobResponse(string $templateName, array $blockNames, array $context = [], ?string $pushUrl = null): Response
|
||||
{
|
||||
$html = '';
|
||||
$oobContext = $context + ['htmx_oob_swap' => true];
|
||||
|
||||
foreach ($blockNames as $blockName) {
|
||||
$html .= $this->renderBlockView($templateName, $blockName, $oobContext);
|
||||
}
|
||||
|
||||
$response = new Response($html);
|
||||
|
||||
if (null !== $pushUrl) {
|
||||
$response->headers->set('HX-Push-Url', $pushUrl);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects in a way htmx understands, falling back to a normal redirect for ordinary requests.
|
||||
*
|
||||
* Note this is a genuine full page navigation. Prefer swapping the affected fragment (plus an
|
||||
* out-of-band toast) and keep this for real destination changes.
|
||||
*/
|
||||
protected function htmxRedirect(Request $request, string $url): Response
|
||||
{
|
||||
if ($request->headers->has('HX-Request')) {
|
||||
return new HxRedirectResponse($url);
|
||||
}
|
||||
|
||||
return $this->redirect($url);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user