miércoles, 20 de abril de 2011

Resumen de entradas con paginación usando JSON

Si bien es cierto que los feeds dan muchas posibilidades para leer los datos de nuestro blog usando JSON de manera relativamente simple, esto tiene dos limitaciones importantes, una objetiva y otra subjetiva. La primera es que no podemos leer TODO ya que siempre nos toparemos con que hay un máximo de 500 items; la segunda es que aún así, cargar cantidades importantes es impráctico porque el tiempo de ejecución se multiplica de manera significativa e incluso, hará que aparezcan ventanas de alerta en los navegadores si ese tiempo supera el tiempo máximo establecido por cada uno de ellos y se nos solicitará autorización para continuar o cancelar.

La combinación de ambas limitaciones es el motivo fundamental por la cual debemos usar valores pequeños; leer sólo una cantidad de entradas y mostrar eso pero claro, uno siempre quiere más; entonces se hace imprescindible que tengamos alguna clase de paginación o navegación que nos permita ir mostrando pedacitos de información e irlas cargando a medida que el usuario las solicita.

Acá es cuando podemos hacer uso del parámetro start-index que es el que nos permite solicitar datos a partir de determinada entrada; por ejemplo, esto cargaría las últimas 10 entradas:
http://miblog.blogspot.com/feeds/posts/default?start-index=1&max-results=10&orderby=published&alt=json-in-script&callback=mifuncion
y esto las siguientes diez:
http://miblog.blogspot.com/feeds/posts/default?start-index=11&max-results=10&orderby=published&alt=json-in-script&callback=mifuncion
y así, sucesivamente:
http://miblog.blogspot.com/feeds/posts/default?start-index=21&max-results=10&orderby=published&alt=json-in-script&callback=mifuncion
http://miblog.blogspot.com/feeds/posts/default?start-index=31&max-results=10&orderby=published&alt=json-in-script&callback=mifuncion
Podría, entonces, combinarse eso con alguno de los tantos scripts que hay pero sería engorroso y además, terminaríamos llenando la memoria con tanta cosa que en algún momento colapsaría el navegador o se haría imposible de usar.

Muchos podrían decir que, sin embargo, hay decenas de sitios donde parece que esto de la paginaciòn funciona ya que podemos navegar por la información sin salir de la página, vemos alguna clase de imagen de carga y avanzamos o retrocedemos pero, ese tipo de sitios suele usar PHP o Ajax, dos cosas que en Blogger nos están vedadas así que hay que buscar una solución alternativa y una de ellas es utilizar la idea de cargar scripts de manera dinámica de tal forma que si eso lo combinamos con lo inverso y podemos remover el script, solucionaríamos el problema y podríamos cargar los datos, usarlos y deshacernos de ellos.

Entonces, vamos a tratar de usar eso para mostrar TODAS las entradas del blog en una misma página sin colapsarla, agregándole la posibilidad de navegar por ellas.


Paginar: he ahí el dilema y, como esto va a ser una mezcolanza de cosas, para poder hacer una buena paginación, lo que debemos hacer es usar algo que ya exista; en este caso, uno de los tantos scripts de PHP que hay pero traducido a JavaScript.

¿Cómo paginamos algo? Necesitamos tres datos: saber la cantidad total de entradas, determinar la cantidad de entradas por página que se mostrarán y saber en qué página estamos; lo primero es un dato que tiene el mismo feed así que podemos leerlo ya que hay muchos gadgets que muestran la cantidad de entradas publicadas:
postporpagina = 50; // es un dato que definimos nosotros
cantidadposts = parseInt(json.feed.openSearch$totalResults.$t); // leemos la cantidad de entradas publicadas
cantidadpaginas = parseInt(cantidadposts / postporpagina) + 1; // y calculamos la cantidad total de páginas
El script completo se puede ver en este archivo de texto pero, de todas maneras, acá van las explicaciones: Ver/Ocultar [+]
// estos son los datos que debemos establecer
var postporpagina = 60; // la cantidad de entradas a mostrar por página
var urlsitio = "http://miblog.blogspot.com"; // la url de nuestro blog
var minpaginas = 5; // la cantidad mínima de páginas a mostrar
var maxpaginas = 10; // la cantidad máxima de páginas a mostrar
var imgxdefecto = "http://xxxxxxxxxxx"; // la dirección de la imagen por defecto a ser utilizada

var firsttime = 0; // lo usamos para detectar la primera vez que se ejecuta
var paginaactual = 1; // contendrá el número de página donde estemos
var cantidadpaginas = 0; // contendrá el número total de páginas
var cantidadposts = 0; // contendrá el número total de entradas

// la función que interpreta los feeds y que siempre es más o menos la misma
function showpageposts(json) {
var entry, posttitle, posturl, postimg;
var salida = "";

// la primera vez que se ejecuta la función, leemos la cantidad de entradas que hay y calculamos la cantidad de páginas
if(cantidadpaginas==0) {
cantidadposts = parseInt(json.feed.openSearch$totalResults.$t);
cantidadpaginas = parseInt(cantidadposts / postporpagina) + 1;
}

// leemos los datos que luego mostraremos
for (var i = 0; i < postporpagina; i++) {
if (i == json.feed.entry.length) { break; } // si no hay más, salimos
entry = json.feed.entry[i];

// el título de las entradas
posttitle = entry.title.$t;

// buscamos la url de las entradas
for (var k = 0; k < entry.link.length; k++) {
if (entry.link[k].rel == 'alternate') {
posturl = entry.link[k].href;
break;
}
}

// buscamos la imagen de las entradas o usamos la que establecimos por defecto
if ("media$thumbnail" in entry) {
postimg = entry.media$thumbnail.url;
} else {
postimg = imgxdefecto;
}

// armamos el código HTML de salida de manera similar a lo que se veía en una <a href="http://vagabundia.blogspot.com/2010/12/usar-json-para-mostrar-las-entradas-del.html" target="_blank">entrada anterior</a>
salida += "<div class='paginaposts'>";
salida += "<a href='" + posturl + "' target='_blank'><img src='" + postimg + "' /></a>";
salida += "<h6><a href='" + posturl + "' target='_blank'>" + posttitle + "</a></h6>";
salida += "</div>";
}

// listo, ahora lo mostramos
document.getElementById("resultados").innerHTML = salida;

// y ejecutamos la función para paginar
paginacion();
}

// esta es la función que muestra la paginación
function paginacion() {
contadorP = 0;
salida = "";

// dependiendo de donde estamos, ponemos un enlace para retroceder o no
if(paginaactual>1) {
salida += "<a class='antesdespues' href='javascript:incluirscript(" + parseInt(paginaactual-1) + ")'>anterior</a>";
} else {
salida += "<span class='deshabilitado'>anterior</span>";
}

// se calcula y se muestran los números de las primeras páginas, una sepación y las últimas páginas
if (cantidadpaginas<(maxpaginas+1)) {
for (contadorP = 1; contadorP <= cantidadpaginas; contadorP++){
if (contadorP==paginaactual) {
salida += "<span class='actual'>" + contadorP + "</span>";
} else {
salida += "<a href='javascript:incluirscript(" + contadorP + ")'>" + contadorP + "</a>";
}
}
} else if(cantidadpaginas>(maxpaginas-1)) {
if(paginaactual<minpaginas) {
for (contadorP=1; contadorP<(maxpaginas-2); contadorP++){
if (contadorP == paginaactual) {
salida += "<span class='actual'>" + contadorP + "</span>";
} else {
salida += "<a href='javascript:incluirscript(" + contadorP + ")'>" + contadorP + "</a>";
}
}
salida += " ... ";
salida += "<a href='javascript:incluirscript(" + parseInt(cantidadpaginas-1) + ")'>" + parseInt(cantidadpaginas-1) + "</a>";
salida += "<a href='javascript:incluirscript(" + cantidadpaginas + ")'>" + cantidadpaginas + "</a>";
} else if(cantidadpaginas-(minpaginas-1)>paginaactual&&paginaactual>(minpaginas-1)) {
salida += "<a href='javascript:incluirscript(1)'>1</a>";
salida += "<a href='javascript:incluirscript(2)'>2</a>";
salida += " ... ";
for (contadorP=paginaactual-2; contadorP<=paginaactual+2; contadorP++) {
if (contadorP==paginaactual) {
salida += "<span class='actual'>" + contadorP + "</span>";
} else {
salida += "<a href='javascript:incluirscript(" + contadorP + ")'>" + contadorP + "</a>";
}
}
salida += " ... ";
salida += "<a href='javascript:incluirscript(" + parseInt(cantidadpaginas-1) + ")'>" + parseInt(cantidadpaginas-1) + "</a>";
salida += "<a href='javascript:incluirscript(" + cantidadpaginas + ")'>" + cantidadpaginas + "</a>";
} else {
salida += "<a href='javascript:incluirscript(1)'>1</a>";
salida += "<a href='javascript:incluirscript(2)'>2</a>";
salida += " ... ";
for (contadorP=cantidadpaginas-(minpaginas+1); contadorP<=cantidadpaginas; contadorP++) {
if (contadorP==paginaactual) {
salida += "<span class='actual'>" + contadorP + "</span>";
} else {
salida += "<a href='javascript:incluirscript(" + contadorP + ")'>" + contadorP + "</a>";
}
}
}
}

// dependiendo de donde estamos, ponemos un enlace para avanzar o no
if (paginaactual<contadorP-1) {
salida += "<a class='antesdespues' href='javascript:incluirscript(" + parseInt(paginaactual+1) + ")'>siguiente</a>";
} else {
salida += "<span class='deshabilitado'>siguiente</span>";
}

// mostramos el resultado
document.getElementById("paginacion").innerHTML = salida;

// y eventualmente, podemos mostrar la cantidad total
var iniciopagina = (paginaactual * postporpagina) - (postporpagina - 1);
var finalpagina = paginaactual * postporpagina;
var totales = "Total de entradas publicadas: " + cantidadposts + " - Mostrando entradas " + iniciopagina + " a " + finalpagina;
document.getElementById("totales").innerHTML = totales;
}

// esta es la función que escribe y ejecuta el script de manera dinámica
function incluirscript(pagina) {

// salvo que sea la primera vez, antes, borramos el script
if(firsttime==1) {removerscript();}

// borramos todo y mostramos algún mensaje de carga
document.getElementById("resultados").innerHTML = "<div id='loadingscript'>cargando ...</div>";
document.getElementById("paginacion").innerHTML = "";
document.getElementById("totales").innerHTML = "";

// calculamos el valor a colocar en start-index
var iniciopagina = (pagina * postporpagina) - (postporpagina - 1);

// y armamos la url del feed
var archivo = urlsitio + "/feeds/posts/default?start-index=" + iniciopagina;
archivo += "&max-results=" + postporpagina;
archivo += "&orderby=published&alt=json-in-script&callback=showpageposts";

// cargamos y ejecutamos el script de manera dinámica
var nuevo = document.createElement('script');
nuevo.setAttribute('type', 'text/javascript');
nuevo.setAttribute('src', archivo);
nuevo.setAttribute('id', 'TEMPORAL');
document.getElementsByTagName('head')[0].appendChild(nuevo);

// establecemos los nuevos datos
firsttime = 1;
paginaactual = pagina;
}

// esta es la función que elimina un script previamente agregado utilizando su ID
function removerscript() {
var el = document.getElementById("TEMPORAL");
var padre = el.parentNode;
padre.removeChild(el);
}

// ejecutamos la función por primera vez cuando se termina de cargar nuestro sitio
onload=function() { incluirscript(1); }

Como todo script, lo colocamos entre etiquetas <script type='text/javascript'> </script> aunque imagino que lo ideal sería usar una página estática y, en ese caso si colocamos el script en la plantilla deberíamos condicionarlo o bien alojarlo en algún servidor externo como DropBox o similar.

Faltaría darle alguna clase de estilo y allí, las posibildiades son muchas; en este ejemplo, usé algo así: Ver/Ocultar [+]
<style>
#resultados { /* es el rectángulo del DIV donde se mostrarán */ }
#loadingscript { /* es el texto de "cargando" */ }

/* estas son las reglas de estilo para las entradas */
.paginaposts { /* cada rectángulo */
border: 1px solid #555;
float: left;
height: 35px;
margin: 1px;
padding: 5px;
width: 197px;
}
.paginaposts a { /* los enlaces */
color: #DDD;
display: block;
font-size: 11px;
text-decoration: none;
}
.paginaposts img { /* la imagen miniatura */
float: left;
height: 36px;
width: 36px;
}
.paginaposts h6 { /* el título de las entradas */
float: right;
height: 45px;
margin: 0;
width: 145px;
}
.paginaposts:hover { /* un efecto hover */
background-image: -moz-linear-gradient(100% 100% 90deg, #338, #33F);
background-image: -webkit-gradient(linear, left bottom, left top, from(#338), to(#33F));
filter:progid: DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr='#FF3333FF', EndColorStr='#FF333388');
border: 1px solid #33F;
}

/* estas son las reglas de estilo para la paginación inferior */
#paginacion { /* el rectángulo contenedor */
color: #BBB;
font-family: Lucida Grande;
font-size: 24px;
font-weight: bold;
height: 35px;
line-height: 28px;
padding: 20px 0;
text-align: center;
}
#paginacion span, #paginacion a { /* cada número, texto o enlace */
border: 1px solid #444;
color: #BBB;
display: inline-block;
font-family: Lucida Grande;
font-size: 24px;
font-weight: bold;
margin: 0 2px;
padding: 0 5px;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
}
#paginacion span.actual { /* resaltamos la página actual */
color: #FFF;
padding: 0 5px;
-moz-box-shadow: 0 0 30px #DDD inset;
-webkit-box-shadow: 0 0 30px #DDD inset;
box-shadow: 0 0 30px #DDD inset;
}
#paginacion a:hover { /* efecto hover sobre los enlaces */
color: #FFF;
-moz-box-shadow: 0 0 20px #666 inset;
-webkit-box-shadow: 0 0 20px #666 inset;
box-shadow: 0 0 20px #666 inset;
}
#paginacion .antesdespues { border: none; }
#paginacion .antesdespues:hover {
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
#paginacion span.deshabilitado { /* los enlaces deshabilitados */
border: none;
color: #666;
}

/* la salida de la cantidad total */
#totales {text-align:center;}
</style>

Entonces, ya tenemos todo así que en este demo online lo que he creado es una página estática y allí he puesto lo siguiente:
<style>
/* la reglas de estilo
</style>

<!-- cargo el script -->
<script src="URL_miscript.js"></script>

<!-- los divs contenedores -->

<!-- aquí se mostrarán los posts -->
<div id="resultados"> </div>

<!-- elimino flotaciones -->
<div style="clear:both;"> </div>

<!-- aquí se mostrará la paginación -->
<div id="paginacion"> </div>

<!-- aquí se mostrará el texto con los totales -->
<div id="totales"> </div>

No hay comentarios:

Publicar un comentario