AJAX & Snippet'ler

Sunucu ve tarayıcı arasında işlevselliğin sıklıkla bölündüğü modern web uygulamaları çağında, AJAX vazgeçilmez bir bağlantı elemanıdır. Nette Framework bize bu alanda hangi seçenekleri sunuyor?

  • şablonun parçalarını, yani snippet'leri gönderme
  • PHP ve JavaScript arasında değişkenleri iletme
  • AJAX isteklerinin hatalarını ayıklama araçları

AJAX İsteği

Bir AJAX isteği, temelde klasik bir HTTP isteğinden farklı değildir. Belirli parametrelerle bir presenter çağrılır. Ve isteğe nasıl yanıt vereceği presenter'a bağlıdır – JSON formatında veri döndürebilir, HTML kodunun bir kısmını, bir XML belgesini vb. gönderebilir.

Tarayıcı tarafında, fetch() fonksiyonunu kullanarak bir AJAX isteği başlatırız:

fetch(url, {
	headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
	// yanıtın işlenmesi
});

Sunucu tarafında, HTTP isteğini kapsayan servisin $httpRequest->isAjax() metoduyla bir AJAX isteğini tanırız. Algılama için X-Requested-With HTTP başlığını kullanır, bu yüzden onu göndermek önemlidir. Presenter içinde $this->isAjax() metodunu kullanabilirsiniz.

Verileri JSON formatında göndermek istiyorsanız, sendJson() metodunu kullanın. Metot ayrıca presenter'ın etkinliğini de sonlandırır.

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

AJAX için tasarlanmış özel bir şablonla yanıt vermeyi planlıyorsanız, bunu aşağıdaki gibi yapabilirsiniz:

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

Snippet'ler

Nette'nin sunucuyu istemciyle bağlamak için sunduğu en güçlü araç snippet'lerdir. Onlar sayesinde, sıradan bir uygulamayı minimum çaba ve birkaç satır kodla AJAX uygulamasına dönüştürebilirsiniz. Tüm bunların nasıl çalıştığını, kodunu GitHub'da bulabileceğiniz Fifteen örneği göstermektedir.

Snippet'ler veya kesitler, tüm sayfanın yeniden yüklenmesi yerine sayfanın yalnızca bölümlerini güncellemenize olanak tanır. Bu sadece daha hızlı ve daha verimli olmakla kalmaz, aynı zamanda daha rahat bir kullanıcı deneyimi de sağlar. Snippet'ler size Ruby on Rails için Hotwire'ı veya Symfony UX Turbo'yu hatırlatabilir. İlginç bir şekilde, Nette snippet'leri 14 yıl önce tanıttı.

Snippet'ler nasıl çalışır? Sayfa ilk yüklendiğinde (AJAX olmayan istek), tüm snippet'ler dahil olmak üzere tüm sayfa yüklenir. Kullanıcı sayfayla etkileşime girdiğinde (örneğin, bir düğmeye tıkladığında, bir form gönderdiğinde vb.), tüm sayfayı yüklemek yerine bir AJAX isteği tetiklenir. Presenter'daki kod eylemi gerçekleştirir ve hangi snippet'lerin güncellenmesi gerektiğine karar verir. Nette bu snippet'leri oluşturur ve JSON formatında bir dizi olarak gönderir. Tarayıcıdaki işleyici kod, alınan snippet'leri sayfaya geri ekler. Bu nedenle, yalnızca değiştirilen snippet'lerin kodu aktarılır, bu da bant genişliğinden tasarruf sağlar ve tüm sayfanın içeriğini aktarmaya kıyasla yüklemeyi hızlandırır.

Naja

Snippet'leri tarayıcı tarafında işlemek için Naja kütüphanesi kullanılır. Bunu bir node.js paketi olarak kurun (Webpack, Rollup, Vite, Parcel ve diğer uygulamalarla kullanım için):

npm install naja

…veya doğrudan sayfa şablonuna ekleyin:

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

Önce kütüphaneyi başlatmanız gerekir:

naja.initialize();

Sıradan bir bağlantıdan (sinyal) veya form gönderiminden bir AJAX isteği oluşturmak için, ilgili bağlantıyı, formu veya düğmeyi ajax sınıfıyla işaretlemek yeterlidir:

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

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

veya

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

Snippet'lerin Yeniden Çizilmesi

Control sınıfının her nesnesi (Presenter'ın kendisi dahil), yeniden çizilmesini gerektiren değişiklikler olup olmadığını takip eder. Bunun için redrawControl() metodu kullanılır:

public function handleLogin(string $user): void
{
	// giriş yaptıktan sonra ilgili bölümü yeniden çizmek gerekir
	$this->redrawControl();
	// ...
}

Nette, neyin yeniden çizileceği konusunda daha da hassas kontrol sağlar. Bahsedilen metot, argüman olarak snippet adını alabilir. Böylece, şablonun bölümleri düzeyinde geçersiz kılma (yani yeniden çizmeyi zorlama) mümkündür. Tüm bileşen geçersiz kılınırsa, her bir snippet'i de yeniden çizilir:

// 'header' snippet'ini geçersiz kılar
$this->redrawControl('header');

Latte'de Snippet'ler

Latte'de snippet kullanmak son derece kolaydır. Şablonun bir bölümünü snippet olarak tanımlamak için, onu {snippet} ve {/snippet} etiketleriyle sarmanız yeterlidir:

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

Snippet, HTML sayfasında özel olarak oluşturulmuş bir id ile bir <div> öğesi oluşturur. Snippet yeniden çizildiğinde, bu öğenin içeriği güncellenir. Bu nedenle, sayfanın ilk oluşturulmasında, başlangıçta boş olsalar bile tüm snippet'lerin de oluşturulması gerekir.

n:attribute kullanarak <div> dışında bir öğeyle de bir snippet oluşturabilirsiniz:

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

Snippet Alanları

Snippet adları ifadeler de olabilir:

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

Bu şekilde birkaç snippet oluşturulur: item-0, item-1 vb. Dinamik bir snippet'i doğrudan geçersiz kılsaydık (örneğin item-1), hiçbir şey yeniden çizilmezdi. Bunun nedeni, snippet'lerin gerçekten kesitler gibi çalışması ve yalnızca kendilerinin doğrudan oluşturulmasıdır. Ancak şablonda aslında item-1 adında bir snippet yoktur. Bu, yalnızca snippet'in etrafındaki kodun, yani foreach döngüsünün yürütülmesiyle ortaya çıkar. Bu nedenle, yürütülmesi gereken şablon bölümünü {snippetArea} etiketiyle işaretleriz:

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

Ve hem snippet'in kendisini hem de tüm üst alanı yeniden çizeriz:

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

Aynı zamanda, $items dizisinin yalnızca yeniden çizilmesi gereken öğeleri içermesini sağlamak uygundur.

Şablona {include} etiketi kullanarak snippet'ler içeren başka bir şablon eklersek, şablon eklemesini tekrar snippetArea içine almalı ve onu snippet ile birlikte geçersiz kılmalıyız:

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

Bileşenlerde Snippet'ler

Bileşenlerde de snippet'ler oluşturabilirsiniz ve Nette bunları otomatik olarak yeniden çizer. Ancak burada belirli bir sınırlama vardır: snippet'leri yeniden çizmek için render() metodunu parametresiz çağırır. Yani, şablonda parametreleri iletmek işe yaramaz:

OK
{control productGrid}

işe yaramayacak:
{control productGrid $arg, $arg}
{control productGrid:paginator}

Kullanıcı Verilerini Gönderme

Snippet'lerle birlikte istemciye herhangi bir ek veri gönderebilirsiniz. Bunları payload nesnesine yazmanız yeterlidir:

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

Parametreleri İletme

Bir bileşene AJAX isteği ile parametreler gönderirsek, bunlar ister sinyal parametreleri ister kalıcı parametreler olsun, istekte bileşenin adını da içeren global adlarını belirtmemiz gerekir. Parametrenin tam adını getParameterId() metodu döndürür.

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

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

Ve bileşendeki karşılık gelen parametrelerle handle metodu:

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