Barras de progreso con jQuery, Ajax y PHP

Las barras de progreso son elementos gráficos que están presentes en casi todas las aplicaciones que realizan operaciones largas tales como cargar grandes archivos, procesamiento de imágenes o procesamiento de vídeos.

Estas barras se utilizan para indicar al usuario que el ordenador está pensando o está ocupado haciendo alguna tarea. Lo mismo puede ocurrir en la carga de páginas web, aplicaciones o cualquier funcionalidad que requiera un proceso.

En este tutorial vamos a explicar un ejemplo práctico que te va a resultar muy útil o te va a servir como referencia para aplicar a tus aplicaciones o páginas web con conseguir el efecto de las barras de progreso funcionando paralelamente con el proceso que se esté ejecutando. Vas a conocer una forma para mostrar barras de progreso de un proceso real utilizando jQuery, Ajax, PHP y MySQLi.

Para la parte visual de las barras de progreso nos vamos a apoyar en la librería más popular para la interfaz gráfica, Bootstrap 4. Gracias a esta tecnología, el aspecto que se puede conseguir es:

En el ejemplo en funcionamiento se actualiza el estado de más de 7.000 comentarios de una tabla que tenemos en una base de datos para hacer pruebas.

Precisamente, en la base de datos crearemos una tabla para guardar el proceso o progreso de la ejecución.

Donde podemos observar los siguientes campos:

  • id_process: Para identificar el proceso. Identificador único.
  • executed: Guardaremos el número de elementos procesados.
  • total: Guardaremos el total de elementos que se deben procesar.
  • percentage: Contiene el porcentaje actual de la ejecución en curso.
  • execute_time: Guardamos el tiempo transcurrido.
  • date_add: Fecha en la que empieza el proceso.
  • date_upd: Fecha en la que se ha actualizado el proceso.

Crea un archivo config.php para conectar con tu base de datos. Algo como lo siguiente te puede servir:

<?php
define('DB_SERVER', 'localhost');
define('DB_SERVER_USERNAME', 'USERNAME');
define('DB_SERVER_PASSWORD', 'PASSWORD');
define('DB_DATABASE', 'DATABASE');
 
$connexion = new mysqli(DB_SERVER, DB_SERVER_USERNAME, DB_SERVER_PASSWORD, DB_DATABASE);
?>

En la parte de arriba del archivo principal del código, agrega el código para conectar con tu base de datos, obtener el número total de comentarios e inicialización del proceso:

<?php 
require('config.php');
$result = $connexion->query('SELECT COUNT(*) as total_comments FROM comments');
$row = $result->fetch_assoc();
$num_total_rows = $row['total_comments'];
 
$update_process = 'UPDATE process SET total = '.$num_total_rows.', percentage = 0, executed = 0, execute_time = "", date_add = now() WHERE id_process = 1';
$connexion->query($update_process);
?>

En el cuerpo de la página o dentro de la etiqueta <body> tenemos un formulario con el siguiente contenido:

<form id="start_form" action="#" method="post">
    <input type="hidden" id="total_comments" name="total_comments" value="<?php echo $num_total_rows; ?>" />
    <div class="form-group">
        <label for="batch">Número de elementos que se procesan en cada iteración</label>
        <select class="form-control" id="batch" name="batch">
            <?php
            //divisors
            for($i = 1; $i < $num_total_rows; $i ++) {
                if ($num_total_rows % $i == 0) {
                    echo '<option value="'.$i.'">'.$i.'</option>';
                }
            }
            ?>
        </select>
    </div>
    <div class="form-group">
        <a href="#" class="btn btn-primary" onclick="executeProcess(0);return false;">
            <i class="fa fa-eye"></i> Ejecutar
        </a>
    </div>
</form>

Mostramos un selector de los divisores del número de comentarios que se van a procesar para poder ejecutar el proceso por un lote divisor del número de elementos a procesar. De esta forma, nos aseguramos de procesar todos los datos y correr la barra de progreso hasta el 100%.

Un botón que permite ejecutar el proceso también está presente en este formulario. Fíjate bien en el atributo onclick del botón o enlace. Estamos llamando a la función principal del proceso con un parámetro inicial = 0.

En el cuerpo de la página o dentro del <body> también debes agregar:

<div id="sending" class="col-lg-12" style="display:none;">
    <h3>Procesando...</h3>
    <div class="progress">
        <div class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" data-progress="0" style="width: 0%;">
            0%
        </div>
    </div>
    <div class="counter-sending">
        (<span id="done">0</span>/<span id="total">0</span>)
    </div>
 
    <div class="execute-time-content">
        Tiempo transcurrido: <span class="execute-time">0 segundos</span>
    </div>
 
    <div class="end-process" style="display:none;">
        <div class="alert alert-success">El proceso ha sido completado. <a href="https://www.jose-aguilar.com/scripts/jquery/ajax-progress-bar/">Probar otra vez</a></div>
    </div>    
</div>
</div>

El contenedor con identificador «sending» inicialmente está oculto pero, se habilita una vez se pulsa el botón «Ejecutar» con el objetivo principal de mostrar la barra de progreso, el número de elementos procesados y el tiempo transcurrido.

El contenedor con clase «execute-time-content» muestra el tiempo transcurrido.

Y, finalmente, el contenedor con clase «end-process» muestra una alerta de confirmación de proceso completado. Este contenedor se mostrará cuando la barra de progreso haya llegado a su valor máximo = 100.

Para implementar la barra de progreso asociada al proceso, en la cabecera o dentro de la etiqueta <head> del archivo HTML, agrega lo siguiente código JavaScript:

<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<script type="text/javascript">
function executeProcess(offset, batch = false) {
    if (batch == false) {
        batch = parseInt($('#batch').val());
    } else {
        batch = parseInt(batch);
    }
 
    if (offset == 0) {
        $('#start_form').hide();
        $('#sending').show();
        $('#sended').text(0);
        $('#total').text($('#total_comments').val());
 
        //reset progress bar
        $('.progress-bar').css('width', '0%');
        $('.progress-bar').text('0%');
        $('.progress-bar').attr('data-progress', '0');
    }
 
    $.ajax({ 
        type: 'POST',
        dataType: "json",
        url : "process.php", 
        data: {
            id_process: 1,
            offset: offset,
            batch: batch
        },
        success: function(response) {
            $('.progress-bar').css('width', response.percentage+'%');
            $('.progress-bar').text(response.percentage+'%');
            $('.progress-bar').attr('data-progress', response.percentage);
 
            $('#done').text(response.executed);
            $('.execute-time').text(response.execute_time);
 
            if (response.percentage == 100) {
                $('.end-process').show();
            } else {
                var newOffset = offset + batch;
 
                executeProcess(newOffset, batch);
            }
        },
        error: function(XMLHttpRequest, textStatus, errorThrown) {
            if (textStatus == 'parsererror') {
                textStatus = 'Technical error: Unexpected response returned by server. Sending stopped.';
            }
            alert(textStatus);
       }
    });
}
</script>

 

Hemos creado la función executeProcess(), que es la función que se ejecuta al pulsar el botón «Ejecutar». Es la función principal, la que se encarga de actualizar la barra de progreso, mostrar el número de elementos procesados y mostrar el tiempo transcurrido.

Es una función recursiva que se llama a si misma en el success de la llamada Ajax con 2 parámetros importantes:

  • offset: Número de comienzo para la consulta limitada de comentarios.
  • batch: Número de elementos del lote. Más bien el número de comentarios que se actualizan en cada llamada Ajax.

En la llamada Ajax estamos ejecutando el archivo process.php, donde está el código que ejecuta el proceso por partes:

<?php
require_once('config.php');
 
$offset = $_POST['offset'];
$batch = $_POST['batch'];
 
$result = $connexion->query(
    'SELECT * FROM comments ORDER BY date_added DESC LIMIT '.$offset.', '.$batch
);
if ($result->num_rows > 0) {
    while ($row_comments = $result->fetch_assoc()) {
        //proceso
        $update_comment = "UPDATE comments SET approved = 1 WHERE comment_id = ".$row_comments['comment_id'];
        $connexion->query($update_comment);
 
        $update_process = 'UPDATE process SET executed = executed + 1 WHERE id_process = 1';
        $connexion->query($update_process);
        //sleep(3);
    }
 
    $result_process = $connexion->query('SELECT * FROM process WHERE id_process = 1');
    $row_process = $result_process->fetch_assoc();
 
    $percentage = round(($row_process['executed'] * 100) / $row_process['total'], 2);
 
    $date_add = new DateTime($row_process['date_add']);
    $date_upd = new DateTime($row_process['date_upd']);
    $diff = $date_add->diff($date_upd);
 
    $execute_time = '';
 
    if ($diff->days > 0) {
        $execute_time .= $diff->days.' dias';
    }
    if ($diff->h > 0) {
        $execute_time .= ' '.$diff->h.' horas';
    }
    if ($diff->i > 0) {
        $execute_time .= ' '.$diff->i.' minutos';
    }
 
    if ($diff->s > 1) {
        $execute_time .= ' '.$diff->s.' segundos';
    } else {
        $execute_time .= ' 1 segundo';
    }
 
    $update_process = 'UPDATE process SET percentage = '.$percentage.', execute_time = "'.(string)$execute_time.'" WHERE id_process = 1';
    $connexion->query($update_process);
 
    $row = array(
        'executed' => $row_process['executed'],
        'total' => $row_process['total'],
        'percentage' => round($percentage, 0),
        'execute_time' => $execute_time
    );
    die(json_encode($row));
}
?>

Como puedes ver, el archivo process.php se encarga de recuperar de la base de datos los elementos a procesar de forma limitada para ejecutar el proceso que sea. En el caso del ejemplo, tan solo estamos actualizando o validando los comentarios.

Después de eso, se actualiza la tabla auxiliar que tenemos para guardar el estado del progreso y mediante la llamada Ajax de tipo JSON retornamos un array con la información que nos interesa. Recuerda que el el success del Ajax se vuelve a llamar recursivamente a la función executeProcess() hasta llegar al porcentaje del 100%.

Este código PHP puede ser más limpio si se trabaja con clases o con una programación más orientada a objetos.

Ver demo Descargar

Autor
Escrito por Jose Aguilar - Director ejecutivo y tecnológico en JA Modules. Experto programador PrestaShop y Experto programador WordPress.
Te ha servido? Valora esta entrada!
1 estrella2 estrellas3 estrellas4 estrellas5 estrellas (6 votos, promedio: 5,00 de 5)
Cargando…
Comparte en las redes sociales

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Ver más sobre