Dinamik Snippet'ler
Latte'nin dinamik snippet'lerini kullanarak, bir sayfanın yalnızca gerçekten değişen bölümlerini (örneğin bir listedeki tek tek öğeleri) AJAX ile nasıl yenilersiniz.
Uygulama geliştirirken, örneğin bir tablonun satırlarında ya da liste öğelerinde AJAX işlemleri yapma ihtiyacı sıkça doğar. Örnek olarak, oturum açmış kullanıcıların her makaleyi “beğen” ya da “beğenme” ile puanlayabildiği bir makale listesini düşünelim. AJAX'sız presenter kodu ve ilgili şablon aşağı yukarı şöyle görünürdü (yalnızca en can alıcı bölümler gösteriliyor; kod, puanlamayı yöneten ve makaleleri getiren bir servisin var olduğunu varsayar; somut gerçekleştirim bu kılavuz için önemli değil):
public function handleLike(int $articleId): void
{
$this->ratingService->saveLike($articleId, $this->user->id);
$this->redirect('this');
}
public function handleUnlike(int $articleId): void
{
$this->ratingService->removeLike($articleId, $this->user->id);
$this->redirect('this');
}
Şablon:
<article n:foreach="$articles as $article">
<h2>{$article->title}</h2>
<div class="content">{$article->content}</div>
{if !$article->liked}
<a n:href="like! $article->id" class=ajax>Beğendim</a>
{else}
<a n:href="unlike! $article->id" class=ajax>Artık beğenmiyorum</a>
{/if}
</article>
AJAX'laştırma
Şimdi bu basit uygulamaya AJAX işlevi ekleyelim. Bir makalenin puanını değiştirmek, tüm sayfayı yeniden yüklemeyi
gerektirecek kadar kritik değil; bu yüzden ideal olarak arka planda AJAX ile gerçekleşmeli. AJAX bağlantılarının
ajax CSS sınıfına sahip olduğu yaygın uzlaşımıyla birlikte eklentilerdeki işleyici betiğini kullanacağız.
Peki bunu tam olarak nasıl gerçekleştiririz? Nette iki yaklaşım sunar: dinamik snippet'ler ve bileşenler. Her ikisinin de artıları ve eksileri var, bu yüzden ikisini de göstereceğiz.
Dinamik Snippet Yolu
Latte terminolojisinde dinamik snippet, {snippet} etiketinin snippet adında bir değişken kullanılan özel bir
biçimidir. Böyle bir snippet şablonda herhangi bir yere konamaz; statik (sıradan) bir snippet'in içine sarılmalı ya da bir
{snippetArea} içinde olmalıdır. Şablonumuzu şöyle değiştirebiliriz:
{snippet articlesContainer}
<article n:foreach="$articles as $article">
<h2>{$article->title}</h2>
<div class="content">{$article->content}</div>
{snippet article-{$article->id}}
{if !$article->liked}
<a n:href="like! $article->id" class=ajax>Beğendim</a>
{else}
<a n:href="unlike! $article->id" class=ajax>Artık beğenmiyorum</a>
{/if}
{/snippet}
</article>
{/snippet}
Artık her makale, adında makalenin ID'si geçen bir snippet tanımlıyor. Tüm bu dinamik snippet'ler de
articlesContainer adlı statik bir snippet'in içine sarılıyor. Bu dış snippet'i atlasaydık Latte bir istisna
fırlatırdı.
Geriye yalnızca presenter'a yeniden çizme mantığını eklemek kalıyor: yalnızca statik sarmalayıcıyı yeniden çizin.
public function handleLike(int $articleId): void
{
$this->ratingService->saveLike($articleId, $this->user->id);
if ($this->isAjax()) {
$this->redrawControl('articlesContainer');
// $this->redrawControl('article-' . $articleId); -- gerekmiyor
} else {
$this->redirect('this');
}
}
İlgili handleUnlike() metodunu da benzer şekilde değiştirin; AJAX çalışıyor!
Ancak bu çözümün bir sakıncası var. AJAX isteğini daha yakından incelersek, uygulama dışarıdan verimli görünse de (yalnızca ilgili makaleye ait tek bir snippet döndürse de), aslında sunucu tarafında tüm snippet'leri render ettiğini görürüz. Gereken snippet'i payload'a koyar, diğerlerini atar (yani onları da gereksiz yere getirip render etmiştir).
Bunu iyileştirmek için $articles koleksiyonunun şablona aktarıldığı yere (diyelim ki
renderDefault() metoduna) müdahale etmemiz gerekir. Sinyal işlemenin render<Something>
metotlarından önce gerçekleştiğinden yararlanacağız:
public function handleLike(int $articleId): void
{
// ...
if ($this->isAjax()) {
// ...
$this->template->articles = [
$this->db->table('articles')->get($articleId),
];
} else {
// ...
}
public function renderDefault(): void
{
if (!isset($this->template->articles)) {
$this->template->articles = $this->db->table('articles');
}
}
Artık sinyal işlenirken şablona makalelerin tümü yerine yalnızca ilgili tek makaleyi içeren bir dizi aktarılıyor; yani
render edip payload'da tarayıcıya göndermeyi düşündüğümüz makale. Sonuç olarak {foreach} döngüsü
yalnızca bir kez çalışıyor ve gereksiz snippet render edilmiyor.
Bileşen Yolu
Tümüyle farklı bir yaklaşım, dinamik snippet'lerden büsbütün kaçınır. Numara, tüm mantığı ayrı bir bileşenin
içine almaktan ibarettir. Puanlamayı presenter yerine, bu iş için ayrılmış bir LikeControl yönetir. Sınıf
şöyle görünür (ayrıca render, handleUnlike gibi metotları da içerir):
class LikeControl extends Nette\Application\UI\Control
{
public function __construct(
private Article $article,
) {
}
public function handleLike(): void
{
$this->ratingService->saveLike($this->article->id, $this->presenter->user->id);
if ($this->presenter->isAjax()) {
$this->redrawControl();
} else {
$this->presenter->redirect('this');
}
}
}
Bileşenin şablonu:
{snippet}
{if !$article->liked}
<a n:href="like!" class=ajax>Beğendim</a>
{else}
<a n:href="unlike!" class=ajax>Artık beğenmiyorum</a>
{/if}
{/snippet}
Doğal olarak view'ın şablonu değişecek ve presenter'a bir factory eklememiz gerekecek. Veritabanından gelen her makale için bu bileşenden bir örnek oluşturacağımızdan, oluşturulmalarını yönetmek için Multiplier sınıfını kullanacağız.
protected function createComponentLikeControl()
{
$articles = $this->db->table('articles');
return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) {
return new LikeControl($articles[$articleId]);
});
}
View'ın şablonu en aza iner (ve snippet'lerden tümüyle arınır!):
<article n:foreach="$articles as $article">
<h2>{$article->title}</h2>
<div class="content">{$article->content}</div>
{control "likeControl-$article->id"}
</article>
Neredeyse bitirdik: uygulama artık AJAX ile çalışacak. Burada da iyileştirme gerekiyor; çünkü Nette Database kullanımı nedeniyle sinyal işlenirken veritabanından yalnızca ilgili makale yerine tüm makaleler gereksiz yere yükleniyor. Buna karşılık üstünlük şu: yalnızca ilgili bileşen örneği render edildiğinden gereksiz render yapılmıyor.