Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Implement a progress bar using Spring 3 MVC?

Can anyone teach me or direct to a working example to satisfy this requirement.

Scenario:

  1. List item My Web App is using spring mvc.
  2. One of the services it provides is that when the user clicks on a button a long running process will occur on the server. (Query database, write files, write logs, etc...) this process can take a few seconds or a few minutes.
  3. *Problem*** How can I implement the service to update the client of its progress.


  4. The service returns true or false if the process was successful.

Thanks for your replies. A code snippet or a complete tutorial will be most helpful.

like image 632
Jefrey Valencia Avatar asked Feb 13 '12 01:02

Jefrey Valencia


2 Answers

Here is a possible solution to this progress bar problem:

task.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<html>
    <head>

        <script src="../js/jquery.min.js"></script>

        <script>
          $(document).ready(function () {
            $.getJSON(window.location.href.concat('/status'), function(data) {
              if (data === "created") {

              } else {
                // task is already being executed
                refreshProgress();
              }
            });
          });

          var width = 0;

          function getProgress() {
            $.getJSON(window.location.href.concat('/progress'), function(percentage) {
              $('#progressBar').css('width', percentage+'%');
              document.getElementById("label").innerHTML = percentage * 1 + '%';
              width = percentage;
            });
          }

          function start() {
            $.ajax({
              type: "post",
              data: $('#task').serialize(),
              success: function(data) {
                $('#progressBar').css('width', 100+'%');
                document.getElementById("label").innerHTML = 100 * 1 + '%';

                // do sth with the data after finished task
              }
            });

            width = 0;
            $('#progressBar').css('width', 0+'%');
            document.getElementById("label").innerHTML = 0 * 1 + '%';

            refreshProgress();
          }

          function refreshProgress() {
            $("#btnStart").prop("disabled",true);

            var id = setInterval(frame, 1000);
            function frame() {
                if (width >= 100) {
                    clearInterval(id);
                    $("#btnStart").prop("disabled",false);

                } else {
                    getProgress();
                }
            }
          }

        </script>

    </head>
    <body>

      <div class="container">

        <h2 class="text-center">Progress Bar Example</h2>
        <div class="progress">
          <div id="progressBar" class="progress-bar" role="progressbar" aria-valuenow="70" aria-valuemin="0" aria-valuemax="100" style="width:0%">
            <div id="label">0%</div>
          </div>
        </div>

        <form:form method="POST" commandName="task" cssClass="form-horizontal">
        <fieldset>

        <div class="form-group">
          <label class="col-md-4 control-label" for="btnStart">Actions</label>
          <div class="col-md-8">
            <button id="btnStart" name="btnStart" class="btn btn-success">Start</button>
            <button id="btnStop" name="btnStop" class="btn btn-danger">Stop</button>
          </div>
        </div>

        </fieldset>
        </form:form>

      </div>

      <script>
        $('#task').submit(function () {
         start();
         return false;
        });
      </script>

    </body>
</html>

TaskController.java

@Controller
@RequestMapping(value = "/task")
public class TaskController {

    private Task task;

    @RequestMapping("")
    protected ModelAndView page() {
        ModelAndView model = new ModelAndView(VIEW_DIR + "task");

        if (this.task == null) {
            this.task = new Task();
        }

        model.addObject("task", this.task);

        return model;
    }

    @RequestMapping(value = "/status", method = GET)
    public @ResponseBody
    String getStatus() {

        return task.getStatus();
    }

    @RequestMapping(value = "/progress", method = GET)
    public @ResponseBody
    int getProgress() {

        return task.getProgress();
    }

    public ModelAndView form(@ModelAttribute Task task) {

        this.task = task;

        ModelAndView model = new ModelAndView(VIEW_DIR + "task");

        task.execute();

        model.addObject("task", this.task);

        return model;
    }

}

Task.java

public class Task {

    private int total;
    private int progress;
    private String status;

    public Task() {
        this.status = "created";

        // TODO get total here or pass via form
    }

    public void execute() {

        status = "executing";

        int i = 0;

        while (i < total && status.equals("executing")) {

            progress = (100 * (i + 1) / total);

            i++;
        }
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public int getProgress() {
        return progress;
    }

    public void setProgress(int progress) {
        this.progress = progress;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
like image 163
zygimantus Avatar answered Nov 13 '22 14:11

zygimantus


There are a good number of ways to handle a scenario like this. One way is to model the work in terms of a "Process", which contains a "status", including a percentage completion.

If you imagine what this might look like on a website, clicking the button to start the process would submit a form that begins the process and assigns some sort of identity to the process, almost like if you were creating any other sort of object. It would then redirect you to a "process status" page.

The process status page would query for the status of the process and display it. It'd probably have a URL parameter for the process's ID. It would perhaps update itself using an AJAX call to return a progress percentage.

On the backend, you now need to solve a couple of problems: finding out the current status of process N, and updating the status of process N. You could accomplish this in a number of ways, including storing the progress in the database or having some sort of in-memory table of running jobs. You could also use some sort of heuristic to estimate a percent. For example, if it's a "register new user" job, maybe it's 20% done if the user's table has an email address, 40% done if the user avatar table has data in it for this user, etc. I don't recommend this as much.

like image 39
Brandon Yarbrough Avatar answered Nov 13 '22 14:11

Brandon Yarbrough