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>

За да превърнете обикновена връзка (сигнал) или подаване на форма в 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
{
}
версия: 4.0