AJAX ve Snippet'ler
Günümüzde modern web uygulamalarının yarısı sunucuda, yarısı da tarayıcıda çalışmaktadır. AJAX hayati bir birleştirici faktördür. Nette Framework ne gibi destekler sunuyor?
- Şablon parçaları gönderme (snippet olarak adlandırılır)
- PHP ve JavaScript arasında değişken aktarımı
- AJAX uygulamaları hata ayıklama
AJAX İsteği
Bir AJAX isteği klasik bir istekten farklı değildir – sunum yapan kişi belirli bir görünüm ve parametrelerle çağrılır. Buna nasıl yanıt verileceği de sunucuya bağlıdır: bir HTML kod parçası (HTML snippet), bir XML belgesi, bir JSON nesnesi veya JavaScript kodu döndüren kendi rutinini kullanabilir.
Sunucu tarafında, bir AJAX isteği $httpRequest->isAjax()
HTTP
isteğini kapsülleyen hizmet yöntemi kullanılarak algılanabilir ( X-Requested-With
HTTP başlığına dayalı
olarak algılar). Sunucunun içinde, $this->isAjax()
yöntemi şeklinde bir kısayol mevcuttur.
JSON'da tarayıcıya veri göndermeye adanmış payload
adında önceden işlenmiş bir nesne vardır.
public function actionDelete(int $id): void
{
if ($this->isAjax()) {
$this->payload->message = 'Success';
}
// ...
}
JSON çıktınız üzerinde tam kontrol sahibi olmak için sunumunuzda sendJson
yöntemini kullanın. Sunucuyu
hemen sonlandırır ve şablon olmadan yaparsınız:
$this->sendJson(['key' => 'value', /* ... */]);
HTML göndermek istiyorsak, AJAX istekleri için özel bir şablon ayarlayabiliriz:
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
// ...
}
Naja
Naja kütüphanesi, tarayıcı tarafında AJAX isteklerini işlemek için kullanılır. Bir node.js paketi olarak yükleyin (Webpack, Rollup, Vite, Parcel ve daha fazlası ile kullanmak için):
npm install naja
…veya doğrudan sayfa şablonuna ekleyin:
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Normal 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şaretlemeniz yeterlidir:
<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>
Parçacıklar
Yerleşik AJAX desteğinin çok daha güçlü bir aracı vardır – snippet'ler. Bunları kullanmak, normal bir uygulamayı yalnızca birkaç satır kod kullanarak bir AJAX uygulamasına dönüştürmeyi mümkün kılar. Tüm bunların nasıl çalıştığı, koduna derlemede veya GitHub'da da erişilebilen Fifteen örneğinde gösterilmiştir.
Snippet'lerin çalışma şekli, ilk (yani AJAX olmayan) istek sırasında tüm sayfanın aktarılması ve ardından her AJAX
alt isteğinde (aynı sunucunun aynı görünümünün
isteği) yalnızca değiştirilen parçaların kodunun daha önce bahsedilen payload
deposuna aktarılmasıdır.
Snippet'ler size Ruby on Rails için Hotwire veya Symfony UX Turbo'yu hatırlatabilir, ancak Nette bunları on dört yıl önce buldu.
Snippet'lerin Geçersiz Kılınması
Control sınıfının her bir torunu (ki bir Presenter da
öyledir), bir istek sırasında yeniden oluşturmasını gerektiren herhangi bir değişiklik olup olmadığını
hatırlayabilir. Bunu işlemek için bir çift yöntem vardır: redrawControl()
ve isControlInvalid()
.
Bir örnek:
public function handleLogin(string $user): void
{
// Nesne, kullanıcı giriş yaptıktan sonra yeniden oluşturulmalıdır
$this->redrawControl();
// ...
}
Ancak Nette, tüm bileşenlerden daha da ince bir çözünürlük sunar. Listelenen yöntemler, isteğe bağlı bir parametre olarak “snippet” olarak adlandırılan bir ismi kabul eder. Bir “snippet” temel olarak şablonunuzdaki bir Latte makrosu tarafından bu amaç için işaretlenmiş bir öğedir, daha sonra bu konuda daha fazla bilgi verilecektir. Böylece bir bileşenden şablonunun sadece parçalarını yeniden çizmesini istemek mümkündür. Bileşenin tamamı geçersiz kılınırsa, tüm parçacıkları yeniden oluşturulur. Bir bileşen, alt bileşenlerinden herhangi birinin geçersiz olması durumunda da “geçersiz” olur.
$this->isControlInvalid(); // -> false
$this->redrawControl('header'); // 'header' adlı parçacığı geçersiz kılar
$this->isControlInvalid('header'); // -> true
$this->isControlInvalid('footer'); // -> false
$this->isControlInvalid(); // -> true, en az bir snippet geçersiz
$this->redrawControl(); // tüm bileşeni, her parçacığı geçersiz kılar
$this->isControlInvalid('footer'); // -> true
Sinyal alan bir bileşen otomatik olarak yeniden çizilmek üzere işaretlenir.
Snippet yeniden çizimi sayesinde hangi öğelerin hangi kısımlarının yeniden işlenmesi gerektiğini tam olarak biliyoruz.
Etiket {snippet} … {/snippet}
Sayfanın oluşturulması normal bir istekle çok benzer şekilde ilerler: aynı şablonlar yüklenir, vb. Ancak önemli olan, çıktıya ulaşmaması gereken kısımların dışarıda bırakılmasıdır; diğer kısımlar bir tanımlayıcı ile ilişkilendirilmeli ve bir JavaScript işleyicisi için anlaşılabilir bir biçimde kullanıcıya gönderilmelidir.
Sözdizimi
Şablonda bir kontrol veya snippet varsa, bunu {snippet} ... {/snippet}
pair etiketini kullanarak
sarmalıyız – bu, işlenen snippet'in “kesilip çıkarılmasını” ve tarayıcıya gönderilmesini sağlayacaktır.
Ayrıca onu bir yardımcı içine alacaktır <div>
etiketi (farklı bir etiket kullanmak mümkündür).
Aşağıdaki örnekte header
adında bir snippet tanımlanmıştır. Bir bileşenin şablonunu da temsil
edebilir:
{snippet header}
<h1>Hello ... </h1>
{/snippet}
dışındaki bir türden bir snippet <div>
veya ek HTML öznitelikleri içeren bir snippet, öznitelik
varyantı kullanılarak elde edilir:
<article n:snippet="header" class="foo bar">
<h1>Hello ... </h1>
</article>
Dinamik Parçacıklar
Nette'de ayrıca bir çalışma zamanı parametresine dayalı olarak dinamik bir adla snippet'ler tanımlayabilirsiniz. Bu, sadece bir satırı değiştirmemiz gereken ancak tüm listeyi onunla birlikte aktarmak istemediğimiz çeşitli listeler için en uygun olanıdır. Bunun bir örneği şöyle olabilir:
<ul n:snippet="itemsContainer">
{foreach $list as $id => $item}
<li n:snippet="item-$id">{$item} <a class="ajax" n:href="update! $id">update</a></li>
{/foreach}
</ul>
Birkaç dinamik snippet içeren itemsContainer
adında bir statik snippet vardır: item-0
,
item-1
ve benzeri.
Dinamik bir snippet'i doğrudan yeniden çizemezsiniz ( item-1
adresinin yeniden çizilmesinin hiçbir etkisi
yoktur), üst snippet'ini yeniden çizmeniz gerekir (bu örnekte itemsContainer
). Bu, üst snippet'in kodunun
yürütülmesine neden olur, ancak daha sonra tarayıcıya yalnızca alt snippet'leri gönderilir. Alt snippet'lerden yalnızca
birini göndermek istiyorsanız, diğer alt snippet'leri oluşturmamak için ana snippet'in girdisini değiştirmeniz gerekir.
Yukarıdaki örnekte, bir AJAX isteği için $list
dizisine yalnızca bir öğe ekleneceğinden emin olmanız
gerekir, bu nedenle foreach
döngüsü yalnızca bir dinamik parçacık yazdıracaktır.
class HomePresenter extends Nette\Application\UI\Presenter
{
/**
* This method returns data for the list.
* Usually this would just request the data from a model.
* For the purpose of this example, the data is hard-coded.
*/
private function getTheWholeList(): array
{
return [
'First',
'Second',
'Third',
];
}
public function renderDefault(): void
{
if (!isset($this->template->list)) {
$this->template->list = $this->getTheWholeList();
}
}
public function handleUpdate(int $id): void
{
$this->template->list = $this->isAjax()
? []
: $this->getTheWholeList();
$this->template->list[$id] = 'Updated item';
$this->redrawControl('itemsContainer');
}
}
Dahil Edilen Bir Şablondaki Parçacıklar
Snippet, farklı bir şablondan dahil edilen bir şablonda olabilir. Bu durumda, ikinci şablondaki dahil etme kodunu
snippetArea
makrosuyla sarmamız gerekir, ardından hem snippetArea'yı hem de gerçek snippet'i yeniden
çizeriz.
Makro snippetArea
, içindeki kodun yürütülmesini sağlar, ancak tarayıcıya yalnızca dahil edilen şablondaki
gerçek parçacık gönderilir.
{* parent.latte *}
{snippetArea wrapper}
{include 'child.latte'}
{/snippetArea}
{* child.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('wrapper');
$this->redrawControl('item');
Ayrıca dinamik snippet'lerle de birleştirebilirsiniz.
Ekleme ve Silme
Listeye yeni bir öğe ekler ve itemsContainer
adresini geçersiz kılarsanız, AJAX isteği yenisini de içeren
parçacıkları döndürür, ancak javascript işleyicisi bunu işleyemez. Bunun nedeni, yeni oluşturulan ID'ye sahip bir HTML
öğesi olmamasıdır.
Bu durumda, en basit yol tüm listeyi bir parçacığa daha sarmak ve hepsini geçersiz kılmaktır:
{snippet wholeList}
<ul n:snippet="itemsContainer">
{foreach $list as $id => $item}
<li n:snippet="item-$id">{$item} <a class="ajax" n:href="update! $id">update</a></li>
{/foreach}
</ul>
{/snippet}
<a class="ajax" n:href="add!">Add</a>
public function handleAdd(): void
{
$this->template->list = $this->getTheWholeList();
$this->template->list[] = 'New one';
$this->redrawControl('wholeList');
}
Aynı şey bir öğeyi silmek için de geçerlidir. Boş snippet göndermek mümkün olabilir, ancak genellikle listeler sayfalandırılabilir ve bir öğeyi silip diğerini (sayfalandırılmış listenin farklı bir sayfasında bulunan) yüklemek karmaşık olacaktır.
Bileşene Parametre Gönderme
AJAX isteği aracılığıyla bileşene parametreler gönderdiğimizde, ister sinyal parametreleri ister kalıcı parametreler
olsun, bileşenin adını da içeren global adlarını sağlamalıyız. Parametrenin tam adı getParameterId()
yöntemini döndürür.
$.getJSON(
{link changeCountBasket!},
{
{$control->getParameterId('id')}: id,
{$control->getParameterId('count')}: count
}
});
Ve bileşende karşılık gelen parametrelerle yöntemi işleyin.
public function handleChangeCountBasket(int $id, int $count): void
{
}