7

This question is not totally answered, please fell free to contribute !


I'm trying to display a simple progress bar while a large form is submitted.

The form contains a dozen of fields, plus some file upload fields, where the user can select a picture. Then, when he clicks on a Create button, the form with data and pictures are submitted and the entity is created in DB. (only one click to submit the form AND the pictures).
Everything works fine, but I'd like to display a progress bar during the submit process.

I have found a lot of tutorials explaining how to display a progress bar, but I don't find anyone explaining how to display a progress bar indicating the percentage of work accomplished by a method, ie, I'd like to see 10%, 25%, etc... during the submit process.

So, basically, this is what I've done : (this is an ASP.NET MVC3 project)

@model MyModel

@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "target-form", enctype = "multipart/form-data" }))
{
    //some Html.TextBoxFor() and other @Html.DropDownListFor

    @Html.TextBoxFor(m => m.File, new { type = "file"})

    <input type="submit" value="Create" class="submitButton" />

    <div id="progressBar"></div>
}

And a basic controller

[HttpPost]
public ActionResult Create(MyModel model)
{
    if (ModelState.IsValid)
    {
        DALLayer dal = new DALLayer()
        dal.AddEntity(model);

        return RedirectToAction("Index", "Home");
    }

    return null;
}

Is it possible to transform my last <div> in a progressBar displaying the state of upload progress ?


Here are my requirements :

  • No plugin (this is a personnal project, I want to understand how to do this by myself).
  • Cross compatible, IE8+ (if possible)
  • jQuery ok, but no Flash.


Thank you very much !

UPDATE 1 Here is a JSFIDDLE, where I'm trying to adapt this link but without success... If you think you can help, you're welcome !


UPDATE 2 Ok, I used acarlon's answer to submit my form with XMLHttpRequest and the datas are correcty posted to the controller. However, the ProgressBar still doesn't appear !
I just replace the data passed to the controller by :

formData = new FormData(document.getElementById("target-form") );
xhr.open("POST", "@Url.Action("MyMethod", "MyController")", true );

and try some different headers, like :

xhr.setRequestHeader("X-File-Name", $('#Files_0__File')[0].files[0].name);
xhr.setRequestHeader("X-File-Size", $('#Files_0__File')[0].files[0].size);
xhr.setRequestHeader("X-File-Type", $('#Files_0__File')[0].files[0].type);
//Also tried with "Content-Length" but it doesn't care about it.

(It was hardcoded here to be sure it has good values. I'll do it in a loop when I'll be more at ease with it.)

And when I submit my form, the XMLHttpRequest send has these fields :

readyState: 4
status: 0 <= should be 200, right ?

And in the error handler, I have these values :

loaded: 0
total: 883526
type: "error"

So the data are submitted to my controller, but I'm unable to display this damned progressbar...

Community
  • 1
  • 1
AlexB
  • 7,302
  • 12
  • 56
  • 74
  • 1
    AlexB, Are you looking for a way to for your progress bar to measure progress in time (% of time used) or steps (2/4 tasks complete)? – Dave Alperovich Feb 24 '14 at 23:04
  • I'm looking for a progress bar to measure progress in time (% of time used) – AlexB Feb 25 '14 at 07:12
  • AlexB, how do you propose to measure time a job should take? or how much time remains? – Dave Alperovich Feb 25 '14 at 07:13
  • Well, I need a progress bar where 0% = job begins and 100% = job ends. Between 0 and 100%, an indicator should be updated every, let's say 0.5sec, to indicate the progress of the task (ie 10%, 50% etc...) – AlexB Feb 25 '14 at 08:01
  • This isn't difficult with a timer. But this approach cant be accurate. I suspect most of the time it will go 10-20-100%; while other times it may reach 100% and hang for a few secs. Considering this kind of range (since you cant guess the real exec time of your process), is this an affect you consider advantageous? – Dave Alperovich Feb 25 '14 at 08:30
  • I've heard about timers, and as you said, this won't indicate the "real" % of work. So this can't be an answer. If, for any reason, the server takes a little longer than usual to answer, I'd like to see it in my progressbar. – AlexB Feb 25 '14 at 08:34
  • Getting % work done from a database activity is a huge undertaking. And one that will extend the length of work. I'm not sure there's a satisfactory answer here. – Dave Alperovich Feb 25 '14 at 09:00
  • Well, it doesn't seems impossible, have you seen my fiddle ? I've found some links on the Internet where people explain how to do this (http://weblogs.asp.net/seanmcalinden/archive/2009/11/15/asynchronous-processing-in-asp-net-mvc-with-ajax-progress-bar.aspx , http://www.dotnetbips.com/articles/859d32c8-945d-4e5d-8c89-775388598f62.aspx) and I was trying to reproduce them... – AlexB Feb 25 '14 at 10:00
  • Yes, I've seen your fiddle AND blog posts. We seem to have a misunderstanding. These posts track **Step Progress**, NOT **Time Progress**. In other words, they report back progress when each file has uploaded. In which case 1/x% will have finished. This goes back to my very first question posted to you, and the answer would be the opposite of what you answered (steps vs time). Do you understand? – Dave Alperovich Feb 25 '14 at 14:04
  • 1
    Hmm, you're right, I probably misunderstood your first question. I think I will revise down my ambitions and try to track Step progress. I this case, I need to know when every file is correctly uploaded to track progress. Knowing that I have a single button to save my entity and upload my file, how can I easily track this progress ? Please see my **Update2** – AlexB Feb 25 '14 at 14:19

2 Answers2

4

You can use XMLHttpRequest to receive updates when uploading files. There are also various jquery-type wrappers to achieve the same. I will describe a solution using XMLHttpRequest. First intercept the form submit.

$("#target-form").submit(function () {

Then create XHR request:

 xhr = new XMLHttpRequest();

Then register to be notified about progress events:

xhr.upload.addEventListener( "progress", function ( evt )
{
    if( evt.lengthComputable )
    {
        var progressPercent = ( evt.loaded / evt.total ) * 100;
        showProgress( value );//your function.
    }
}, false );


//Some other events you will probably want to subscribe to
xhr.addEventListener( "load", function ()
{            
    fileUploadComplete( this.responseText );
}, false );

xhr.addEventListener( "error", function ( first, second, third )
{
    fileUploadComplete( "Error: Image format not supported." );
}, false );

xhr.addEventListener( "abort", function ()
{
    fileUploadComplete( "Error: Upload was cancelled. Please try again." );
}, false );

Open XHR passing in any arguments (id is shown as an example)

xhr.open( "post", '@Html.Raw( @Url.Action( "UploadImage", new { someId = id } ) )', true );

// Set appropriate headers                
xhr.setRequestHeader( "Content-Type", "multipart/form-data" );
xhr.setRequestHeader( "X-File-Name", name );

// Send the file
xhr.send( file );

Then in the controller:

public ActionResult UploadImage( int someId, HttpPostedFileBase userFile  )
{
    ...    
}

You will receive the updates in the update handler.

Extension for long running server task

If there is some long running task on the server (such as writing the data to the database), then you need to chunk the operations (so that you can give updates on completion of each chunk) on the server and implement code to handle a long running service that you can query from javascript. See here. The basic idea is to start the task on the server side and periodically check the progress from Javascript. You may want to have two separate progress bars, one for uploading and one for the server side operation. This provides more information to the user - they will know that the file upload is complete and now some server-side operation is happening.

Community
  • 1
  • 1
acarlon
  • 16,764
  • 7
  • 75
  • 94
  • Ok my bounty expires in 1 hour, so I have to reward someone and your solution seems the best. I can't try right now, so I'll do it today and ask you for some precision if needed. – AlexB Mar 03 '14 at 08:27
  • OK, that sounds fine. – acarlon Mar 03 '14 at 09:03
  • @AlexB - also, look at this solution which is very similar to what I have posted above, but includes more details: http://buildstarted.com/2011/07/17/asp-net-mvc-3-file-uploads-using-the-fileapi/ – acarlon Mar 03 '14 at 09:09
  • Thanks for your additional link, I will take a look to it. Reading your solution, I have one more question : When I submit my form, I need to submit my pictures AND my form (some textboxes, dropdownlist etc.) How can I pass my `Model` (containing fields for pictures AND other values) via the `XMLHttpRequest`. You passed an `id` in your sample, but I can't pass an `MyModel` (client-side didn't know about `MyModel`). You can see my Controller at the end of my first post. Have I to modify it ? – AlexB Mar 03 '14 at 09:27
  • @AlexB - Have a look at these two links: http://stackoverflow.com/a/16982068/746754 and http://stackoverflow.com/a/7913354/746754 for two options. One is to call serialize and the other is to pass in a javascript object that has the same members as your model. The third option, of course is to pass in the members of your model as individual parameters and then reconstruct the model in the controller. – acarlon Mar 03 '14 at 09:37
  • That's funny, your second link is the one where I learnt to do my first ajax calls some months ago ! Well, so I have to create an object in JS equivalent to my model, because I can't do something like `xhr.open( "post", '@Html.Raw( @Url.Action( "UploadImage", new { $('#target-form').serialize() }`. I hoped I could use some "black magic" in one or two lines of code... Well, no matter, I will do it. – AlexB Mar 03 '14 at 09:50
  • @AlexB - have a look at http://stackoverflow.com/a/18592868/746754 at then end of the answer there is an example showing `FormData`. I remember looking at it a while ago, but I did not end up using it. It may be an easier approach than recreating the Model. I have to head off for the night, but I will check back in the morning. – acarlon Mar 03 '14 at 09:57
  • I used your answer, but I still encounter some issues, could you please take a look to my **Update2** ? – AlexB Mar 03 '14 at 15:42
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/48924/discussion-between-acarlon-and-alexb) – acarlon Mar 04 '14 at 06:27
2

Add an ajax timer control which checks progress with the server and set time interval (say every 2 seconds).

Execute your server task on a new thread. This will cause your post back to return immediately whilst executing your long task in the background. Start the ajax timer.

As the long task works let it update a session variable which contains the % work done.

When the timer postbacks to the sever, fetch the % work done from the session variable.

You can design a progress bar by having an inner div and an outer div. Set the width of the inner div to the % work done: $('#progressBarWorkDone').css('width', WorkDone + '%');

<div id="progressBar" style="width:400px;">    
<div id="progressBarWorkDone" style="width:0px;height:10px;background-color:red;"><div/> 
<div/>

Note, if you don't know how to use the timer control, you can also execute AJAX calls from javascript using setInterval(function(){ AJAX CALLBACK GOES HERE },1000);

However having said all that it way simpler to just put an animated gif inside your progress bar div. Hide the div, and then show the div when the submit button is clicked.

<input type="submit" onclick="$('#progressBar').show();" value="Create" class="submitButton" />

<div id="progressBar" style="display:none;"><img src="myProgressBar.gif" /></div>
MarzSocks
  • 4,229
  • 3
  • 22
  • 35
  • How can the progress bar be notified of the progression of the form submission with your code ?? – AlexB Feb 20 '14 at 09:04
  • The timer control is doing regular postbacks to the server fetching the % of work done. And then updating the client browser. – MarzSocks Feb 20 '14 at 09:23
  • Your updated answer is really interesting. I prefer the first solution because user can see "in real time" the progress of form submission. However, I'm not sure to really understand how to accomplish this (sorry, beginner here). Have I to create a new thread when my Create action is called in my controller ? And how to define the % of work done ? Thanks for your help ! – AlexB Feb 20 '14 at 09:31
  • I must admit - showing progress real time is not the easiest of things. Especially with your requirement of no plugs :-). However it can be done. To help you further, can you tell me how much JQuery AJAX experience you have? – MarzSocks Feb 20 '14 at 10:03
  • I don't have a lot of jQuery / AJAX experience, I learn it by myself on my free-time, but I can understand quickly if I have a good tutorial ;-) BTW, when I say "in real time", I don't want necessarily the exact percentage level, but an indication, like "10%" (even if in reality we are at 13%). I keep searching on the web, waiting for your answer. – AlexB Feb 20 '14 at 10:08
  • In that case I suggest doing some homework around AJAX postbacks because this would be required to achieve your goal without a plugin. There are always 2 steps in doing an AJAX postback. First server needs to be configured to accept the postback. Secondly, the client needs some javascript to execute the postback, jquery can do this so for the client see: https://api.jquery.com/jQuery.post/ For the server to accept postbacks from the client (the RESTful way) see: http://www.codeproject.com/Articles/105273/Create-RESTful-WCF-Service-API-Step-By-Step-Guide – MarzSocks Feb 21 '14 at 06:04
  • I have a lot of AJAX call in my project with `$.ajax({ ... })` so I think I have basics with them. Please take a look to my **Update** to see my progress – AlexB Feb 21 '14 at 09:59