AJAX とスニペット
最新のWebアプリケーションの時代では、機能がサーバーとブラウザの間で分割されることが多いため、AJAXは不可欠な接続要素です。Nette Frameworkはこの分野でどのような可能性を提供しているでしょうか?
- テンプレートの一部、いわゆるスニペットの送信
- PHPとJavaScript間の変数渡し
- AJAXリクエストのデバッグツール
AJAXリクエスト
AJAXリクエストは、基本的に通常のHTTPリクエストと変わりません。特定のパラメータでPresenterが呼び出されます。そして、Presenterがリクエストにどのように応答するかは、Presenter次第です。JSON形式のデータを返す、HTMLコードの一部を送信する、XMLドキュメントを送信するなど、さまざまな方法があります。
ブラウザ側では、fetch()
関数を使用してAJAXリクエストを初期化します。
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
// レスポンスの処理
});
サーバー側では、HTTPリクエストをカプセル化するサービス の
$httpRequest->isAjax()
メソッドでAJAXリクエストを認識します。検出には
X-Requested-With
HTTPヘッダーを使用するため、これを送信することが重要です。Presenter内では
$this->isAjax()
メソッドを使用できます。
JSON形式でデータを送信したい場合は、sendJson()
メソッドを使用します。このメソッドはPresenterの動作も終了させます。
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にあります。
スニペット、つまり切り抜きは、ページ全体を再読み込みする代わりに、ページの一部だけを更新することを可能にします。これはより速く、より効率的であるだけでなく、より快適なユーザーエクスペリエンスも提供します。スニペットは、Ruby on RailsのHotwireやSymfony UX Turboを思い出させるかもしれません。興味深いことに、Netteはスニペットを14年も前に導入しました。
スニペットはどのように機能しますか?ページの最初の読み込み(非AJAXリクエスト)では、すべてのスニペットを含むページ全体が読み込まれます。ユーザーがページと対話する(例えば、ボタンをクリックする、フォームを送信するなど)と、ページ全体を読み込む代わりにAJAXリクエストが発行されます。Presenterのコードはアクションを実行し、どのスニペットを更新する必要があるかを決定します。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>
または
<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}
スニペットは、特別な生成された id
を持つ <div>
要素をHTMLページに作成します。スニペットが再描画されると、この要素のコンテンツが更新されます。したがって、ページの初期レンダリング時に、たとえ最初は空であっても、すべてのスニペットもレンダリングする必要があります。
<div>
以外の要素でスニペットを作成することもできます。n:attribute
を使用します。
<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}
動作しません:
{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
{
}