Quando si realizzano applicazioni gestionali in PHP, cosa c'è di più noioso della gestione della paginazione dell'output? Poche cose, credo...
Per ridurre lo stress da routine noiose ho pensato di realizzare questo piccolo script, sulla falsa riga della paginazione di Facebook, che grazie a
jQuery,
Ajax e
PHP, permette di raggiungere in modo semplice e, spero, pulito lo scopo prefissato.
Il tutto si realizza utilizzando due distinti moduli:
- il primo è quello necessario per la visualizzazione dei risultati, non necessita di codice PHP al suo interno, utilizza la libreria jQuery ed una manciata di righe Javascript per interfacciarsi con l'altro modulo;
- il secondo è un modulo PHP che si occupa della estrazione e preparazione dei dati da visualizzare.
Iniziamo a vedere il primo modulo:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="it" lang="it" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
.align-center {text-align: center;}
#data-div {margin: auto; padding: 10px;}
#EOJ {display: none; width: 100%; margin: auto;}
</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
<script>
// Check presenza dell'oggetto nella viewport attuale
// ATTENZIONE: verifica solo lo scroll verticale!!!
function IsInVerticalViewport(Obj) {
vTop = $(window).scrollTop();
vBot = vTop + $(window).height();
objTop = $(Obj.selector).position().top;
objHeight = $(Obj.selector).height();
return ((objTop < vBot) && (objTop + objHeight > vTop));
};
$(window).scroll(function() {
if (IsInVerticalViewport($("#loading")))
if ($("#loading").is(":visible"))
LoadDataBlock(200);
});
$(document).ready(function() {
$(window).scroll();
})
function LoadDataBlock(blocksize) {
$.ajax({
url:"index.ajax.php",
type: "POST",
dataType: "json",
data: {BlockSize: blocksize},
success:function(result){
$("div#data-div").append(result.data);
if(result.eod) {
$("div#loading").hide();
$("div#EOJ").show();
}
},
error: function(Request, Status, Errors){
$("div#data-div").html("Chiamata fallita: "+Status+" "+Errors);
}
});
}
</script>
</head>
<body>
<div class="align-center">
<h1>Esempio di paginazione progressiva (tipo Facebook)</h1>
Il DIV seguente funge da contenitore dei dati passati dalla routine PHP
</div>
<div id="data-div"></div>
<div class="align-center"id="loading">
<img src=loading.gif>
</div>
<div class="align-center" id="EOJ">
<h2>Ricerca completata</h2>
</div>
</body>
</html>
Il funzionamento di questo script è molto semplice, mi limito a verificare tramite la funzione
IsInVerticalViewport se il DIV
loading è presente o meno nella porzione di pagina visibile all'utente e questo test lo eseguo ad ogni operazione di scroll della pagina stessa utilizzando il blocco
$(window),scroll(function() {... Al suo interno verifico che se il DIV è presente nella viewport attuale e se è visible: se le condizioni sono soddisfatte eseguo una chiamata alla routine
LoadDataBlock con un parametro che analizzeremo in seguito.
Dal momento che alla prima esecuzione il DIV molto probabilmente sarà già visibile nella viewport, non essendo presente ancora alcun dato da visualizzare, chiamo manualmente la routine di scroll. Utilizzo
$(document).ready per essere sicuro che questa porzione di codice sia eseguita solo a DOM interamente caricato.
L'ultima funzione da prendere in esame è
LoadDataBlock che si occupa di interfacciare il file html con lo script PHP di elaborazione dati. Esegue una chiamata tramite Ajax al modulo PHP utilizzando il metodo POST, gli passa il parametro ricevuto (blocksize) e resta in attesa del risultato inviato dal modulo PHP (in questo caso index.ajax.php) codificato in formato
JSON e composto da una parte dati (
result.data) ed una parte return-code (
result.eod).
Se la chiamata ha avuto successo come prima cosa appende al DIV
data-div i dati ricevuti in
result.data spostando di fatto il DIV
loading fuori dalla viewport corrente(vedi precisazione paragrafo successivo), quindi verifica il valore di
result.eod che utilizzo come segnalatore di fine dati; se
result.eod=true nasconde il div
loading e rende visibile il div
EOJ che serve a segnalare all'utente che i dati sono terminati.
Il parametro passato alla routine
LoadDataBlock serve a comunicare a PHP quanti dati dovrà restituire. Nell'esempio proposto 200 equivale a 200 righe di dati, sufficienti a spostare fuori dalla viewport il DIV
loading ma non troppe da causare un rallentamento in fase di ricezione dati; mi sembra chiaro che questo parametro vada modificato sulla base dell'output generato. Il valore minimo è 1.
Questo è lo script PHP:
<?php
//*******************************************************
// Blocco di codice necessario per implementare nelle
// versioni di PHP antecedenti la 5.2.0 le funzioni
// json_encode() e json_decode() (qui uso solo la prima)
//
// Grazie a Michal Migurski <mike-json@teczno.com>
//*******************************************************
require_once('JSON.php');
if( !function_exists('json_encode') ) {
function json_encode($data) {
$json = new Services_JSON();
return( $json->encode($data) );
}
}
// Start della sessione
session_name("paging");
session_start();
// Stringa di output
$output = "";
// Permette di comuicare al chiamante
// che i dati sono finiti
$eod = false;
// Dimensione del blocco da restituire
if (isset($_POST["blocksize"]))
$lines = $_POST["blocksize"];
else
$lines = 100;
// Punto di partenza
if (isset($_SESSION["start"]))
$from = $_SESSION["start"];
else
$from = 0;
// Questa è la sezione che genera i dati
// che saranno restituiti al chiamante
// dopo la codifica in formato JSON
for ($i = $from; $i < $from+$lines; $i++) {
//Simulazione di fine dati
if ($i > 987) {
$eod=true;
break;
}
$output .= "La radice quadrata di $i è ".sqrt($i)."<br>";
}
// Aggiorno la sessione con il puntatore corretto
// per la prossima esecuzione
if ($eod)
session_destroy();
else
$_SESSION["start"] = $from + $lines;
// Codifica output in JSON e restituzione al chiamante
echo json_encode(array('data' => $output, 'eod' => $eod));
?>
A causa della mancanza di
json_encode() e
json_decode() nelle versioni di PHP antecedenti la 5.2.0, ho dovuto utilizzare
una classe di Michal Migurski per implementare queste funzioni.
Per poter tenere traccia in modo semplice della progressione della paginazione ho utilizzato le sessioni.
Inizio aprendo la sessione, inizializzo alcune variabili, controllo se il parametro che mi aspetto sia stato passato, quindi verifico da che punto devo iniziare ad estrarre i dati; se la sessione non è inizializzata forzo la variabile
$from a 0, altrimenti la valorizzo con il contenuto di
$_SESSION["start"].
Per non utilizzare database, nell'esempio proposto, i dati consistono in semplici righe contenenti un numero progressivo e la sua radice quadrata (vedi ciclo
For). Ovviamente utilizzando basi dati sarebbe il caso di sostituire il ciclo
For con un ciclo
While di lettura del database tramite una query tipo "
Select [campi] from [database] where [condizioni] limit $from , $lines".
A questo punto accumulo i dati, già formattati in formato pronto per la visualizzazione, in una variabile (
$output), valorizzo la variabile
$eod a
true qualora il ciclo di lettura dovesse finire prima del previsto quindi, all'uscita del ciclo, verifico il suo valore: se
true distruggo la sessione, se
false aggiorno la sessione con il puntatore al nuovo inizio.
Notare che nel ciclo
For simulo la condizione di fine dati fermandomi al raggiungimento di una certa soglia.
Nell'ultima riga eseguo l'encoding in formato JSON della stringa
$output ed il gioco è fatto.
L'esempio qui proposto può essere scaricato da
questo link, mentre una versione "Live" la potete provare
qui.