Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
|
d2b733bd0e | |
|
4dcdf2b1cf | |
|
e868cfeaf4 | |
|
28d5856908 | |
|
30baf4a2a7 | |
|
0dba6fb137 | |
|
94ad49396a |
|
@ -1,130 +0,0 @@
|
||||||
<?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,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace CheeseCake\Controller\Traits;
|
||||||
|
|
||||||
|
use Cake\Core\Configure;
|
||||||
|
use Cake\ORM\Table;
|
||||||
|
use Cake\ORM\TableRegistry;
|
||||||
|
|
||||||
|
trait OverrideTableTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Table|null
|
||||||
|
*/
|
||||||
|
protected ?Table $_table = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This object's default table alias.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected ?string $defaultTable = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $_tableConfigKey = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the table instance
|
||||||
|
*
|
||||||
|
* @return Table
|
||||||
|
*/
|
||||||
|
public function getTable(string $tableName = null)
|
||||||
|
{
|
||||||
|
if ($this->_table instanceof Table) {
|
||||||
|
return $this->_table;
|
||||||
|
}
|
||||||
|
$this->getTableConfigKey();
|
||||||
|
$table = $tableName;
|
||||||
|
if (!isset($table)) {
|
||||||
|
$table = $this->defaultTable;
|
||||||
|
if (Configure::read($this->_tableConfigKey)) {
|
||||||
|
$table = Configure::read($this->_tableConfigKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->_table = TableRegistry::getTableLocator()->get($table);
|
||||||
|
|
||||||
|
return $this->_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTableConfigKey()
|
||||||
|
{
|
||||||
|
if (!$this->_tableConfigKey) {
|
||||||
|
$this->_tableConfigKey = $this->getPlugin() . '.' . $this->defaultTable . '.table';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_tableConfigKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the users table
|
||||||
|
*
|
||||||
|
* @param Table $table table
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setTable(Table $table)
|
||||||
|
{
|
||||||
|
$this->_table = $table;
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,30 +47,35 @@ class ActiveLinkHelper extends Helper
|
||||||
$target = $options['target'];
|
$target = $options['target'];
|
||||||
unset($options['target']);
|
unset($options['target']);
|
||||||
if (is_string($target)) {
|
if (is_string($target)) {
|
||||||
if (Router::normalize($currentUrl) == Router::normalize($target)) {
|
return $this->_linkFromStringTarget($currentUrl, $target, $title, $url, $options);
|
||||||
$options['class'] = $this->_addClass($options);
|
|
||||||
|
|
||||||
return $this->Html->link($title, $url, $options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (is_array($target)) {
|
if (!is_array($target)) {
|
||||||
if (!array_key_exists('plugin', $currentUrl)) {
|
return $this->Html->link($title, $url, $options);
|
||||||
$currentUrl['plugin'] = false;
|
}
|
||||||
}
|
|
||||||
if (!array_key_exists('prefix', $currentUrl)) {
|
if (!array_key_exists('plugin', $currentUrl)) {
|
||||||
$currentUrl['prefix'] = false;
|
$currentUrl['plugin'] = false;
|
||||||
}
|
}
|
||||||
foreach ($target as $targetKey => $targetValue) {
|
if (!array_key_exists('prefix', $currentUrl)) {
|
||||||
if (is_array($targetValue)) {
|
$currentUrl['prefix'] = false;
|
||||||
return 'test';
|
}
|
||||||
}
|
if (isset($target['or']) && $target['or']) {
|
||||||
if (!array_key_exists($targetKey, $currentUrl) || $targetValue != $currentUrl[$targetKey]) {
|
foreach ($target['or'] as $singleTargetToMatch) {
|
||||||
|
if ($this->_matchesUrlFromArrayTarget($currentUrl, $singleTargetToMatch)) {
|
||||||
|
$options['class'] = $this->_addClass($options);
|
||||||
|
|
||||||
return $this->Html->link($title, $url, $options);
|
return $this->Html->link($title, $url, $options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$options['class'] = $this->_addClass($options);
|
return $this->Html->link($title, $url, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$this->_matchesUrlFromArrayTarget($currentUrl, $target)) {
|
||||||
|
return $this->Html->link($title, $url, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
$options['class'] = $this->_addClass($options);
|
||||||
|
|
||||||
return $this->Html->link($title, $url, $options);
|
return $this->Html->link($title, $url, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,4 +90,33 @@ class ActiveLinkHelper extends Helper
|
||||||
|
|
||||||
return array_key_exists('class', $providedOptions) ? $providedOptions['class'] . ' ' . $activeClass : $activeClass;
|
return array_key_exists('class', $providedOptions) ? $providedOptions['class'] . ' ' . $activeClass : $activeClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function _linkFromStringTarget(array $current, string $targetString, string $title, array|string|null $url, array $options)
|
||||||
|
{
|
||||||
|
if (Router::normalize($current) == Router::normalize($targetString)) {
|
||||||
|
$options['class'] = $this->_addClass($options);
|
||||||
|
|
||||||
|
return $this->Html->link($title, $url, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->Html->link($title, $url, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _matchesUrlFromArrayTarget(array $current, array $targetUrl)
|
||||||
|
{
|
||||||
|
foreach ($targetUrl as $targetKey => $targetValue) {
|
||||||
|
if (is_array($targetValue)) {
|
||||||
|
if (!in_array($current[$targetKey], $targetValue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!array_key_exists($targetKey, $current) || $targetValue != $current[$targetKey]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<?= $this->Html->link(__('New {{ singularHumanName }}'), ['action' => 'add'], ['class' => 'button float-right']) ?>
|
<?= $this->Html->link(__('New {{ singularHumanName }}'), ['action' => 'add'], ['class' => 'button float-right']) ?>
|
||||||
{% set done = [] %}
|
{% set done = [] %}
|
||||||
<h3><?= __('{{ pluralHumanName }}') ?></h3>
|
<h3><?= __('{{ pluralHumanName }}') ?></h3>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive" id="table-container">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -1,201 +0,0 @@
|
||||||
<?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'));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue