Guía 29 – informar a través de barra de progreso

En este ejercicio intento resolver 2 situaciones que normalmente se producen en proyectos que suelen ser complejos, a saber:

  1. Definir una «barra de progresión» para indicar cuanto del trabajo lleva hecho el usuario que está utilizando la aplicación.
  2. Informar que el sistema está haciendo una actualización y que el usuario debe esperar a que termine para continuar.

El caso de uso típico de la (1) opción es cuando estamos haciendo una encuesta o simplemente, cuando estamos cumplimentando datos en varias etapas. La barra informa de lo que llevamos hecho y/o de lo que falta para terminar.

En el caso de la (2) opción es cuando estamos haciendo un proceso de actualización que no es inmediato y debemos indicar al usuario que se está produciendo una actualización y debe esperar a que termine.

DEMO: https://fhumanes.com/progress_bar

Solución técnica (Caso 1)

El aspecto del ejemplo es:

El ejemplo consta de 3 Pasos, por ello, en cada uno aplica un 33% de progreso.

Para mostrar la barra he utilizado la librería: https://www.jqueryscript.net/loading/jQuery-Plugin-To-Manipulate-Bootstrap-Progress-Bars.html

Cada uno de los pasos es una página EDIT de la misma tabla en PHPRunner. Para mostrar la barra utilizo la solución de SNIPPET de PHPRunner y el código es:

$page = $_SESSION['page'];
$index = ($page)*33;
$html = <<<EOT
<script src="Bootstrap-Progress-Bars/bootstrap-brogressbar-manager.min.js"></script>
<div id="progress_bar"></div>
<script>
myBar = $('#progress_bar').progressbarManager({      
 // whether to log to console
  debug : false ,
  // for the default bar 
  currentValue : 0 ,
  // for the default bar 
  totalValue : 100 ,
  // for the default bar 
  style : 'warning' ,
  // for default bar
  animate : true ,
  // for default bar
  stripe : true ,
  // Whether to create default bar
  addDefaultBar : true
});

myBar.setValue($index); // Update the progress bar and set to a new value
</script>
EOT;
echo $html;

Como podréis comprobar, es muy fácil de implementar. He utilizado varias páginas porque así puedo validar los campos de cada paso. Esta solución no funcionará con los «step»  de PHPRunner, ya que entre una solapa y otra no se comunica con el servidor. Se debería programar de otra forma.

Solución técnica (Caso 2)

Para este caso he programado 3 casos:

  • (a) Ejecución de un proceso «pesado» cuando iniciamos una página. En este caso cuando accedemos a LIST.
  • (b) Ejecución de un proceso «pesado» cuando actualizamos un campo de la pantalla. En este caso cuando utilizamos la acción EDIT.
  • (c) Ejecución de un proceso «pesado» cuando añadimos un nuevo registro. En este caso cuando utilizamos la acción ADD.

Para el caso (a)  utilizamos la librería de JavaScript https://www.jqueryscript.net/loading/jQuery-Plugin-For-Bootstrap-Loading-Modal-With-Progress-Bar-waitingFor.html

Se puede personalizar todo lo que sale en la ventana modal.

También se ha utilizado un SNIPPET con este código:

$html = <<<EOT
<script src="bootstrap-waitingfor/bootstrap-waitingfor.js"></script>
<script>

$.ajax({
    type: "POST", //we are using POST method to submit the data to the server side
    url: './ajax_control.php', // get the route value
    beforeSend: function () {//We add this before send to disable the button once we submit it so that we prevent the multiple click
      waitingDialog.show('5 seg. ---- Loading Something...',{
        // if the option is set to boolean false, it will hide the header and "message" will be set in a paragraph above the progress bar.
        // When headerText is a not-empty string, "message" becomes a content above the progress bar and headerText string will be set as a text inside the H3;
        headerText: 'Header Custom',
        // this will generate a heading corresponding to the size number
        headerSize: 3,
        // extra class(es) for the header tag
        headerClass: '',
        // bootstrap postfix for dialog size, e.g. "sm", "m"
        dialogSize: 'm',
        // bootstrap postfix for progress bar type, e.g. "success", "warning";
        progressType: '',
        // determines the tag of the content element
        contentElement: 'p',
        // extra class(es) for the content tag
        contentClass: 'content'
      });
    },
    success: function (response) {//once the request successfully process to the server side it will return result here
      // waitingDialog.hide(); 
    },
    complete: function() {
      waitingDialog.hide();
    },
    error: function (XMLHttpRequest, textStatus, errorThrown) {
    // You can put something here if there is an error from submitted request
    }
 });
</script>

EOT;
echo $html;

Para los casos (b) y (c) he utilizado la solución definida en esta URL, adaptando la imagen GIF a la que me ha gustado (hay cientos de GIF, por lo que puedes sustituir la seleccionada por la que te guste)
https://www.tutorialrepublic.com/faq/how-to-show-loading-spinner-in-jquery.php

En ambos casos he utilizado el SNIPPET:

<?php
$html = <<<EOT
<style>
.overlay{
    display: none;
    position: fixed;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: 999;
    background: rgba(255,255,255,0.8) url("calculated.gif") center no-repeat;

}
/* Turn off scrollbar when body element has the loading class */
body.loading{
    overflow: hidden;   
}
/* Make spinner image visible when body element has the loading class */
body.loading .overlay{
    display: block;
}
</style>
Aquí el GIF  /* Este texto hay que quitarlo | This text must be removed */
<div class="overlay"></div>

</script>

EOT;

echo $html;

La imagen de cómo queda es:

En el caso (b) he programado el evento para ejecutarlo:

var ctrl_text = Runner.getControl(pageid, 'text');

ctrl_text.on('change', function(e){
  $.get("ajax_control.php", function(data){			
        // $("body").html(data);
    }); 
  $("body").addClass("loading"); // Carga del gif

  $(document).on({
     ajaxStart: function(){
        // $("body").addClass("loading"); 
     },
     ajaxStop: function(){ 
        $("body").removeClass("loading"); 
     }    
  });
});

En el caso (c) es un poco más complejo ya que tal y como lo he programado se lanzan 2 hilos de ejecución en paralelo:

  1. La actualización de la acción ADD.
  2. El proceso pesado por el que informamos al usuario.

Lo que tenemos que asegurar es que el (1) termine para ejecutar el (2) y cuando termine este, continuará el (1) para indicar cuál es la siguiente página según se haya indicado en PHPRunner.

Para la sincronización se utiliza la SESIÓN y una variable de sesión. Al gestionarse las variables de sesión a través de un fichero, PHP hace un bloqueo del mismo para que ningún proceso destruya información de otro proceso. Se utiliza este mecanismo para sincronizar los procesos.

Aspecto de la aplicación:

Programación del evento que lanza el bloqueo:

var ctrl_text = Runner.getControl(pageid, 'text');

this.on('beforeSave', function(formObj, fieldControlsArr, pageObj){

  $.get("ajax_control_2.php", function(data){			
        // $("body").html(data);
    }); 
  $("body").addClass("loading"); // Carga del gif

  $(document).on({
     ajaxStart: function(){
        // $("body").addClass("loading"); 
     },
     ajaxStop: function(){ 
        $("body").removeClass("loading"); 
     }    
  });
});

Proceso «ajax_control_2.php» que utilizamos en el ejemplo:

<?php
require_once("include/dbcommon.php"); // DataBase PHPRunner

while (true)  { // Control to wait for the ADD operation has finished
 if (isset($_SESSION['SaveTable2'])) { // 
      break;	
  } else {
      session_write_close();   // Close SESSION
      sleep(1); 								//  Sleep for 1 seconds
      session_start();					// Open SESSION
  }
}


sleep(5); // Test

unset($_SESSION['SaveTable2']); // Delete SESSION

echo "1;'';Bye Bye";
?>

Observar en línea 4 el bucle del WHILE para controlar que el proceso del ADD ya ha terminado de actualizar la Base de Datos.

Programación hecha en el evento «After Record Added» :

$_SESSION['SaveTable2'] = ''; //  Control process Ajax Refresh

while (true)  { //  Control of whether AJAX process has finished
 if (!isset($_SESSION['SaveTable2'])) { //
      break;	
  } else {
      session_write_close();   // Close SESSION
      sleep(1); 		//  Sleep for 1 seconds
      session_start();	// Open SESSION
  }
}

Aquí, en línea 3, aparece el WHILE para esperar a que el proceso «pesado» termine de ejecutarse.

Espero que os guste y os sea útil. Como siempre, si tenéis dudas y necesitáis una explicación contactar conmigo a través de mi email [email protected]

Os dejo los fuentes del ejemplo para que lo podáis instalar en vuestros equipos y hacer todas las pruebas que consideréis oportunas.

Adjuntos

Archivo Tamaño de archivo Descargas
zip PHPRunner 10.4 y backup de base de datos 1 MB 490

Blog personal para facilitar soporte gratuito a usuarios de React y PHPRunner