Merge branch 'htmx' into develop
This commit is contained in:
commit
f604754a98
|
@ -2,6 +2,7 @@
|
||||||
/composer.phar
|
/composer.phar
|
||||||
/phpunit.xml
|
/phpunit.xml
|
||||||
/.phpunit.result.cache
|
/.phpunit.result.cache
|
||||||
|
/.phpunit.cache
|
||||||
/phpunit.phar
|
/phpunit.phar
|
||||||
/config/Migrations/schema-dump-default.lock
|
/config/Migrations/schema-dump-default.lock
|
||||||
/vendor/
|
/vendor/
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace CheeseCake\Controller\Component;
|
||||||
|
|
||||||
|
use Cake\Controller\Component;
|
||||||
|
use Cake\Log\Log;
|
||||||
|
use Cake\Routing\Router;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Htmx component
|
||||||
|
*/
|
||||||
|
class HtmxComponent extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Default configuration.
|
||||||
|
*
|
||||||
|
* @var array<string, mixed>
|
||||||
|
*/
|
||||||
|
protected array $_defaultConfig = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Cake\Controller\Controller
|
||||||
|
*/
|
||||||
|
protected $controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $config
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function initialize(array $config): void {
|
||||||
|
parent::initialize($config);
|
||||||
|
|
||||||
|
$this->controller = $this->getController();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to check if request is from HTMX
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isHtmx()
|
||||||
|
{
|
||||||
|
return $this->controller->getRequest()->getHeaderLine('HX-Request') === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to check if request is Boosted
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isBoosted()
|
||||||
|
{
|
||||||
|
return $this->controller->getRequest()->getHeaderLine('HX-Boosted') === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get HTMX target id
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getHtmxTarget()
|
||||||
|
{
|
||||||
|
$target = $this->controller->getRequest()->getHeaderLine('HX-Target');
|
||||||
|
|
||||||
|
return $target ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get HTMX trigger id
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getHtmxTrigger()
|
||||||
|
{
|
||||||
|
$trigger = $this->controller->getRequest()->getHeaderLine('HX-Trigger');
|
||||||
|
|
||||||
|
return $trigger ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get HTMX trigger name
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getHtmxTriggerName()
|
||||||
|
{
|
||||||
|
$trigger = $this->controller->getRequest()->getHeaderLine('HX-Trigger-Name');
|
||||||
|
|
||||||
|
return $trigger ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers to cache this request.
|
||||||
|
* Opposite of Controller::disableCache()
|
||||||
|
*
|
||||||
|
* @param array|string $redirectTo
|
||||||
|
* @param bool $full if client side redirect should be a full page reload or not
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function clientSideRedirect(array|string $redirectTo, bool $full = false): void
|
||||||
|
{
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
if (is_array($redirectTo)) {
|
||||||
|
$redirectTo = Router::url($redirectTo);
|
||||||
|
}
|
||||||
|
$header = $full ? 'HX-Redirect' : 'HX-Location';
|
||||||
|
$response = $response->withHeader($header, $redirectTo);
|
||||||
|
|
||||||
|
$this->controller->setResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers to cache this request.
|
||||||
|
* Opposite of Controller::disableCache()
|
||||||
|
*
|
||||||
|
* @param array|string $redirectTo
|
||||||
|
* @param bool $full if client side redirect should be a full page reload or not
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function clientSideRefresh(): void
|
||||||
|
{
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$response = $response->withHeader('HX-Refresh', 'true');
|
||||||
|
|
||||||
|
$this->controller->setResponse($response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace CheeseCake\Test\TestCase\Controller\Component;
|
||||||
|
|
||||||
|
use Cake\Controller\ComponentRegistry;
|
||||||
|
use Cake\Controller\Controller;
|
||||||
|
use Cake\Core\Configure;
|
||||||
|
use Cake\Event\Event;
|
||||||
|
use Cake\Http\ServerRequest;
|
||||||
|
use Cake\Routing\Route\DashedRoute;
|
||||||
|
use Cake\Routing\RouteBuilder;
|
||||||
|
use Cake\Routing\Router;
|
||||||
|
use Cake\TestSuite\TestCase;
|
||||||
|
use CheeseCake\Controller\Component\HtmxComponent;
|
||||||
|
|
||||||
|
class HtmxComponentTest extends TestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RouteBuilder
|
||||||
|
*/
|
||||||
|
protected RouteBuilder $routeBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Controller
|
||||||
|
*/
|
||||||
|
protected $controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Cake\Http\ServerRequest
|
||||||
|
*/
|
||||||
|
protected $request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var HtmxComponent
|
||||||
|
*/
|
||||||
|
protected $component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
parent::setUp();
|
||||||
|
// Setup our component and provide it a basic controller.
|
||||||
|
// If your component relies on Application features, use AppController.
|
||||||
|
$request = new ServerRequest();
|
||||||
|
$this->controller = new Controller($request);
|
||||||
|
$registry = new ComponentRegistry($this->controller);
|
||||||
|
|
||||||
|
$this->component = new HtmxComponent($registry);
|
||||||
|
|
||||||
|
$this->routeBuilder = Router::createRouteBuilder('/');
|
||||||
|
$this->routeBuilder->scope('/', function (RouteBuilder $routes) {
|
||||||
|
$routes->setRouteClass(DashedRoute::class);
|
||||||
|
$routes->get(
|
||||||
|
'/tests',
|
||||||
|
['controller' => 'Tests', 'action' => 'index']
|
||||||
|
);
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function tearDown(): void {
|
||||||
|
parent::tearDown();
|
||||||
|
|
||||||
|
unset($this->Controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testIsHtmx() {
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('HX-Request', 'true'));
|
||||||
|
$this->assertTrue($this->component->isHtmx());
|
||||||
|
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('hx-request', 'true'));
|
||||||
|
$this->assertTrue($this->component->isHtmx());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testIsBoosted() {
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('HX-Boosted', 'true'));
|
||||||
|
$this->assertTrue($this->component->isBoosted());
|
||||||
|
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('hx-boosted', 'true'));
|
||||||
|
$this->assertTrue($this->component->isBoosted());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testGetHtmxTarget() {
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('HX-Target', 'test'));
|
||||||
|
$this->assertEquals('test', $this->component->getHtmxTarget());
|
||||||
|
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('hx-target', 'TEST'));
|
||||||
|
$this->assertEquals('TEST', $this->component->getHtmxTarget());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testGetHtmxTrigger() {
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('HX-Trigger', 'test'));
|
||||||
|
$this->assertEquals('test', $this->component->getHtmxTrigger());
|
||||||
|
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('hx-trigger', 'TEST'));
|
||||||
|
$this->assertEquals('TEST', $this->component->getHtmxTrigger());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testGetHtmxTriggerName() {
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('HX-Trigger-Name', 'test'));
|
||||||
|
$this->assertEquals('test', $this->component->getHtmxTriggerName());
|
||||||
|
|
||||||
|
$this->controller->setRequest($this->controller->getRequest()->withHeader('hx-trigger-name', 'TEST'));
|
||||||
|
$this->assertEquals('TEST', $this->component->getHtmxTriggerName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testClientSideRedirectString()
|
||||||
|
{
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Redirect'));
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Location'));
|
||||||
|
$this->component->clientSideRedirect('/');
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Redirect'));
|
||||||
|
$this->assertEquals('/', $response->getHeaderLine('HX-Location'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testClientSideRedirectStringFull()
|
||||||
|
{
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Redirect'));
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Location'));
|
||||||
|
$this->component->clientSideRedirect('/', true);
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('/', $response->getHeaderLine('HX-Redirect'));
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Location'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testClientSideRedirectArray()
|
||||||
|
{
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Redirect'));
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Location'));
|
||||||
|
$this->component->clientSideRedirect([
|
||||||
|
'controller' => 'Tests',
|
||||||
|
'action' => 'index',
|
||||||
|
]);
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Redirect'));
|
||||||
|
$this->assertEquals('/tests', $response->getHeaderLine('HX-Location'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testClientSideRedirectArrayFull()
|
||||||
|
{
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Redirect'));
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Location'));
|
||||||
|
$this->component->clientSideRedirect([
|
||||||
|
'controller' => 'Tests',
|
||||||
|
'action' => 'index',
|
||||||
|
], true);
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('/tests', $response->getHeaderLine('HX-Redirect'));
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Location'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testClientSideRefresh()
|
||||||
|
{
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('', $response->getHeaderLine('HX-Refresh'));
|
||||||
|
$this->component->clientSideRefresh();
|
||||||
|
$response = $this->controller->getResponse();
|
||||||
|
$this->assertEquals('true', $response->getHeaderLine('HX-Refresh'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
use Cake\Http\BaseApplication;
|
||||||
|
use Cake\Http\MiddlewareQueue;
|
||||||
|
use Cake\Routing\Middleware\RoutingMiddleware;
|
||||||
|
use Cake\Routing\Route\DashedRoute;
|
||||||
|
use Cake\Routing\RouteBuilder;
|
||||||
|
|
||||||
|
class Application extends BaseApplication {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Cake\Routing\RouteBuilder $routes
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function routes(RouteBuilder $routes): void {
|
||||||
|
$routes->scope('/', function(RouteBuilder $routes) {
|
||||||
|
$routes->fallbacks(DashedRoute::class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to set in your App Class
|
||||||
|
* @return \Cake\Http\MiddlewareQueue
|
||||||
|
*/
|
||||||
|
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue {
|
||||||
|
$middlewareQueue->add(new RoutingMiddleware($this));
|
||||||
|
|
||||||
|
return $middlewareQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue