Nette Documentation Preview

syntax
AJAX & Snippets
***************
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

AJAX & Snippets

In der Ära moderner Webanwendungen, in der die Funktionalität oft zwischen Server und Browser aufgeteilt ist, ist AJAX ein unverzichtbares Bindeglied. Welche Möglichkeiten bietet uns das Nette Framework in diesem Bereich?

  • Senden von Teilen des Templates, sogenannten Snippets
  • Übergabe von Variablen zwischen PHP und JavaScript
  • Tools zum Debuggen von AJAX-Anfragen

AJAX-Anfrage

Eine AJAX-Anfrage unterscheidet sich im Grunde nicht von einer klassischen HTTP-Anfrage. Ein Presenter wird mit bestimmten Parametern aufgerufen. Und es liegt am Presenter, wie er auf die Anfrage reagiert – er kann Daten im JSON-Format zurückgeben, einen Teil des HTML-Codes senden, ein XML-Dokument usw.

Auf der Browserseite initialisieren wir die AJAX-Anfrage mit der Funktion fetch():

fetch(url, {
	headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
	// Verarbeitung der Antwort
});

Auf der Serverseite erkennen wir eine AJAX-Anfrage mit der Methode $httpRequest->isAjax() des die HTTP-Anfrage kapselnden Services. Zur Erkennung verwendet sie den HTTP-Header X-Requested-With, daher ist es wichtig, diesen mitzusenden. Innerhalb des Presenters kann die Methode $this->isAjax() verwendet werden.

Wenn Sie Daten im JSON-Format senden möchten, verwenden Sie die Methode sendJson(). Die Methode beendet auch die Aktivität des Presenters.

public function actionExport(): void
{
	$this->sendJson($this->model->getData);
}

Wenn Sie planen, mit einem speziellen Template für AJAX zu antworten, können Sie dies wie folgt tun:

public function handleClick($param): void
{
	if ($this->isAjax()) {
		$this->template->setFile('path/to/ajax.latte');
	}
	// ...
}

Snippets

Das stärkste Mittel, das Nette zur Verbindung von Server und Client bietet, sind Snippets. Dank ihnen können Sie eine gewöhnliche Anwendung mit minimalem Aufwand und wenigen Codezeilen in eine AJAX-Anwendung verwandeln. Wie das Ganze funktioniert, demonstriert das Beispiel Fifteen, dessen Code Sie auf GitHub finden.

Snippets, oder Ausschnitte, ermöglichen es, nur Teile der Seite zu aktualisieren, anstatt die gesamte Seite neu zu laden. Dies ist nicht nur schneller und effizienter, sondern bietet auch eine komfortablere Benutzererfahrung. Snippets erinnern vielleicht an Hotwire für Ruby on Rails oder Symfony UX Turbo. Interessanterweise hat Nette Snippets bereits 14 Jahre früher eingeführt.

Wie funktionieren Snippets? Beim ersten Laden der Seite (nicht-AJAX-Anfrage) wird die gesamte Seite einschließlich aller Snippets geladen. Wenn der Benutzer mit der Seite interagiert (z. B. auf einen Button klickt, ein Formular absendet usw.), wird anstelle des Ladens der gesamten Seite eine AJAX-Anfrage ausgelöst. Der Code im Presenter führt die Aktion aus und entscheidet, welche Snippets aktualisiert werden müssen. Nette rendert diese Snippets und sendet sie in Form eines Arrays im JSON-Format. Der Verarbeitungscode im Browser fügt die empfangenen Snippets wieder in die Seite ein. Es wird also nur der Code der geänderten Snippets übertragen, was Bandbreite spart und das Laden im Vergleich zur Übertragung des gesamten Seiteninhalts beschleunigt.

Naja

Zur Verarbeitung von Snippets auf der Browserseite dient die Bibliothek Naja. Diese installieren Sie als node.js-Paket (zur Verwendung mit Anwendungen wie Webpack, Rollup, Vite, Parcel und anderen):

npm install naja

…oder fügen Sie sie direkt in das Seiten-Template ein:

<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>

Zuerst muss die Bibliothek initialisiert werden:

naja.initialize();

Um aus einem gewöhnlichen Link (Signal) oder dem Absenden eines Formulars eine AJAX-Anfrage zu machen, genügt es, den entsprechenden Link, das Formular oder den Button mit der Klasse ajax zu kennzeichnen:

<a n:href="go!" class="ajax">Go</a>

<form n:name="form" class="ajax">
    <input n:name="submit">
</form>

oder

<form n:name="form">
    <input n:name="submit" class="ajax">
</form>

Neuzeichnen von Snippets

Jedes Objekt der Klasse Control (einschließlich des Presenters selbst) verfolgt, ob Änderungen aufgetreten sind, die sein Neuzeichnen erfordern. Dazu dient die Methode redrawControl():

public function handleLogin(string $user): void
{
	// nach dem Login muss der relevante Teil neu gezeichnet werden
	$this->redrawControl();
	// ...
}

Nette ermöglicht eine noch feinere Kontrolle darüber, was neu gezeichnet werden soll. Die genannte Methode kann nämlich als Argument den Namen des Snippets entgegennehmen. Man kann also auf der Ebene von Template-Teilen invalidieren (sprich: Neuzeichnen erzwingen). Wenn die gesamte Komponente invalidiert wird, wird auch jedes ihrer Snippets neu gezeichnet:

// invalidiert das Snippet 'header'
$this->redrawControl('header');

Snippets in Latte

Die Verwendung von Snippets in Latte ist extrem einfach. Um einen Teil des Templates als Snippet zu definieren, umschließen Sie ihn einfach mit den Tags {snippet} und {/snippet}:

{snippet header}
	<h1>Hello ... </h1>
{/snippet}

Das Snippet erstellt in der HTML-Seite ein <div>-Element mit einer speziellen generierten id. Beim Neuzeichnen des Snippets wird dann der Inhalt dieses Elements aktualisiert. Daher ist es notwendig, dass beim erstmaligen Rendern der Seite auch alle Snippets gerendert werden, auch wenn sie anfangs leer sein mögen.

Sie können auch ein Snippet mit einem anderen Element als <div> erstellen, indem Sie ein n:Attribut verwenden:

<article n:snippet="header" class="foo bar">
	<h1>Hello ... </h1>
</article>

Snippet-Bereiche

Snippet-Namen können auch Ausdrücke sein:

{foreach $items as $id => $item}
	<li n:snippet="item-{$id}">{$item}</li>
{/foreach}

So entstehen mehrere Snippets item-0, item-1 usw. Wenn wir ein dynamisches Snippet direkt invalidieren würden (zum Beispiel item-1), würde nichts neu gezeichnet werden. Der Grund dafür ist, dass Snippets wirklich wie Ausschnitte funktionieren und nur sie selbst direkt gerendert werden. Im Template gibt es jedoch faktisch kein Snippet namens item-1. Dieses entsteht erst durch die Ausführung des Codes um das Snippet herum, also der foreach-Schleife. Wir kennzeichnen daher den Teil des Templates, der ausgeführt werden soll, mit dem Tag {snippetArea}:

<ul n:snippetArea="itemsContainer">
	{foreach $items as $id => $item}
		<li n:snippet="item-{$id}">{$item}</li>
	{/foreach}
</ul>

Und lassen sowohl das Snippet selbst als auch den gesamten übergeordneten Bereich neu zeichnen:

$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');

Gleichzeitig ist es ratsam sicherzustellen, dass das Array $items nur die Elemente enthält, die neu gezeichnet werden sollen.

Wenn wir mittels des {include}-Tags ein anderes Template einfügen, das Snippets enthält, muss das Einfügen des Templates ebenfalls in eine snippetArea eingeschlossen und diese zusammen mit dem Snippet invalidiert werden:

{snippetArea include}
	{include 'included.latte'}
{/snippetArea}
{* included.latte *}
{snippet item}
	...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');

Snippets in Komponenten

Sie können Snippets auch in Komponenten erstellen und Nette wird sie automatisch neu zeichnen. Es gibt jedoch eine gewisse Einschränkung: Für das Neuzeichnen von Snippets ruft Nette die Methode render() ohne Parameter auf. Das bedeutet, dass die Übergabe von Parametern im Template nicht funktioniert:

OK
{control productGrid}

wird nicht funktionieren:
{control productGrid $arg, $arg}
{control productGrid:paginator}

Senden von Benutzerdaten

Zusammen mit den Snippets können Sie beliebige zusätzliche Daten an den Client senden. Schreiben Sie diese einfach in das payload-Objekt:

public function actionDelete(int $id): void
{
	// ...
	if ($this->isAjax()) {
		$this->payload->message = 'Success';
	}
}

Parameterübergabe

Wenn wir einer Komponente über eine AJAX-Anfrage Parameter senden, seien es Signalparameter oder persistente Parameter, müssen wir bei der Anfrage deren globalen Namen angeben, der auch den Namen der Komponente enthält. Den vollständigen Namen des Parameters gibt die Methode getParameterId() zurück.

let url = new URL({link //foo!});
url.searchParams.set({$control->getParameterId('bar')}, bar);

fetch(url, {
	headers: {'X-Requested-With': 'XMLHttpRequest'},
})

Und die handle-Methode mit den entsprechenden Parametern in der Komponente:

public function handleFoo(int $bar): void
{
}