AJAX и фрагменти
В ерата на съвременните уеб приложения, където функционалността често се разпростира между сървъра и браузъра, AJAX е важен свързващ елемент. Какви възможности предлага Nette Framework в тази област?
- Изпращане на части от шаблона, т.нар. фрагменти
- предаване на променливи между PHP и JavaScript
- инструменти за отстраняване на грешки при AJAX заявките
Заявка AJAX
Заявката AJAX не се различава съществено от класическата HTTP заявка. Извиква се презентатор с определени параметри. От водещия зависи как да отговори на заявката – той може да върне данни във формат JSON, да изпрати част от HTML код, XML документ и т.н.
От страна на браузъра инициираме AJAX заявка, като използваме
функцията fetch()
:
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
// обработка на отговора
});
От страна на сървъра AJAX заявката се разпознава чрез метода
$httpRequest->isAjax()
на услугата, която капсулира HTTP заявката. Той използва HTTP
заглавието X-Requested-With
, така че е от съществено значение да го
изпратите. В рамките на презентатора можете да използвате метода
$this->isAjax()
.
Ако искате да изпратите данни във формат JSON, използвайте метода sendJson()
метод.
Методът също така прекратява дейността на презентатора.
public function actionExport(): void
{
$this->sendJson($this->model->getData);
}
Ако планирате да отговорите със специален шаблон, предназначен за AJAX, можете да го направите по следния начин:
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
//...
}
Извадки
Най-мощният инструмент, предлаган от Nette за свързване на сървъра с клиента, са фрагментите. С тях можете да превърнете едно обикновено приложение в AJAX приложение с минимални усилия и няколко реда код. Примерът Fifteen демонстрира как работи всичко това, а кодът му може да бъде намерен в GitHub.
Извадките или изрезките ви позволяват да актуализирате само части от страницата, вместо да презареждате цялата страница. Това е по-бързо и по-ефективно, а също така осигурява по-удобно потребителско изживяване. Snippets може да ви напомнят за Hotwire за Ruby on Rails или Symfony UX Turbo. Интересното е, че Nette въвежда фрагментите 14 години по-рано.
Как работят отрязъците? Когато страницата се зарежда за първи път (заявка, която не е свързана с AJAX), се зарежда цялата страница, включително всички фрагменти. Когато потребителят взаимодейства със страницата (напр. щракне върху бутон, изпрати формуляр и т.н.), вместо да се зареди цялата страница, се прави AJAX заявка. Кодът в презентатора извършва действието и решава кои фрагменти се нуждаят от актуализиране. Nette визуализира тези фрагменти и ги изпраща под формата на JSON масив. След това кодът за обработка в браузъра вмъква получените фрагменти обратно в страницата. Следователно се прехвърля само кодът на променените фрагменти, което спестява честотна лента и ускорява зареждането в сравнение с прехвърлянето на цялото съдържание на страницата.
Naja
За обработка на фрагменти от страна на браузъра се използва библиотеката Naja. Инсталирайте я като пакет за node.js (за използване с приложения като Webpack, Rollup, Vite, Parcel и други):
npm install naja
… или я вмъкнете директно в шаблона на страницата:
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Първо трябва да инициализирате библиотеката:
naja.initialize();
За да превърнете обикновена връзка (сигнал) или подаване на форма в AJAX
заявка, просто маркирайте съответната връзка, форма или бутон с класа
ajax
:
<a n:href="go!" class="ajax">Go</a>
<form n:name="form" class="ajax">
<input n:name="submit">
</form>
or
<form n:name="form">
<input n:name="submit" class="ajax">
</form>
Прерисуване на фрагменти
Всеки обект от класа Control
(включително самият Presenter) запазва запис дали са настъпили промени,
които налагат прерисуването му. За тази цел се използва методът
redrawControl()
.
public function handleLogin(string $user): void
{
// след като влезете в системата, е необходимо да прерисувате съответната част
$this->redrawControl();
//...
}
Nette също така позволява по-фино управление на това, което трябва да се прерисува. Гореспоменатият метод може да приема името на фрагмента като аргумент. По този начин е възможно да се обезсили (което означава: да се наложи прерисуване) на ниво част от шаблона. Ако целият компонент бъде обезсилен, всеки фрагмент от него също ще бъде прерисуван:
// обезсилва фрагмента 'header'
$this->redrawControl('header');
Извадки в Latte
Използването на фрагменти в Latte е изключително лесно. За да
определите част от шаблона като фрагмент, просто я обвийте в тагове
{snippet}
и {/snippet}
:
{snippet header}
<h1>Hello ... </h1>
{/snippet}
Извадката създава елемент <div>
в HTML страницата със
специално генериран id
. При прерисуване на фрагмент
съдържанието на този елемент се актуализира. Следователно при
първоначалното визуализиране на страницата трябва да се визуализират
и всички фрагменти, дори ако първоначално те могат да бъдат празни.
Можете също така да създадете фрагмент с елемент, различен от
<div>
като използвате атрибут n::
<article n:snippet="header" class="foo bar">
<h1>Hello ... </h1>
</article>
Области на извадките
Имената на фрагментите могат да бъдат и изрази:
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
По този начин ще получим няколко фрагмента като item-0
,
item-1
и т.н. Ако директно обезсилим динамичен фрагмент (например
item-1
), нищо няма да бъде прерисувано. Причината е, че фрагментите
функционират като истински откъси и само те самите се визуализират
директно. В шаблона обаче технически няма фрагмент с име item-1
.
Той се появява само при изпълнение на заобикалящия го код на фрагмента,
в този случай цикъла foreach. Следователно ще маркираме частта от шаблона,
която трябва да се изпълни, с тага {snippetArea}
:
<ul n:snippetArea="itemsContainer">
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
</ul>
И ще прерисуваме както отделния фрагмент, така и цялата обща област:
$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');
Също така е важно да се гарантира, че масивът $items
съдържа само
елементите, които трябва да бъдат прерисувани.
При вмъкване на друг шаблон в основния с помощта на тага {include}
,
който има фрагменти, е необходимо отново да се обвие включеният шаблон
в snippetArea
и да се обезсилят заедно и фрагментът, и областта:
{snippetArea include}
{include 'included.latte'}
{/snippetArea}
{* included.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');
Извадки в компонентите
Можете да създавате фрагменти в компонентите и Nette автоматично ще ги
прерисува. Има обаче специфично ограничение: за да прерисува отрязъци,
той извиква метода render()
без никакви параметри. По този начин
подаването на параметри в шаблона няма да работи:
OK
{control productGrid}
will not work:
{control productGrid $arg, $arg}
{control productGrid:paginator}
Изпращане на потребителски данни
Заедно с фрагментите можете да изпращате всякакви допълнителни
данни на клиента. Просто ги запишете в обекта payload
:
public function actionDelete(int $id): void
{
//...
if ($this->isAjax()) {
$this->payload->message = 'Success';
}
}
Изпращане на параметри
Когато изпращаме параметри към компонента чрез AJAX заявка, независимо
дали става въпрос за сигнални или постоянни параметри, трябва да
предоставим тяхното глобално име, което съдържа и името на компонента.
Пълното име на параметъра се връща от метода getParameterId()
.
let url = new URL({link //foo!});
url.searchParams.set({$control->getParameterId('bar')}, bar);
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
Метод за обработка със съответните параметри в компонента:
public function handleFoo(int $bar): void
{
}