Dinamik Snippet'ler
Uygulama geliştirirken, örneğin bir tablonun tek tek satırları veya bir listenin öğeleri üzerinde AJAX işlemleri yapma ihtiyacı oldukça sık ortaya çıkar. Örnek olarak, makalelerin bir listesini seçebiliriz, burada her makale için giriş yapmış kullanıcının “beğen/beğenme” derecelendirmesini seçmesine izin veririz. AJAX olmadan presenter ve ilgili şablonun kodu yaklaşık olarak aşağıdaki gibi görünecektir (en önemli bölümleri listeliyorum, kod derecelendirmeleri işaretlemek ve makale koleksiyonunu almak için bir servisin varlığını varsayar – belirli uygulama bu kılavuzun amaçları için önemli değildir):
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ğen</a>
{else}
<a n:href="unlike! $article->id" class=ajax>artık beğenmiyorum</a>
{/if}
</article>
AJAXlaştırma
Şimdi bu basit uygulamayı AJAX ile donatalım. Bir makalenin derecelendirmesini değiştirmek, bir yönlendirme gerektirecek
kadar önemli değildir ve bu nedenle ideal olarak arka planda AJAX ile gerçekleşmelidir. Eklentilerden yardımcı betiği AJAX bağlantılarının
ajax
CSS sınıfına sahip olduğu olağan kuralıyla kullanacağız.
Ancak, bunu tam olarak nasıl yapacağız? Nette 2 yol sunar: dinamik snippet'ler yolu ve bileşenler yolu. Her ikisinin de artıları ve eksileri vardır, bu yüzden onları birer birer göstereceğiz.
Dinamik Snippet Yolu
Latte terminolojisinde dinamik bir snippet, snippet adında bir değişkenin kullanıldığı {snippet}
makrosunun
özel bir kullanım durumunu ifade eder. Böyle bir snippet şablonda herhangi bir yerde bulunamaz – statik bir snippet, yani
sıradan bir snippet veya {snippetArea}
içinde sarmalanmalıdır. Şablonumuzu aşağıdaki gibi
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ğen</a>
{else}
<a n:href="unlike! $article->id" class=ajax>artık beğenmiyorum</a>
{/if}
{/snippet}
</article>
{/snippet}
Her makale şimdi adında makale ID'si bulunan bir snippet tanımlar. Tüm bu snippet'ler daha sonra
articlesContainer
adlı tek bir snippet ile birlikte sarmalanır. Bu sarmalayıcı snippet'i atlarsak, Latte bizi
bir istisna ile uyaracaktır.
Geriye presenter'a yeniden çizimi eklemek kalıyor – sadece statik sarmalayıcıyı yeniden çizmek yeterlidir.
public function handleLike(int $articleId): void
{
$this->ratingService->saveLike($articleId, $this->user->id);
if ($this->isAjax()) {
$this->redrawControl('articlesContainer');
// $this->redrawControl('article-' . $articleId); -- gerekli değil
} else {
$this->redirect('this');
}
}
Benzer şekilde, kardeş metot handleUnlike()
'ı da değiştiririz ve AJAX işlevseldir!
Ancak çözümün bir dezavantajı var. AJAX isteğinin nasıl ilerlediğini daha fazla incelersek, uygulamanın dışarıdan verimli görünmesine rağmen (belirli makale için yalnızca tek bir snippet döndürür), aslında sunucuda tüm snippet'leri oluşturduğunu fark ederiz. İstenen snippet'i payload'a yerleştirdi ve diğerlerini attı (bu nedenle onları veritabanından tamamen gereksiz yere aldı).
Bu süreci optimize etmek için, $articles
koleksiyonunu şablona ilettiğimiz yere müdahale etmemiz gerekecek
(diyelim ki renderDefault()
metodunda). Sinyal işlemenin render<Something>
metotlarından önce
gerçekleştiği gerçeğ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');
}
}
Şimdi, sinyal işlenirken, tüm makaleleri içeren koleksiyon yerine şablona yalnızca tek bir makale içeren bir dizi
iletilir – yani, tarayıcıya payload'da oluşturmak ve göndermek istediğimiz makale. {foreach}
bu nedenle
yalnızca bir kez çalışır ve fazladan snippet oluşturulmaz.
Bileşen Yolu
Tamamen farklı bir çözüm yaklaşımı dinamik snippet'lerden kaçınır. Hile, tüm mantığı özel bir bileşene
aktarmaktır – bundan sonra derecelendirme girişi presenter tarafından değil, özel bir LikeControl
tarafından
yönetilecektir. Sınıf aşağıdaki gibi görünecektir (ayrıca render
, handleUnlike
vb. metotları
da içerecektir):
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şen şablonu:
{snippet}
{if !$article->liked}
<a n:href="like!" class=ajax>beğen</a>
{else}
<a n:href="unlike!" class=ajax>artık beğenmiyorum</a>
{/if}
{/snippet}
Tabii ki, görünüm şablonumuz değişecek ve presenter'a bir fabrika eklememiz gerekecek. Bileşeni veritabanından aldığımız makale sayısı kadar oluşturacağımız için, onu “çoğaltmak” 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]);
});
}
Görünüm şablonu gerekli minimuma indirildi (ve tamamen snippet'lerden arındırıldı!):
<article n:foreach="$articles as $article">
<h2>{$article->title}</h2>
<div class="content">{$article->content}</div>
{control "likeControl-$article->id"}
</article>
Neredeyse bitti: uygulama artık AJAX ile çalışacak. Burada da uygulamayı optimize etmemiz gerekiyor, çünkü Nette Database kullanımı nedeniyle, sinyal işlenirken veritabanından tüm makaleler gereksiz yere yüklenir, oysa sadece bir tanesi yeterlidir. Ancak avantajı, bunların oluşturulmamasıdır, çünkü gerçekten sadece bizim bileşenimiz oluşturulur.