How to use Progressbar with Ajax and PHP?

Asked

Viewed 16,259 times

16

I found some examples where the developer implements a setInterval trying to predict the import time and in the complete ajax put 100% making that bar Progress get "a bit buggy" or jump from nowhere to the end.

Example:

var progressBar = $(".progress-bar");

setInterval(addProgress, 1000);

function addProgress() {
  var width = progressBar.width() + 15;
  progressBar.width(width);
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

<div class="container">
  <br>
  <h4 class="text-center">Importando arquivos</h4>
  <div class="container">
    <div class="progress">
      <div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100">
        <span class="sr-only"></span>
      </div>
    </div>
  </div>
</div>

My doubts:

How will I predict how long the requisition will take ?

Is there any way to make onprogress to pick up the progress of the request ?

If yes this break in multiple requests can affect traffic( why depending on the size of the task will take and cost several requests)?

Is there any other recommended way to make progressBar?

Updating:

I’m performing the import of some information I picked up in various files .csv.

In another system that uses Delphi, they can determine the time run to perform progressionBar counting how many lines have the file.

They make two progress bars, one general, which will be the sum of all lines in the files, and one which is the sum of lines in the current file.

I need to reproduce the same behavior, but I don’t know how to reproduce it using Ajax and PHP, would like an analytical explanation if possible.

  • 1

    I was going to offer a reward for this question, I need an urgent solution, I am using a method mequetrefe that gives to shame rsrsrs. I’m also testing the other answers!

  • @Júniormoreira and the author, updated my answer to demonstrate a functional example, however it is easy to notice from my explanation that it is a complicated process and may not be worth the effort.

  • Have you been able to implement the load bar? Have further questions?

  • @Marcoauréliodeleu not yet, I’m trying to ensure that all my import is working so that later I worry about the progressibar part, I thought it would be fast but are several validations

4 answers

15


Fundamentally, your problem lies in determining how long it will take the server to respond to your request. The problem with the solutions that depend on Xmlhttprequest is that you can only measure the upload time of the request, that is, how long it would take your computer to upload the file. csv to the server (for example).

How will I predict how long the requisition will take ?

Your best chance is by trial and error in determining an approximable formula f(x) = t where x would be the number of lines in the file and t the time it would take your server to process such a request.

Even so, you would be subject to need at least 2 separate requests for PHP to answer the number of lines in the file before you start processing it OR do this count in Javascript (both bad solutions).

Still in that line of reasoning, if one day when you switch server¹, your formula may be declared wrong in case of incompatibility. Processors, Memories or hard disk with greater efficiency will take different times to process the same algorithm. The same goes for upgrading server applications, OS update or PHP versions that influence performance.

Note¹: In case of shared server rents, the machine can be changed without you being notified.

Is there any way to make onprogress to pick up the progress of the request ?

There’s a difference between sending the request and response time. It can even be relatively easy to calculate the size (in bytes) of your request and determine when it will end, but this only takes into account the first step of the process (sending the request). The response time cannot be discovered at the time the request occurs.

If yes this break in multiple requests can affect traffic( why depending on the size of the task will take and cost several requests)?

I don’t quite understand what you mean here, but you are referring to sending 1 line at a time (by ajax request) just for the pleasure of building a Progress bar faithful, I strongly recommend that you do not. This could generate a very high number of resource consumption totally unnecessary. Keep in mind that once you upload an entire file, there has been processing on the client machine until the request reaches the server, processing the web server and then initializing your application in PHP. If each line in the file is handled in a separate request, you will be using your web server to initialize the same PHP application several times unnecessarily.

Is there any other recommended way to make progressBar?

There is an option theoretically valid which suggests multiple requests for ask the server about the progress of the main request. However, I believe such an option is only possible if your main request is long enough. The process would consist of something that would respect some rules:

  • Executes the main request.
  • Update an information shareable on the progress of the request from time to time.
  • Create a loop in the frontend that executes a request specific to each x times
  • This secondary request would be able to read the same information that the main request is updating from time to time.

The server consumption would be depending on the variable x that you would determine to ask the server for progress. The response of such a request would be able to offer you information as reliable as you could measure progress within your main code (main request).


Update

Using the repository as a basis jQuery-Ajax-Progress for englercj, I was able to implement the concept I presented as theoretical. What should be taken into consideration are the following items:

  • Each time you "interrupt" the main process to save the progress (in this case, setProgress function within the loop), your main request generates extra processing and takes longer.
  • Each request the client makes to the server affects the performance of your server.
  • This is a highly simple example for the sole purpose of teaching, as I use the server file system to save progress (hard disk access is very slow!)

You can clone my repository on https://github.com/deleugpn/jquery-ajax-progress or follow the following snippets (or check an online demo here http://solucoesideais.com.br/dev/progressbar/demo.html)

Note: If your request php status. is awaiting the end of Progress.php in order to be triggered, your problem may be in your browser not authorizing the call.

Progress.php

This is the file that contains your main request.

ini_set('max_execution_time', 60);

// The bigger this number, the longer the request will take
$max = 100000000;

/**
 * Store the current progress in a file to be read by status.php
 * @param $progress
 */
function setProgress($progress) {
    $file = __DIR__ . '/p';
    if (!is_file($file))
        touch($file);
    file_put_contents($file, $progress);
}

// Loop to delay the request a lot
for ($i = 0; $i < $max; $i++) {

    /**
     * Each 100'000 runs, the progress will be "recorded"
     * This is where you should learn what number is good for your loop that won't affect
     * the requests' performance.
     */
    if ($i % 100000 === 0) {
        setProgress(($i * 100) / $max);
    }
}

echo true;

php status.

This is the file that will tell you how your main request is going.

// Make a request to another domain so browser doesn't block you
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');

if (is_file(__DIR__ . '/p')) {
    echo file_get_contents(__DIR__ . '/p'); // Note: if the file is busy (being rewritten, this will result in nothing.
} else {
    echo 0;
}

demo html.

This is the progress bar demo file.

<!DOCTYPE html>
<html>
<head>
    <title>Ajax Progress</title>

    <!-- Meta -->
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

    <meta name="description" content="">
    <meta name="viewport" content="width=device-width">

    <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"/>

    <!-- Google CDN -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js"></script>
    <link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/black-tie/jquery-ui.css"/>

    <!--<script src="js/jquery.ajax-progress.js"></script>-->
    <script>
        var request = {
            checkStatus: function () {
                $.ajax({
                    method: 'GET',
                    url: 'server/status.php',
                    dataType: 'json',
                    success: function (data) {
                        if(data)
                            request.setStatus(data);
                    }
                });
            },
            setStatus: function (status) {
                $('#prog')
                        .progressbar('option', 'value', status)
                        .children('.ui-progressbar-value')
                        .html(status.toPrecision(3) + '%')
                        .css('display', 'block');
            },
            _interval: null,
            clearInterval: function () {
                clearInterval(request._interval);
            }

        };
        $(function () {
            $('#prog').progressbar({value: 0});

            request._interval = setInterval(request.checkStatus, 1000);

            $.ajax({
                method: 'GET',
                url: 'server/progress.php',
                dataType: 'json',
                success: function () {
                    request.clearInterval();
                    request.setStatus(100);
                },
                error: function () {
                    request.setStatus(0);
                    request.clearInterval();
                    console.log('AWWW! Error!!');
                }
            });
        });
    </script>
</head>
<body>
<div id="prog"></div>
</body>
</html>
  • +1,Excellent answer, I’m testing possible solution!

  • I will pass to my programmer PHP test! + 1

  • @Juniors as I specified in the answer, is not a very recommended option. It is very laborious and dude (in computational resources) and is only useful if the request takes at least more than 10 seconds of server processing.

  • @Marcoauréliodeleu Since you do not recommend the use of progressbar, which you would use to inform the progress of the import to the user ?

  • @Gabrielrodrigues The times I worked with importing files, usually it was me who performed them and not an end user. I have never developed an end-user import module. If your files take less than 15~20 seconds to be processed, I suggest simply using a "Loader" as Android uses, something that is loading 'infinitely'. If the time exceeds that average of 20 seconds, maybe you should simply upload the file, save it to disk (on the server) and warn your user that the import will happen in the next hour.

  • Then you put a Crontab on the server to run hourly (for example) or every 5 minutes, with the responsibility of going through all the files upados, process and remove them. Then send an email to the user notifying him that the import has been completed.

  • Now, if the import needs to be immediate and will take more than 5~10 minutes, this technique posted here can be useful as long as you do periodic checks without harming much the process as a whole.

Show 2 more comments

5

Following a possible solution, this solution is based on the event subscribe progress of XMLHttpRequest used by the helper $.ajax jQuery.

var progressBar = $(".progress-bar");

function addProgress(percentual) {
  progressBar.width(percentual);
};

$.ajax({
  xhr: function() {
    var xhr = new window.XMLHttpRequest();
    xhr.upload.addEventListener("progress", function(evt) {
      if (evt.lengthComputable) {
        var percentComplete = evt.loaded / evt.total;
        addProgress(percentComplete * 100 + '%');
      }
    }, false);
    xhr.addEventListener("progress", function(evt) {
      if (evt.lengthComputable) {
        var percentComplete = evt.loaded / evt.total;
        addProgress((percentComplete * 100) + '%');
      }
    }, false);
    return xhr;
  },
  type: 'POST', //Or 'GET',
  url: "http://posttestserver.com/post.php",
  data: {
    post: true,
    postfor: 'fun'
  },
  success: function(data) {
    alert(data);
  }
});
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

<div class="container">
  <br>
  <h4 class="text-center">Importando arquivos</h4>
  <div class="container">
    <div class="progress">
      <div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100">
        <span class="sr-only"></span>
      </div>
    </div>
  </div>
</div>

follows an example: http://jsfiddle.net/xv2whsb/

  • Have you tested it? I tested it here and the evt does not return me the lengthComputable, let alone the evt.total to carry out the division by evt.loaded appearing.

  • 2

    @Highlander added a jsfiddle to the answer

5

Example where the percentage accompanies the file upload:

Necessary to include these files:

<script src="../Content/js/jquery.min.js"></script>
<script src="../Content/js/jquery.form.js"></script>  
<script src="../Content/CSS/bootstrap/js/bootstrap.min.js"></script>

Script:

<script>
    $(document).ready(function() {
        var progressbar     = $('#progressbar');
        var statustxt       = $('#statustxt');
        var submitbutton    = $("#submit");
        var myform          = $("#upload");
        var output          = $("#output");
        var completed       = '0%';
        $(myform).ajaxForm({
            beforeSend: function() {
                submitbutton.attr('disabled', '');  //dasabilita o o botao submit
                statustxt.empty();
                progressbar.width(completed);
                statustxt.html(completed); //set status text
                statustxt.css('color','#000');
            },
            uploadProgress: function(event, position, total, percentComplete) {
                progressbar.width(percentComplete + '%')
                statustxt.html(percentComplete + '%');
                if(percentComplete>50)
                {
                    statustxt.css('color','#000');
                }

            },
            complete: function(response) {
                myform.resetForm();  // reseta o formulario
                submitbutton.removeAttr('disabled'); // habilita o submit
                window.location.replace("pagina.php?id=<?=$id?>");    
            }
        });
    });
</script>

Progress bar (of bootstrap):

 <div class="col-md-6">
       <label>Arquivo: <b class="red">*</b>  </label>
       <input type="file" class="form-control" name="arquivo" value="" required >
 </div>

 <div class="col-md-12">
    <div class="progress" style="display:none;" id="progress">
        <div class="progress-bar" id="progressbar" role="progressbar"  >
            <div id="statustxt">0%</div>
         </div>
    </div>
</div>

3

Analyzing the presented follow-up model of a detailed request in the @Marco Aurélio Deleu.

I found that there were some drawbacks that he himself mentioned. In some import tests, more than 300 requests were executed to check the status of a main request.

I searched by other methods and found one that follows a lower resource consumption, and allows easier control not needing a additional file to keep track of the status, without also simulating the setInterval to analyze x times what is the percentage of progress.

This technique briefly allows with the use of buffering the return of data (percentage of progress) while running the main process, exempting the need for requests in parallel.

So come on, my idea is to use buffer(ob_start(),ob_flush(),flush(),ob_end_flush) of PHP with onprogress ajax, below an example simulating an import with Progress-bar:

Example:

JS:

var progressBarGeral = $("#geral");
var progressBarAtual = $("#atual");

$.ajax({
     type: 'POST',
     url: 'importador456.php?&acao=importacao',
     xhrFields: {
        onprogress: function (e){
           var data = e.currentTarget.response;

           if (data.lastIndexOf('|') >= 0) {
              var val2 = data.slice((data.lastIndexOf('|') + 1));
              var val = val2.split("-");
              var numProgressGeral = parseFloat(val[1]);
              var numProgressAtual = parseFloat(val[0]);

              progressBarGeral.width(numProgressGeral + '%').text(numProgressGeral + '%');
              progressBarAtual.width(numProgressAtual + '%').text(numProgressAtual + '%');
           }
        }
     }, success: function (data) {
        console.log(data);
     }, error: function (data) {
        alert(data.responseText);
     }
});

PHP:

header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Content-Type: application/xml; charset=utf-8");

ob_start();
set_time_limit(0);
$arr = array(50000,50000);
$a = 0;
for ($j = 0; $j<=1; $j++):
   for ($i = 0; $i<= $arr[$j]; $i++):
      echo '|'.(($i * 100) / $arr[$j]).'-'.(($a * 100) / ($arr[0]+$arr[1]));
      usleep(1);
      ob_flush();
      flush();
      $a++;
   endfor;
endfor;
ob_end_flush();

HTML:

<label>Progresso Geral</label>
<div class="progress">
  <div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" id="geral">
    <span class="sr-only"></span>
  </div>
</div>
<label>Progresso Atual</label>
<div class="progress">
  <div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" id="atual">
    <span class="sr-only"></span>
  </div>
</div>

Browser other questions tagged

You are not signed in. Login or sign up in order to post.