En este ejercicio intento resolver 2 situaciones que normalmente se producen en proyectos que suelen ser complejos, a saber:
- Definir una «barra de progresión» para indicar cuanto del trabajo lleva hecho el usuario que está utilizando la aplicación.
- 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:
- La actualización de la acción ADD.
- 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.