Интеграция CakePHP для htmx.
Поддерживаемые версии CakePHP >= 4.x и 5.x.
cd
в корень папки вашего приложения (где находится файл composer.json
) и выполните следующую команду:
composer require zunnu/cake-htmx
Затем загрузите плагин с помощью консоли CakePHP:
./bin/cake plugin load CakeHtmx
Чтобы установить htmx, просмотрите их документацию.
Основная функциональность в настоящее время заключена в компоненте Htmx. Чтобы загрузить компонент, вам нужно будет изменить src/Controller/AppController.php
и загрузить компонент Htmx в функцию initialize()
$ this -> loadComponent ( ' CakeHtmx.Htmx ' );
Вы можете использовать детектор, чтобы проверить, является ли запрос Htmx.
$ 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
Используя компонент, вы можете проверить более подробную информацию о запросе.
$ 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
redirect
Htmx может инициировать перенаправление на стороне клиента, когда получает ответ с заголовком HX-Redirect
.
$ this -> Htmx -> redirect ( ' /somewhere-else ' );
clientRefresh
Htmx инициирует перезагрузку страницы, когда получит ответ с заголовком HX-Refresh
. clientRefresh
— это пользовательский ответ, который позволяет вам отправить такой ответ. Он не принимает аргументов, поскольку Htmx игнорирует любой контент.
$ this -> Htmx -> clientRefresh ();
stopPolling
При использовании триггера опроса Htmx прекратит опрос, когда встретит ответ со специальным кодом состояния HTTP 286. stopPolling
— это пользовательский ответ с этим кодом состояния.
$ this -> Htmx -> stopPolling ();
См. документацию для всех остальных доступных заголовков.
$ 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
Кроме того, вы можете инициировать события на стороне клиента, используя методы addTrigger
.
$ this -> Htmx
-> addTrigger ( ' myEvent ' )
-> addTriggerAfterSettle ( ' myEventAfterSettle ' )
-> addTriggerAfterSwap ( ' myEventAfterSwap ' );
Если вы хотите передать подробности вместе с событием, вы можете использовать второй аргумент для отправки тела. Он поддерживает строки или массивы.
$ this -> Htmx -> addTrigger ( ' myEvent ' , ' Hello from myEvent ' )
-> addTriggerAfterSettle ( ' showMessage ' , [
' level ' => ' info ' ,
' message ' => ' Here is a Message '
]);
Вы можете вызывать эти методы несколько раз, если хотите вызвать несколько событий.
$ this -> Htmx
-> addTrigger ( ' trigger1 ' , ' A Message ' )
-> addTrigger ( ' trigger2 ' , ' Another Message ' )
Чтобы добавить токен CSRF ко всем вашим запросам, добавьте код ниже в свой макет.
document.body.addEventListener( ' htmx:configRequest ' , (event) => {
event.detail.headers[ ' X-CSRF-Token ' ] = " <?= $ this -> getRequest ()->getAttribute('csrfToken') ?> " ;
})
Функция setBlock()
позволяет визуализировать определенный блок, удаляя при этом другие блоки, которые могут быть визуализированы. Это особенно полезно, когда вам нужно обновить только часть представления.
$ this -> Htmx -> setBlock ( ' userTable ' );
Функция addBlock()
позволяет вам добавить определенный блок в список блоков, которые должны быть отображены.
$ this -> Htmx -> addBlock ( ' userTable ' );
Функция addBlocks()
позволяет добавлять несколько блоков в список блоков, которые должны быть отображены.
$ this -> Htmx -> addBlocks ([ ' userTable ' , ' pagination ' ]);
Htmx поддерживает обновление нескольких целей, возвращая несколько частичных ответов с помощью hx-swap-oop
. См. пример. Users index search functionality with pagination update
Обратите внимание, если вы работаете с таблицами, как в примере. Возможно, вам придется добавить
< script type = "text/javascript" >
htmx.config.useTemplateFragments = true;
</ script >
В вашем шаблоне или макете.
В этом примере мы реализуем функцию поиска по индексу пользователей, используя Htmx для динамической фильтрации результатов. Мы обернем тело нашей таблицы внутри viewBlock под usersTable
. Когда страница загрузится, мы отобразим viewBlock usersTable
.
// 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>
В нашем контроллере мы проверим, является ли запрос Htmx, и если да, то мы будем отображать только viewBlock usersTable
.
// 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 ' );
}
}
В этом примере мы реализуем функцию динамического поиска по индексу пользователей с помощью Htmx. Это позволит нам фильтровать результаты в режиме реального времени и соответствующим образом обновлять нумерацию страниц. Мы обернем тело нашей таблицы внутри viewBlock под usersTable
и нашего блока разбиения на pagination
. Когда страница загружается, мы отображаем как usersTable
, так и viewBlock pagination
.
// 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 ' => ' <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>
// pagination
<?php $ this -> start ( ' pagination ' ); ?>
<nav aria-label="Page navigation" id="pagination">
<ul class="pagination justify-content-center">
<?php $ this -> Paginator -> setTemplates ([
' prevActive ' => ' <li class="page-item pagination-previous"><a class="page-link" hx-get="{{url}}" hx-target="#search-results" hx-push-url="true" href="#">{{text}}</a></li> ' ,
' prevDisabled ' => ' <li class="page-item disabled pagination-previous"><a class="page-link" hx-get="{{url}}" hx-target="#search-results" hx-push-url="true" href="#">{{text}}</a></li> ' ,
' number ' => ' <li class="page-item"><a class="page-link" hx-get="{{url}}" hx-target="#search-results" hx-push-url="true" href="#">{{text}}</a></li> ' ,
' current ' => ' <li class="page-item active"><a class="page-link" hx-get="{{url}}" hx-target="#search-results" hx-push-url="true" href="#">{{text}}</a></li> ' ,
' nextActive ' => ' <li class="page-item pagination-next"><a class="page-link" hx-get="{{url}}" hx-target="#search-results" hx-push-url="true" href="#">{{text}}</a></li> ' ,
' nextDisabled ' => ' <li class="page-item disabled pagination-next"><a class="page-link" hx-get="{{url}}" hx-target="#search-results" hx-push-url="true" href="#">{{text}}</a></li> ' ,
' first ' => ' <li class="page-item pagination-next"><a class="page-link" hx-get="{{url}}" hx-target="#search-results" hx-push-url="true" href="#">{{text}}</a></li> ' ,
' last ' => ' <li class="page-item pagination-next"><a class="page-link" hx-get="{{url}}" hx-target="#search-results" hx-push-url="true" href="#">{{text}}</a></li> ' ,
]); ?>
<?= $ this -> Paginator -> first ( ' <i class="fas fa-angles-left"></i> ' , [ ' escape ' => false ]) ?>
<?= $ this -> Paginator -> prev ( ' <i class="fas fa-chevron-left"></i> ' , [ ' escape ' => false ]) ?>
<?= $ this -> Paginator -> numbers ([ ' first ' => 1 , ' last ' => 1 , ' modulus ' => 3 ]) ?>
<?= $ this -> Paginator -> next ( ' <i class="fas fa-chevron-right"></i> ' , [ ' escape ' => false ]) ?>
<?= $ this -> Paginator -> last ( ' <i class="fas fa-angles-right"></i> ' , [ ' escape ' => false ]) ?>
</ul>
</nav>
<?php $ this -> end (); ?>
<?= $ this -> fetch ( ' pagination ' ); ?>
В нашем контроллере мы проверим, является ли запрос Htmx, и если да, то мы будем отображать только viewBlock usersTable
.
// 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 ' );
}
}
Лицензировано по лицензии MIT.