Skip to content

Commit

Permalink
Merge pull request #2 from zunnu/5.x-dev
Browse files Browse the repository at this point in the history
Added ability to render specific block
  • Loading branch information
zunnu authored Jul 22, 2024
2 parents c9708eb + d13f8f4 commit 6aff839
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 2 deletions.
117 changes: 117 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,123 @@ document.body.addEventListener('htmx:configRequest', (event) => {
})
```

## 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' => '<div class="col-10 col-md-6 col-lg-5">{{content}}</div>'
]
]); ?>

<table id="usersTable" class="table table-hover table-white-bordered">
<thead>
<tr>
<th scope="col"><?= 'id' ?></th>
<th scope="col"><?= 'Name' ?></th>
<th scope="col"><?= 'Email' ?></th>
<th scope="col"><?= 'Modified' ?></th>
<th scope="col"><?= 'Created' ?></th>
<th scope="col" class="actions"><?= __('Actions') ?></th>
</tr>
</thead>

<tbody id="search-results">
<?php $this->start('usersTable'); ?>
<?php foreach ($users as $user): ?>
<tr>
<td><?= $user->id ?></td>
<td><?= h($user->name) ?></td>
<td><?= h($user->email) ?></td>
<td><?= $user->modified ?></td>
<td><?= $user->created ?></td>
<td class="actions">
<?= $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
]
); ?>
</td>
</tr>
<?php endforeach; ?>
<?php $this->end(); ?>

<?php echo $this->fetch('usersTable'); ?>
</tbody>
</table>
```
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;
}
}
}

$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');
}
}
```



## License

Licensed under [The MIT License][mit].
Expand Down
61 changes: 59 additions & 2 deletions src/Controller/Component/HtmxComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ class HtmxComponent extends Component
*/
protected array $_defaultConfig = [];

/**
* The name of the block.
*
* @var string|null
*/
protected ?string $block = null;

/**
* List of triggers to use on request
*
Expand All @@ -39,6 +46,19 @@ class HtmxComponent extends Component
*/
private array $triggersAfterSwap = [];

/**
* Get the callbacks this class is interested in.
*
* @return array<string, mixed>
*/
public function implementedEvents(): array
{
return [
'View.beforeRender' => 'beforeRender',
'View.afterRender' => 'afterRender',
];
}

/**
* Initialize properties.
*
Expand All @@ -54,13 +74,28 @@ public function initialize(array $config): void
*
* @return void
*/
public function beforeRender(): void
public function beforeRender($event): void
{
if ($this->getController()->getRequest()->is('htmx')) {
$this->prepare();
}
}

/**
* afterRender callback.
*
* If setBlock is used this will render the set block if it exists
*
* @return void
*/
public function afterRender($event)
{
if (!empty($this->block) && $event->getSubject()->exists($this->block)) {
$block = $event->getSubject()->fetch($this->block);
$event->getSubject()->assign('content', $block);
}
}

/**
* The current URL of the browser when the htmx request was made.
*
Expand Down Expand Up @@ -289,7 +324,7 @@ public function clientRefresh(): ?Response
* @param array $headers The headers that will be set
* @return \Cake\Http\Response|null
*/
public function stopPolling($content = '', array $headers = []): ?Response
public function stopPolling(string $content = '', array $headers = []): ?Response
{
$response = $this->getController()->getResponse();

Expand Down Expand Up @@ -322,4 +357,26 @@ private function encodeTriggers(array $triggers): string

return implode(',', array_keys($triggers));
}

/**
* Set a specific block to render
*
* @param string|null $block Name of the block
*/
public function setBlock(?string $block): static
{
$this->block = $block;

return $this;
}

/**
* Get the block that will be rendered
*
* @return string|null
*/
public function getBlock(): ?string
{
return $this->block;
}
}

0 comments on commit 6aff839

Please sign in to comment.