Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/zunnu/cake-htmx
CakePHP helper library for Htmx
https://github.com/zunnu/cake-htmx
ajax backend cakephp cakephp-plugin frontend htmx htmx-cakephp php web-development
Last synced: about 20 hours ago
JSON representation
CakePHP helper library for Htmx
- Host: GitHub
- URL: https://github.com/zunnu/cake-htmx
- Owner: zunnu
- Created: 2024-01-30T11:26:10.000Z (12 months ago)
- Default Branch: main
- Last Pushed: 2024-08-21T13:29:15.000Z (5 months ago)
- Last Synced: 2025-01-25T14:02:49.624Z (2 days ago)
- Topics: ajax, backend, cakephp, cakephp-plugin, frontend, htmx, htmx-cakephp, php, web-development
- Language: PHP
- Homepage:
- Size: 62.5 KB
- Stars: 9
- Watchers: 2
- Forks: 4
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- awesome-htmx - CakePHP-htmx - CakePHP integration for Htmx. (Tools)
README
# cake-htmx
CakePHP integration for [htmx](https://htmx.org/).
Supported CakePHP Versions >= [4.x](https://github.com/zunnu/cake-htmx/tree/4.x) and 5.x.
## Installing Using [Composer][composer]
`cd` to the root of your app folder (where the `composer.json` file is) and run the following command:
```
composer require zunnu/cake-htmx
```
Then load the plugin by using CakePHP's console:```
./bin/cake plugin load CakeHtmx
```
To install htmx please browse [their documentation](https://htmx.org/docs/#installing)## Usage
Main functionality is currently wrapped inside Htmx component.
To load the component you will need to modify your `src/Controller/AppController.php` and load the Htmx component in the `initialize()` function
```php
$this->loadComponent('CakeHtmx.Htmx');
```### Request
You can use detector to check if the request is Htmx.
```php
$this->getRequest()->is('htmx') // Always true if the request is performed by Htmx
$this->getRequest()->is('boosted') // Indicates that the request is via an element using hx-boost
$this->getRequest()->is('historyRestoreRequest') // True if the request is for history restoration after a miss in the local history cache
```
Using the component you can check more specific details about the request.
```php
$this->Htmx->getCurrentUrl(); // The current URL of the browser
$this->Htmx->getPromptResponse(); // The user response to an hx-prompt
$this->Htmx->getTarget(); // The id of the target element if it exists
$this->Htmx->getTriggerName(); // The name of the triggered element if it exists
$this->Htmx->getTriggerId(); // The id of the triggered element if it exists
```### Response
- `redirect`Htmx can trigger a client side redirect when it receives a response with the `HX-Redirect` [header](https://htmx.org/reference/#response_headers).
```php
$this->Htmx->redirect('/somewhere-else');
```- `clientRefresh`
Htmx will trigger a page reload when it receives a response with the `HX-Refresh` [header](https://htmx.org/reference/#response_headers). `clientRefresh` is a custom response that allows you to send such a response. It takes no arguments, since Htmx ignores any content.
```php
$this->Htmx->clientRefresh();
```- `stopPolling`
When using a [polling trigger](https://htmx.org/docs/#polling), Htmx will stop polling when it encounters a response with the special HTTP status code 286. `stopPolling` is a custom response with that status code.
```php
$this->Htmx->stopPolling();
```See the documentation for all the remaining [available headers](https://htmx.org/reference/#response_headers).
```php
$this->Htmx->location($location) // Allows you to do a client-side redirect that does not do a full page reload
$this->Htmx->pushUrl($url) // pushes a new url into the history stack
$this->Htmx->replaceUrl($url) // replaces the current URL in the location bar
$this->Htmx->reswap($option) // Allows you to specify how the response will be swapped
$this->Htmx->retarget($selector); // A CSS selector that updates the target of the content update to a different element on the page
```
Additionally, you can trigger [client-side events](https://htmx.org/headers/hx-trigger/) using the `addTrigger` methods.```php
$this->Htmx
->addTrigger('myEvent')
->addTriggerAfterSettle('myEventAfterSettle')
->addTriggerAfterSwap('myEventAfterSwap');
```If you want to pass details along with the event you can use the second argument to send a body. It supports strings or arrays.
```php
$this->Htmx->addTrigger('myEvent', 'Hello from myEvent')
->addTriggerAfterSettle('showMessage', [
'level' => 'info',
'message' => 'Here is a Message'
]);
```You can call those methods multiple times if you want to trigger multiple events.
```php
$this->Htmx
->addTrigger('trigger1', 'A Message')
->addTrigger('trigger2', 'Another Message')
```## CSRF token
To add CSRF token to all your request add below code to your layout.
```php
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['X-CSRF-Token'] = "= $this->getRequest()->getAttribute('csrfToken') ?>";
})
```## Rendering blocks and OOB Swap
The `setBlock()` function allows you to render a specific block while removing other blocks that might be rendered. This is particularly useful when you need to update only a portion of your view.```php
$this->Htmx->setBlock('userTable');
```
The `addBlock()` function allows you to add a specific block to the list of blocks that should be rendered.```php
$this->Htmx->addBlock('userTable');
```
The `addBlocks()` function allows you to add multiple blocks to the list of blocks that should be rendered
```php
$this->Htmx->addBlocks(['userTable', 'pagination']);
```### OOB Swap
Htmx supports updating multiple targets by returning multiple partial responses with [`hx-swap-oop`](https://htmx.org/docs/#oob_swaps).
See the example `Users index search functionality with pagination update`
Note if you are working with tables like in the example. You might need to add
```javascripthtmx.config.useTemplateFragments = true;
```
In your template or layout.## Examples
### Users index search functionality
In this example, we will implement a search functionality for the users' index using Htmx to filter results dynamically. We will wrap our table body inside a [viewBlock](https://book.cakephp.org/5/en/views.html#using-view-blocks) called `usersTable`. When the page loads, we will render the `usersTable` [viewBlock](https://book.cakephp.org/5/en/views.html#using-view-blocks).
```php
// Template/Users/index.php= $this->Form->control('search', [
'label' => false,
'placeholder' => __('Search'),
'type' => 'text',
'required' => false,
'class' => 'form-control input-text search',
'value' => !empty($search) ? $search : '',
'hx-get' => $this->Url->build(['controller' => 'Users', 'action' => 'index']),
'hx-trigger' => "keyup changed delay:200ms",
'hx-target' => "#search-results",
'templates' => [
'inputContainer' => '{{content}}'
]
]); ?>
= 'id' ?>
= 'Name' ?>
= 'Email' ?>
= 'Modified' ?>
= 'Created' ?>
= 'Actions' ?>
start('usersTable'); ?>
= $user->id ?>
= h($user->name) ?>
= h($user->email) ?>
= $user->modified ?>
= $user->created ?>
= $this->Html->link('Edit',
[
'action' => 'edit',
$user->id
],
[
'escape' => false
]
); ?>
= $this->Form->postLink('Delete',
[
'action' => 'delete',
$user->id
],
[
'confirm' => __('Are you sure you want to delete user {0}?', $user->email),
'escape' => false
]
); ?>
end(); ?>fetch('usersTable'); ?>
```
In out controller we will check if the request is Htmx and if so then we will only render the `usersTable` [viewBlock](https://book.cakephp.org/5/en/views.html#using-view-blocks).```php
// src/Controller/UsersController.phppublic function index()
{
$search = null;
$query = $this->Users->find('all');if ($this->request->is('get')) {
if(!empty($this->request->getQueryParams())) {
$data = $this->request->getQueryParams();if(isset($data['search'])) {
$data = $data['search'];
$conditions = [
'OR' => [
'Users.id' => (int)$data,
'Users.name LIKE' => '%' . $data . '%',
'Users.email LIKE' => '%' . $data . '%',
],
];
$query = $query->where([$conditions]);
$search = $data;
}
}
}$users = $query->toArray();
$this->set(compact('users', 'search'));if($this->getRequest()->is('htmx')) {
$this->viewBuilder()->disableAutoLayout();// we will only render the usersTable viewblock
$this->Htmx->setBlock('usersTable');
}
}
```### Users index search functionality with pagination update
In this example, we will implement a dynamic search functionality for the users' index using Htmx. This will allow us to filter results in real-time and update pagination accordingly. We will wrap our table body inside a [viewBlock](https://book.cakephp.org/5/en/views.html#using-view-blocks) called `usersTable` and our pagination to `pagination` block. When the page loads, we will render both the `usersTable` and `pagination` [viewBlock](https://book.cakephp.org/5/en/views.html#using-view-blocks).```php
// Template/Users/index.php= $this->Form->control('search', [
'label' => false,
'placeholder' => __('Search'),
'type' => 'text',
'required' => false,
'class' => 'form-control input-text search',
'value' => !empty($search) ? $search : '',
'hx-get' => $this->Url->build(['controller' => 'Users', 'action' => 'index']),
'hx-trigger' => 'keyup changed delay:200ms',
'hx-target' => '#search-results',
'hx-push-url' => 'true',
'templates' => [
'inputContainer' => '{{content}}'
]
]); ?>
= 'id' ?>
= 'Name' ?>
= 'Email' ?>
= 'Modified' ?>
= 'Created' ?>
= 'Actions' ?>
start('usersTable'); ?>
= $user->id ?>
= h($user->name) ?>
= h($user->email) ?>
= $user->modified ?>
= $user->created ?>
= $this->Html->link('Edit',
[
'action' => 'edit',
$user->id
],
[
'escape' => false
]
); ?>
= $this->Form->postLink('Delete',
[
'action' => 'delete',
$user->id
],
[
'confirm' => __('Are you sure you want to delete user {0}?', $user->email),
'escape' => false
]
); ?>
end(); ?>fetch('usersTable'); ?>
// pagination
start('pagination'); ?>
- {{text}} ',
- {{text}} ',
- {{text}} ',
- {{text}} ',
- {{text}} ',
- {{text}} ',
- {{text}} ',
- {{text}} ',
Paginator->setTemplates([
'prevActive' => '
'prevDisabled' => '
'number' => '
'current' => '
'nextActive' => '
'nextDisabled' => '
'first' => '
'last' => '
]); ?>
= $this->Paginator->first('', ['escape' => false]) ?>
= $this->Paginator->prev('', ['escape' => false]) ?>
= $this->Paginator->numbers(['first' => 1, 'last' => 1, 'modulus' => 3]) ?>
= $this->Paginator->next('', ['escape' => false]) ?>
= $this->Paginator->last('', ['escape' => false]) ?>
end(); ?>
= $this->fetch('pagination'); ?>
```
In out controller we will check if the request is Htmx and if so then we will only render the `usersTable` [viewBlock](https://book.cakephp.org/5/en/views.html#using-view-blocks).
```php
// src/Controller/UsersController.php
public function index()
{
$search = null;
$query = $this->Users->find('all');
if ($this->request->is('get')) {
if(!empty($this->request->getQueryParams())) {
$data = $this->request->getQueryParams();
if(isset($data['search'])) {
$data = $data['search'];
$conditions = [
'OR' => [
'Users.id' => (int)$data,
'Users.name LIKE' => '%' . $data . '%',
'Users.email LIKE' => '%' . $data . '%',
],
];
$query = $query->where([$conditions]);
$search = $data;
}
}
}
$this->paginate['limit'] = 200;
$users = $this->paginate($query);
$this->set(compact('users', 'search'));
if($this->getRequest()->is('htmx')) {
$this->viewBuilder()->disableAutoLayout();
// render users table and pagination blocks
$this->Htmx->addBlock('usersTable')->addBlock('pagination');
}
}
```
## License
Licensed under [The MIT License][mit].
[cakephp]:http://cakephp.org
[composer]:http://getcomposer.org
[mit]:http://www.opensource.org/licenses/mit-license.php