Etiqueta: AJAX

  • Por qué tus páginas con DataTables tardan tanto en cargar

    Por qué tus páginas con DataTables tardan tanto en cargar

    Una página que tarda mucho en cargar es una página mala.

    Lo sabés vos y lo saben tus usuarios.

    Si tu sitio tarda más de 5 segundos en mostrarse tus visitantes se sentirán así:

    Las páginas que usan DataTables se ven bien y son muy funcionales pero pueden tardar una eternidad en desplegarse.

    Más de la mitad de los usuarios que no encuentran pronto lo que buscan se van a otro sitio.

    Y los que se quedan desconfían de la profesionalidad de quien las desarrolló.

    ¿Y quién quiere contratar a alguien que entrega trabajos de dudosa calidad?

    Más vale dedicar unos minutos a aprender cómo mejorarlas.

    Factores que afectan el tiempo de carga de tu página

    En su forma más simple, un DataTable puede cargarse usando un arreglo JavaScript.

    Basta un código como este:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="//cdn.datatables.net/1.10.24/css/jquery.dataTables.min.css">
        <title>DataTables example</title>
    </head>
    <body>
    <h1>Behold... the power of DataTables!</h1>
    <table id="theTable" class="display" style="width: 100%">
        <thead>
            <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Price</th>
            </tr>
        </thead>
        <tfoot>
            <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Price</th>
            </tr>
        </tfoot>
    </table>
    </body>
    </html>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
    <script src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.min.js"></script>
    <script type="application/javascript">
        $(document).ready( function () {
            $('#theTable').DataTable({
                'data': [
                    [1, 'Box', 10.0],
                    [2, 'Chair', 20.2]
                ],
            });
        } );
    </script>

    Por supuesto que si toda la información tuviese que estar hardcodeada no sería muy útil la aplicación, ¿cierto?

    Una versión algo más realista sería combinar este código con algo de PHP que levante información de una db, por ejemplo:

    <?php
    
    try {
        $conn = new PDO('sqlite:dt.sq3');
    } catch (PDOException $exception) {
        die($exception->getMessage());
    }
    
    $sql = "SELECT * FROM products";
    $st = $conn
    ->query($sql);
    
    if ($st) {
    $rs = $st->fetchAll(PDO::FETCH_ASSOC );
    ?>
    <script type="application/javascript">
        $(document).ready( function () {
            $('#theTable').DataTable({
                'data': [
                    <?php
                    foreach ($rs as $row) {
                        ?>
                    [ '<?php echo $row['id'];?>', '<?php echo $row['name'];?>', '<?php echo $row['price'];?>' ],
                        <?php
                    }
                    ?>
                ],
            });
        } );
    </script>
    <?php
    } else {
        var_dump($conn->errorInfo());
        die;
    }

    Hasta aquí, todo perfecto.

    El problema se presenta cuando la cantidad de registros es grande.

    La definición de grande es un poco esquiva… podés empezar a notar problemas con 500 registros pero si estás tratando con algo como 150 000 o más definitivamente tus páginas van a tardar una eternidad en mostrarse.

    Aún si tenés páginas pequeñas (Digamos, de 10 registros cada una), el tiempo total de carga no se modifica en absoluto…

    ¿Por qué sucede esto?

    Como en cualquier otro script, esto es, a grosso modo lo que ocurre:

    1. El servidor recibe la petición
    2. Se ejecuta el PHP
    3. Se realiza la consulta a la db
    4. El servidor recibe todos los registros de la tabla
    5. Se genera el HTML y el JavaScript
    6. Se envía todo al cliente
    7. El cliente interpreta el HTML y el JavaScript
    8. Se ejecuta el JavaScript y se dibuja la tabla y todo lo demás

    Como te podrás imaginar, para trabajar con los registros de la primera página no es realmente necesario tener en memoria las otras 500…

    Toda esa información que no se utilizará no hace más que sobrecargar tanto al servidor (especialmente al de bases de datos) como al cliente.

    Es más, si hay muchos usuarios accediendo a la vez el problema se va multiplicando, con lo cual la experiencia de uso es peor para todos 🙁

    Podrías intentar atacar el problema mejorar los recursos de hardware, implementando algún tipo de caché o similar y es posible que todo eso ayudara.

    Sin embargo, existe una solución mucho más al alcance de tus manos.

    La solución: Procesamiento del lado del Servidor

    Y aquí viene la genialidad de DataTables… ¡lo pensaron todo estos muchachos!

    Este plugin admite dos modos de procesamiento: Client-Side (Es decir, todo se hace en el navegador del cliente) o Server-Side (El procesamiento se hace del lado del servidor y sólo se realiza el rendering del lado del cliente).

    El modo por defecto es, por supuesto, Client-Side.

    He ahí la clave del problema.

    Trabajar con Server-Side es un poco más complicado, pero tampoco es terrible.

    Lo más importante es comprender que, en este modo, DataTables hará una llamada Ajax cada vez que se requiera re-dibujar la información de la tabla (Al paginar, ordenar, etc…).

    Para comenzar a usar este modo necesitás cambiar la configuración de tu DataTable por algo como:

    $('#theTable').DataTable( {
        serverSide: true,
        ajax: '/get_data.php'
    } );

    Claro que, a diferencia del ejemplo en que la tabla se carga por Ajax sólo al comienzo (o cuando se refresca), esta versión de get_data.php deberá ser bastante más inteligente para poder responder a las órdenes que le dará el FrontEnd.

    Qué envía el FrontEnd al BackEnd en DataTables Server-Side

    La comunicación entre FrontEnd y BackEnd se realizará a través del envío de parámetros a través de la URL de la llamada (salvo que lo cambies explícitamente).

    Esto implica que tu script php deberá recogerlos desde $_GET.

    A modo de resumen te comento los más importantes (La lista completa la podés encontrar acá):

    start: Número de registro por el que debe comenzar la paginación

    length: Cantidad de registros que se espera que devuelva el servidor. Es decir, a partir del registro start, retorná length registros (Similar al LIMIT X OFFSET Y de SQL)

    search: Arreglo que contiene los parámetros de la búsqueda realizada por el usuario. Esta búsqueda es global, es decir, aplica a todas aquellas columnas marcadas como searchable.

    • value: El texto ingresado por el usuario en el cuadro de búsqueda
    • regex: Booleano que indica si el value debe ser interpretado en forma literal o como expresión regular

    columns: Arreglo con una entrada por cada columna de la tabla.

    Cada entrada de este arreglo será, a su vez, un arreglo con las siguientes claves:

    • data: información de mapeo entre el arreglo de datos y la tabla HTML (En este ejemplo se trata del índice que vincula la columna en la visualización con el campo en la consulta: id => 0, name => 1, price => 2)
    • name: Nombre de la columna como está definido en la propiedad columns.name (En este ejemplo no lo estamos usando así que será un string vacío).
    • searchable: Booleano que indica si es posible realizar búsquedas sobre esta columna
    • orderable: Booleano que indica si es posible realizar ordenamientos basados en esta columna
    • search: Arreglo que contiene los parámetros de la búsqueda realizada por el usuario. Similar a la búsqueda global, sólo que aplicada a esta columna en particular (Un caso algo más avanzado y no muy frecuentemente utilizado).
      • value: El texto ingresado por el usuario en el cuadro de búsqueda
      • regex: Booleano que indica si el value debe ser interpretado en forma literal o como expresión regular

    order: Arreglo que especifica el criterio de ordenamiento que debe aplicarse, contendrá una entrada por cada criterio a utilizar (Para el caso de que se quieran combinar varios). Dentro de cada entrada contendrá un arreglo con dos claves:

    1. column: Número de columna por la que se quiere ordenar
    2. dir: String con la dirección del ordenamiento (asc para ascendente, desc para descendiente)

    Como te podrás imaginar, la URL de la llamada puede ser algo extensa 🙂

    ¿No me creés? ¿Qué te parece esta?

    http://localhost:8000/get_data.php?draw=1&columns%5B0%5D%5Bdata%5D=0&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=1&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=2&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&start=0&length=10&search%5Bvalue%5D=&search%5Bregex%5D=false&_=1619613946473

    Si la pasás a través de urldecode queda un poquito más entendible:

    http://localhost:8000/get_data.php?draw=1&columns[0][data]=0&columns[0][name]=&columns[0][searchable]=true&columns[0][orderable]=true&columns[0][search][value]=&columns[0][search][regex]=false&columns[1][data]=1&columns[1][name]=&columns[1][searchable]=true&columns[1][orderable]=true&columns[1][search][value]=&columns[1][search][regex]=false&columns[2][data]=2&columns[2][name]=&columns[2][searchable]=true&columns[2][orderable]=true&columns[2][search][value]=&columns[2][search][regex]=false&order[0][column]=0&order[0][dir]=asc&start=0&length=10&search[value]=&search[regex]=false&_=1619613946473

    O, todavía mejor si lo ves desde la consola del navegador:

    De modo que si del lado de tu PHP hacés un var_dump($_GET) te vas a encontrar con:

    array (size=7)
      'draw' => string '1' (length=1)
      'columns' => 
        array (size=3)
          0 => 
            array (size=5)
              'data' => string '0' (length=1)
              'name' => string '' (length=0)
              'searchable' => string 'true' (length=4)
              'orderable' => string 'true' (length=4)
              'search' => 
                array (size=2)
                  ...
          1 => 
            array (size=5)
              'data' => string '1' (length=1)
              'name' => string '' (length=0)
              'searchable' => string 'true' (length=4)
              'orderable' => string 'true' (length=4)
              'search' => 
                array (size=2)
                  ...
          2 => 
            array (size=5)
              'data' => string '2' (length=1)
              'name' => string '' (length=0)
              'searchable' => string 'true' (length=4)
              'orderable' => string 'true' (length=4)
              'search' => 
                array (size=2)
                  ...
      'order' => 
        array (size=1)
          0 => 
            array (size=2)
              'column' => string '0' (length=1)
              'dir' => string 'asc' (length=3)
      'start' => string '0' (length=1)
      'length' => string '10' (length=2)
      'search' => 
        array (size=2)
          'value' => string '' (length=0)
          'regex' => string 'false' (length=5)
      '_' => string '1619615674442' (length=13)

    Y de ahí… bueno, habrá que hacer la consulta que corresponda, por ejemplo:

    <?php
    
    try {
        $conn = new PDO('sqlite:dt.sq3');
    } catch (PDOException $exception) {
        die($exception->getMessage());
    }
    
    $orderBy = " ORDER BY ";
    foreach ($_GET['order'] as $order) {
        $orderBy .= $order['column'] + 1 . " {$order['dir']}, ";
    }
    
    $orderBy = substr($orderBy, 0, -2);
    $where = '';
    
    $columns = $_GET['columns'];
    $fields = ['id', 'name', 'price'];
    $where = '';
    
    foreach ($columns as $k => $column) {
        if ($search = $column['search']['value']) {
            $where .= $fields[$k].' = '.$search.' AND ';
        }
    }
    
    $where = substr($where, 0, -5);
    $length = $_GET['length'];
    $start = $_GET['start'];
    
    $sql = "SELECT * FROM products ".($where ? "WHERE $where " : '')."$orderBy LIMIT $length OFFSET $start";

    (Me tomé una pequeña licencia… asumí que el search no será una expresión regular… te queda como ejercicio :))

    Cómo debe responder el BackEnd al FrontEnd en DataTables Server-Side

    Por otro lado, es importante que tu php sepa qué es lo que DataTables está esperando como respuesta, de otro modo la comunicación no podrá realizarse exitosamente.

    Lo principal: DataTables espera un resultado JSON que debe contener:

    data: El arreglo de los registros que se levantaron de la base

    recordsTotal: Número total de registros que existen en la base (Sin aplicar filtros)

    recordsFiltered: Número de registros que devuelve la consulta (Una vez aplicados los filtros).

    En definitiva, el código php se completa con:

    $countSql = "SELECT count(id) as Total FROM products";
    $countSt = $conn
        ->query($countSql);
    
    $total = $countSt->fetch()['Total'];
    
    $sql = "SELECT * FROM products ".($where ?? "WHERE $where ")."$orderBy LIMIT $length OFFSET $start";
    $st = $conn
        ->query($sql);
    
    if ($st) {
        $rs = $st->fetchAll(PDO::FETCH_FUNC, fn($id, $name, $price) => [$id, $name, $price] );
    
        echo json_encode([
            'data' => $rs,
            'recordsTotal' => $total,
            'recordsFiltered' => count($rs),
        ]);
    } else {
        var_dump($conn->errorInfo());
        die;
    }

    El código completo lo podés descargar de aquí.

    DataTables ServerSide FTW!

    En este artículo aprendiste las razones por las que tu página con DataTables tarda mucho en cargar y cómo podés acelerar los tiempos de carga.

    ¿Siempre te conviene usar ServerSide? No

    Si la cantidad de registros es pequeña (O más bien diría si no hay retrasos visibles) no te conviene hacer una implementación con esta complejidad, pero en cambio, si los DataTables se están volviendo un cuello de botella SeverSide puede ser una buena solución.

    Ahora que lo sabés, andá a acelerar esa página que los usuarios están esperando!

  • Construyendo una tabla dinámica con PHP, MySQL, DataTables y Ajax

    Construyendo una tabla dinámica con PHP, MySQL, DataTables y Ajax

    ¿Cuántas veces te enfrentaste a la necesidad de mostrar información resumida en forma de tablas?

    O, puesto de otro modo: ¿qué aplicación no requiere del uso de tablas?

    Por supuesto que se puede usar HTML puro y quedará algo más o menos aceptable… pero con las herramientas (¡y los usuarios!) que tenemos hoy en día no podemos darnos el lujo de presentar una experiencia de usuario aceptable.

    Debemos dar lo mejor que podamos.

    Y lo mejor hoy en día implica interactividad.

    Los usuarios quieren poder buscar, ordenar, paginar… prácticamente tener un Excel directo en su aplicación.

    Ya te estarás imaginando el delirio de javascript que tendrías que ponerte a escribir para hacer todo esto desde cero, ¿no? De sólo pensarlo me dan ganas de retomar el curso de peluquería que dejé a la mitad 🙂

    Afortunadamente no es necesario re-inventar la rueda.

    Como podrás imaginarte, existen ya varios componentes que pueden adquirirse gratis (o a muy bajo costo) y que te ahorrarán muchísimas horas de trabajo y dolores de cabeza.

    DataTables es un plugin de jQuery que resuelve muy bien esta necesidad, desafortunadamente la documentación es algo compleja de seguir…

    Es por eso que me propuse escribir este artículo donde te presento un ejemplo completo de implementación de una tabla dinámica que utiliza DataTables combinado con un script php que actúa a modo de WebService REST para levantar los datos de una base.

    Cómo se ve un DataTable

    Empecemos por lo más importante: ¿sirve un DataTable para resolver tu necesidad?

    Una imagen vale más que mil palabras, así que te muestro el ejemplo de una tabla en la que se visualiza el inventario de un comercio: una tabla en la que se verán los datos de cada uno de los productos (Id, Nombre, Precio):

    No está mal, ¿no?

    Repasemos lo que tenemos:

    • ¿Paginación? Lo tenemos!
    • ¿Búsqueda? Lo tenemos!
    • ¿Ordenamiento? Lo tenemos!

    ¿Qué más se le puede pedir a la vida? 🙂

    Vamos ahora a ver qué hay detrás de la cortina.

    El FrontEnd

    El frontend consta de un archivo HTML que se ve así:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="//cdn.datatables.net/1.10.24/css/jquery.dataTables.min.css">
        <title>DataTables example</title>
    </head>
    <body>
    <h1>Behold... the power of DataTables!</h1>
    <table id="theTable" class="display" style="width: 100%">
        <thead>
            <tr>
                <th>id</th>
                <th>name</th>
                <th>price</th>
            </tr>
        </thead>
        <tfoot>
            <tr>
                <th>id</th>
                <th>name</th>
                <th>price</th>
            </tr>
        </tfoot>
    </table>
    </body>
    </html>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
    <script src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.min.js"></script>
    <script type="application/javascript">
        $(document).ready( function () {
            $('#theTable').DataTable({
                ajax: '/get_data.php',
            });
        } );
    </script>

    Analicémoslo un poco:

    <link rel="stylesheet" href="//cdn.datatables.net/1.10.24/css/jquery.dataTables.min.css">

    Esta parte incluye la hoja que aporta los estilos que necesita DataTables.

    La parte principal es la tabla:

    <table id="theTable" class="display" style="width: 100%">
        <thead>
            <tr>
                <th>id</th>
                <th>name</th>
                <th>price</th>
            </tr>
        </thead>
        <tfoot>
            <tr>
                <th>id</th>
                <th>name</th>
                <th>price</th>
            </tr>
        </tfoot>
    </table>

    Es importante que la tabla esté bien formada (Que tenga su sección thead, el tfoot es opcional).

    No hace falta que le pongas un tbody, lo hace todo el plugin.

    Luego tenemos el código JavaScript:

    <script type="application/javascript">
        $(document).ready( function () {
            $('#theTable').DataTable({
                ajax: '/get_data.php'
            });
        } );
    </script>

    Es muy sencillito, se trata de invocar una función cuando el documento está cargado por completo.

    Usando jQuery eso se logra con:

    $(document).ready();

    Y lo que ves dentro es el callback que se invocará cuando este evento ocurra.

    $('#theTable')

    Es la forma de seleccionar el objeto cuyo id es theTable.

    En este caso se trata, obviamente, de la tabla que definimos en el HTML.

    Y al invocar el método DataTable estamos transformando esa simple tabla en un objeto mucho más complejo.

    Por último, este método recibe un parámetro: un objeto que contiene la configuración de la nueva tabla.

    En nuestro caso se ve así:

    {
       ajax: '/get_data.php'
    }

    Lo cual significa que le estamos pasando una configuración con una única propiedad: ajax y su valor (/get_data.php) es la URL de donde se obtendrán, mediante una llamada ajax, los valores con los que se rellenará la tabla.

    El BackEnd

    Ahora que viste cómo funciona el lado cliente, demos una recorrida por la parte del php:

    <?php
    
    try {
        $conn = new PDO('mysql:host=localhost;dbname=ecommerce','root','root');
    } catch (PDOException $exception) {
        die($exception->getMessage());
    }
    
    $sql = "SELECT * FROM products";
    $st = $conn
        ->query($sql);
    
    if ($st) {
        $rs = $st->fetchAll(PDO::FETCH_FUNC, fn($id, $name, $price) => [$id, $name, $price] );
    
        echo json_encode([
            'data' => $rs,
        ]);
    } else {
        var_dump($conn->errorInfo());
        die;
    }

    En este ejemplo estoy usando PDO aunque bien podría usar MySQLi ya que se trata de una base MySQL.

    La primera parte:

    try {
        $conn = new PDO('mysql:host=localhost;dbname=ecommerce','root','root');
    } catch (PDOException $exception) {
        die($exception->getMessage());
    }

    Intenta realizar una conexión y, en caso de fallar emite un mensaje por pantalla para ayudar con el debugging.

    Con este código:

    $sql = "SELECT * FROM products";
    $st = $conn
        ->query($sql);

    Se intenta hacer la consulta de selección de los datos de los productos de la base.

    Si la consulta fue exitosa buscamos el resultado para enviarlo al FrontEnd respondiendo a la llamada Ajax:

    $rs = $st->fetchAll(PDO::FETCH_FUNC, fn($id, $name, $price) => [$id, $name, $price] );
    
    echo json_encode([
        'data' => $rs,
    ]);

    La primera línea se ve algo extraña, ¿no?

    Paso a explicar 🙂

    Un detalle que puede ahorrarte varios dolores de cabeza es comprender cómo DataTables espera recibir los resultados del webservice que le brinda los datos.

    Se trata de un objeto json con este formato:

    {
       data:[
         [ f1, f2, f3 ],
         [ f1, f2, f3 ],
         [ f1, f2, f3 ],
       ]
    }

    Donde f* corresponde a un campo del registro encontrado.

    En este caso, para que todo salga bien, el php debe responder con algo como:

    {
       data:[
         [ 1, 'Chair', 200.0 ],
         [ 2, 'Table', 500.0 ],
         [ 3, 'Shoes', 450.0 ],
       ]
    }

    Y hay que aclarar: DataTables es muy estricto con esto!

    Es por eso que usé PDO::FETCH_FUNC (Bueno, por eso y porque soy un fanático de la programación funcional) para generar, en base a los registros obtenidos, el arreglo correspondiente.

    Y luego es simple: con la función json_encode se genera el string que se requiere.

    En conclusión

    Espero que este pequeño ejemplo te haya ayudado a comprender lo que podés lograr con este plugin y un poco de php.

    La próxima vez que tengas que mostrar resultados en forma de tabla probalo y comentá cómo te fué 🙂

    Ah! Y si querés descargar el código completo podés hacerlo desde GitHub.

  • Cómo pasar una variable de JavaScript a PHP

    Cómo pasar una variable de JavaScript a PHP

    Si estás programando algún sistema web medianamente complejo, es muy probable que te hayas enfrentado a este problema alguna vez.

    En muy resumidas cuentas, lo que estás intentando hacer es algo como:

    var variable_js = 2;
    $variable_php = variable_js;

    Sería lindo que todo funcionara de esa forma, ¿no? Lamentablemente, la cosa no es tan fácil (Pero tampoco es tan difícil en realidad).

    Por qué no se puede pasar directamente un valor de Js a PHP

    Esta pregunta esconde un poco de confusión respecto de cómo funcionan las aplicaciones web. Algo parecido a lo que comentaba en este artículo.

    El punto es que PHP y JavaScript se ejecutan en lugares y momentos diferentes.

    Es como si estuvieses leyendo un libro y te encontraras con algo que no comprendés del todo y para solucionarlo le hicieras la pregunta al libro… algo no va a funcionar.

    Empecemos entonces por comprender el ciclo de vida de una aplicación web.

    Cómo es el ciclo de vida de una aplicación web

    El flujo típico de una aplicación web es el siguiente:

    1. El cliente hace un pedido al servidor (Envía un comando HTTP)
    2. El servidor lo recibe y lo analiza
      1. Si se trata de un php se lo pasa al intérprete correspondiente
      2. El intérprete procesa el código y genera una salida (Usualmente HTML)
    3. El servidor envía la salida hacia el cliente
    4. El cliente analiza la respuesta y dibuja la página
    5. El cliente ejecuta el código Js

    Entonces, si observás este ciclo, te darás cuenta que, para el momento en que la variable Js aparece en escena… php ya está haciendo cualquier otra cosa.

    Pues entonces… ¿no hay solución? ¡No tan rápido!

    Lo que necesitas es poder ejecutar JS y PHP en paralelo (es decir, en un mismo tiempo).

    Cómo enviar información de JavaScript a PHP

    La solución al problema pasa por usar un poco de imaginación… y ajax :).

    Cómo funciona una aplicación con AJAX:

    1. El cliente hace un pedido al servidor (Envía un comando HTTP)
    2. El servidor lo recibe y lo analiza
      1. Si se trata de un php se lo pasa al intérprete correspondiente
      2. El intérprete procesa el código y genera una salida (Usualmente HTML)
    3. El servidor envía la salida hacia el cliente
    4. El cliente analiza la respuesta y dibuja la página
    5. El cliente ejecuta el código Js
      1. Dentro del código se ejecuta una segunda petición al servidor (la cual tiene su propio ciclo de vida que culmina con una respuesta HTTP).
      2. El cliente recibe la respuesta del servidor
      3. El cliente actúa sobre la respuesta recibida

    Dependiendo de cuál sea tu necesidad puntual (Es decir, qué necesitas que la aplicación haga una vez reciba la variable de JS), deberás determinar si el cliente debe bloquearse (Es decir, no hacer nada hasta que PHP complete su procesamiento de la información enviada) o puede continuar con alguna otra tarea.

    Un ejemplo de esto sería:

    <?php
    $var = 1;
    ?>
    <html>
    	<body>
    		<input type="button" value="Enviar variable" id="send"/>
    	</body>
    	<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
    	<script type="text/javascript">
    	$('#send').click( function() {
    	alert('Enviando!');
    		$.ajax(
    				{
    					url: 'get_var.php?var=<?php echo $var; ?>',
    					success: function( data ) {
    						alert( 'El servidor devolvio "' + data + '"' );
    					}
    				}
    			)
    		}
    	);
    	</script>
    </html>

    Y el get_var.php:

    <?php
    echo 'Recibi '.$_GET['var'];

    Y de ese modo se puede comunicar JavaScript hacia PHP.

  • Un dashboard en tiempo real basado en PHP y Bootstrap

    Un dashboard en tiempo real basado en PHP y Bootstrap

    Algo que está muy de moda por estos días es la creación de tableros de comandos (Dashboards) que se mantengan actualizados en tiempo real.

    Si bien la definición de tiempo real es algo vaga (Formalmente se trata de sistemas en los cuales el tiempo de respuesta es crítico), hay una suerte de conocimiento en común respecto de lo que quiere decir: que los cambios se vean en forma inmediata (o casi).

    En lo que hace a aplicaciones web, de lo que se trata es de permitir al visitante recibir novedades sin tener que recargar la página.

    Para lograr este efecto se necesitan dos partes coordinadas:

    1. Una aplicación front-end con la que el usuario interactuará en forma directa
    2. Un servidor que mantenga la información actualizada en todo momento

    Un frontend para el Dashboard

    Del lado del frontend, lo que necesitaremos serán dos cosas:

    1. Una capa de presentación (HTML + CSS)
    2. Una capa de interacción con el backend (JavaScript)

    Presentación del dashboard

    Del lado de la presentación, dado que se trata de un dashboard, lo que querremos hacer es mostrar información resumida, en algún formato fácil de interpretar por una persona (Usualmente se tratará de gráficos pero podemos usar cualquier forma que nos parezca adecuada).

    Una librería que resulta muy útil para realizar este tipo de tareas es Bootstrap (Que, de paso, nos da una visualización estándar).

    En este ejemplo haré un dashboard muy sencillo, sólo a modo de ilustración, pero con los mismos principios puedes hacer otros mucho más vistosos.

    Veamos el HTML:

    <!doctype html>
    <html lang="en">
    <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
              integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    
        <title>Hello, world!</title>
    </head>
    <body>
    <h1>Hola, este es mi dashboard</h1>
    <div class="container">
        <div class="row">
            <div class="col-sm">
                <div class="card">
                    <div class="card-body">
                        Usuarios conectados en este momento: <strong><span id="connected_users">0</span></strong>
                    </div>
                </div>
            </div>
            <div class="col-sm">
                <div class="card">
                    <div class="card-body">
                        Facturación del día: <strong>$ <span id="daily_revenue">0.00</span></strong>
                    </div>
                </div>
            </div>
        </div>
    </div>
    </body>
    </html>

    Aquí lo que estoy haciendo es crear el esqueleto de mi dashboard.

    Para el ejemplo tomé dos métricas que pueden resultar interesantes:

    • Cantidad de usuarios conectados
    • Facturación del día

    La pantalla inicial se verá así:


    Si te fijas, el tag link (en la sección head) incluye el CSS que necesito para usar Bootstrap.

    Interacción con el backend del dashboard

    El siguiente paso es agregar la interacción con el backend (¡Que aún no está hecho! Cierto… no nos adelantemos, al final del artículo tendrás todo lo que buscas :).

    Lo primero es incluir la librería jQuery:

    <script
            src="https://code.jquery.com/jquery-3.4.1.min.js"
            integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
            crossorigin="anonymous"></script>

    Podría escribir todo el JavaScript necesario sin utilizarla, es cierto, pero sería muchísimo más trabajo del necesario y, honestamente, prefiero evitármelo :p

    Algo que puedes notar es que estoy incluyendo la librería desde otro servidor (code.jquery.com, un CDN).

    Y ahora me queda incluir algo de código mío (el que efectivamente interactuará con mi servidor):

    <script type="text/javascript">
        window.setInterval(function () {
            updateStats();
        }, 2000);
    
        function updateStats() {
            updateConnectedUsers();
            updateDailyRevenue();
        }
    
        function updateConnectedUsers() {
            jQuery.get(
                'get_connected_users.php',
                function (data) {
                    $('#connected_users').text(data);
                }
            );
        }
    
        function updateDailyRevenue() {
            jQuery.get(
                'get_daily_revenue.php',
                function (data) {
                    $('#daily_revenue').text(data);
                }
            );
        }
    </script>

    Aquí lo que estoy haciendo es generar un mecanismo de polling de modo que el cliente esté constantemente pidiendo actualizaciones al servidor (y reflejándolas en el HTML para el visitante).

    Muy bien, sólo nos queda ver el backend 🙂

    Un backend para el dashboard

    Del lado del backend sólo debo crear los archivos que puedan responder a las preguntas del frontend (get_connected_users.php y get_daily_revenue.php).

    En mi ejemplo haré algo de trampa, no voy a consultar a una base de datos ni nada, simplemente generaré valores aleatorios, pero claramente, en una aplicación real, deberías usar algún tipo de almacenamiento.

    get_connected_users.php:

    <?php
    
    header('Content-Type: text/javascript');
    echo rand(0, 200);

    get_daily_revenue.php:

    <?php
    
    session_start();
    
    $acummulatedRevenue = $_SESSION['acummulatedRevenue'] ?? 0;
    
    header('Content-Type: text/javascript');
    
    $_SESSION['acummulatedRevenue'] = $acummulatedRevenue + rand( 0, 100000 ) / 100;
    
    echo number_format( $_SESSION['acummulatedRevenue'], 2, ',', '.' );

    Lo importante de ambos scripts es la línea header(‘Content-Type: text/javascript’);.

    Esto es lo que hace que la llamada Ajax entienda que lo que recibe del servidor es, efectivamente, texto jSON

    Dashboards más profesionales

    Con lo que te mostré hasta aquí tienes todo lo necesario para hacer un dashboard básico (y bastante poco estético :p).

    Ciertamente existen plantillas de dashboards mucho más atractivas que puedes usar (o ser creativo y crear las tuyas!), algunas que puedes probar:

    ¡Ya estás list@ para agregar bonitos dashboards a tus aplicaciones! ¡Adelante!

  • Cómo hacer un autocomplete con PHP

    Cómo hacer un autocomplete con PHP

    Es muy común hoy en día encontrarnos con formularios que deben completarse mediante alguna opción pre-existente en el sistema.

    Si las opciones son pocas, lo más usual es utilizar un dropdown (un objeto basado en el tag select de HTML), pero si la cantidad de opciones es grande, esto puede volverse un fastidio para el usuario.

    Una forma mejor es dejar que el usuario ingrese el texto que quiera y dejar que el sistema autocomplete el resto.

    Para lograr este efecto se requiere una combinación de factores:

    1. Un servicio que pueda tomar el texto introducido por el usuario y devuelva una lista de opciones disponibles que coincidan.
    2. Una página capaz de tomar la entrada del usuario, interactuar con el servicio y presentar las opciones al usuario.

    Un servicio que busque opciones que coincidan con el texto que ingresó el usuario

    Vamos a comenzar por diseñar un script php que resuelva esta parte del problema:

    <?php
    
    $options = [
            "ActionScript",
          "AppleScript",
          "Asp",
          "BASIC",
          "C",
          "C++",
          "Clojure",
          "COBOL",
          "ColdFusion",
          "Erlang",
          "Fortran",
          "Groovy",
          "Haskell",
          "Java",
          "JavaScript",
          "Lisp",
          "Perl",
          "PHP",
          "Python",
          "Ruby",
          "Scala",
          "Scheme"
    ];
    
    if ( $term = $_GET['term'] ?? '' ) {
            $matches = array_filter( $options, function( $option ) use ( $term ) {
                    return strpos( strtolower($option), $term ) !== false;
            } );
    
            header( 'Content-Type: text/javascript' );
            echo json_encode( array_values($matches) );
    }

    A este pequeño script le llamaremos get_matches.php.

    Un formulario que use el autocomplete

    La segunda parte de la solución es un formulario capaz de tomar la entrada del usuario, enviársela al servicio y actuar según la respuesta recibida.

    Se verá de este modo:

    <html>
    <body>
    <form>
    	<p><label for="language">Language</label><input type="text" name="language" id="language"/></p>
    	<p><select id="multiple" style="display: none;" size="10"/></p>
    </form>
    </body>
    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
      <script>
    	$( "#language" ).on( 'input', function() {
    			$('#multiple').hide();
    			$.ajax(
    				{
    					url: 'get_matches.php?term=' + $(this).val(),
    					success: function( data ) {
    						if ( data.length == 1 ) {
    							$('#language').val( data[0] );
    						} else {
    							if ( data.length > 1 ) {
    								$("#multiple").find('option').remove();
    								data.forEach( function(e) {
    									$("#multiple").append("<option>" + e + "</option>");
    								});
    								$("#multiple").prop( 'size', data.length );
    								$("#multiple").show();
    							}
    						} 
    					},
    					dataType: 'json',
    				}
    			);
    		}
    	);
    	
    	$( "#multiple" ).click( function() {
    		$('#language').val( $(this).val() );
    		$('#multiple').hide();
    	}
    	);
      </script>
    </html>

    Para verlo todo funcionando basta con iniciar el servidor incorporado a PHP:

    php -S localhost:8000

    Y abrir un navegador en localhost:8000 para ver:

    Y luego, a medida que vayamos escribiendo veremos aparecer opciones:

    Y cuando hayamos escrito una opción que acota la lista a un solo candidato automáticamente se llenará el campo.

    Algunas notas

    Habrás notado que, en realidad, la magia se hace del lado del frontend (¡Mucho javascipt!).

    Lo que se podría (y probablemente debería!) modificarse es el modo de conseguir las opciones desde PHP.

    En este ejemplo usamos un arreglo estático, pero en un caso más real lo haríamos a través de alguna consulta a una base de datos, algo como:

    SELECT name FROM languages WHERE name LIKE '%TERM%';

    Reemplazando TERM por lo que haya ingresado el usuario.

    Esto funcionará bien mientras la tabla sobre la que queramos buscar no sea extremadamente grande (Unos pocos miles de registros debería soportar sin mucho problema)… si la base es más grande o queremos hacer búsquedas menos exactas deberemos utilizar alguna otra herramienta como Solr, Sphinx o ElasticSearch.