Merhabalar, bugün jQuery ve PHP kullanarak veritabanındaki verileri pagination ile nasıl çekebileceğimizi anlatacağım.

Daha önce jQuery ile Infinite Scroll Yapımını blogumda paylaşmıştım ve paginationInfinite Scroll karşılaştırması yapmıştım. Şimdi ise Pagination nasıl yapılır onu anlatacağım. Bunun için bize biraz jQuery ve PHP bilgisi gerekiyor. Ekstra olarak işimizi kolaylaştırması için jQuery ile yazılmış bir Pagination plugini kullanacağız.

Başlamadan önce klasör yapımdan bahsedeyim hemen. Localhostumda pagination adında bir klasör oluşturdum. Sonra bu klasörün altında diğer klasörleri oluşturdum. Bunlar;

  • app klasörü: db.php ve query.php dosyalarımın olacağı klasör
  • css klasörü: Css dosyalarımın olacağı klasör
  • js klasörü: JavaScript dosyalarımın olacağı klasör
  • uploads klasörü: Upload edilen dosyaların olacağı klasör (bu yazımda sunucuya dosya yüklemeden bahsetmeyeceğim)
  • Son olarak pagination klasörünün içinde bulunan index.php dosyası

Pagination yaparken senaryomuz veritabanından ürünlerini çeken bir web sitesi olacak. Yani veritabanımızda ürünler olacak ve bu ürünleri pagination ile sayfa sayfa çekeceğiz. Bunu yaparken jQuery’nin $.post() fonksiyonunu kullanacağımız için her indicatore tıkladığımızda sayfayı yenilemek zorunda kalmayacağız.

Artık asıl konuya girebiliriz 🙂

Her zamanki gibi işe bir HTML iskeleti oluşturarak başlayalım ve bu iskeleti index adında bir php dosyası oluşturup o dosyanın içine ekleyelim. Örnek olarak şöyle bir index.php oluşturabiliriz.

<!DOCTYPE html>
<html>

<head>
	<!--Bootstrap Css CDN-->
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">

	<!--jQuery CDN-->
	<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

	<!--Bootstrap Js CDN-->
	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>

	<!--twbs Pagination Js File-->
	<script src="js/jquery.twbsPagination.min.js"></script>

	<!--Main Js File-->
	<script src="js/main.js"></script>

	<style>
		.pagination { margin: 0 auto; }

		.product_img { width: 100%; height: 200px; object-fit: cover; margin-bottom: 20px; }
	</style>
</head>

<body>
	<div class="pagination">
		<div class="container">
			<div class="row products"></div>
			<div class="row pagination_indicators mt-5"></div>
		</div>
	</div>
</body>

<script>
	pagination("/pagination/api/query.php", <?php echo 2 ?>, ".pagination_indicators", ".products");
</script>

</html>

Burada index dosyamıza include ettiğimiz bazı dosyalar var. Bunlar; Bootstrap, Bootstrap.js, jQuery, bahsetmiş olduğum Pagination Plugini olan twbsPagination.min.js ve son olarak kendi oluşturmuş olduğum main.js dosyalarıdır. Bu dosyaların sonrasında eklediğim bir style kısmı var ama kısma ileride değineceğim.

Şimdi <body></body> kısmına gelelim. Burada pagination adında ana divimiz var ve bütün verilerimiz bu divin içinde olacak. Bu divin içerisinde bulunan container classdiv Bootstrap’taki row yapısını kullanabilmek için gerekli olan kapsayıcı div. Bu divin içinde de iki adet row classına sahip div var. Bunlardan ilki olan ve ayrıca products classına sahip olan divde veritabanından çekeceğimiz veriler olacak. İkinci ve pagination_indicators classına sahip olan divde ise bizim sayfa numaralarımız olacak. Ayrıca mt-5 (margin-top) classı vererek classın bootstraptaki değerine göre üstten boşluk bırakmasını sağladık.
Son olarak main.js dosyasında oluşturduğum pagination() adlı fonksiyonu parametreleriyle çağırıp işin HMTL kısmını bitirdim.

Buraya kadar görsel olarak yapmamız gerekenleri yaptık. Şimdi JavaScript kısmına geçelim ve elimizdeki verileri kullanarak jQuery ile bir post işlemi gerçekleştirip post işlemimize uygun verileri çekip ekrana bastıralım ve işi bitirelim.


Yukarıdaki kodda da gördüğünüz gibi pagination() adında bir fonksiyon var. Bu fonksiyonu oluşturmak istememin sebebi farklı sayfalarda da kullanmak istediğim zaman aynı kodları tekrar yazmak yerine bir kere yazıp ilgili yerleri parametre olarak gönderip kod ve zaman tasarrufu yapmaktır.

Şimdi main.js adında bir dosya oluşturalım ve aşağıdaki gibi bir kod yazıp bu kodları satır satır açıklayalım.

var base_url = window.location.origin; //Eg: http://localhost/

function pagination(url = "", totalPage = 1, dom = ".pagination", dom1 = ".pagination", firstBtnTxt="İlk", prevBtnTxt="Önceki", nextBtnTxt="Sonraki", lastBtnTxt="Son") {

    $(dom).twbsPagination({
        totalPages: totalPage,
        visiblePages: 5,
        first: firstBtnTxt,
        prev: prevBtnTxt,
        next: nextBtnTxt,
        last: lastBtnTxt,
        onPageClick: function(event, page) {
            $.post(base_url + url, { transaction: "pagination", page_number: page }, function(data) {
                data = JSON.parse(data);

                if (data.products.length > 0)
                    $(dom1).html(data.products);
                
                else
                    $(dom1).html("<h4 class='w-100 text-center'>" + data.message + "</h4>");
            });
        }
    });

}

Burada en başta tanımlamış olduğumuz base_url değişkeni bizim dosyamızın bulunduğu sitenin kök URL’sini bize verir. Yani localhostta çalıştığımızı ve url adresimizin “http://localhost/pagination/” olduğunu varsayacak olursak bu değişkenin değeri “http://localhost/” olacaktır. Böylece bu kodu başka bir projede kullandığınızda ya da projeyi bir sunucuya yüklediğinizde sürekli base_urlyi değiştirmek zorunda kalmayacaksınız.

Sonrasında ise pagination() fonksiyonuna geçiyoruz. Bu fonksiyona baktığınızda sekiz adet parametre olduğunu göreceksiniz. Bu parametreleri hemen alt alta sıralayıp açıklıyorum:

url: $.Post() fonksiyonunun nereye post atacağını belirtiyor.

totalPage: Toplam sayfa sayısı. Yani sayfada görünecek indicator sayısı.

dom: Indicatorlerin hangi div elemanına bastırılacağını tutuyor.

dom1: Veritabanından gelen verilerin hangi div elemanına bastırılacağını tutuyor.

firstBtnTxt: İlk sayfaya dönmek için kullanılan indicator üzerinde yazacak olan metin.

prevBtnTxt: Bir Önceki sayfaya dönmek için kullanılan indicator üzerinde yazacak olan metin.

nextBtnTxt: Bir sonraki sayfaya dönmek için kullanılan indicator üzerinde yazacak olan metin.

lastBtnTxt: Son sayfaya dönmek için kullanılan indicator üzerinde yazacak olan metin.

Yazdığım fonksiyondaki parametrelerinin hepsinde default olarak bir değer var fakat bu değerleri kullanmak istemezseniz fonksiyonu çağırırken parametre olarak istediğiniz değerleri sırasıyla yazabilirsiniz. Şimdi fonksiyonun içindeki twbsPagination() fonksiyonuna gelelim. Burada bizim parametre olarak gönderdiğimiz değerler ve ekstra olarak visiblePages adında ekstra bir properties var. visiblePages, paginationun olduğu sayfada görünecek maksimum (önceki, sonraki gibi indicatorler hariç) indicator sayısını belirtiyor. Burayı ben 5 olarak sabitledim ama isterseniz burayı da parametre olarak gönderebilirsiniz. (Bu arada eğer twbsPagination pluginini incelemek isterseniz github hesabına buradan ulaşabilirsiniz.) Indicatorlerle işimiz bittiğine göre şimdi veriyi çekip ekrana basmak kaldı. Bu işlemi fonksiyonun içinde bulunan onPageClick ile yapacağız. Bu özelliğe atanan fonksiyonun içinde event ve page adında iki adet parametre var. Biz şu an için sadece page parametresini kullanacağız. Bu paramatre bize tıkladığımız indicatorde bulunan sayfa sayısını döndürüyor. Yani ben 1 yazan indicatore tıkladığımda bu parametre bana 1 değerini vermiş oluyor ve bende bunu PHP kısmında işleyip birinci sayfadaki verileri getir diyebiliyorum. Daha sonra jQuery içerisinde bulunan $.post() fonksiyonu ile ilgili yere post işlemini gerçekleştiriyorum. Bu fonksiyonda bulunan base_url + url kısmı bana post atacağım yeri söylüyor. base_url bize ana urlyi döndürüyor demiştik. urlde bizim post atacağımız yer demiştik. Bu ikisinin birleşimi kendi oluşturduğum fonksiyondaki parametre değeri göz önünde bulundurduğumuzda bana şöyle bir çıktı veriyor: “http://localhost/pagination/api/query.php”. Benim post atacağım url bu. Url kısmından sonra post işleminde hangi verileri göndereceğimi belirliyorum. Birden fazla olduğu için süslü parantez içerisine post_degiskeni : degisken_degeri şeklinde post edeceğim verileri giriyorum. Transaction adındaki değişken PHP kısmında benim hangi işlemi yapacağımı belirtiyor. Aslında bir nevi güvenlik kontrolü de diyebiliriz. Bu değer olmadan gönderilen post işlemlerinde hata sonucu dönecek. Mesajı PHP kısmına geçtiğimizde göreceğiz. Son olarak bir fonksiyonda burada veritabanından gelen verilerimiz için yazıyoruz. Bu fonksiyonun data adında bir parametresi var, bu parametre veritabanından dönen tüm değerleri tutuyor fakat bu değerlerin JSON formatında olması lazım aksi halde consolede hata alırsınız. PHP kısmında JSON olarak döndürdüğümüz verileri JSON.parse() ile decode ediyoruz ve bu işlem bize bir array döndürüyor. Henüz oluşturmadığımız datamızı da çektiğimize göre ekrana basabiliriz 🙂
Burada kullanmış olduğum if bloğunda products etiketine sahip olan dizinin değeri sıfırdan büyükse verilerimizi ilgili div içerisine bas değilse de döndürülen message değerini ekrana bas demiş olduk. Burada yer alan <h4> etiketindeki w-100 classı “width:100%” css kodunun bootstrapteki karşılığı. Bu classı kullandığınızda boşuna css yazmanıza gerek kalmıyor. Ardından gelen “text-center” ise yazıyı ortalıyor.

Sanırım JavaScript tarafını da bitirmiş olduk. Son olarak PHP kısmını da yazıp konuyu toparlayalım.

Ana klasörümüzün içinde api adında bir klasör açıp içine db.php ve query.php adında iki PHP dosyası oluşturuyoruz.

İlk öncelikle db.php dosyasının kodlarını yazıp satır satır açıklayacağım.

//Connecting the database 
$host = "localhost"; //Server where the database is located
$db="pagination"; //The database name
$user="root"; //The Database Username
$pw=""; //The Database password
$charset="utf8"; //You required a charset for right CRUD process. you can search the internet for character sets that support your language

$pdo = new PDO("mysql:host=$host;dbname=$db;charset=$charset", $user, $pw); //Database connection
$pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );

//Bypass Access Control Allow Origin Error
header("Access-Control-Allow-Origin: Access-Control-Allow-Origin: *");
?>
$host: Veritabanımızın bulunduğu sunucu.
$db: veritabanının adı
$user: Mysql kullanıcı adı
$pw: Mysql şifresi
$charset: Veritabanındaki verilerin karakter seti (Bizdeki UTF-8)
$pdo: Yeni bir PDO oluşturur.
header("..."): Access Allow Origin hatasını bastırmaya yarar. 

Şimdi de asıl kodlarımızın olduğu query.php dosyasındaki kodlara geçip satır satır anlatayım.

<?php
include_once("db.php");

if(isset($_POST["transaction"]) && $_POST["transaction"] == "pagination")
{
	$page = ($_POST["page_number"] - 1) * 2;

	$sql = $pdo->prepare("SELECT *, (SELECT category_name FROM categories WHERE id = p.product_category) as category_name FROM products as p ORDER BY p.id DESC LIMIT :page_no, 2");
	$sql->bindParam(':page_no', $page, PDO::PARAM_INT);
	$sql->execute();

	if( $sql->rowCount() > 0 )
	{
		$template = "";

		foreach ($sql as $s){
			$template .= "
			<div class='col-sm-3'>
			  	<div class='card'>
			    	<div class='card-body'>
			    		<img src='uploads/" . $s["product_cover"] . "' class='product_img'>
			        	
			        	<h5 class='card-title'>" . $s["product_name"] . "</h5>
			        	<p class='card-text'>
			        		<span class='text-muted float-left'>" . $s["category_name"] . "</span> - 
							<span>" . number_format($s["product_price"], 2, "," , ",") . "₺</span>
			        	</p>
			        	<a href='' class='btn btn-primary'>Detay</a>
			     	</div>
			    </div>
			</div>
			";
		}

		echo json_encode( array("status" => "success", "products" => $template) );
	}
	else
	{
		echo json_encode( array("status" => "error", "message" => "Hiç Ürün Bulunamadı!") ); 
	}
}
else
{
	echo json_encode( array("status" => "error", "message" => "Tanımsız İşlem!") );
}
?>

Burada ilk öncelikle veritabanına bağlanabilmek için db.php dosyasını include yani buraya dahil ettik. Daha sonra “transaction” değerinin post edilip edilmediğine ve değerinin “pagination”a eşit olup olmadığına baktık.

Şimdi post ettiğimiz sayfa numarasından 1 çıkartıp sonucu 2 ile çarpıyoruz. 1 çıkarmamızın nedeni şu; biz sorgumuzda LIMIT kullanarak veritabanından çekeceğimiz verileri sınırlandıracağız. Bunu yapabilmek için LIMIT x,x yazacağız. Burada ilk değer tablodaki kaçıncı veriden başlayacağımızı, diğeri ise o veri dahil toplamda kaç veri çekeceğimizi belirtiyor dolayısıyla kullanıcı front tarafında 1. sayfaya tıkladığında bize 1 değeri dönecek ve 1. değer dahil toplamda 2 değer döndür diyeceğiz. Buraya kadar herşey normal görünüyor olabilir ama yazılımda index her zaman sıfırdan başladığı için bu sayıyı 1 eksiltmemiz gerekiyor. Eksilen sayıyı 2 ile çarpmamın sebebi ise şu; veritabanında fazla verim olmadığı için her sayfada 2 ürün bastırmayı düşünüyorum. Bu yüzden ben bu sayıyı 2 ile çarptım. Siz kaç ürün döndürecekseniz o sayıyla çarpın. Aklınıza hemen yukarıda açıkladığım LIMIT kısmı gelebilir. Madem orada kaç veri çekeceğimizi belirtiyoruz burada neden yine o sayıyla çarpıyoruz diyebilirsiniz. Bunu yapmamızın nedeni kaçıncı satırdan başlayarak veri çekeceğimizi belirtmek. Yani 2. sayfaya tıklandığında post edilecek olan 2 sayısını bir eksiltip 2 ile çarpıyoruz ve yine 2 sayısını elde ediyoruz. Sonra LIMIT ile diyoruz ki; bak kardeşim ben az önce 1. sayfada 2 veri çekmiştim. O yüzden sen bana yine aynı verileri döndürme, al şu 2 sayısını bana tablodaki ikinci veriden sonrakileri gönder.

Şimdi de SQL sorgumuza geçelim.

Sorgumuzda iki adet SELECT var. Birincisi PRODUCTS tablosundaki verileri çekiyor ikincisi ise çekilen ürüne ait kategoriyi CATEGORIES tablosundan çekiyor. Kodun devamında bahsetmiş olduğum gibi LIMIT kısmı yer alıyor. Buradaki 2 bahsettiğim gibi veritabanından 2’şer 2’şer veri çekmemize yarıyor. öncesinde yer alan parametre ise bizim gönderdiğimiz sayfa sayısı yani aslında tablodaki kaçıncı satırdan başlayacağını belirlediğimiz sayı.

Bu sayıyı bir alt satırda bindParam() ile gönderiyoruz. Execute() içerisinde array ile yollamak yerine neden bindParam() ile gönderdik?
Çünkü sayıyı bindParam() ile göndermediğimiz zaman parametrenin türünü integer olarak belirleyemediğimiz için PHP bize hata döndürecek. Bu yüzden bindParam() ile gönderdik ve bunu üç parametre halinde yaptık. İlk parametre iki nokta üst üsteden (:) sonra gelen belirtecimizin adı, belirtecimizin taşıyacağı değer ve parametrenin veri türü.
Daha sonra execute() ile SQL sorgusunu çalıştırıyoruz.

Artık son aşamaya geldik…

If bloğunda SQL sorgumuzun bize değer döndürüp döndürmediğini kontrol ediyoruz. Eğer bize bir değer döndürdüyse foreach() döngüsüyle verileri $template değişkenine aktarıyoruz. Burada aktardığımız veri aslında HTML çıktısı. Sadece ilgili yerlere veritabanından çektiğimiz verileri ekliyoruz.

Evet, sonunda bir yazının daha sonuna geldik.
Projenin kaynak kodlarına Github hesabımdan ulaşabilirsiniz.
İyi günler 🙂