Tablas dinámicas con AJAX

Tutorial para hacer tablas dinámicas con PHP y AJAX.

turner

archivado en: PHP/AJAX / 15 diciembre, 2012

OJO: Este post está obsoleto. Recomiendo en su lugar emplear el plug-in DataTable

 

Hace un par de meses preparé unas tablas dinámicas en AJAX para una web. El mecanismo es muy sencillo, solo hay que preparar la query al vuelo, pero el proceso es bastante enrevesado, por lo que trataré de explicarlo de a poquitos. En cualquier caso, que nadie se asuste. Insisto,  lo que tenemos que preparar no es más que una miserable query.

Para que se pueda llevar a la práctica con facilidad, he usado una bbdd que hay entre las herramientas para desarrolladores de mysql, una que e se llama sakila (descargar), aquí podéis ver una demo de cómo quedará al final y aquí podéis descargaros los archivos desde github, lo que facilitará seguir esta entrada.

A. Primeros pasos

1. Conexion

Lo primero es preparar los datos de la conexión en un archivo que podéis llamar como más os guste. De aquí en adelante, lo denominaré mi_conexion.php.

  1. <?php
  2. $dbhost = 'host';
  3. $dbuser = 'usuario';
  4. $dbpass = 'contraseña';
  5. $dbname = 'nombre de la base de datos';
  6. $link_id = new mysqli($dbhost,$dbuser,$dbpass,$dbname);
  7. if ($link_id ->connect_error) {
  8. echo "Error de Connexion ($link_id->connect_errno)
  9. $link_id->connect_error\n";
  10. exit;
  11. }
  12. ?>

2. Página principal

En la página principal, que he llamado tablas.php, preparamos el tinglado habitual de HTML5.

Es muy importante definir un div con un id que necesitaremos  luego. Para el ejemplo lo he denominado «muestra_contenido_ajax». En este div cargaremos el resultado de la consulta.

  1. !DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>
  6. tablas dinámicas
  7. </title>
  8. <style type="text/css">
  9. </style>
  10. </head>
  11. <body>
  12. <h3>Actores</h3>
  13. <div id="muestra_contenido_ajax">
  14. </div> 
  15. </body>
  16. </html>

3. Proceso PHP

En un tercer archivo, que aquí he llamado op_tablas.php, preparamos la consulta y la mostramos. De momento, vamos a seleccionar solo dos campos, nombre y apellido, first_name y last_name, de la tabla actor, los ordenamos por el apellido y limitamos la consulta a 25 resultados.

  1. <?php
  2. require_once('zap_conexion.php');
  3. $resultado = $link_id->query("SELECT first_name, last_name FROM actor ORDER by last_name LIMIT 25"); ?>
  4. <table class="tabla_actores">
  5. <thead><tr>
  6. <th>Apellido</th>
  7. <th>Nombre</th>
  8. </tr></thead>
  9. <?php
  10. while ($filas=$resultado->fetch_assoc()) { ?>
  11. <tr>
  12. <td><?php echo $filas['first_name'] ?></td>
  13. <td><?php echo $filas['last_name'] ?></td>
  14. </tr>
  15. <?php } ?>
  16. </table>

4. Funciones Ajax 

A continuación preparamos en un archivo aparte el objeto XMLHttpRequest() que servirá de puente entre el JavaScript y el PHP. Podemos llamar al archivo funciones_ajax.js (a estas alturas, carece de sentido hacerlo compatible con IE6).

  1. function load_tablas (){
  2. xmlhttp=new XMLHttpRequest();
  3. xmlhttp.onreadystatechange=function() {
  4. if (xmlhttp.readyState==4 && xmlhttp.status==200) {
  5. document.getElementById("muestra_contenido_ajax").innerHTML=xmlhttp.responseText;
  6. }
  7. }
  8. xmlhttp.open("GET","op_tablas.php", true);
  9. xmlhttp.send();
  10. }

Si no sabemos AJAX da igual, cortamos y pegamos el código anterior en un archivo que se llame funciones_ajax.js. Lo único en que debemos fijarnos es que en la línea 5 se hace referencia al id del div donde se cargará el archivo op_tablas.php, el cual se llama en la línea 8.

5. Ensamblar las piezas

Ya solo hace falta ensamblar todo. Para eso incluimos en el head de tablas.php el script de AJAX:

  • <script src="funciones_ajax.js"></script>

Y en el body llamamos a la función cuando se cargue (onload):

  • <body onload="load_tablas()">

B. Order By

1. Flechas y CSS

Lo primero que vamos a cambiar dinámicamente es el orden en que se muestran los resultados. Es decir, que cuando se pulse una flechica hacia arriba se ordenen de forma ascendente y viceversa. Añadimos las flechas a cada encabezado de la tabla en op_tablas.php:

  • <th><div class="flechas">&#9650; &#9660; </div> <br />Apellido</th>
  • <th><div class="flechas">&#9650; &#9660; </div> <br />Nombre</th>

Y, ya que estamos, en tablas.php añadimos un par de estilos, que en la vida real, por limpieza, habría que trasladar a un documento .css y enlazar desde aquí. Son 4 chorradas, pues no quiero sobrecargar este tutorial de CSS, lo que nos interesa es el PHP y el JavaScript.

  1. <style type="text/css">
  2. body {
  3. margin: 5%;
  4. font-family: verdana, arial;
  5. font-size: 100%;
  6. }
  7. .tabla_actores {
  8. font-size: 0.8em;
  9. border-spacing:0;
  10. border-collapse: collapse;
  11. }
  12. .tabla_actores th {
  13. border: 1px solid #666;
  14. background-color: #999;
  15. color: #fff;
  16. padding: 0.4em;
  17. width: 150px;
  18. }
  19. .tabla_actores td {
  20. border: 1px solid #ccc;
  21. margin:0;
  22. padding: 0.4em;
  23. }
  24. .tabla_actores tr:nth-child(2n+1) {
  25. background-color: #ededed;
  26. }
  27. .flechas {
  28. cursor: pointer;
  29. }
  30. </style>

2. Envío...

En cada una de las flechas, añadimos un span que permita incluir una llamada a la función load_tablas(), a la cual pasamos como argumento la cadena de texto que nos servirá para ordenar por un criterio u otro:

  1. <thead><tr>
  2. <th><div class="flechas">
  3. <span onclick="load_tablas('last_name ASC')"> &#9650;</span>
  4. <span onclick="load_tablas('last_name DESC')">&#9660; </span>
  5. </div>
  6. <br />
  7. Apellido</th>
  8. <th><div class="flechas">
  9. <span onclick="load_tablas('first_name ASC')"> &#9650;</span>
  10. <span onclick="load_tablas('first_name DESC')">&#9660; </span>
  11. </div>
  12. <br />
  13. Nombre
  14. </th>
  15. </tr></thead>

Ese argumento lo recogemos en la función AJAX que nos sirve de puente con PHP y lo pasamos al archivo op_tablas.php.

  1. function load_tablas (orderby){
  2. xmlhttp=new XMLHttpRequest();
  3. xmlhttp.onreadystatechange=function() {
  4. if (xmlhttp.readyState==4 && xmlhttp.status==200) {
  5. document.getElementById("muestra_contenido_ajax").innerHTML=xmlhttp.responseText;
  6. }
  7. }
  8. xmlhttp.open("GET","op_tablas.php?orderby="+orderby, true);
  9. xmlhttp.send();
  10. }

Ya solo falta inicializar la función cuando se cargue la página con un valor por defecto.

  • <body onload="load_tablas('last_name ASC')">

3. ...Respuesta

En op_tablas.php recogemos la variable, antes de la conexión, y la insertamos en la query.

  • // recogemos las variables
  • if (isset($_REQUEST['orderby']) && $_REQUEST['orderby'] !="") {
  • $orderby = strip_tags(sqlite_escape_string($_REQUEST['orderby']));
  • }
  • require_once('zap_conexion.php');
  • // armamos la query
  • $resultado = $link_id->query("SELECT first_name, last_name FROM actor ORDER BY ".$orderby." LIMIT 25");

Y ya con esto podemos ordenar la tabla de forma ascendente o descendente dinámicamente.

Vamos a complicarlo un poco más  😆

B. Paginación

1. El listado

La paginación es la parte más enrevesada de todo el proceso. Aquí jugamos con LIMIT, la cláusula de la query que delimita cuántos resultados mostrar. Recordemos que LIMIT admite dos argumentos numéricos. En el primero el número de filas que debe saltarse, es decir, desde qué fila debe empezar a seleccionar y el segundo cuántas selecciona:

  • LIMIT filas que se salta, filas que selecciona;

Por lo tanto, la dificultad estriba en delimitar el número de filas a saltar y luego «recordar» cuántas se ha saltado.

Para definir las filas a mostrar establecemos un parámetro (el segundo) en op_tablas.php y lo multiplicamos por una variable que recoge el número de filas mostradas, como veremos más adelante.

  • $cantidad=12;
  • $inicial = $pg * $cantidad;

Esos son los dos valores que le pasaremos a la query:

  • $resultado = $link_id->query("SELECT first_name, last_name FROM actor ORDER BY ".$orderby." LIMIT $inicial, $cantidad");

Vamos ahora con el listado. Lo primero es saber cuántas filas hay en total, lo cual podemos hacer seleccionando todas las filas y contándolas con el método num_rows (esto se podría optimizar haciendo solo una vez el cálculo fuera de este archivo y recuperando la variable global, pero para no liar más esta entrada lo dejo así):

  1. // sacamos todos los resultados que hay en total
  2. $resultado_paginacion = $link_id->query("SELECT first_name FROM actor");
  3. // los contamos
  4. $row_cnt = $resultado_paginacion->num_rows;
  5. // los dividimos por el número de resultados a mostrar por página y los redondeamos
  6. $separador = ceil($row_cnt/$cantidad);

$row-cont, por lo tanto, es una variable con el número total de filas que hay en la consulta inicial ($resultado). Ahora tenemos que dividirla por el número de filas que se deben mostrar en cada consulta, es decir, por el número indicado en $cantidad, y ese número lo redondeamos con ceil y lo almacenamos en una variable que he llamado $separador, pero que cada cual denomine como le venga en gana.

  • $separador = ceil($row_cnt/$cantidad);

A continuación, chequeamos si hay más resultados que la cantidad a mostrar por página, pues de lo contrario no tiene sentido que añadamos ninguna paginación:

  • if ($row_cnt>$cantidad) {
  • // aquí va el tinglado
  • }

En ese caso, armamos la lista, diferenciando por CSS en qué página estamos.

  1. // comprobamos que hay más resultados en la tabla que los mostrados por página
  2. if ($row_cnt>$cantidad) { ?>
  3. <ul class="lista_paginacion">
  4. <!-- primer número cuando la página sea cero -->
  5. <li <?php if ($pg == 0) { ?> class="lista_numero_marcado" <?php } ?> >
  6. <span onclick="load_tablas('<?php echo $orderby; ?>', 0)"> 1 </span></li>
  7. <!-- los demás -->
  8. <?php for($i=1; $i<$separador; $i++) { ?>
  9. <li <?php if ($i == $pg) { ?> class="lista_numero_marcado" <?php } ?> >
  10. <span onclick="load_tablas('<?php echo $orderby; ?>',<?php echo $i; ?>)">
  11. <?php echo $i+1; ?>
  12. </span></li>
  13. <?php } // cierra el for ?>
  14. </ul>
  15. <?php } // cierra el if inicial ?>

Fijaos que en las líneas 7 y 11 volvemos a llamar a la función load_tablas, pero ahora le pasamos dos parámetros. En el primero recogemos la variable $orderby, que es la que hemos definido antes para saber cuál debe ser el orden. En el segundo, para la primera posición, es decir, el número 1 del listado, un cero; y en los demás casos el valor del contador.

2. CSS

Añadimos un par de estilos en las CSS:

  1. ul.lista_paginacion li {
  2. list-style-type: none;
  3. display:inline-block;
  4. width: 1.5em;
  5. text-align: center;
  6. cursor: pointer;
  7. border: 1px solid #ccc;
  8. padding: 0.2em;
  9. }
  10. .lista_numero_marcado {
  11. color:#c00;
  12. font-weight: 900;
  13. }

3. Enviar y recoger la paginación

Ahora ya solo nos falta incluir el segundo parámetro en los demás sitios donde llamamos a la función load_tablas. En el body, inicializamos con 0:

  • <body onload="load_tablas('last_name ASC', 0)">

Y en las flechas recogemos el valor de la variable $pg.

  1. <th><div class="flechas">
  2. <span onclick="load_tablas('last_name ASC',<?php echo $pg; ?>)"> &#9650;</span>
  3. <span onclick="load_tablas('last_name DESC',<?php echo $pg; ?>)">&#9660; </span>
  4. </div>
  5. <br />
  6. Apellido</th>
  7. <th><div class="flechas">
  8. <span onclick="load_tablas('first_name ASC',<?php echo $pg; ?>)"> &#9650;</span>
  9. <span onclick="load_tablas('first_name DESC',<?php echo $pg; ?>)">&#9660; </span>
  10. </div>
  11. <br />
  12. Nombre
  13. </th>

4. Últimos detalles

Ya solo falta incorporar el segundo parámetro a la función:

  1. function load_tablas (orderby, paginacion) {
  2. xmlhttp=new XMLHttpRequest();
  3. xmlhttp.onreadystatechange=function() {
  4. if (xmlhttp.readyState==4 && xmlhttp.status==200) {
  5. document.getElementById("muestra_contenido_ajax").innerHTML=xmlhttp.responseText;
  6. }
  7. }
  8. xmlhttp.open("GET","op_tablas.php?orderby="+orderby+"&paginacion="+paginacion, true);
  9. xmlhttp.send();
  10. }

Y recuperarlo en op_tablas.php:

  • if (isset($_REQUEST['paginacion']) && $_REQUEST['paginacion'] !="") {
  • $pg = strip_tags(sqlite_escape_string($_REQUEST['paginacion']));
  • }

C. Seguridad

El proceso se puede complicar todo lo que se necesite. Por ejemplo, podríamos definir dinámicamente el WHERE a partir de algún filtrado de los usuarios o añadir más campos.... en fin, lo que sea, pero esta entrada ya es bastante espesa como para liarla más. Si acaso otro día hago una segunda parte.

Lo único importante es comprender el mecanismo:

  1. En las funciones se recogen y se envían todos los parámetros que se van a necesitar.
  2. En el Body, onload, inicializamos los parámetros.
  3. La función AJAX es la que sirve de puente entre el JavaScript del lado del cliente y el PHP del lado del servidor.
  4. La consulta se puede generar dinámicamente incluyendo las variables que envía la función AJAX.

Para terminar esta entrada, unos detallines sobre seguridad. El momento más vulnerable de la aplicación es cuando se recogen las variables, que es la puerta por la que se pueden colar basurillas varias. Así siempre nos cercioraremos de quitarles cualquier código HTML que puedan traer con strip_tags y, además, para prevenir inyecciones de SQL, las pasamos por un sqlite_escape_string, que se cepilla las comillas.

Pero podemos hacer aún algo más, como utilizar las funciones de manejo de variables para estar seguros de que contienen lo correcto. Por ejemplo, sabemos que la variable paginacion solo puede ser un número, por lo que nos cercioramos de que así sea con is_numeric y, en caso contrario, salimos del flujo del código con un exit.

  • if (isset($_REQUEST['paginacion']) && $_REQUEST['paginacion'] !="") {
  • if (is_numeric($_REQUEST['paginacion'])==false) { ?>
  • Algo va mal
  • <?php exit;
  • } else {
  • $pg = strip_tags(sqlite_escape_string($_REQUEST['paginacion']));
  • }
  • }

Para validar el segundo parámetro se pueden hacer varias cosas. La más sencilla, como son muy pocos datos, es definir un array con los valores posibles que puede tener, los chequeamos con in_array() y si no está entre esos, lanzamos un exit. Además, como sabemos cuánto debe medir la cadena larga, podríamos contar los caracteres y si los sobrepasa dar un error.... en fin, cualquier detalle que permita validar los datos.

  • $check_orderby = array("last_name ASC", "last_name DESC", "first_name ASC", "first_name DESC");
  • if (isset($_REQUEST['orderby']) && $_REQUEST['orderby'] !="") {
  • if (in_array($_REQUEST['orderby'], $check_orderby) OR strlen($_REQUEST['orderby']) > 15 ) {
  • $orderby = strip_tags(sqlite_escape_string($_REQUEST['orderby']));
  • } else { ?>
  • Algo va mal
  • <?php exit;
  • }
  • }

ADENDA

Respuesta a Btoro (añadiendo el WHERE).

Entonces, la clave está en ir construyendo la query dinámicamente. Ahora solo lo hace con el ASC /DESC y el LIMIT, pero lo que planteas necesita que trabajemos también el WHERE.

Entonces, bastaría con añadir una tercera variable al tinglado.

Por ejemplo, imagina que quieres filtrar en función de 3 parámetros (los dos campos, el apellido o el nombre), que aquí pongo como si fueran un menú, pero que podrías hacer con select...

Observa que ahora le pasamos 3 parámetros al objeto HR

<span class="opcion_barra_navegacion_inferior_principal"><a onclick="javascript: load_tablas(0, 0, 0)" >All</a></span>
<span class="opcion_barra_navegacion_inferior_principal"><a onclick="javascript: load_tablas(1, 0, 0)" >Apellidos</a></span>
<span class="opcion_barra_navegacion_inferior_principal"><a onclick="javascript: load_tablas(2, 0, 0)" >Nombre</a></span>

Luego, en la página que procesamos la respuesta, una vez recogida la variable, le asignamos las columnas que sean: if (tal == 0), tal = "last_name, name"; si 1, "last_name"; si 2, "name".

Metemos esa variable en la query: SELECT $tal FROM...

Además, cuando armes la tabla, en función de esa variable, añades la columna o no.

Y, por último, en los demás sitios donde estás generando la tabla dinámica, añades la variable que has creado:

<span onclick="load_tablas(<?php echo $tal ?>, 'first_name ASC',<?php echo $pg; ?>)"> &#9650;</span>

Bueno, no sé si así contado deprisa ha quedado claro, pero es que voy voladísimo.

ADENDA 2

1. En el controlador (ie, en index)

  • form id="formulario_search" name="formulario_search">
  • <input type="text" size="30" id="formulario_search_cas" name="formulario_search_cas" value="Search by Title" onFocus="vaciar2(this, 'Search by Title')" onBlur="verificar_vacio2(this, 'Search by Title')" onkeyup="showResult(this.value)" class="blue"/>
  • <div id="livesearch" class="form_search_admin"></div>
  • </form>

2. El HR

  • function showResult(str)
  • {
  • if (str.length==0)
  • {
  • document.getElementById("livesearch").innerHTML="";
  • document.getElementById("livesearch").style.border="0px";
  • return;
  • }
  • if (window.XMLHttpRequest)
  • {// IE7+, Firefox, Chrome, Opera, Safari
  • xmlhttp=new XMLHttpRequest();
  • }
  • else
  • {// IE6, IE5
  • xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  • }
  • xmlhttp.onreadystatechange=function()
  • {
  • if (xmlhttp.readyState==4 && xmlhttp.status==200)
  • {
  • document.getElementById("livesearch").innerHTML=xmlhttp.responseText;
  • document.getElementById("livesearch").style.border="1px solid #A5ACB2";
  • }
  • }
  • xmlhttp.open("GET","search.php?q="+str,true);
  • xmlhttp.send();
  • }

3. El proceso PHP 

  • if (isset($_REQUEST['q']) && $_REQUEST['q'] !="") {
  • $video_title = strip_tags(addslashes($_REQUEST['q']));
  • } ?>
  • <div class='form_search_admin_content'>
  • <?php if (strlen($video_title) > 0){
  • $resultado_search = $link_id->query("SELECT video_title, id_videos FROM videos_gen WHERE video_title LIKE '%$video_title%' ORDER by video_title");
  • $row_cnt = $resultado_search->num_rows;
  • if ($row_cnt==0) { ?>
  • <span class ='red'> no macht </span>
  • <?php } else {
  • while ($filas=$resultado_search->fetch_assoc()) {
  • echo "<a href='movies?id_video=".$filas['id_videos']."' target='_parent'>";
  • echo $filas['video_title'];
  • echo "</a></br>";
  • }
  • }
  • } ?>
  • </div>

|| Tags: ,

valoración de los lectores sobre Tablas dinámicas con AJAX

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración negativa
  • 3.5 sobre 5 (26 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

7 respuestas a “Tablas dinámicas con AJAX

  1. BTORO el dijo:

    Hola,

    me ha parecido muy interesante, pero justo me ha faltado también el filtrado por alguno de los campos de la tabla.

    He intentado adaptarlo a lo que tú has puesto poniendo un input para filtrar en una de las columnas, pero cuando escribo una letra y se me va a filtrar, pierdo el foco del input y no consigo volver a ponerlo.

    ¿Alguna idea¿

    Gracias
    btoro

  2. marcos el dijo:

    Hola Btoro, gracias.

    Sí, tienes razón, pero es que como ya era un toxo-post no quise complicarlo más.

    Te respondo en el post. Si no queda claro, avisa y te envío unas cosas por correo.

  3. BTORO el dijo:

    Hola,

    Ante todo, muchísimas gracias por responder tan rápido.

    Pero creo que me he explicado mal. No me refiero a ver qué campos mostrar sino a filtrar por valores de los campos.

    Por ejemplo, yo muestro dos columnas, nombre y apellidos y quiero que en la cabecera de la tabla además de la ordenación, me aparezca un input donde puedo ir escribiendo el filtro que quiero aplicar a ese campo.

    Conforme voy escribiendo las letras, va filtrando los resultados que cumplen con el patrón.

    He avanzado desde ayer bastante en el tema, pero uno de los problemas que tengo es que pierdo el foco del input cada vez que meto una letra y no es operativo.

    No sé si me he explicado bien. ¿Alguna idea?

    Gracias
    Belén

  4. marcos el dijo:

    un segundo, que voy a pegar el código que hice para una web en otra adenda para que quede más claro…

  5. marcos el dijo:

    Bueno, no sé si queda claro con la adenda dos.

    Entonces las claves son:

    a) llamar a la función HR onkeyup:

    onkeyup=”showResult(this.value)”

    b) Usar un WHERE LIKE en el el proceso PHP, donde el WHERE LIKE es el valor introducido en el formulario.